From f99ae04ce5eb055d0a5a4853ed0fd7390e554cb9 Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Thu, 23 Jul 2020 10:41:44 -0700 Subject: [PATCH] feat(plugin-manifest): support SVG favicon (#25276) Co-authored-by: Yogi --- packages/gatsby-plugin-manifest/README.md | 2 +- .../__snapshots__/gatsby-ssr.js.snap | 36 ++++++++++++++++++ .../src/__tests__/gatsby-node.js | 37 +++++++++++++++++++ .../src/__tests__/gatsby-ssr.js | 7 ++-- .../gatsby-plugin-manifest/src/gatsby-node.js | 4 ++ .../gatsby-plugin-manifest/src/gatsby-ssr.js | 15 +++++++- 6 files changed, 96 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-plugin-manifest/README.md b/packages/gatsby-plugin-manifest/README.md index a697c04a6e4ff..cd3c725673bcf 100644 --- a/packages/gatsby-plugin-manifest/README.md +++ b/packages/gatsby-plugin-manifest/README.md @@ -238,7 +238,7 @@ module.exports = { #### Disable favicon -A favicon is generated by default in automatic and hybrid modes (a 32x32 PNG, included via a `` tag in the document head). +A favicon is generated by default in automatic and hybrid modes (a 32x32 PNG, included via a `` tag in the document head). Additionally, if an SVG icon is provided as the source, it will be used in the document head without modification as a favicon. The PNG will still be created and included as a fallback. Including the SVG icon allows creating a responsive icon with CSS Media Queries such as [dark mode](https://catalin.red/svg-favicon-light-dark-theme/#browser-support-and-fallbacks) and [others](https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/#other-media-queries). You can set the `include_favicon` plugin option to `false` to opt-out of this behavior. 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 f7b95fedf987e..52c5840f11c93 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 @@ -31,9 +31,15 @@ Array [ exports[`gatsby-plugin-manifest Cache Busting Does file name cache busting if "cache_busting_mode" option is set to name 1`] = ` Array [ + , , , , , , , , , , , , { writeFileSync: jest.fn(), mkdirSync: jest.fn(), readFileSync: jest.fn().mockImplementation(() => `someIconImage`), + copyFileSync: jest.fn(), statSync: jest.fn(), } }) @@ -27,6 +28,7 @@ jest.mock(`sharp`, () => { return { width: 128, height: 128, + format: `png`, } } })() @@ -98,6 +100,7 @@ describe(`Test plugin manifest options`, () => { fs.writeFileSync.mockReset() fs.mkdirSync.mockReset() fs.existsSync.mockReset() + fs.copyFileSync.mockReset() sharp.mockClear() }) @@ -225,6 +228,7 @@ describe(`Test plugin manifest options`, () => { // disabled by the `include_favicon` option. expect(sharp).toHaveBeenCalledTimes(2) expect(sharp).toHaveBeenCalledWith(icon, { density: size }) + expect(fs.copyFileSync).toHaveBeenCalledTimes(0) }) it(`fails on non existing icon`, async () => { @@ -485,4 +489,37 @@ describe(`Test plugin manifest options`, () => { JSON.stringify(expectedResults[2]) ) }) + + it(`writes SVG to public if src icon is SVG`, async () => { + sharp.mockReturnValueOnce({ + metadata: () => { + return { format: `svg` } + }, + }) + const icon = `this/is/an/icon.svg` + const specificOptions = { + ...manifestOptions, + icon: icon, + } + + await onPostBootstrap({ ...apiArgs }, specificOptions) + + expect(fs.copyFileSync).toHaveBeenCalledWith( + expect.stringContaining(`icon.svg`), + expect.stringContaining(`favicon.svg`) + ) + + expect(fs.copyFileSync).toHaveBeenCalledTimes(1) + }) + + it(`does not write SVG to public if src icon is PNG`, async () => { + const specificOptions = { + ...manifestOptions, + icon: `this/is/an/icon.png`, + } + + await onPostBootstrap({ ...apiArgs }, specificOptions) + + expect(fs.copyFileSync).toHaveBeenCalledTimes(0) + }) }) diff --git a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js index 159670b965e53..61bbabf6bd763 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js +++ b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js @@ -15,7 +15,8 @@ const onRenderBody = (args, pluginOptions) => { let headComponents const setHeadComponents = args => (headComponents = headComponents.concat(args)) -const defaultIcon = `pretend/this/exists.png` +const defaultIcon = `pretend/this/exists.svg` + const ssrArgs = { setHeadComponents, pathname: `/`, @@ -276,7 +277,7 @@ describe(`gatsby-plugin-manifest`, () => { }) it(`Does query cache busting if "cache_busting_mode" option is set to undefined`, () => { - onRenderBody(ssrArgs, { icon: true }) + onRenderBody(ssrArgs, { icon: defaultIcon }) expect(headComponents).toMatchSnapshot() }) }) @@ -285,7 +286,7 @@ describe(`gatsby-plugin-manifest`, () => { it(`Adds link favicon tag if "include_favicon" is set to true`, () => { onRenderBody(ssrArgs, { icon: defaultIcon, - include_favicon: defaultIcon, + include_favicon: true, legacy: false, cache_busting_mode: `none`, }) diff --git a/packages/gatsby-plugin-manifest/src/gatsby-node.js b/packages/gatsby-plugin-manifest/src/gatsby-node.js index 508e0d10fb3f5..f1504230109d9 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-node.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-node.js @@ -253,6 +253,10 @@ const makeManifest = async ({ // the resized image(s) if (faviconIsEnabled) { await processIconSet(favicons) + + if (metadata.format === `svg`) { + fs.copyFileSync(icon, path.join(`public`, `favicon.svg`)) + } } } diff --git a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js index 223bd199e3318..b35a5d0859bad 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js @@ -31,14 +31,27 @@ exports.onRenderBody = ( // If icons were generated, also add a favicon link. if (srcIconExists) { if (insertFaviconLinkTag) { + if (icon?.endsWith(`.svg`)) { + headComponents.push( + + ) + } favicons.forEach(favicon => { headComponents.push( ) })