diff --git a/packages/gatsby-plugin-manifest/README.md b/packages/gatsby-plugin-manifest/README.md index e98e19c0cb46f..a526d42a739a7 100644 --- a/packages/gatsby-plugin-manifest/README.md +++ b/packages/gatsby-plugin-manifest/README.md @@ -15,7 +15,7 @@ If you're using this plugin together with [`gatsby-plugin-offline`](https://www. this plugin should be listed _before_ the offline plugin so that it can cache the created manifest.webmanifest. -If you use the "automatic mode" (described below), this plugin will also add a favicon link to your html pages. +If you use the "automatic mode" or "hybrid mode" (described below), this plugin will also add a favicon link. ## Install @@ -33,70 +33,71 @@ There are three modes in which icon generation can function: automatic, hybrid, ### Automatic mode -In the automatic mode, you are responsible for defining the entire web app manifest except for the icons portion. You only provide a high resolution source icon. The icons themselves and the needed config will be generated at build time. See the example below: - -```javascript -// In your gatsby-config.js -plugins: [ - { - resolve: `gatsby-plugin-manifest`, - options: { - name: `GatsbyJS`, - short_name: `GatsbyJS`, - start_url: `/`, - background_color: `#f7f0eb`, - theme_color: `#a2466c`, - display: `minimal-ui`, - icon: `src/images/icon.png`, // This path is relative to the root of the site. +In the automatic mode, you are responsible for defining the entire web app manifest except for the icons portion. You only provide a high resolution source icon. The icons themselves and the needed config will be generated at build time. See the example `gatsby-config.js` below: + +```javascript:title=gatsby-config.js +module.exports = { + plugins: [ + { + resolve: `gatsby-plugin-manifest`, + options: { + name: `GatsbyJS`, + short_name: `GatsbyJS`, + start_url: `/`, + background_color: `#f7f0eb`, + theme_color: `#a2466c`, + display: `minimal-ui`, + icon: `src/images/icon.png`, // This path is relative to the root of the site. + }, }, - }, -] + ], +} ``` When in automatic mode the following json array is injected into the manifest configuration you provide and the icons are generated from it. The source icon you provide should be at least as big as the largest icon being generated. -```javascript -;[ +```json +[ { - src: `icons/icon-48x48.png`, - sizes: `48x48`, - type: `image/png`, + "src": "icons/icon-48x48.png", + "sizes": "48x48", + "type": "image/png" }, { - src: `icons/icon-72x72.png`, - sizes: `72x72`, - type: `image/png`, + "src": "icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png" }, { - src: `icons/icon-96x96.png`, - sizes: `96x96`, - type: `image/png`, + "src": "icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png" }, { - src: `icons/icon-144x144.png`, - sizes: `144x144`, - type: `image/png`, + "src": "icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png" }, { - src: `icons/icon-192x192.png`, - sizes: `192x192`, - type: `image/png`, + "src": "icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" }, { - src: `icons/icon-256x256.png`, - sizes: `256x256`, - type: `image/png`, + "src": "icons/icon-256x256.png", + "sizes": "256x256", + "type": "image/png" }, { - src: `icons/icon-384x384.png`, - sizes: `384x384`, - type: `image/png`, + "src": "icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png" }, { - src: `icons/icon-512x512.png`, - sizes: `512x512`, - type: `image/png`, - }, + "src": "icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } ] ``` @@ -104,73 +105,99 @@ The automatic mode is the easiest option for most people. ### Hybrid mode -However, if you want to include more or fewer sizes, then the hybrid option is for you. Like automatic mode, you should include a high resolution icon to generate smaller icons from. But unlike automatic mode, you provide the `icons` array config and icons are generated based on the sizes defined in your config. Here's an example: - -```javascript -// In your gatsby-config.js -plugins: [ - { - resolve: `gatsby-plugin-manifest`, - options: { - name: `GatsbyJS`, - short_name: `GatsbyJS`, - start_url: `/`, - background_color: `#f7f0eb`, - theme_color: `#a2466c`, - display: `minimal-ui`, - icon: `src/images/icon.png`, // This path is relative to the root of the site. - icons: [ - { - src: `/favicons/android-chrome-192x192.png`, - sizes: `192x192`, - type: `image/png`, - }, - { - src: `/favicons/android-chrome-512x512.png`, - sizes: `512x512`, - type: `image/png`, - }, - ], +However, if you want to include more or fewer sizes, then the hybrid option is for you. Like automatic mode, you should include a high resolution icon to generate smaller icons from. But unlike automatic mode, you provide the `icons` array config and icons are generated based on the sizes defined in your config. Here's an example `gatsby-config.js`: + +```javascript:title=gatsby-config.js +module.exports = { + plugins: [ + { + resolve: `gatsby-plugin-manifest`, + options: { + name: `GatsbyJS`, + short_name: `GatsbyJS`, + start_url: `/`, + background_color: `#f7f0eb`, + theme_color: `#a2466c`, + display: `minimal-ui`, + icon: `src/images/icon.png`, // This path is relative to the root of the site. + icons: [ + { + src: `/favicons/android-chrome-192x192.png`, + sizes: `192x192`, + type: `image/png`, + }, + { + src: `/favicons/android-chrome-512x512.png`, + sizes: `512x512`, + type: `image/png`, + }, + ], + }, }, - }, -] + ], +} ``` The hybrid option allows the most flexibility while still not requiring you to create most icons sizes manually. ### Manual mode -In the manual mode, you are responsible for defining the entire web app manifest and providing the defined icons in the static directory. Only icons you provide will be available. There is no automatic resizing done for you. See the example below: +In the manual mode, you are responsible for defining the entire web app manifest and providing the defined icons in the static directory. Only icons you provide will be available. There is no automatic resizing done for you. See the example `gatsby-config.js` below: + +```javascript:title=gatsby-config.js +module.exports = { + plugins: [ + { + resolve: `gatsby-plugin-manifest`, + options: { + name: `GatsbyJS`, + short_name: `GatsbyJS`, + start_url: `/`, + background_color: `#f7f0eb`, + theme_color: `#a2466c`, + display: `minimal-ui`, + icons: [ + { + // Everything in /static will be copied to an equivalent + // directory in /public during development and build, so + // assuming your favicons are in /favicons, + // you can reference them here + src: `/favicons/android-chrome-192x192.png`, + sizes: `192x192`, + type: `image/png`, + }, + { + src: `/favicons/android-chrome-512x512.png`, + sizes: `512x512`, + type: `image/png`, + }, + ], + }, + }, + ], +} +``` -```javascript -// In your gatsby-config.js -plugins: [ - { - resolve: `gatsby-plugin-manifest`, - options: { - name: `GatsbyJS`, - short_name: `GatsbyJS`, - start_url: `/`, - background_color: `#f7f0eb`, - theme_color: `#a2466c`, - display: `minimal-ui`, - icons: [ - { - // Everything in /static will be copied to an equivalent - // directory in /public during development and build, so - // assuming your favicons are in /favicons, - // you can reference them here - src: `/favicons/android-chrome-192x192.png`, - sizes: `192x192`, - type: `image/png`, - }, - { - src: `/favicons/android-chrome-512x512.png`, - sizes: `512x512`, - type: `image/png`, - }, - ], +## Legacy `apple-touch-icon` links + +iOS 11.3 added support for webmanifest spec, so this plugin doesn't add `apple-touch-icon` links to `` by default. If you need or want to support older version of iOS you can set `legacy` option to `true` in plugin configuration: + +```javascript:title=gatsby-config.js +module.exports = { + plugins: [ + { + resolve: `gatsby-plugin-manifest`, + options: { + name: `GatsbyJS`, + short_name: `GatsbyJS`, + start_url: `/`, + background_color: `#f7f0eb`, + theme_color: `#a2466c`, + display: `minimal-ui`, + icon: `src/images/icon.png`, // This path is relative to the root of the site. + legacy: true, // this will add apple-touch-icon links to + }, }, - }, -] + ], +} ``` diff --git a/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-ssr.js.snap b/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-ssr.js.snap new file mode 100644 index 0000000000000..b35baa328e7c9 --- /dev/null +++ b/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-ssr.js.snap @@ -0,0 +1,102 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`gatsby-plugin-manifest Adds "shortcut icon" and "manifest" links and "theme_color" meta tag to head 1`] = ` +Array [ + , + , + , +] +`; + +exports[`gatsby-plugin-manifest Creates legacy apple touch links if opted in Using default set of icons 1`] = ` +Array [ + , + , + , + , + , + , + , + , + , + , + , +] +`; + +exports[`gatsby-plugin-manifest Creates legacy apple touch links if opted in Using user specified list of icons 1`] = ` +Array [ + , + , + , + , + , +] +`; diff --git a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js index b1b478a28e7a9..b3ac1c1b66d7d 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js +++ b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js @@ -10,6 +10,10 @@ const path = require(`path`) const { onPostBootstrap } = require(`../gatsby-node`) describe(`Test plugin manifest options`, () => { + beforeEach(() => { + fs.writeFileSync.mockReset() + }) + it(`correctly works with default parameters`, async () => { await onPostBootstrap([], { name: `GatsbyJS`, @@ -23,6 +27,7 @@ describe(`Test plugin manifest options`, () => { expect(filePath).toEqual(path.join(`public`, `manifest.webmanifest`)) expect(contents).toMatchSnapshot() }) + it(`fails on non existing icon`, done => { fs.statSync.mockReturnValueOnce({ isFile: () => false }) onPostBootstrap([], { @@ -45,4 +50,34 @@ describe(`Test plugin manifest options`, () => { done() }) }) + + it(`doesn't write extra properties to manifest`, async () => { + const manifestOptions = { + name: `GatsbyJS`, + short_name: `GatsbyJS`, + start_url: `/`, + background_color: `#f7f0eb`, + theme_color: `#a2466c`, + display: `minimal-ui`, + icons: [ + { + src: `icons/icon-48x48.png`, + sizes: `48x48`, + type: `image/png`, + }, + ], + } + const pluginSpecificOptions = { + icon: undefined, + legacy: true, + plugins: [], + } + await onPostBootstrap([], { + ...manifestOptions, + ...pluginSpecificOptions, + }) + + const content = JSON.parse(fs.writeFileSync.mock.calls[0][1]) + expect(content).toEqual(manifestOptions) + }) }) diff --git a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js new file mode 100644 index 0000000000000..1b578fec00ee2 --- /dev/null +++ b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js @@ -0,0 +1,71 @@ +// const { shallow } = require(`enzyme`) +const { onRenderBody } = require(`../gatsby-ssr`) + +let headComponents +const setHeadComponents = args => (headComponents = headComponents.concat(args)) + +const ssrArgs = { + setHeadComponents, +} + +describe(`gatsby-plugin-manifest`, () => { + beforeEach(() => { + global.__PATH_PREFIX__ = `` + headComponents = [] + }) + + it(`Adds "shortcut icon" and "manifest" links and "theme_color" meta tag to head`, () => { + onRenderBody(ssrArgs, { icon: true, theme_color: `#000000` }) + expect(headComponents).toMatchSnapshot() + }) + + describe(`Creates legacy apple touch links if opted in`, () => { + it(`Using default set of icons`, () => { + onRenderBody(ssrArgs, { + icon: true, + theme_color: `#000000`, + legacy: true, + }) + expect(headComponents).toMatchSnapshot() + }) + + it(`Using user specified list of icons`, () => { + onRenderBody(ssrArgs, { + icon: true, + theme_color: `#000000`, + legacy: true, + icons: [ + { + src: `/favicons/android-chrome-48x48.png`, + sizes: `48x48`, + type: `image/png`, + }, + { + src: `/favicons/android-chrome-512x512.png`, + sizes: `512x512`, + type: `image/png`, + }, + ], + }) + expect(headComponents).toMatchSnapshot() + }) + }) + + it(`Creates href attributes using pathPrefix`, () => { + global.__PATH_PREFIX__ = `/path-prefix` + + onRenderBody(ssrArgs, { + icon: true, + theme_color: `#000000`, + legacy: true, + }) + + headComponents + .filter(component => component.type === `link`) + .forEach(component => { + expect(component.props.href).toEqual( + expect.stringMatching(/^\/path-prefix\//) + ) + }) + }) +}) diff --git a/packages/gatsby-plugin-manifest/src/gatsby-node.js b/packages/gatsby-plugin-manifest/src/gatsby-node.js index 9f23cd5b09678..5d26997e93b3b 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-node.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-node.js @@ -20,12 +20,11 @@ function generateIcons(icons, srcIcon) { exports.onPostBootstrap = (args, pluginOptions) => new Promise((resolve, reject) => { - const { icon } = pluginOptions - const manifest = { ...pluginOptions } + const { icon, ...manifest } = pluginOptions // Delete options we won't pass to the manifest.webmanifest. delete manifest.plugins - delete manifest.icon + delete manifest.legacy // If icons are not manually defined, use the default icon set. if (!manifest.icons) { diff --git a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js index 65952e21f4362..8f01b191883cb 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js @@ -1,25 +1,25 @@ import React from "react" import { withPrefix } from "gatsby" +import { defaultIcons } from "./common.js" exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => { + const icons = pluginOptions.icons || defaultIcons + // If icons were generated, also add a favicon link. if (pluginOptions.icon) { - let favicon = `/icons/icon-48x48.png` + let favicon = icons && icons.length ? icons[0].src : null - // The icon path could be different in hybrid mode - // this takes the first one of the possible icons - if (pluginOptions.icons && pluginOptions.icons.length) { - favicon = pluginOptions.icons[0].src + if (favicon) { + setHeadComponents([ + , + ]) } - - setHeadComponents([ - , - ]) } + setHeadComponents([ { content={pluginOptions.theme_color} />, ]) + + if (pluginOptions.legacy) { + setHeadComponents( + icons.map(icon => ( + + )) + ) + } }