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

[offline-plugin] Add support for SW caching of prefetched resources #6566

Merged
merged 7 commits into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/gatsby-plugin-offline/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/gatsby-ssr.js
/gatsby-browser.js
/app-shell.js
/prefetch-catcher.js
2 changes: 1 addition & 1 deletion packages/gatsby-plugin-offline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"dependencies": {
"@babel/runtime": "7.0.0-beta.52",
"cheerio": "^1.0.0-rc.2",
"sw-precache": "^5.0.0"
"sw-precache": "^5.2.1"
},
"devDependencies": {
"@babel/cli": "7.0.0-beta.52",
Expand Down
39 changes: 39 additions & 0 deletions packages/gatsby-plugin-offline/src/gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
exports.registerServiceWorker = () => true

let swNotInstalled = true
const pathnameResources = []

exports.onPrefetchPathname = ({ pathname, getResourcesForPathname }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use a comment to explain that here we record resources that are prefetched before the sw has been installed and can cache things directly.

if (swNotInstalled && `serviceWorker` in navigator) {
pathnameResources.push(
new Promise(resolve => {
getResourcesForPathname(pathname, resources => {
resolve(resources)
})
})
)
}
}

exports.onServiceWorkerInstalled = () => {
swNotInstalled = false

// grab nodes from head of document
const nodes = document.querySelectorAll(
`head > script[src], head > link[as=script]`
)

// get all script URLs
const scripts = [].slice
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ol' skool dom hacking

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

.call(nodes)
.map(node => (node.src ? node.src : node.href))

Promise.all(pathnameResources).then(pageResources => {
pageResources.forEach(pageResource => {
const [script] = scripts.filter(s =>
s.includes(pageResource.page.componentChunkName)
)
fetch(pageResource.page.jsonURL)
fetch(script)
})
})
}
18 changes: 8 additions & 10 deletions packages/gatsby-plugin-offline/src/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,15 @@ exports.onPostBuild = (args, pluginOptions) => {
// holds any paths for scripts and links
const criticalFilePaths = []

$(`script`)
.filter((_, elem) => $(elem).attr(`src`) !== undefined)
.each((_, elem) => {
criticalFilePaths.push(`${rootDir}${$(elem).attr(`src`)}`)
})
$(`script[src], link[as=script]`).each((_, elem) => {
const $elem = $(elem)
const url = $elem.attr(`src`) || $elem.attr(`href`)
const blackListRegex = /\.xml$/

$(`link`)
.filter((_, elem) => $(elem).attr(`as`) !== `script`)
.each((_, elem) => {
criticalFilePaths.push(`${rootDir}${$(elem).attr(`href`)}`)
})
if (!blackListRegex.test(url)) {
criticalFilePaths.push(`${rootDir}${url}`)
}
})

const options = {
staticFileGlobs: files.concat([
Expand Down
13 changes: 8 additions & 5 deletions packages/gatsby/cache-dir/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const fetchPageResourceMap = () => {
return fetchingPageResourceMapPromise
}

const createJsonURL = jsonName => `${__PATH_PREFIX__}/static/d/${jsonName}.json`

const fetchResource = resourceName => {
// Find resource
let resourceFunction
Expand All @@ -52,9 +54,7 @@ const fetchResource = resourceName => {
} else {
resourceFunction = () => {
const fetchPromise = new Promise((resolve, reject) => {
const url = `${__PATH_PREFIX__}/static/d/${
jsonDataPaths[resourceName]
}.json`
const url = createJsonURL(jsonDataPaths[resourceName])
var req = new XMLHttpRequest()
req.open(`GET`, url, true)
req.withCredentials = true
Expand Down Expand Up @@ -198,7 +198,10 @@ const queue = {
// Tell plugins with custom prefetching logic that they should start
// prefetching this path.
if (!prefetchTriggered[path]) {
apiRunner(`onPrefetchPathname`, { pathname: path })
apiRunner(`onPrefetchPathname`, {
pathname: path,
getResourcesForPathname: queue.getResourcesForPathname,
})
prefetchTriggered[path] = true
}

Expand Down Expand Up @@ -355,7 +358,7 @@ const queue = {
getResourceModule(page.jsonName),
]).then(([component, json]) => {
const pageResources = { component, json, page }

pageResources.page.jsonURL = createJsonURL(jsonDataPaths[page.jsonName])
pathScriptsCache[path] = pageResources
cb(pageResources)

Expand Down
12 changes: 10 additions & 2 deletions packages/gatsby/cache-dir/register-service-worker.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import emitter from "./emitter"
import { apiRunner } from "./api-runner-browser"

if (`serviceWorker` in navigator) {
navigator.serviceWorker
.register(`${__PATH_PREFIX__}/sw.js`)
.then(function(reg) {
reg.addEventListener(`updatefound`, () => {
apiRunner(`onServiceWorkerUpdateFound`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's pass in reg as an option (though maybe call it just serviceWorker to all the API calls in case plugins want access to it.

// The updatefound event implies that reg.installing is set; see
// https://w3c.github.io/ServiceWorker/#service-worker-registration-updatefound-event
const installingWorker = reg.installing
Expand All @@ -21,12 +22,19 @@ if (`serviceWorker` in navigator) {
// At this point, everything has been precached.
// It's the perfect time to display a "Content is cached for offline use." message.
console.log(`Content is now available offline!`)
emitter.emit(`sw:installed`)

// post to service worker that install is complete
apiRunner(`onServiceWorkerInstalled`)
}
break

case `redundant`:
console.error(`The installing service worker became redundant.`)
apiRunner(`onServiceWorkerRedundant`)
break

case `active`:
apiRunner(`onServiceWorkerActive`)
break
}
})
Expand Down
21 changes: 21 additions & 0 deletions packages/gatsby/src/utils/api-browser-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ exports.wrapRootComponent = true
* for plugins with custom prefetching logic.
* @param {object} $0
* @param {object} $0.pathname The pathname whose resources should now be prefetched
* @param {object} $0.getResourcesForPathname Function for fetching resources related to pathname
*/
exports.onPrefetchPathname = true

Expand All @@ -126,3 +127,23 @@ exports.disableCorePrefetching = true
* };
*/
exports.replaceHydrateFunction = true

/**
* Inform plugins of when a service worker has been installed.
*/
exports.onServiceWorkerInstalled = true

/**
* Inform plugins of when a service worker has an update available.
*/
exports.onServiceWorkerUpdateFound = true

/**
* Inform plugins of when a service worker has become active.
*/
exports.onServiceWorkerActive = true

/**
* Inform plugins of when a service worker is redundant.
*/
exports.onServiceWorkerRedundant = true
Loading