diff --git a/.npmignore b/.npmignore index 18e58ba2..4f4960b9 100644 --- a/.npmignore +++ b/.npmignore @@ -12,6 +12,7 @@ __snapshots__/* *.js.map src/* dist/models/*.js +!dist/models/meta.js bin/install # Declaration files diff --git a/README.md b/README.md index b67805c1..2282d715 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,9 @@ PWA Asset Generator automates the image generation in a creative way. Having [Pu * When it’s an image source, it is centered over the background option you provide πŸŒ… * When it’s an HTML source, you can go as creative as you like; position your logo, use SVG filters, use variable fonts, use gradient backgrounds, use typography and etc. Your html file is rendered on Chrome before taking screenshots for each resolution 🎨 -* It uses [puppeteer-core](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core) instead of puppeteer and only installs Chromium if it doesn't exist on the system. Saves waste of ~100mb of disk space and many seconds from the world for each user 🌎 +* It uses [puppeteer-core](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core) instead of puppeteer and only installs Chromium if it doesn't exist on the system. Saves waste of ~110-150mb of disk space and many seconds from the world per each user 🌎 + +* Supports dark mode splash screens on iOS! So, you can provide both light πŸŒ• and dark 🌚 splash screen images to differentiate your apps look & feel based on user preference πŸŒ™ ## Install @@ -76,9 +78,10 @@ $ pwa-asset-generator --help -q --quality Image quality: 0...100 (Only for JPEG) [default: 100] -h --splash-only Only generate splash screens [default: false] -c --icon-only Only generate icons [default: false] - -f --favicon Generate favicon [default: false] + -f --favicon Generate favicon image and HTML meta tag [default: false] -l --landscape-only Only generate landscape splash screens [default: false] -r --portrait-only Only generate portrait splash screens [default: false] + -d --dark-mode Generate iOS splash screen meta with (prefers-color-scheme: dark) media attr [default: false] -u --single-quotes Generate HTML meta tags with single quotes [default: false] -g --log Logs the steps of the library process [default: true] @@ -88,6 +91,8 @@ $ pwa-asset-generator --help $ pwa-asset-generator https://your-cdn-server.com/assets/logo.png ./ -t jpeg -q 90 --splash-only --portrait-only $ pwa-asset-generator logo.svg ./assets --scrape false --icon-only --path "%PUBLIC_URL%" $ pwa-asset-generator logo.svg ./assets --icon-only --favicon + $ pwa-asset-generator logo.svg ./assets --dark-mode --background dimgrey --splash-only --type jpeg --quality 80 + $ pwa-asset-generator logo.svg ./assets --padding "calc(50vh - 5%) calc(50vw - 10%)" $ pwa-asset-generator https://raw.githubusercontent.com/onderceylan/pwa-asset-generator/HEAD/static/logo.png ./temp -p "15%" -b "linear-gradient(to right, #fa709a 0%, #fee140 100%)" Flag examples @@ -105,6 +110,7 @@ $ pwa-asset-generator --help --favicon --landscape-only --portrait-only + --dark-mode --single-quotes --log false ``` @@ -115,7 +121,7 @@ $ pwa-asset-generator --help const pwaAssetGenerator = require('pwa-asset-generator'); (async () => { - const { savedImages, htmlContent, manifestJsonContent } = await pwaAssetGenerator.generateImages( + const { savedImages, htmlMeta, manifestJsonContent } = await pwaAssetGenerator.generateImages( 'https://raw.githubusercontent.com/onderceylan/pwa-asset-generator/HEAD/static/logo.png', './temp', { @@ -130,4 +136,16 @@ const pwaAssetGenerator = require('pwa-asset-generator'); ## Troubleshooting +### "No usable sandbox!" error on Linux In case of getting "No usable sandbox!" error on Linux, you need to enable [system sandboxing](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#setting-up-chrome-linux-sandbox). + +### How to make an image smaller or larger relative to the background? +The default value for the padding surrounding the image is 10%. But it's just a css padding value that you can configure and override yourself with **-p --padding** option. + +1. You can use a more advanced padding value based on your taste and goal; + + **Larger logo:** `--padding "calc(50vh - 20%) calc(50vw - 40%)"` + + **Smaller logo:** `--padding "calc(50vh - 5%) calc(50vw - 10%)"` + +2. You can create your own html input file which uses css media queries and provides different padding options based on breakpoints: https://material.io/design/layout/responsive-layout-grid.html#breakpoints diff --git a/src/__snapshots__/cli.test.ts.snap b/src/__snapshots__/cli.test.ts.snap index 781e2eb7..27c79a58 100644 --- a/src/__snapshots__/cli.test.ts.snap +++ b/src/__snapshots__/cli.test.ts.snap @@ -17,12 +17,14 @@ exports[`generates icons and splash screens when both only flags exist 1`] = ` + + @@ -104,12 +106,14 @@ exports[`generates icons and splash screens with path prefix 1`] = ` + + @@ -191,6 +195,7 @@ exports[`generates icons only 1`] = ` + @@ -205,6 +210,7 @@ exports[`generates landscape splash screens only 1`] = ` " + @@ -243,6 +249,7 @@ exports[`generates portrait splash screens only 1`] = ` " + @@ -281,6 +288,7 @@ exports[`generates splash screens only 1`] = ` " + diff --git a/src/__snapshots__/main.test.ts.snap b/src/__snapshots__/main.test.ts.snap index 4038a41a..72959448 100644 --- a/src/__snapshots__/main.test.ts.snap +++ b/src/__snapshots__/main.test.ts.snap @@ -1,14 +1,328 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`generates icons and splash screens when called via function 1`] = ` +exports[`generates dark mode splash screen html as part of htmlMeta 1`] = ` Object { - "htmlContent": " - - - - - + "htmlMeta": Object { + "appleLaunchImageDarkMode": " + + + + + + + + + + + + + + + + + + +", + "appleMobileWebAppCapable": " +", + }, + "manifestJsonContent": Array [], + "savedImages": Array [ + Object { + "height": 2732, + "name": "apple-splash-dark-2048-2732", + "orientation": "portrait", + "path": "temp/apple-splash-dark-2048-2732.jpeg", + "scaleFactor": 2, + "width": 2048, + }, + Object { + "height": 2048, + "name": "apple-splash-dark-2732-2048", + "orientation": "landscape", + "path": "temp/apple-splash-dark-2732-2048.jpeg", + "scaleFactor": 2, + "width": 2732, + }, + Object { + "height": 2388, + "name": "apple-splash-dark-1668-2388", + "orientation": "portrait", + "path": "temp/apple-splash-dark-1668-2388.jpeg", + "scaleFactor": 2, + "width": 1668, + }, + Object { + "height": 1668, + "name": "apple-splash-dark-2388-1668", + "orientation": "landscape", + "path": "temp/apple-splash-dark-2388-1668.jpeg", + "scaleFactor": 2, + "width": 2388, + }, + Object { + "height": 2224, + "name": "apple-splash-dark-1668-2224", + "orientation": "portrait", + "path": "temp/apple-splash-dark-1668-2224.jpeg", + "scaleFactor": 2, + "width": 1668, + }, + Object { + "height": 1668, + "name": "apple-splash-dark-2224-1668", + "orientation": "landscape", + "path": "temp/apple-splash-dark-2224-1668.jpeg", + "scaleFactor": 2, + "width": 2224, + }, + Object { + "height": 2048, + "name": "apple-splash-dark-1536-2048", + "orientation": "portrait", + "path": "temp/apple-splash-dark-1536-2048.jpeg", + "scaleFactor": 2, + "width": 1536, + }, + Object { + "height": 1536, + "name": "apple-splash-dark-2048-1536", + "orientation": "landscape", + "path": "temp/apple-splash-dark-2048-1536.jpeg", + "scaleFactor": 2, + "width": 2048, + }, + Object { + "height": 2688, + "name": "apple-splash-dark-1242-2688", + "orientation": "portrait", + "path": "temp/apple-splash-dark-1242-2688.jpeg", + "scaleFactor": 3, + "width": 1242, + }, + Object { + "height": 1242, + "name": "apple-splash-dark-2688-1242", + "orientation": "landscape", + "path": "temp/apple-splash-dark-2688-1242.jpeg", + "scaleFactor": 3, + "width": 2688, + }, + Object { + "height": 2436, + "name": "apple-splash-dark-1125-2436", + "orientation": "portrait", + "path": "temp/apple-splash-dark-1125-2436.jpeg", + "scaleFactor": 3, + "width": 1125, + }, + Object { + "height": 1125, + "name": "apple-splash-dark-2436-1125", + "orientation": "landscape", + "path": "temp/apple-splash-dark-2436-1125.jpeg", + "scaleFactor": 3, + "width": 2436, + }, + Object { + "height": 1792, + "name": "apple-splash-dark-828-1792", + "orientation": "portrait", + "path": "temp/apple-splash-dark-828-1792.jpeg", + "scaleFactor": 2, + "width": 828, + }, + Object { + "height": 828, + "name": "apple-splash-dark-1792-828", + "orientation": "landscape", + "path": "temp/apple-splash-dark-1792-828.jpeg", + "scaleFactor": 2, + "width": 1792, + }, + Object { + "height": 2208, + "name": "apple-splash-dark-1242-2208", + "orientation": "portrait", + "path": "temp/apple-splash-dark-1242-2208.jpeg", + "scaleFactor": 3, + "width": 1242, + }, + Object { + "height": 1242, + "name": "apple-splash-dark-2208-1242", + "orientation": "landscape", + "path": "temp/apple-splash-dark-2208-1242.jpeg", + "scaleFactor": 3, + "width": 2208, + }, + Object { + "height": 1334, + "name": "apple-splash-dark-750-1334", + "orientation": "portrait", + "path": "temp/apple-splash-dark-750-1334.jpeg", + "scaleFactor": 2, + "width": 750, + }, + Object { + "height": 750, + "name": "apple-splash-dark-1334-750", + "orientation": "landscape", + "path": "temp/apple-splash-dark-1334-750.jpeg", + "scaleFactor": 2, + "width": 1334, + }, + Object { + "height": 1136, + "name": "apple-splash-dark-640-1136", + "orientation": "portrait", + "path": "temp/apple-splash-dark-640-1136.jpeg", + "scaleFactor": 2, + "width": 640, + }, + Object { + "height": 640, + "name": "apple-splash-dark-1136-640", + "orientation": "landscape", + "path": "temp/apple-splash-dark-1136-640.jpeg", + "scaleFactor": 2, + "width": 1136, + }, + ], +} +`; + +exports[`generates favicon html as part of htmlMeta 1`] = ` +Object { + "htmlMeta": Object { + "appleMobileWebAppCapable": " +", + "appleTouchIcon": " + + + +", + "favicon": " +", + }, + "manifestJsonContent": Array [ + Object { + "sizes": "192x192", + "src": "temp/manifest-icon-192.png", + "type": "image/png", + }, + Object { + "sizes": "512x512", + "src": "temp/manifest-icon-512.png", + "type": "image/png", + }, + ], + "savedImages": Array [ + Object { + "height": 180, + "name": "apple-icon-180", + "orientation": null, + "path": "temp/apple-icon-180.png", + "scaleFactor": undefined, + "width": 180, + }, + Object { + "height": 167, + "name": "apple-icon-167", + "orientation": null, + "path": "temp/apple-icon-167.png", + "scaleFactor": undefined, + "width": 167, + }, + Object { + "height": 152, + "name": "apple-icon-152", + "orientation": null, + "path": "temp/apple-icon-152.png", + "scaleFactor": undefined, + "width": 152, + }, + Object { + "height": 120, + "name": "apple-icon-120", + "orientation": null, + "path": "temp/apple-icon-120.png", + "scaleFactor": undefined, + "width": 120, + }, + Object { + "height": 192, + "name": "manifest-icon-192", + "orientation": null, + "path": "temp/manifest-icon-192.png", + "scaleFactor": undefined, + "width": 192, + }, + Object { + "height": 512, + "name": "manifest-icon-512", + "orientation": null, + "path": "temp/manifest-icon-512.png", + "scaleFactor": undefined, + "width": 512, + }, + Object { + "height": 196, + "name": "favicon-196", + "orientation": null, + "path": "temp/favicon-196.png", + "scaleFactor": undefined, + "width": 196, + }, + ], +} +`; + +exports[`generates icons and splash screens when called via function 1`] = ` +Object { + "htmlMeta": Object { + "appleLaunchImage": " ", + "appleMobileWebAppCapable": " +", + "appleTouchIcon": " + + + +", + }, "manifestJsonContent": Array [ Object { "sizes": "192x192", diff --git a/src/cli.ts b/src/cli.ts index 658554cb..c58a7094 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -24,9 +24,10 @@ $ pwa-asset-generator --help -q --quality Image quality: 0...100 (Only for JPEG) [default: 100] -h --splash-only Only generate splash screens [default: false] -c --icon-only Only generate icons [default: false] - -f --favicon Generate favicon [default: false] + -f --favicon Generate favicon image and HTML meta tag [default: false] -l --landscape-only Only generate landscape splash screens [default: false] -r --portrait-only Only generate portrait splash screens [default: false] + -d --dark-mode Generate iOS splash screen meta with (prefers-color-scheme: dark) media attr [default: false] -u --single-quotes Generate HTML meta tags with single quotes [default: false] -g --log Logs the steps of the library process [default: true] @@ -36,6 +37,8 @@ $ pwa-asset-generator --help $ pwa-asset-generator https://your-cdn-server.com/assets/logo.png ./ -t jpeg -q 90 --splash-only --portrait-only $ pwa-asset-generator logo.svg ./assets --scrape false --icon-only --path "%PUBLIC_URL%" $ pwa-asset-generator logo.svg ./assets --icon-only --favicon + $ pwa-asset-generator logo.svg ./assets --dark-mode --background dimgrey --splash-only --type jpeg --quality 80 + $ pwa-asset-generator logo.svg ./assets --padding "calc(50vh - 5%) calc(50vw - 10%)" $ pwa-asset-generator https://raw.githubusercontent.com/onderceylan/pwa-asset-generator/HEAD/static/logo.png ./temp -p "15%" -b "linear-gradient(to right, #fa709a 0%, #fee140 100%)" Flag examples @@ -53,6 +56,7 @@ $ pwa-asset-generator --help --favicon --landscape-only --portrait-only + --dark-mode --single-quotes --log false `, @@ -69,6 +73,7 @@ const logger = preLogger('cli', cli.flags); (async (): Promise => { try { await generateImages(cli.input[0], cli.input[1], cli.flags, logger); + process.exit(0); } catch (e) { logger.error(e); process.exit(1); diff --git a/src/config/constants.ts b/src/config/constants.ts index 36963112..9ec1dc04 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -1,4 +1,30 @@ import { Orientation } from '../models/image'; +import { HTMLMetaNames, HTMLMetaSelector } from '../models/meta'; + +const HTML_META_ORDERED_SELECTOR_LIST: HTMLMetaSelector[] = [ + { + name: HTMLMetaNames.favicon, + selector: 'link[rel="icon"]', + }, + { + name: HTMLMetaNames.appleTouchIcon, + selector: 'link[rel="apple-touch-icon"]', + }, + { + name: HTMLMetaNames.appleMobileWebAppCapable, + selector: 'meta[name="apple-mobile-web-app-capable"]', + }, + { + name: HTMLMetaNames.appleLaunchImage, + selector: + 'link[rel="apple-touch-startup-image"]:not([media^="(prefers-color-scheme: dark)"])', + }, + { + name: HTMLMetaNames.appleLaunchImageDarkMode, + selector: + 'link[rel="apple-touch-startup-image"][media^="(prefers-color-scheme: dark)"]', + }, +]; export default { FLAGS: { @@ -79,6 +105,11 @@ export default { alias: 'f', default: false, }, + darkMode: { + type: 'boolean', + alias: 'd', + default: false, + }, }, PUPPETEER_LAUNCH_ARGS: [ @@ -121,16 +152,19 @@ export default { FAVICON_SIZES: [196], + HTML_META_ORDERED_SELECTOR_LIST, + FAVICON_FILENAME_PREFIX: 'favicon', APPLE_ICON_FILENAME_PREFIX: 'apple-icon', APPLE_SPLASH_FILENAME_PREFIX: 'apple-splash', + APPLE_SPLASH_FILENAME_DARK_MODE_POSTFIX: '-dark', MANIFEST_ICON_FILENAME_PREFIX: 'manifest-icon', APPLE_HIG_SPLASH_SCR_SPECS_DATA_GRID_SELECTOR: 'table tbody tr', WAIT_FOR_SELECTOR_TIMEOUT: 1000, BROWSER_SHELL_TIMEOUT: 60000, - FAVICON_META_HTML: (size: number, url: string, mimeType: string): string => `\ - + FAVICON_META_HTML: (size: number, url: string, mimeType: string): string => + ` `, SHELL_HTML_FOR_LOGO: ( @@ -163,8 +197,8 @@ export default { `, - APPLE_TOUCH_ICON_META_HTML: (size: number, url: string): string => `\ - + APPLE_TOUCH_ICON_META_HTML: (size: number, url: string): string => + ` `, APPLE_LAUNCH_SCREEN_META_HTML: ( @@ -173,13 +207,14 @@ export default { url: string, scaleFactor: number, orientation: Orientation, + darkMode: boolean, ): string => { /* eslint-disable */ if (orientation === 'portrait') { return `\ + media="${darkMode ? '(prefers-color-scheme: dark) and ' : ''}(device-width: ${width / scaleFactor}px) and (device-height: ${height / scaleFactor}px) and (-webkit-device-pixel-ratio: ${scaleFactor}) and (orientation: ${orientation})"> `; } @@ -187,7 +222,7 @@ export default { return `\ + media="${darkMode ? '(prefers-color-scheme: dark) and ' : ''}(device-width: ${height / scaleFactor}px) and (device-height: ${width / scaleFactor}px) and (-webkit-device-pixel-ratio: ${scaleFactor}) and (orientation: ${orientation})"> `; /* eslint-enable */ }, diff --git a/src/helpers/images.ts b/src/helpers/images.ts index 631af694..49f3f38f 100644 --- a/src/helpers/images.ts +++ b/src/helpers/images.ts @@ -55,14 +55,21 @@ const getSplashScreenImages = ( uniformSplashScreenData: SplashScreenSpec[], options: Options, ): Image[] => { + let appleSplashFilenamePrefix = constants.APPLE_SPLASH_FILENAME_PREFIX; + if (options.darkMode) { + appleSplashFilenamePrefix += + constants.APPLE_SPLASH_FILENAME_DARK_MODE_POSTFIX; + } + return uniqWith( uniformSplashScreenData.reduce((acc: Image[], curr: SplashScreenSpec) => { let images: Image[] = acc; + if (!options.landscapeOnly) { images = [ ...images, mapToImageFileObj( - constants.APPLE_SPLASH_FILENAME_PREFIX, + appleSplashFilenamePrefix, curr.portrait.width, curr.portrait.height, curr.scaleFactor, @@ -74,7 +81,7 @@ const getSplashScreenImages = ( images = [ ...images, mapToImageFileObj( - constants.APPLE_SPLASH_FILENAME_PREFIX, + appleSplashFilenamePrefix, curr.landscape.width, curr.landscape.height, curr.scaleFactor, diff --git a/src/helpers/meta.ts b/src/helpers/meta.ts index 5552a876..d9451741 100644 --- a/src/helpers/meta.ts +++ b/src/helpers/meta.ts @@ -5,6 +5,7 @@ import file from './file'; import { SavedImage } from '../models/image'; import { ManifestJsonIcon } from '../models/result'; import { Options } from '../models/options'; +import { HTMLMeta, HTMLMetaNames, HTMLMetaSelector } from '../models/meta'; const generateIconsContentForManifest = ( savedImages: SavedImage[], @@ -60,6 +61,7 @@ const generateAppleLaunchImageHtml = ( savedImages: SavedImage[], indexHtmlPath: string, pathPrefix = '', + darkMode: boolean, ): string => { return savedImages .filter(image => @@ -72,6 +74,7 @@ const generateAppleLaunchImageHtml = ( pathPrefix + file.getRelativeImagePath(indexHtmlPath, path), scaleFactor as number, orientation, + darkMode, ), ) .join(''); @@ -87,27 +90,60 @@ const getPathPrefix = (pathPrefix: string): string => { const generateHtmlForIndexPage = ( savedImages: SavedImage[], options: Options, -): string => { +): HTMLMeta => { const indexHtmlPath = options.index || ''; const pathPrefix = options.path || ''; const prependPath = getPathPrefix(pathPrefix); - let html = ''; + const htmlMeta: HTMLMeta = { + [HTMLMetaNames.appleMobileWebAppCapable]: ` +`, + }; - if (options.favicon) { - html += `${generateFaviconHtml(savedImages, indexHtmlPath, prependPath)} -`; + if (!options.splashOnly) { + if (options.favicon) { + htmlMeta.favicon = `${generateFaviconHtml( + savedImages, + indexHtmlPath, + prependPath, + )}`; + } + + htmlMeta.appleTouchIcon = `${generateAppleTouchIconHtml( + savedImages, + indexHtmlPath, + prependPath, + )}`; } - html += `\ -${generateAppleTouchIconHtml(savedImages, indexHtmlPath, prependPath)} - -${generateAppleLaunchImageHtml(savedImages, indexHtmlPath, prependPath)}`; + if (!options.iconOnly) { + if (options.darkMode) { + htmlMeta.appleLaunchImageDarkMode = `${generateAppleLaunchImageHtml( + savedImages, + indexHtmlPath, + prependPath, + true, + )}`; + } else { + htmlMeta.appleLaunchImage = `${generateAppleLaunchImageHtml( + savedImages, + indexHtmlPath, + prependPath, + false, + )}`; + } + } if (options.singleQuotes) { - return html.replace(/"/gm, "'"); + Object.keys(htmlMeta).forEach((metaKey: string) => { + const metaContent = htmlMeta[metaKey as keyof HTMLMeta]; + if (metaContent) { + metaContent.replace(/"/gm, "'"); + } + }); + return htmlMeta; } - return html; + return htmlMeta; }; const addIconsToManifest = async ( @@ -139,8 +175,22 @@ const addIconsToManifest = async ( ); }; +const formatMetaTags = (htmlMeta: HTMLMeta): string => { + return constants.HTML_META_ORDERED_SELECTOR_LIST.reduce( + (acc: string, meta: HTMLMetaSelector) => { + if (htmlMeta.hasOwnProperty(meta.name)) { + return `\ +${acc} +${htmlMeta[meta.name]}`; + } + return acc; + }, + '', + ); +}; + const addMetaTagsToIndexPage = async ( - htmlContent: string, + htmlMeta: HTMLMeta, indexHtmlFilePath: string, ): Promise => { if (!(await file.pathExists(indexHtmlFilePath, file.WRITE_ACCESS))) { @@ -150,17 +200,50 @@ const addMetaTagsToIndexPage = async ( const indexHtmlFile = await file.readFile(indexHtmlFilePath); const $ = cheerio.load(indexHtmlFile); - // TODO: Find a way to remove tags without leaving newlines behind - $( - 'link[rel="apple-touch-startup-image"], link[rel="apple-touch-icon"], meta[name="apple-mobile-web-app-capable"]', - ).remove(); + const HEAD_SELECTOR = 'head'; + const hasElement = (selector: string): boolean => { + return $(selector).length > 0; + }; + + const hasDarkModeElement = (): boolean => { + const darkModeMeta = constants.HTML_META_ORDERED_SELECTOR_LIST.find( + (m: HTMLMetaSelector) => + m.name === HTMLMetaNames.appleLaunchImageDarkMode, + ); + if (darkModeMeta) { + return $(darkModeMeta.selector).length > 0; + } + return false; + }; - $('head').append(`${htmlContent}`); + // TODO: Find a way to remove tags without leaving newlines behind + constants.HTML_META_ORDERED_SELECTOR_LIST.forEach( + (meta: HTMLMetaSelector) => { + 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, $.html()); }; export default { + formatMetaTags, addIconsToManifest, addMetaTagsToIndexPage, generateHtmlForIndexPage, diff --git a/src/helpers/url.ts b/src/helpers/url.ts index 697764b2..a8e03ae6 100644 --- a/src/helpers/url.ts +++ b/src/helpers/url.ts @@ -68,7 +68,7 @@ const getShellHtml = async ( ); } - logger.log('Saving html shell with provided image url'); + logger.log('Generating shell html with provided image url'); return useShell(true); } @@ -76,7 +76,7 @@ const getShellHtml = async ( throw Error(`Cannot find path ${source}. Please check if file exists`); } - logger.log('Saving html shell with provided image source'); + logger.log('Generating shell html with provided image source'); return useShell(); }; diff --git a/src/main.test.ts b/src/main.test.ts index 37038323..fdb1cc29 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -1,6 +1,6 @@ import { generateImages } from './main'; -const TEST_TIMEOUT_IN_MILLIS = 90000; +const TEST_TIMEOUT_IN_MILLIS = 120000; test('generates icons and splash screens when called via function', async () => { jest.setTimeout(TEST_TIMEOUT_IN_MILLIS); @@ -15,3 +15,31 @@ test('generates icons and splash screens when called via function', async () => expect(result).toMatchSnapshot(); }); + +test('generates favicon html as part of htmlMeta', async () => { + jest.setTimeout(TEST_TIMEOUT_IN_MILLIS); + + const result = await generateImages('./static/logo.png', './temp', { + scrape: false, + iconOnly: true, + favicon: true, + log: false, + }); + + expect(result).toMatchSnapshot(); +}); + +test('generates dark mode splash screen html as part of htmlMeta', async () => { + jest.setTimeout(TEST_TIMEOUT_IN_MILLIS); + + const result = await generateImages('./static/logo.png', './temp', { + scrape: false, + splashOnly: true, + type: 'jpeg', + quality: 80, + darkMode: true, + log: false, + }); + + expect(result).toMatchSnapshot(); +}); diff --git a/src/main.ts b/src/main.ts index 75dfb0d4..4373ccc7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,7 +20,7 @@ import { LoggerFunction } from './models/logger'; import pwaAssetGenerator = require('pwa-asset-generator'); (async () => { - const { savedImages, htmlContent, manifestJsonContent } = await pwaAssetGenerator.generateImages( + const { savedImages, htmlMeta, manifestJsonContent } = await pwaAssetGenerator.generateImages( 'https://raw.githubusercontent.com/onderceylan/pwa-asset-generator/HEAD/static/logo.png', './temp', { @@ -76,7 +76,7 @@ async function generateImages( savedImages, modOptions.manifest, ); - const htmlContent = meta.generateHtmlForIndexPage(savedImages, modOptions); + const htmlMeta = meta.generateHtmlForIndexPage(savedImages, modOptions); if (!modOptions.splashOnly) { if (modOptions.manifest) { @@ -96,7 +96,7 @@ async function generateImages( } if (modOptions.index) { - await meta.addMetaTagsToIndexPage(htmlContent, modOptions.index); + await meta.addMetaTagsToIndexPage(htmlMeta, modOptions.index); logger.success( `iOS meta tags are saved to index html file ${modOptions.index}`, ); @@ -107,10 +107,10 @@ async function generateImages( logger.success( 'Below is the iOS meta tags content for your index.html file. You can copy/paste it manually', ); - logger.raw(`\n${htmlContent}\n`); + logger.raw(`\n${meta.formatMetaTags(htmlMeta)}\n`); } - return { savedImages, htmlContent, manifestJsonContent }; + return { savedImages, htmlMeta, manifestJsonContent }; } export { generateImages }; diff --git a/src/models/meta.ts b/src/models/meta.ts new file mode 100644 index 00000000..837c0fd1 --- /dev/null +++ b/src/models/meta.ts @@ -0,0 +1,20 @@ +export enum HTMLMetaNames { + favicon = 'favicon', + appleTouchIcon = 'appleTouchIcon', + appleMobileWebAppCapable = 'appleMobileWebAppCapable', + appleLaunchImage = 'appleLaunchImage', + appleLaunchImageDarkMode = 'appleLaunchImageDarkMode', +} + +export interface HTMLMeta { + [HTMLMetaNames.favicon]?: string; + [HTMLMetaNames.appleTouchIcon]?: string; + [HTMLMetaNames.appleMobileWebAppCapable]: string; + [HTMLMetaNames.appleLaunchImage]?: string; + [HTMLMetaNames.appleLaunchImageDarkMode]?: string; +} + +export interface HTMLMetaSelector { + name: keyof HTMLMeta; + selector: string; +} diff --git a/src/models/options.ts b/src/models/options.ts index c157fea6..f3c46773 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -109,11 +109,18 @@ export interface Options { readonly singleQuotes: boolean; /** - Generates favicon images and HTML meta tags + Generate favicon images and HTML meta tags @default false */ readonly favicon: boolean; + + /** + Generate iOS splash screen meta with (prefers-color-scheme: dark) media attr + + @default false + */ + readonly darkMode: boolean; } export type CLIOptions = Partial; diff --git a/src/models/result.ts b/src/models/result.ts index cf006211..abf5994a 100644 --- a/src/models/result.ts +++ b/src/models/result.ts @@ -1,4 +1,5 @@ import { SavedImage } from './image'; +import { HTMLMeta } from './meta'; export interface ManifestJsonIcon { /** @@ -50,21 +51,21 @@ export interface Result { @example ```javascript [{ - name: 'apple-splash-1136-640', - width: 1136, - height: 640, - scaleFactor: 2, - path: 'temp/apple-splash-1136-640.png', - orientation: 'landscape' - }, + name: 'apple-splash-1136-640', + width: 1136, + height: 640, + scaleFactor: 2, + path: 'temp/apple-splash-1136-640.png', + orientation: 'landscape' + }, { - name: 'apple-icon-180', - width: 180, - height: 180, - scaleFactor: null, - path: 'temp/apple-icon-180.png', - orientation: null - }] + name: 'apple-icon-180', + width: 180, + height: 180, + scaleFactor: null, + path: 'temp/apple-icon-180.png', + orientation: null + }] ``` */ savedImages: SavedImage[]; @@ -73,14 +74,17 @@ export interface Result { Meta tags to be added to index.html file @example - ```html - - - - + ```javascript + { + favicon: '\n', + appleTouchIcon: '\n'; + appleMobileWebAppCapable: '\n'; + appleLaunchImage: '\n'; + appleLaunchImageDarkMode: '\n' + } ``` */ - htmlContent: string; + htmlMeta: HTMLMeta; /** Icons to be added to manifest.json's icons property @@ -88,15 +92,15 @@ export interface Result { @example ```json [{ - "src": "assets/pwa/manifest-icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, + "src": "assets/pwa/manifest-icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, { - "src": "assets/pwa/manifest-icon-512.png", - "sizes": "512x512", - "type": "image/png" - }] + "src": "assets/pwa/manifest-icon-512.png", + "sizes": "512x512", + "type": "image/png" + }] ``` */ manifestJsonContent: ManifestJsonIcon[];