Skip to content

Commit

Permalink
feat(gatsby-plugin-offline): Allow appending custom scripts (#11626)
Browse files Browse the repository at this point in the history
  • Loading branch information
vtenfys authored and sidharthachatterjee committed Aug 30, 2019
1 parent dc8e9f3 commit 275344e
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 121 deletions.
103 changes: 93 additions & 10 deletions packages/gatsby-plugin-offline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,44 +19,127 @@ in the service worker.
plugins: [`gatsby-plugin-offline`]
```

## Overriding options
## Available options

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

The default config is as follows. Warning: You can break the offline support by
changing these options, so tread carefully.
- `appendScript` lets you specify a file to be appended at the end of the generated service worker (`sw.js`). For example:

```javascript:title=gatsby-config.js
plugins: [
{
resolve: `gatsby-plugin-offline`,
options: {
appendScript: `src/custom-sw-code.js`,
},
},
]
```

```javascript:title=src/custom-sw-code.js
// show a notification after 15 seconds (the notification
// permission must be granted first)
setTimeout(() => {
self.registration.showNotification("Hello, world!")
}, 15000)
// register a custom navigation route
const customRoute = new workbox.routing.NavigationRoute(({ event }) => {
// ...
})
workbox.routing.registerRoute(customRoute)
```

- `workboxConfig` allows you to override the default Workbox options - see [Overriding Workbox configuration](#overriding-workbox-configuration). For example:

```javascript:title=gatsby-config.js
plugins: [
{
resolve: `gatsby-plugin-offline`,
options: {
workboxConfig: {
importWorkboxFrom: `cdn`,
},
},
},
]
```

## Upgrading from 2.x

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.
For example, here is a 2.x config:
```javascript
plugins: [
{
resolve: `gatsby-plugin-offline`,
options: {
importWorkboxFrom: `cdn`,
},
},
]
```
Here is the equivalent 3.x config:
```javascript
plugins: [
{
resolve: `gatsby-plugin-offline`,
options: {
workboxConfig: {
importWorkboxFrom: `cdn`,
},
},
},
]
```
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.
## Overriding Workbox configuration
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).
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.
```javascript
const options = {
importWorkboxFrom: `local`,
globDirectory: rootDir,
globPatterns,
modifyUrlPrefix: {
modifyURLPrefix: {
// If `pathPrefix` is configured by user, we should replace
// the default prefix with `pathPrefix`.
"/": `${pathPrefix}/`,
},
cacheId: `gatsby-plugin-offline`,
// Don't cache-bust JS or CSS files, and anything in the static directory,
// since these files have unique URLs and their contents will never change
dontCacheBustUrlsMatching: /(\.js$|\.css$|static\/)/,
dontCacheBustURLsMatching: /(\.js$|\.css$|static\/)/,
runtimeCaching: [
{
// Use cacheFirst since these don't need to be revalidated (same RegExp
// and same reason as above)
urlPattern: /(\.js$|\.css$|static\/)/,
handler: `cacheFirst`,
handler: `CacheFirst`,
},
{
// page-data.json files are not content hashed
urlPattern: /^https?:.*\page-data\/.*\/page-data\.json/,
handler: `NetworkFirst`,
},
{
// Add runtime caching of various other page resources
urlPattern: /^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/,
handler: `staleWhileRevalidate`,
handler: `StaleWhileRevalidate`,
},
{
// Google Fonts CSS (doesn't end in .css so we need to specify it)
urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,
handler: `staleWhileRevalidate`,
handler: `StaleWhileRevalidate`,
},
],
skipWaiting: true,
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby-plugin-offline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"idb-keyval": "^3.2.0",
"lodash": "^4.17.15",
"slash": "^3.0.0",
"workbox-build": "^3.6.3"
"workbox-build": "^4.3.1"
},
"devDependencies": {
"@babel/cli": "^7.5.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(`Hello, world!`)
59 changes: 59 additions & 0 deletions packages/gatsby-plugin-offline/src/__tests__/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const { onPostBuild } = require(`../gatsby-node`)
const fs = require(`fs`)

jest.mock(`fs`)
jest.mock(`../get-resources-from-html`, () => () => [])

let swText = ``

jest.mock(`workbox-build`, () => {
return {
generateSW() {
return Promise.resolve({ count: 1, size: 1, warnings: [] })
},
}
})

describe(`onPostBuild`, () => {
const componentChunkName = `chunkName`
const chunks = [`chunk1`, `chunk2`]

// Mock webpack.stats.json
const stats = {
assetsByChunkName: {
[componentChunkName]: chunks,
},
}

// Mock out filesystem functions
fs.readFileSync.mockImplementation(file => {
if (file === `${process.cwd()}/public/webpack.stats.json`) {
return JSON.stringify(stats)
} else if (file === `public/sw.js`) {
return swText
} else if (file.match(/\/sw-append\.js/)) {
return ``
} else {
return jest.requireActual(`fs`).readFileSync(file)
}
})

fs.appendFileSync.mockImplementation((file, text) => {
swText += text
})

fs.createReadStream.mockImplementation(() => {
return { pipe() {} }
})

fs.createWriteStream.mockImplementation(() => {})

it(`appends to sw.js`, async () => {
await onPostBuild(
{ pathPrefix: `` },
{ appendScript: `${__dirname}/fixtures/custom-sw-code.js` }
)

expect(swText).toContain(`console.log(\`Hello, world!\`)`)
})
})
32 changes: 21 additions & 11 deletions packages/gatsby-plugin-offline/src/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,47 +70,46 @@ exports.onPostBuild = (args, pluginOptions) => {
importWorkboxFrom: `local`,
globDirectory: rootDir,
globPatterns,
modifyUrlPrefix: {
modifyURLPrefix: {
// If `pathPrefix` is configured by user, we should replace
// the default prefix with `pathPrefix`.
"/": `${pathPrefix}/`,
},
cacheId: `gatsby-plugin-offline`,
// Don't cache-bust JS or CSS files, and anything in the static directory,
// since these files have unique URLs and their contents will never change
dontCacheBustUrlsMatching: /(\.js$|\.css$|static\/)/,
dontCacheBustURLsMatching: /(\.js$|\.css$|static\/)/,
runtimeCaching: [
{
// Use cacheFirst since these don't need to be revalidated (same RegExp
// and same reason as above)
urlPattern: /(\.js$|\.css$|static\/)/,
handler: `cacheFirst`,
handler: `CacheFirst`,
},
{
// page-data.json files are not content hashed
urlPattern: /^https?:.*\page-data\/.*\/page-data\.json/,
handler: `networkFirst`,
handler: `NetworkFirst`,
},
{
// Add runtime caching of various other page resources
urlPattern: /^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/,
handler: `staleWhileRevalidate`,
handler: `StaleWhileRevalidate`,
},
{
// Google Fonts CSS (doesn't end in .css so we need to specify it)
urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,
handler: `staleWhileRevalidate`,
handler: `StaleWhileRevalidate`,
},
],
skipWaiting: true,
clientsClaim: true,
}

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

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

fs.appendFileSync(`public/sw.js`, `\n` + swAppend)

if (pluginOptions.appendScript) {
let userAppend
try {
userAppend = fs.readFileSync(pluginOptions.appendScript, `utf8`)
} catch (e) {
throw new Error(`Couldn't find the specified offline inject script`)
}
fs.appendFileSync(`public/sw.js`, `\n` + userAppend)
}

console.log(
`Generated ${swDest}, which will precache ${count} files, totaling ${size} bytes.`
)
Expand Down
3 changes: 2 additions & 1 deletion packages/gatsby-plugin-offline/src/sw-append.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const navigationRoute = new NavigationRoute(async ({ event }) => {
}

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

workbox.routing.registerRoute(navigationRoute)
Expand Down
Loading

0 comments on commit 275344e

Please sign in to comment.