Skip to content

Commit 275344e

Browse files
vtenfyssidharthachatterjee
authored andcommitted
feat(gatsby-plugin-offline): Allow appending custom scripts (#11626)
1 parent dc8e9f3 commit 275344e

File tree

7 files changed

+293
-121
lines changed

7 files changed

+293
-121
lines changed

packages/gatsby-plugin-offline/README.md

+93-10
Original file line numberDiff line numberDiff line change
@@ -19,44 +19,127 @@ in the service worker.
1919
plugins: [`gatsby-plugin-offline`]
2020
```
2121

22-
## Overriding options
22+
## Available options
2323

24-
When adding this plugin to your `gatsby-config.js`, you can pass in options (via the `options` key) to
25-
override the default [Workbox](https://developers.google.com/web/tools/workbox/modules/workbox-build) config.
24+
As of `gatsby-plugin-offline` 3.0.0, the following options are available:
2625

27-
The default config is as follows. Warning: You can break the offline support by
28-
changing these options, so tread carefully.
26+
- `appendScript` lets you specify a file to be appended at the end of the generated service worker (`sw.js`). For example:
27+
28+
```javascript:title=gatsby-config.js
29+
plugins: [
30+
{
31+
resolve: `gatsby-plugin-offline`,
32+
options: {
33+
appendScript: `src/custom-sw-code.js`,
34+
},
35+
},
36+
]
37+
```
38+
39+
```javascript:title=src/custom-sw-code.js
40+
// show a notification after 15 seconds (the notification
41+
// permission must be granted first)
42+
setTimeout(() => {
43+
self.registration.showNotification("Hello, world!")
44+
}, 15000)
45+
46+
// register a custom navigation route
47+
const customRoute = new workbox.routing.NavigationRoute(({ event }) => {
48+
// ...
49+
})
50+
workbox.routing.registerRoute(customRoute)
51+
```
52+
53+
- `workboxConfig` allows you to override the default Workbox options - see [Overriding Workbox configuration](#overriding-workbox-configuration). For example:
54+
55+
```javascript:title=gatsby-config.js
56+
plugins: [
57+
{
58+
resolve: `gatsby-plugin-offline`,
59+
options: {
60+
workboxConfig: {
61+
importWorkboxFrom: `cdn`,
62+
},
63+
},
64+
},
65+
]
66+
```
67+
68+
## Upgrading from 2.x
69+
70+
To upgrade from 2.x to 3.x, move any existing options into the `workboxConfig` option. If you haven't specified any options, you have nothing to do.
71+
72+
For example, here is a 2.x config:
73+
74+
```javascript
75+
plugins: [
76+
{
77+
resolve: `gatsby-plugin-offline`,
78+
options: {
79+
importWorkboxFrom: `cdn`,
80+
},
81+
},
82+
]
83+
```
84+
85+
Here is the equivalent 3.x config:
86+
87+
```javascript
88+
plugins: [
89+
{
90+
resolve: `gatsby-plugin-offline`,
91+
options: {
92+
workboxConfig: {
93+
importWorkboxFrom: `cdn`,
94+
},
95+
},
96+
},
97+
]
98+
```
99+
100+
In version 3, Workbox is also upgraded to version 4 so you may need to update your `workboxConfig` if any of those changes apply to you. Please see the [docs on Google Developers](https://developers.google.com/web/tools/workbox/guides/migrations/migrate-from-v3) for more information.
101+
102+
## Overriding Workbox configuration
103+
104+
When adding this plugin to your `gatsby-config.js`, you can use the option `workboxConfig` to override the default Workbox config. To see the full list of options, see [this article on Google Developers](https://developers.google.com/web/tools/workbox/modules/workbox-build#full_generatesw_config).
105+
106+
The default `workboxConfig` is as follows. Note that some of these options are configured automatically, e.g. `globPatterns`. If you're not sure about what all of these options mean, it's best to leave them as-is - otherwise, you may end up causing errors on your site, causing old files to be remain cached, or even breaking offline support.
29107

30108
```javascript
31109
const options = {
32110
importWorkboxFrom: `local`,
33111
globDirectory: rootDir,
34112
globPatterns,
35-
modifyUrlPrefix: {
113+
modifyURLPrefix: {
36114
// If `pathPrefix` is configured by user, we should replace
37115
// the default prefix with `pathPrefix`.
38116
"/": `${pathPrefix}/`,
39117
},
40118
cacheId: `gatsby-plugin-offline`,
41119
// Don't cache-bust JS or CSS files, and anything in the static directory,
42120
// since these files have unique URLs and their contents will never change
43-
dontCacheBustUrlsMatching: /(\.js$|\.css$|static\/)/,
121+
dontCacheBustURLsMatching: /(\.js$|\.css$|static\/)/,
44122
runtimeCaching: [
45123
{
46124
// Use cacheFirst since these don't need to be revalidated (same RegExp
47125
// and same reason as above)
48126
urlPattern: /(\.js$|\.css$|static\/)/,
49-
handler: `cacheFirst`,
127+
handler: `CacheFirst`,
128+
},
129+
{
130+
// page-data.json files are not content hashed
131+
urlPattern: /^https?:.*\page-data\/.*\/page-data\.json/,
132+
handler: `NetworkFirst`,
50133
},
51134
{
52135
// Add runtime caching of various other page resources
53136
urlPattern: /^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/,
54-
handler: `staleWhileRevalidate`,
137+
handler: `StaleWhileRevalidate`,
55138
},
56139
{
57140
// Google Fonts CSS (doesn't end in .css so we need to specify it)
58141
urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,
59-
handler: `staleWhileRevalidate`,
142+
handler: `StaleWhileRevalidate`,
60143
},
61144
],
62145
skipWaiting: true,

packages/gatsby-plugin-offline/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"idb-keyval": "^3.2.0",
1313
"lodash": "^4.17.15",
1414
"slash": "^3.0.0",
15-
"workbox-build": "^3.6.3"
15+
"workbox-build": "^4.3.1"
1616
},
1717
"devDependencies": {
1818
"@babel/cli": "^7.5.5",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log(`Hello, world!`)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const { onPostBuild } = require(`../gatsby-node`)
2+
const fs = require(`fs`)
3+
4+
jest.mock(`fs`)
5+
jest.mock(`../get-resources-from-html`, () => () => [])
6+
7+
let swText = ``
8+
9+
jest.mock(`workbox-build`, () => {
10+
return {
11+
generateSW() {
12+
return Promise.resolve({ count: 1, size: 1, warnings: [] })
13+
},
14+
}
15+
})
16+
17+
describe(`onPostBuild`, () => {
18+
const componentChunkName = `chunkName`
19+
const chunks = [`chunk1`, `chunk2`]
20+
21+
// Mock webpack.stats.json
22+
const stats = {
23+
assetsByChunkName: {
24+
[componentChunkName]: chunks,
25+
},
26+
}
27+
28+
// Mock out filesystem functions
29+
fs.readFileSync.mockImplementation(file => {
30+
if (file === `${process.cwd()}/public/webpack.stats.json`) {
31+
return JSON.stringify(stats)
32+
} else if (file === `public/sw.js`) {
33+
return swText
34+
} else if (file.match(/\/sw-append\.js/)) {
35+
return ``
36+
} else {
37+
return jest.requireActual(`fs`).readFileSync(file)
38+
}
39+
})
40+
41+
fs.appendFileSync.mockImplementation((file, text) => {
42+
swText += text
43+
})
44+
45+
fs.createReadStream.mockImplementation(() => {
46+
return { pipe() {} }
47+
})
48+
49+
fs.createWriteStream.mockImplementation(() => {})
50+
51+
it(`appends to sw.js`, async () => {
52+
await onPostBuild(
53+
{ pathPrefix: `` },
54+
{ appendScript: `${__dirname}/fixtures/custom-sw-code.js` }
55+
)
56+
57+
expect(swText).toContain(`console.log(\`Hello, world!\`)`)
58+
})
59+
})

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

+21-11
Original file line numberDiff line numberDiff line change
@@ -70,47 +70,46 @@ exports.onPostBuild = (args, pluginOptions) => {
7070
importWorkboxFrom: `local`,
7171
globDirectory: rootDir,
7272
globPatterns,
73-
modifyUrlPrefix: {
73+
modifyURLPrefix: {
7474
// If `pathPrefix` is configured by user, we should replace
7575
// the default prefix with `pathPrefix`.
7676
"/": `${pathPrefix}/`,
7777
},
7878
cacheId: `gatsby-plugin-offline`,
7979
// Don't cache-bust JS or CSS files, and anything in the static directory,
8080
// since these files have unique URLs and their contents will never change
81-
dontCacheBustUrlsMatching: /(\.js$|\.css$|static\/)/,
81+
dontCacheBustURLsMatching: /(\.js$|\.css$|static\/)/,
8282
runtimeCaching: [
8383
{
8484
// Use cacheFirst since these don't need to be revalidated (same RegExp
8585
// and same reason as above)
8686
urlPattern: /(\.js$|\.css$|static\/)/,
87-
handler: `cacheFirst`,
87+
handler: `CacheFirst`,
8888
},
8989
{
9090
// page-data.json files are not content hashed
9191
urlPattern: /^https?:.*\page-data\/.*\/page-data\.json/,
92-
handler: `networkFirst`,
92+
handler: `NetworkFirst`,
9393
},
9494
{
9595
// Add runtime caching of various other page resources
9696
urlPattern: /^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/,
97-
handler: `staleWhileRevalidate`,
97+
handler: `StaleWhileRevalidate`,
9898
},
9999
{
100100
// Google Fonts CSS (doesn't end in .css so we need to specify it)
101101
urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,
102-
handler: `staleWhileRevalidate`,
102+
handler: `StaleWhileRevalidate`,
103103
},
104104
],
105105
skipWaiting: true,
106106
clientsClaim: true,
107107
}
108108

109-
// pluginOptions.plugins is assigned automatically when the user hasn't
110-
// specified custom options - Workbox throws an error with unsupported
111-
// parameters, so delete it.
112-
delete pluginOptions.plugins
113-
const combinedOptions = _.defaults(pluginOptions, options)
109+
const combinedOptions = {
110+
...options,
111+
...pluginOptions.workboxConfig,
112+
}
114113

115114
const idbKeyvalFile = `idb-keyval-iife.min.js`
116115
const idbKeyvalSource = require.resolve(`idb-keyval/dist/${idbKeyvalFile}`)
@@ -129,6 +128,17 @@ exports.onPostBuild = (args, pluginOptions) => {
129128
.replace(/%appFile%/g, appFile)
130129

131130
fs.appendFileSync(`public/sw.js`, `\n` + swAppend)
131+
132+
if (pluginOptions.appendScript) {
133+
let userAppend
134+
try {
135+
userAppend = fs.readFileSync(pluginOptions.appendScript, `utf8`)
136+
} catch (e) {
137+
throw new Error(`Couldn't find the specified offline inject script`)
138+
}
139+
fs.appendFileSync(`public/sw.js`, `\n` + userAppend)
140+
}
141+
132142
console.log(
133143
`Generated ${swDest}, which will precache ${count} files, totaling ${size} bytes.`
134144
)

packages/gatsby-plugin-offline/src/sw-append.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const navigationRoute = new NavigationRoute(async ({ event }) => {
2525
}
2626

2727
const offlineShell = `%pathPrefix%/offline-plugin-app-shell-fallback/index.html`
28-
return await caches.match(offlineShell)
28+
const offlineShellWithKey = workbox.precaching.getCacheKeyForURL(offlineShell)
29+
return await caches.match(offlineShellWithKey)
2930
})
3031

3132
workbox.routing.registerRoute(navigationRoute)

0 commit comments

Comments
 (0)