From 5f656f83ce70a0f034f1cefb101318ac9f1cd9b9 Mon Sep 17 00:00:00 2001 From: github0013 Date: Tue, 12 Mar 2019 18:28:45 +0900 Subject: [PATCH] feat(gatsby-plugin-manifest): add cache busting to icon url (#8343) * change cacheId generation only when the icon file is available * fix linter yarn lint:js --fix * update peerDependencies * change to createContentDigest * use fs-extra for consistency * feat: add cache busting used internal digest generator till the global one is available converted the node api to use ASYNC await to simplify new code added cache busting to favicon, manifest, and legacy head links * test: add tests for cache busting * Docs: added docs for cache busting to gatsby-plugin-manifest * test: fix createContentDigest test failing * fix: generateIcons not called when cache busting disabled * test: add tests to confirm icons are being generated, or not as they're suppose to be. * fix peer dep * cleanup code * clenaup tests * fix windows test --- packages/gatsby-plugin-manifest/README.md | 57 +++- packages/gatsby-plugin-manifest/package.json | 2 +- .../__snapshots__/gatsby-node.js.snap | 2 - .../__snapshots__/gatsby-ssr.js.snap | 285 +++++++++++------- .../src/__tests__/common.js | 36 ++- .../src/__tests__/gatsby-node.js | 84 ++++-- .../src/__tests__/gatsby-ssr.js | 17 +- packages/gatsby-plugin-manifest/src/common.js | 37 ++- .../gatsby-plugin-manifest/src/gatsby-node.js | 94 +++--- .../gatsby-plugin-manifest/src/gatsby-ssr.js | 29 +- 10 files changed, 443 insertions(+), 200 deletions(-) diff --git a/packages/gatsby-plugin-manifest/README.md b/packages/gatsby-plugin-manifest/README.md index 56fdc3fa8ce7d..be32c6d9f238c 100644 --- a/packages/gatsby-plugin-manifest/README.md +++ b/packages/gatsby-plugin-manifest/README.md @@ -35,7 +35,8 @@ There are three modes in which icon generation can function: automatic, hybrid, 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 +```js +// in gatsby-config.js module.exports = { plugins: [ { @@ -110,7 +111,8 @@ The automatic mode is the easiest option for most people. 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 +```js +// in gatsby-config.js module.exports = { plugins: [ { @@ -149,7 +151,8 @@ The hybrid option allows the most flexibility while still not requiring you to c 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 +```js +// in gatsby-config.js module.exports = { plugins: [ { @@ -189,7 +192,8 @@ module.exports = { iOS 11.3 added support for service workers but not the complete webmanifest spec. Therefore iOS won't recognize the icons defined in the webmanifest and the creation of `apple-touch-icon` links in `` is needed. This plugin creates them by default. If you don't want those icons to be generated you can set the `legacy` option to `false` in plugin configuration: -```javascript:title=gatsby-config.js +```js +// in gatsby-config.js module.exports = { plugins: [ { @@ -213,7 +217,8 @@ module.exports = { By default `gatsby-plugin-manifest` inserts `` tag to html output. This can be problematic if you want to programatically control that tag - for example when implementing light/dark themes in your project. You can set `theme_color_in_head` plugin option to `false` to opt-out of this behavior. -```javascript:title=gatsby-config.js +```js +// in gatsby-config.js module.exports = { plugins: [ { @@ -237,7 +242,8 @@ module.exports = { Excludes `` link tag to html output. You can set `include_favicon` plugin option to `false` to opt-out of this behaviour. -```javascript:title=gatsby-config.js +```js +// in gatsby-config.js module.exports = { plugins: [ { @@ -250,7 +256,6 @@ module.exports = { theme_color: `#a2466c`, display: `standalone`, icon: `src/images/icon.png`, // This path is relative to the root of the site. - theme_color_in_head: false, // This will avoid adding theme-color meta tag. include_favicon: false, // This will exclude favicon link tag }, }, @@ -258,6 +263,41 @@ module.exports = { } ``` +## Disabling or changing "[Cache Busting](https://www.keycdn.com/support/what-is-cache-busting)" Mode + +Cache Busting allows your updated icon to be quickly/easily visible to your sites visitors. HTTP caches could otherwise keep an old Icon around for days and weeks. Cache busting is only done in 'automatic' and 'hybrid' modes. + +Cache busting works by calculating a unique "digest" or "hash" of the provided icon and modifying links and file names of generated images with that unique digest. If you ever update your icon, the digest will change and caches will be busted. + +**Options:** + +- **\`query\`** - This is the default mode. File names are unmodified but a URL query is appended to all links. e.g. `icons/icon-48x48.png?digest=abc123` + +- **\`name\`** - Changes the cache busting mode to be done by file name. File names and links are modified with the icon digest. e.g. `icons/icon-48x48-abc123.png` (only needed if your CDN does not support URL query based cache busting) + +- **\`none\`** - Disables cache busting. File names and links remain unmodified. + +```js +// in 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: `standalone`, + icon: `src/images/icon.png`, // This path is relative to the root of the site. + cache_busting_mode: `none`, // `none`, `name` or `query` + }, + }, + ], +} +``` + ## Enable CORS using `crossorigin` attribute Add a `crossorigin` attribute to the manifest `` link tag. @@ -268,7 +308,8 @@ You can find more information about `crossorigin` on MDN. [https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) -```javascript:title=gatsby-config.js +```js +// in gatsby-config.js module.exports = { plugins: [ { diff --git a/packages/gatsby-plugin-manifest/package.json b/packages/gatsby-plugin-manifest/package.json index 227d58572fabb..a5fd72080ba89 100644 --- a/packages/gatsby-plugin-manifest/package.json +++ b/packages/gatsby-plugin-manifest/package.json @@ -30,7 +30,7 @@ "license": "MIT", "main": "index.js", "peerDependencies": { - "gatsby": "^2.0.0" + "gatsby": "^2.0.15" }, "repository": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-manifest", "scripts": { diff --git a/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-node.js.snap b/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-node.js.snap index 3dbff6ee5d17a..3f6f3d8ce0ac6 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-node.js.snap +++ b/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-node.js.snap @@ -1,5 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Test plugin manifest options correctly works with default parameters 1`] = `"{\\"name\\":\\"GatsbyJS\\",\\"short_name\\":\\"GatsbyJS\\",\\"start_url\\":\\"/\\",\\"background_color\\":\\"#f7f0eb\\",\\"theme_color\\":\\"#a2466c\\",\\"display\\":\\"standalone\\",\\"icons\\":[{\\"src\\":\\"icons/icon-48x48.png\\",\\"sizes\\":\\"48x48\\",\\"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-144x144.png\\",\\"sizes\\":\\"144x144\\",\\"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-384x384.png\\",\\"sizes\\":\\"384x384\\",\\"type\\":\\"image/png\\"},{\\"src\\":\\"icons/icon-512x512.png\\",\\"sizes\\":\\"512x512\\",\\"type\\":\\"image/png\\"}]}"`; - -exports[`Test plugin manifest options fails on non existing icon 1`] = `"icon (non/existing/path) does not exist as defined in gatsby-config.js. Make sure the file exists relative to the root of the site."`; 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 index 8dd12bae0a008..5e1f435516377 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-ssr.js.snap +++ b/packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-ssr.js.snap @@ -11,42 +11,42 @@ Array [ name="theme-color" />, , , , , , , , , @@ -56,7 +56,7 @@ Array [ exports[`gatsby-plugin-manifest Adds "shortcut icon" and "manifest" links and "theme_color" meta tag to head 1`] = ` Array [ , , , , , , , , , , @@ -121,42 +121,42 @@ Array [ name="theme-color" />, , , , , , , , , @@ -171,52 +171,52 @@ Array [ rel="manifest" />, , , , , , , , , ] `; -exports[`gatsby-plugin-manifest Adds link favicon if "include_favicon" option is not provided 1`] = ` +exports[`gatsby-plugin-manifest Adds link favicon tag if "include_favicon" is set to true 1`] = ` Array [ , , , , , , , , , , ] `; -exports[`gatsby-plugin-manifest Adds link favicon tag if "include_favicon" is set to true 1`] = ` +exports[`gatsby-plugin-manifest Creates legacy apple touch links Using default set of icons 1`] = ` Array [ , , + , , , , , , , , , ] `; -exports[`gatsby-plugin-manifest Creates legacy apple touch links Using default set of icons 1`] = ` +exports[`gatsby-plugin-manifest Creates legacy apple touch links Using user specified list of icons 1`] = ` Array [ , , , , +] +`; + +exports[`gatsby-plugin-manifest Does file name cache busting if "cache_busting_mode" option is set to name 1`] = ` +Array [ , , , , , , -] -`; - -exports[`gatsby-plugin-manifest Creates legacy apple touch links Using user specified list of icons 1`] = ` -Array [ , , - , , , @@ -410,52 +410,52 @@ Array [ rel="manifest" />, , , , , , , , , ] `; -exports[`gatsby-plugin-manifest Does not add a "theme_color" meta tag to head if "theme_color" option is not provided or is an empty string 1`] = ` +exports[`gatsby-plugin-manifest Does not add a "theme_color" meta tag to head if "theme_color" option is not provided or is an empty string, Adds link favicon if "include_favicon" option is not provided 1`] = ` Array [ , , , , , , , , , , @@ -512,42 +512,42 @@ Array [ rel="manifest" />, , , , , , , , , @@ -557,7 +557,7 @@ Array [ exports[`gatsby-plugin-manifest Does not create legacy apple touch links If "legacy" options is false and using default set of icons 1`] = ` Array [ , , , ] `; + +exports[`gatsby-plugin-manifest doesn't add cache busting if "cache_busting_mode" option is set to none 1`] = ` +Array [ + , + , + , + , + , + , + , + , + , + , +] +`; diff --git a/packages/gatsby-plugin-manifest/src/__tests__/common.js b/packages/gatsby-plugin-manifest/src/__tests__/common.js index 40f71ef63d9b3..218202b9dab45 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/common.js +++ b/packages/gatsby-plugin-manifest/src/__tests__/common.js @@ -1,5 +1,10 @@ const path = require(`path`) -const { defaultIcons, doesIconExist } = require(`../common`) +const { + defaultIcons, + doesIconExist, + createContentDigest, + addDigestToPath, +} = require(`../common`) describe(`gatsby-plugin-manifest`, () => { describe(`defaultIcons`, () => { @@ -19,4 +24,33 @@ describe(`gatsby-plugin-manifest`, () => { expect(doesIconExist(iconSrc)).toBeFalsy() }) }) + + describe(`createContentDigest`, () => { + it(`returns valid digest`, () => { + const iconSrc = `thisIsSomethingToHash` + expect(createContentDigest(iconSrc)).toBe( + `24ac9308d3adace282339005aff676bd1576f061` + ) + }) + }) + + describe(`addDigestToPath`, () => { + it(`returns unmodified path`, () => { + expect( + addDigestToPath(`icons/icon-48x48.png`, `thisismydigest`, `none`) + ).toBe(`icons/icon-48x48.png`) + }) + + it(`returns query modified path`, () => { + expect( + addDigestToPath(`icons/icon-48x48.png`, `thisismydigest`, `query`) + ).toBe(`icons/icon-48x48.png?v=thisismydigest`) + }) + + it(`returns fileName modified path`, () => { + expect( + addDigestToPath(`icons/icon-48x48.png`, `thisismydigest`, `name`) + ).toBe(`icons/icon-48x48-thisismydigest.png`) + }) + }) }) diff --git a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js index ed5bcbf198233..7ae8021f9e184 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js +++ b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js @@ -2,6 +2,7 @@ jest.mock(`fs`, () => { return { existsSync: jest.fn().mockImplementation(() => true), writeFileSync: jest.fn(), + readFileSync: jest.fn().mockImplementation(() => `someIconImage`), statSync: jest.fn(), } }) @@ -25,14 +26,32 @@ jest.mock(`sharp`, () => { sharp.concurrency = jest.fn() return sharp }) + const fs = require(`fs`) const path = require(`path`) const sharp = require(`sharp`) const { onPostBootstrap } = require(`../gatsby-node`) +const manifestOptions = { + name: `GatsbyJS`, + short_name: `GatsbyJS`, + start_url: `/`, + background_color: `#f7f0eb`, + theme_color: `#a2466c`, + display: `standalone`, + icons: [ + { + src: `icons/icon-48x48.png`, + sizes: `48x48`, + type: `image/png`, + }, + ], +} + describe(`Test plugin manifest options`, () => { beforeEach(() => { fs.writeFileSync.mockReset() + sharp.mockClear() }) // the require of gatsby-node performs the invoking @@ -51,6 +70,7 @@ describe(`Test plugin manifest options`, () => { }) const [filePath, contents] = fs.writeFileSync.mock.calls[0] expect(filePath).toEqual(path.join(`public`, `manifest.webmanifest`)) + expect(sharp).toHaveBeenCalledTimes(0) expect(contents).toMatchSnapshot() }) @@ -78,11 +98,13 @@ describe(`Test plugin manifest options`, () => { }) expect(sharp).toHaveBeenCalledWith(icon, { density: size }) + expect(sharp).toHaveBeenCalledTimes(1) }) - it(`fails on non existing icon`, done => { + it(`fails on non existing icon`, async () => { fs.statSync.mockReturnValueOnce({ isFile: () => false }) - onPostBootstrap([], { + + return onPostBootstrap([], { name: `GatsbyJS`, short_name: `GatsbyJS`, start_url: `/`, @@ -98,38 +120,62 @@ describe(`Test plugin manifest options`, () => { }, ], }).catch(err => { - expect(err).toMatchSnapshot() - done() + expect(sharp).toHaveBeenCalledTimes(0) + expect(err).toBe( + `icon (non/existing/path) does not exist as defined in gatsby-config.js. Make sure the file exists relative to the root of the site.` + ) }) }) 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: `standalone`, - icons: [ - { - src: `icons/icon-48x48.png`, - sizes: `48x48`, - type: `image/png`, - }, - ], - } const pluginSpecificOptions = { icon: undefined, legacy: true, plugins: [], theme_color_in_head: false, + cache_busting_mode: `name`, + } + await onPostBootstrap([], { + ...manifestOptions, + ...pluginSpecificOptions, + }) + expect(sharp).toHaveBeenCalledTimes(0) + const content = JSON.parse(fs.writeFileSync.mock.calls[0][1]) + expect(content).toEqual(manifestOptions) + }) + + it(`does file name based cache busting`, async () => { + fs.statSync.mockReturnValueOnce({ isFile: () => true }) + + const pluginSpecificOptions = { + icon: `images/gatsby-logo.png`, + legacy: true, + cache_busting_mode: `name`, + } + await onPostBootstrap([], { + ...manifestOptions, + ...pluginSpecificOptions, + }) + + expect(sharp).toHaveBeenCalledTimes(1) + const content = JSON.parse(fs.writeFileSync.mock.calls[0][1]) + expect(content).toEqual(manifestOptions) + }) + + it(`does not do cache cache busting`, async () => { + fs.statSync.mockReturnValueOnce({ isFile: () => true }) + + const pluginSpecificOptions = { + icon: `images/gatsby-logo.png`, + legacy: true, + cache_busting_mode: `none`, } await onPostBootstrap([], { ...manifestOptions, ...pluginSpecificOptions, }) + expect(sharp).toHaveBeenCalledTimes(1) 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 index 87c367e5ec86b..61f2ae02bbfdb 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js +++ b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js @@ -1,3 +1,9 @@ +jest.mock(`fs`, () => { + return { + readFileSync: jest.fn().mockImplementation(() => `someIconImage`), + } +}) + const { onRenderBody } = require(`../gatsby-ssr`) let headComponents @@ -52,7 +58,7 @@ describe(`gatsby-plugin-manifest`, () => { expect(headComponents).toMatchSnapshot() }) - it(`Adds link favicon if "include_favicon" option is not provided`, () => { + it(`Does not add a "theme_color" meta tag to head if "theme_color" option is not provided or is an empty string, Adds link favicon if "include_favicon" option is not provided`, () => { onRenderBody(ssrArgs, { icon: true }) expect(headComponents).toMatchSnapshot() }) @@ -62,8 +68,13 @@ describe(`gatsby-plugin-manifest`, () => { expect(headComponents).toMatchSnapshot() }) - it(`Does not add a "theme_color" meta tag to head if "theme_color" option is not provided or is an empty string`, () => { - onRenderBody(ssrArgs, { icon: true }) + it(`doesn't add cache busting if "cache_busting_mode" option is set to none`, () => { + onRenderBody(ssrArgs, { icon: true, cache_busting_mode: `none` }) + expect(headComponents).toMatchSnapshot() + }) + + it(`Does file name cache busting if "cache_busting_mode" option is set to name`, () => { + onRenderBody(ssrArgs, { icon: true, cache_busting_mode: `name` }) expect(headComponents).toMatchSnapshot() }) diff --git a/packages/gatsby-plugin-manifest/src/common.js b/packages/gatsby-plugin-manifest/src/common.js index 682f8453f223a..ae4d6b58fe4cf 100644 --- a/packages/gatsby-plugin-manifest/src/common.js +++ b/packages/gatsby-plugin-manifest/src/common.js @@ -1,4 +1,6 @@ -const fs = require(`fs`) +import fs from "fs" +import sysPath from "path" +import crypto from "crypto" // default icons for generating icons exports.defaultIcons = [ @@ -53,10 +55,37 @@ exports.doesIconExist = function doesIconExist(srcIcon) { try { return fs.statSync(srcIcon).isFile() } catch (e) { - if (e.code === `ENOENT`) { - return false - } else { + if (e.code !== `ENOENT`) { throw e } + + return false } } + +exports.createContentDigest = function createContentDigest(content) { + let digest = crypto + .createHash(`sha1`) + .update(content) + .digest(`hex`) + + return digest +} + +/** + * @param {string} path The generic path to an icon + * @param {string} digest The digest of the icon provided in the plugin's options. + */ +exports.addDigestToPath = function(path, digest, method) { + if (method === `name`) { + const parsedPath = sysPath.parse(path) + + return `${parsedPath.dir}/${parsedPath.name}-${digest}${parsedPath.ext}` + } + + if (method === `query`) { + return `${path}?v=${digest}` + } + + return path +} diff --git a/packages/gatsby-plugin-manifest/src/gatsby-node.js b/packages/gatsby-plugin-manifest/src/gatsby-node.js index 4823741070619..b7a0c8252e016 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-node.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-node.js @@ -2,7 +2,12 @@ const fs = require(`fs`) const path = require(`path`) const Promise = require(`bluebird`) const sharp = require(`sharp`) -const { defaultIcons, doesIconExist } = require(`./common.js`) +const { + defaultIcons, + doesIconExist, + addDigestToPath, + createContentDigest, +} = require(`./common.js`) sharp.simd(true) @@ -34,49 +39,64 @@ function generateIcons(icons, srcIcon) { }) } -exports.onPostBootstrap = (args, pluginOptions) => - new Promise((resolve, reject) => { - const { icon, ...manifest } = pluginOptions +exports.onPostBootstrap = async (args, pluginOptions) => { + const { icon, ...manifest } = pluginOptions - // Delete options we won't pass to the manifest.webmanifest. + // Delete options we won't pass to the manifest.webmanifest. + delete manifest.plugins + delete manifest.legacy + delete manifest.theme_color_in_head + delete manifest.cache_busting_mode + delete manifest.crossOrigin - delete manifest.plugins - delete manifest.legacy - delete manifest.theme_color_in_head - delete manifest.crossOrigin + // If icons are not manually defined, use the default icon set. + if (!manifest.icons) { + manifest.icons = defaultIcons + } - // If icons are not manually defined, use the default icon set. - if (!manifest.icons) { - manifest.icons = defaultIcons + // Determine destination path for icons. + const iconPath = path.join(`public`, path.dirname(manifest.icons[0].src)) + + //create destination directory if it doesn't exist + if (!fs.existsSync(iconPath)) { + fs.mkdirSync(iconPath) + } + + // Only auto-generate icons if a src icon is defined. + if (icon !== undefined) { + // Check if the icon exists + if (!doesIconExist(icon)) { + throw `icon (${icon}) does not exist as defined in gatsby-config.js. Make sure the file exists relative to the root of the site.` } - // Determine destination path for icons. - const iconPath = path.join(`public`, path.dirname(manifest.icons[0].src)) + //add cache busting + const cacheMode = + typeof pluginOptions.cache_busting_mode !== `undefined` + ? pluginOptions.cache_busting_mode + : `query` - //create destination directory if it doesn't exist - if (!fs.existsSync(iconPath)) { - fs.mkdirSync(iconPath) + //if cacheBusting is being done via url query icons must be generated before cache busting runs + if (cacheMode === `query`) { + await generateIcons(manifest.icons, icon) } - fs.writeFileSync( - path.join(`public`, `manifest.webmanifest`), - JSON.stringify(manifest) - ) - - // Only auto-generate icons if a src icon is defined. - if (icon !== undefined) { - // Check if the icon exists - if (!doesIconExist(icon)) { - reject( - `icon (${icon}) does not exist as defined in gatsby-config.js. Make sure the file exists relative to the root of the site.` - ) - } - generateIcons(manifest.icons, icon).then(() => { - //images have been generated - console.log(`done generating icons for manifest`) - resolve() + if (cacheMode !== `none`) { + const iconDigest = createContentDigest(fs.readFileSync(icon)) + + manifest.icons.forEach(icon => { + icon.src = addDigestToPath(icon.src, iconDigest, cacheMode) }) - } else { - resolve() } - }) + + //if file names are being modified by cacheBusting icons must be generated after cache busting runs + if (cacheMode !== `query`) { + await generateIcons(manifest.icons, icon) + } + } + + //Write manifest + fs.writeFileSync( + path.join(`public`, `manifest.webmanifest`), + JSON.stringify(manifest) + ) +} diff --git a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js index 5053e167053ef..30ad8667919dd 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js @@ -1,6 +1,9 @@ import React from "react" import { withPrefix } from "gatsby" -import { defaultIcons } from "./common.js" +import { defaultIcons, createContentDigest, addDigestToPath } from "./common.js" +import fs from "fs" + +let iconDigest = null exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => { // We use this to build a final array to pass as the argument to setHeadComponents at the end of onRenderBody. @@ -10,10 +13,19 @@ exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => { const legacy = typeof pluginOptions.legacy !== `undefined` ? pluginOptions.legacy : true - // The user has an option to opt out of the favicon link tag being inserted into the head. + const cacheBusting = + typeof pluginOptions.cache_busting_mode !== `undefined` + ? pluginOptions.cache_busting_mode + : `query` + + // If icons were generated, also add a favicon link. if (pluginOptions.icon) { let favicon = icons && icons.length ? icons[0].src : null + if (cacheBusting !== `none`) { + iconDigest = createContentDigest(fs.readFileSync(pluginOptions.icon)) + } + const insertFaviconLinkTag = typeof pluginOptions.include_favicon !== `undefined` ? pluginOptions.include_favicon @@ -24,7 +36,7 @@ exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => { ) } @@ -42,11 +54,10 @@ exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => { // The user has an option to opt out of the theme_color meta tag being inserted into the head. if (pluginOptions.theme_color) { - let insertMetaTag = Object.keys(pluginOptions).includes( - `theme_color_in_head` - ) - ? pluginOptions.theme_color_in_head - : true + let insertMetaTag = + typeof pluginOptions.theme_color_in_head !== `undefined` + ? pluginOptions.theme_color_in_head + : true if (insertMetaTag) { headComponents.push( @@ -65,7 +76,7 @@ exports.onRenderBody = ({ setHeadComponents }, pluginOptions) => { key={`gatsby-plugin-manifest-apple-touch-icon-${icon.sizes}`} rel="apple-touch-icon" sizes={icon.sizes} - href={withPrefix(`${icon.src}`)} + href={withPrefix(addDigestToPath(icon.src, iconDigest, cacheBusting))} /> ))