Skip to content

Commit f99ae04

Browse files
moonmeisterYogi
and
Yogi
authored
feat(plugin-manifest): support SVG favicon (#25276)
Co-authored-by: Yogi <[email protected]>
1 parent 08d2d70 commit f99ae04

File tree

6 files changed

+96
-5
lines changed

6 files changed

+96
-5
lines changed

packages/gatsby-plugin-manifest/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ module.exports = {
238238

239239
#### Disable favicon
240240

241-
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).
241+
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).
242242

243243
You can set the `include_favicon` plugin option to `false` to opt-out of this behavior.
244244

packages/gatsby-plugin-manifest/src/__tests__/__snapshots__/gatsby-ssr.js.snap

+36
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,15 @@ Array [
3131

3232
exports[`gatsby-plugin-manifest Cache Busting Does file name cache busting if "cache_busting_mode" option is set to name 1`] = `
3333
Array [
34+
<link
35+
href="/favicon-00913339321ee5a854812aea11f8a5d4.svg"
36+
rel="icon"
37+
type="image/svg+xml"
38+
/>,
3439
<link
3540
href="/favicon-32x32-00913339321ee5a854812aea11f8a5d4.png"
3641
rel="icon"
42+
type="image/png"
3743
/>,
3844
<link
3945
href="/manifest.webmanifest"
@@ -84,9 +90,15 @@ Array [
8490

8591
exports[`gatsby-plugin-manifest Cache Busting Does query cache busting if "cache_busting_mode" option is set to query 1`] = `
8692
Array [
93+
<link
94+
href="/favicon.svg?v=00913339321ee5a854812aea11f8a5d4"
95+
rel="icon"
96+
type="image/svg+xml"
97+
/>,
8798
<link
8899
href="/favicon-32x32.png?v=00913339321ee5a854812aea11f8a5d4"
89100
rel="icon"
101+
type="image/png"
90102
/>,
91103
<link
92104
href="/manifest.webmanifest"
@@ -137,9 +149,15 @@ Array [
137149

138150
exports[`gatsby-plugin-manifest Cache Busting Does query cache busting if "cache_busting_mode" option is set to undefined 1`] = `
139151
Array [
152+
<link
153+
href="/favicon.svg?v=00913339321ee5a854812aea11f8a5d4"
154+
rel="icon"
155+
type="image/svg+xml"
156+
/>,
140157
<link
141158
href="/favicon-32x32.png?v=00913339321ee5a854812aea11f8a5d4"
142159
rel="icon"
160+
type="image/png"
143161
/>,
144162
<link
145163
href="/manifest.webmanifest"
@@ -190,9 +208,15 @@ Array [
190208

191209
exports[`gatsby-plugin-manifest Cache Busting doesn't add cache busting if "cache_busting_mode" option is set to none 1`] = `
192210
Array [
211+
<link
212+
href="/favicon.svg"
213+
rel="icon"
214+
type="image/svg+xml"
215+
/>,
193216
<link
194217
href="/favicon-32x32.png"
195218
rel="icon"
219+
type="image/png"
196220
/>,
197221
<link
198222
href="/manifest.webmanifest"
@@ -262,9 +286,15 @@ Array [
262286

263287
exports[`gatsby-plugin-manifest Favicon Adds link favicon tag if "include_favicon" is set to true 1`] = `
264288
Array [
289+
<link
290+
href="/favicon.svg"
291+
rel="icon"
292+
type="image/svg+xml"
293+
/>,
265294
<link
266295
href="/favicon-32x32.png"
267296
rel="icon"
297+
type="image/png"
268298
/>,
269299
<link
270300
href="/manifest.webmanifest"
@@ -424,9 +454,15 @@ Array [
424454

425455
exports[`gatsby-plugin-manifest Manifest Link Generation Adds "icon" and "manifest" links and "theme_color" meta tag to head 1`] = `
426456
Array [
457+
<link
458+
href="/favicon.svg?v=00913339321ee5a854812aea11f8a5d4"
459+
rel="icon"
460+
type="image/svg+xml"
461+
/>,
427462
<link
428463
href="/favicon-32x32.png?v=00913339321ee5a854812aea11f8a5d4"
429464
rel="icon"
465+
type="image/png"
430466
/>,
431467
<link
432468
href="/manifest.webmanifest"

packages/gatsby-plugin-manifest/src/__tests__/gatsby-node.js

+37
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ jest.mock(`fs`, () => {
44
writeFileSync: jest.fn(),
55
mkdirSync: jest.fn(),
66
readFileSync: jest.fn().mockImplementation(() => `someIconImage`),
7+
copyFileSync: jest.fn(),
78
statSync: jest.fn(),
89
}
910
})
@@ -27,6 +28,7 @@ jest.mock(`sharp`, () => {
2728
return {
2829
width: 128,
2930
height: 128,
31+
format: `png`,
3032
}
3133
}
3234
})()
@@ -98,6 +100,7 @@ describe(`Test plugin manifest options`, () => {
98100
fs.writeFileSync.mockReset()
99101
fs.mkdirSync.mockReset()
100102
fs.existsSync.mockReset()
103+
fs.copyFileSync.mockReset()
101104
sharp.mockClear()
102105
})
103106

@@ -225,6 +228,7 @@ describe(`Test plugin manifest options`, () => {
225228
// disabled by the `include_favicon` option.
226229
expect(sharp).toHaveBeenCalledTimes(2)
227230
expect(sharp).toHaveBeenCalledWith(icon, { density: size })
231+
expect(fs.copyFileSync).toHaveBeenCalledTimes(0)
228232
})
229233

230234
it(`fails on non existing icon`, async () => {
@@ -485,4 +489,37 @@ describe(`Test plugin manifest options`, () => {
485489
JSON.stringify(expectedResults[2])
486490
)
487491
})
492+
493+
it(`writes SVG to public if src icon is SVG`, async () => {
494+
sharp.mockReturnValueOnce({
495+
metadata: () => {
496+
return { format: `svg` }
497+
},
498+
})
499+
const icon = `this/is/an/icon.svg`
500+
const specificOptions = {
501+
...manifestOptions,
502+
icon: icon,
503+
}
504+
505+
await onPostBootstrap({ ...apiArgs }, specificOptions)
506+
507+
expect(fs.copyFileSync).toHaveBeenCalledWith(
508+
expect.stringContaining(`icon.svg`),
509+
expect.stringContaining(`favicon.svg`)
510+
)
511+
512+
expect(fs.copyFileSync).toHaveBeenCalledTimes(1)
513+
})
514+
515+
it(`does not write SVG to public if src icon is PNG`, async () => {
516+
const specificOptions = {
517+
...manifestOptions,
518+
icon: `this/is/an/icon.png`,
519+
}
520+
521+
await onPostBootstrap({ ...apiArgs }, specificOptions)
522+
523+
expect(fs.copyFileSync).toHaveBeenCalledTimes(0)
524+
})
488525
})

packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const onRenderBody = (args, pluginOptions) => {
1515
let headComponents
1616
const setHeadComponents = args => (headComponents = headComponents.concat(args))
1717

18-
const defaultIcon = `pretend/this/exists.png`
18+
const defaultIcon = `pretend/this/exists.svg`
19+
1920
const ssrArgs = {
2021
setHeadComponents,
2122
pathname: `/`,
@@ -276,7 +277,7 @@ describe(`gatsby-plugin-manifest`, () => {
276277
})
277278

278279
it(`Does query cache busting if "cache_busting_mode" option is set to undefined`, () => {
279-
onRenderBody(ssrArgs, { icon: true })
280+
onRenderBody(ssrArgs, { icon: defaultIcon })
280281
expect(headComponents).toMatchSnapshot()
281282
})
282283
})
@@ -285,7 +286,7 @@ describe(`gatsby-plugin-manifest`, () => {
285286
it(`Adds link favicon tag if "include_favicon" is set to true`, () => {
286287
onRenderBody(ssrArgs, {
287288
icon: defaultIcon,
288-
include_favicon: defaultIcon,
289+
include_favicon: true,
289290
legacy: false,
290291
cache_busting_mode: `none`,
291292
})

packages/gatsby-plugin-manifest/src/gatsby-node.js

+4
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ const makeManifest = async ({
253253
// the resized image(s)
254254
if (faviconIsEnabled) {
255255
await processIconSet(favicons)
256+
257+
if (metadata.format === `svg`) {
258+
fs.copyFileSync(icon, path.join(`public`, `favicon.svg`))
259+
}
256260
}
257261
}
258262

packages/gatsby-plugin-manifest/src/gatsby-ssr.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,27 @@ exports.onRenderBody = (
3131
// If icons were generated, also add a favicon link.
3232
if (srcIconExists) {
3333
if (insertFaviconLinkTag) {
34+
if (icon?.endsWith(`.svg`)) {
35+
headComponents.push(
36+
<link
37+
key={`gatsby-plugin-manifest-icon-link-svg`}
38+
rel="icon"
39+
href={withPrefix(
40+
addDigestToPath(`favicon.svg`, cacheDigest, cacheBusting)
41+
)}
42+
type="image/svg+xml"
43+
/>
44+
)
45+
}
3446
favicons.forEach(favicon => {
3547
headComponents.push(
3648
<link
37-
key={`gatsby-plugin-manifest-icon-link`}
49+
key={`gatsby-plugin-manifest-icon-link-png`}
3850
rel="icon"
3951
href={withPrefix(
4052
addDigestToPath(favicon.src, cacheDigest, cacheBusting)
4153
)}
54+
type="image/png"
4255
/>
4356
)
4457
})

0 commit comments

Comments
 (0)