Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-manifest): support SVG favicon #25276

Merged
merged 9 commits into from
Jul 23, 2020
2 changes: 1 addition & 1 deletion packages/gatsby-plugin-manifest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<link rel="icon" />` tag in the document head).
A favicon is generated by default in automatic and hybrid modes (a 32x32 PNG, included via a `<link rel="icon" />` 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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
<link
href="/favicon-00913339321ee5a854812aea11f8a5d4.svg"
rel="icon"
type="image/svg+xml"
/>,
<link
href="/favicon-32x32-00913339321ee5a854812aea11f8a5d4.png"
rel="icon"
type="image/png"
/>,
<link
href="/manifest.webmanifest"
Expand Down Expand Up @@ -84,9 +90,15 @@ Array [

exports[`gatsby-plugin-manifest Cache Busting Does query cache busting if "cache_busting_mode" option is set to query 1`] = `
Array [
<link
href="/favicon.svg?v=00913339321ee5a854812aea11f8a5d4"
rel="icon"
type="image/svg+xml"
/>,
<link
href="/favicon-32x32.png?v=00913339321ee5a854812aea11f8a5d4"
rel="icon"
type="image/png"
/>,
<link
href="/manifest.webmanifest"
Expand Down Expand Up @@ -137,9 +149,15 @@ Array [

exports[`gatsby-plugin-manifest Cache Busting Does query cache busting if "cache_busting_mode" option is set to undefined 1`] = `
Array [
<link
href="/favicon.svg?v=00913339321ee5a854812aea11f8a5d4"
rel="icon"
type="image/svg+xml"
/>,
<link
href="/favicon-32x32.png?v=00913339321ee5a854812aea11f8a5d4"
rel="icon"
type="image/png"
/>,
<link
href="/manifest.webmanifest"
Expand Down Expand Up @@ -190,9 +208,15 @@ Array [

exports[`gatsby-plugin-manifest Cache Busting doesn't add cache busting if "cache_busting_mode" option is set to none 1`] = `
Array [
<link
href="/favicon.svg"
rel="icon"
type="image/svg+xml"
/>,
<link
href="/favicon-32x32.png"
rel="icon"
type="image/png"
/>,
<link
href="/manifest.webmanifest"
Expand Down Expand Up @@ -262,9 +286,15 @@ Array [

exports[`gatsby-plugin-manifest Favicon Adds link favicon tag if "include_favicon" is set to true 1`] = `
Array [
<link
href="/favicon.svg"
rel="icon"
type="image/svg+xml"
/>,
<link
href="/favicon-32x32.png"
rel="icon"
type="image/png"
/>,
<link
href="/manifest.webmanifest"
Expand Down Expand Up @@ -424,9 +454,15 @@ Array [

exports[`gatsby-plugin-manifest Manifest Link Generation Adds "icon" and "manifest" links and "theme_color" meta tag to head 1`] = `
Array [
<link
href="/favicon.svg?v=00913339321ee5a854812aea11f8a5d4"
rel="icon"
type="image/svg+xml"
/>,
<link
href="/favicon-32x32.png?v=00913339321ee5a854812aea11f8a5d4"
rel="icon"
type="image/png"
/>,
<link
href="/manifest.webmanifest"
Expand Down
37 changes: 37 additions & 0 deletions packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ jest.mock(`fs`, () => {
writeFileSync: jest.fn(),
mkdirSync: jest.fn(),
readFileSync: jest.fn().mockImplementation(() => `someIconImage`),
copyFileSync: jest.fn(),
statSync: jest.fn(),
}
})
Expand All @@ -27,6 +28,7 @@ jest.mock(`sharp`, () => {
return {
width: 128,
height: 128,
format: `png`,
}
}
})()
Expand Down Expand Up @@ -98,6 +100,7 @@ describe(`Test plugin manifest options`, () => {
fs.writeFileSync.mockReset()
fs.mkdirSync.mockReset()
fs.existsSync.mockReset()
fs.copyFileSync.mockReset()
sharp.mockClear()
})

Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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)
})
})
7 changes: 4 additions & 3 deletions packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: `/`,
Expand Down Expand Up @@ -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()
})
})
Expand All @@ -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`,
})
Expand Down
4 changes: 4 additions & 0 deletions packages/gatsby-plugin-manifest/src/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`))
}
}
}

Expand Down
15 changes: 14 additions & 1 deletion packages/gatsby-plugin-manifest/src/gatsby-ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<link
key={`gatsby-plugin-manifest-icon-link-svg`}
rel="icon"
href={withPrefix(
addDigestToPath(`favicon.svg`, cacheDigest, cacheBusting)
)}
type="image/svg+xml"
/>
)
}
favicons.forEach(favicon => {
headComponents.push(
<link
key={`gatsby-plugin-manifest-icon-link`}
key={`gatsby-plugin-manifest-icon-link-png`}
rel="icon"
href={withPrefix(
addDigestToPath(favicon.src, cacheDigest, cacheBusting)
)}
type="image/png"
/>
)
})
Expand Down