UNPKG

pwa-asset-generator

Version:

Automates PWA asset generation and image declaration. Automatically generates icon and splash screen images, favicons and mstile images. Updates manifest.json and index.html files with the generated images according to Web App Manifest specs and Apple Hum

155 lines 6.97 kB
import { load } from 'cheerio'; import pretty from 'pretty'; import { lookup } from 'mime-types'; import path from 'node:path'; import constants from '../config/constants.js'; import file from './file.js'; import { HTMLMetaNames } from '../models/meta.js'; const generateOutputPath = (options, imagePath, isManifest = false) => { const { path: pathPrefix, pathOverride, index: indexHtmlPath, manifest: manifestJsonPath, } = options; const outputFilePath = (isManifest ? manifestJsonPath : indexHtmlPath); if (pathOverride !== undefined) { return `${pathOverride}/${path.parse(imagePath).base}`; } if (pathPrefix && !isManifest) { return `${pathPrefix}/${file.getRelativeImagePath(outputFilePath, imagePath)}`; } return file.getRelativeImagePath(outputFilePath, imagePath); }; const generateIconsContentForManifest = (savedImages, options) => savedImages .filter((image) => image.name.startsWith(constants.MANIFEST_ICON_FILENAME_PREFIX)) .reduce((curr, { path: imagePath, width, height }) => { const icon = { src: generateOutputPath(options, imagePath), sizes: `${width}x${height}`, type: `image/${file.getExtension(imagePath)}`, }; if (!options.maskable) { return curr.concat(icon); } return curr.concat([ { ...icon, purpose: 'any' }, { ...icon, purpose: 'maskable' }, ]); }, []); const generateAppleTouchIconHtml = (savedImages, options) => savedImages .filter((image) => image.name.startsWith(constants.APPLE_ICON_FILENAME_PREFIX)) .map(({ path: imagePath }) => constants.APPLE_TOUCH_ICON_META_HTML(generateOutputPath(options, imagePath), options.xhtml)) .join(''); const generateFaviconHtml = (savedImages, options) => savedImages .filter((image) => image.name.startsWith(constants.FAVICON_FILENAME_PREFIX)) .map(({ width, path: imagePath }) => constants.FAVICON_META_HTML(width, generateOutputPath(options, imagePath), lookup(imagePath), options.xhtml)) .join(''); const generateMsTileImageHtml = (savedImages, options) => savedImages .filter((image) => image.name.startsWith(constants.MS_ICON_FILENAME_PREFIX)) .map(({ width, height, path: imagePath }) => constants.MSTILE_IMAGE_META_HTML(constants.MSTILE_SIZE_ELEMENT_NAME_MAP[`${width}x${height}`], generateOutputPath(options, imagePath), options.xhtml)) .join(''); const generateAppleLaunchImageHtml = (savedImages, options, darkMode) => savedImages .filter((image) => image.name.startsWith(constants.APPLE_SPLASH_FILENAME_PREFIX)) .map(({ width, height, path: imagePath, scaleFactor, orientation }) => constants.APPLE_LAUNCH_SCREEN_META_HTML(width, height, generateOutputPath(options, imagePath), scaleFactor, orientation, darkMode, options.xhtml)) .join(''); const generateHtmlForIndexPage = (savedImages, options) => { const htmlMeta = { [HTMLMetaNames.appleMobileWebAppCapable]: `<meta name="apple-mobile-web-app-capable" content="yes"${options.xhtml ? ' /' : ''}> `, }; if (!options.splashOnly) { if (options.favicon) { htmlMeta[HTMLMetaNames.favicon] = `${generateFaviconHtml(savedImages, options)}`; } htmlMeta[HTMLMetaNames.appleTouchIcon] = `${generateAppleTouchIconHtml(savedImages, options)}`; } if (!options.iconOnly) { if (options.darkMode) { htmlMeta[HTMLMetaNames.appleLaunchImageDarkMode] = `${generateAppleLaunchImageHtml(savedImages, options, true)}`; } else { htmlMeta[HTMLMetaNames.appleLaunchImage] = `${generateAppleLaunchImageHtml(savedImages, options, false)}`; } } if (options.mstile) { htmlMeta[HTMLMetaNames.msTileImage] = `${generateMsTileImageHtml(savedImages, options)}`; } if (options.singleQuotes) { Object.keys(htmlMeta).forEach((metaKey) => { const metaContent = htmlMeta[metaKey]; if (metaContent) { metaContent.replace(/"/gm, "'"); } }); return htmlMeta; } return htmlMeta; }; const addIconsToManifest = async (manifestContent, manifestJsonFilePath) => { if (!(await file.isPathAccessible(manifestJsonFilePath, file.WRITE_ACCESS))) { throw Error(`Cannot write to manifest json file ${manifestJsonFilePath}`); } const manifestJson = JSON.parse((await file.readFile(manifestJsonFilePath))); const newManifestContent = { ...manifestJson, icons: [...manifestContent], }; if (manifestJson.icons) { newManifestContent.icons = [ ...newManifestContent.icons, ...manifestJson.icons.filter((icon) => !manifestContent.some((man) => man.sizes === icon.sizes)), ]; } return file.writeFile(manifestJsonFilePath, JSON.stringify(newManifestContent, null, 2)); }; const formatMetaTags = (htmlMeta) => constants.HTML_META_ORDERED_SELECTOR_LIST.reduce((acc, meta) => { if (htmlMeta.hasOwnProperty(meta.name)) { return `\ ${acc} ${htmlMeta[meta.name]}`; } return acc; }, ''); const addMetaTagsToIndexPage = async (htmlMeta, indexHtmlFilePath, xhtml) => { if (!(await file.isPathAccessible(indexHtmlFilePath, file.WRITE_ACCESS))) { throw Error(`Cannot write to index html file ${indexHtmlFilePath}`); } const indexHtmlFile = await file.readFile(indexHtmlFilePath); const $ = load(indexHtmlFile, { xml: xhtml, }); const HEAD_SELECTOR = 'head'; const hasElement = (selector) => $(selector).length > 0; const hasDarkModeElement = () => { const darkModeMeta = constants.HTML_META_ORDERED_SELECTOR_LIST.find((m) => m.name === HTMLMetaNames.appleLaunchImageDarkMode); if (darkModeMeta) { return $(darkModeMeta.selector).length > 0; } return false; }; // TODO: Find a way to remove tags without leaving newlines behind constants.HTML_META_ORDERED_SELECTOR_LIST.forEach((meta) => { if (htmlMeta.hasOwnProperty(meta.name) && htmlMeta[meta.name] !== '') { const content = `${htmlMeta[meta.name]}`; if (hasElement(meta.selector)) { $(meta.selector).remove(); } // Because meta tags with dark mode media attr has to be declared after the regular splash screen meta tags if (meta.name === HTMLMetaNames.appleLaunchImage && hasDarkModeElement()) { $(HEAD_SELECTOR).prepend(`\n${content}`); } else { $(HEAD_SELECTOR).append(`${content}\n`); } } }); return file.writeFile(indexHtmlFilePath, pretty($.html(), { ocd: true })); }; export default { formatMetaTags, addIconsToManifest, addMetaTagsToIndexPage, generateHtmlForIndexPage, generateBrowserConfigXml: generateMsTileImageHtml, generateIconsContentForManifest, }; //# sourceMappingURL=meta.js.map