From 260f6acf0e7f670826be2c8465aadd41c69fef4f Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 15 Dec 2018 17:13:46 -0800 Subject: [PATCH] perf: download and cache polyfills on-the-fly (#814) * perf: download and cache polyfills on-the-fly * fixup the localhost switch for service worker, does nothing --- package.json | 1 + src/routes/_utils/asyncModules.js | 20 ------- src/routes/_utils/asyncPolyfills.js | 19 ++++++ src/routes/_utils/loadPolyfills.js | 2 +- src/routes/_utils/serviceWorkerClient.js | 2 +- src/service-worker.js | 74 +++++------------------- 6 files changed, 36 insertions(+), 82 deletions(-) create mode 100644 src/routes/_utils/asyncPolyfills.js diff --git a/package.json b/package.json index 7d929c4..ccd6ba2 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "ignore": [ "dist", "src/routes/_utils/asyncModules.js", + "src/routes/_utils/asyncPolyfills.js", "src/routes/_components/dialog/asyncDialogs.js" ] }, diff --git a/src/routes/_utils/asyncModules.js b/src/routes/_utils/asyncModules.js index c7ae0b3..c067f2a 100644 --- a/src/routes/_utils/asyncModules.js +++ b/src/routes/_utils/asyncModules.js @@ -4,26 +4,6 @@ export const importTimeline = () => import( /* webpackChunkName: 'Timeline' */ '../_components/timeline/Timeline.html' ).then(getDefault) -export const importIntersectionObserver = () => import( - /* webpackChunkName: 'intersection-observer' */ 'intersection-observer' - ) - -export const importRequestIdleCallback = () => import( - /* webpackChunkName: 'requestidlecallback' */ 'requestidlecallback' - ) - -export const importWebAnimationPolyfill = () => import( - /* webpackChunkName: 'web-animations-js' */ 'web-animations-js' - ) - -export const importIndexedDBGetAllShim = () => import( - /* webpackChunkName: 'indexeddb-getall-shim' */ 'indexeddb-getall-shim' - ) - -export const importCustomElementsPolyfill = () => import( - /* webpackChunkName: '@webcomponents/custom-elements' */ '@webcomponents/custom-elements' - ) - export const importPageLifecycle = () => import( /* webpackChunkName: 'page-lifecycle' */ 'page-lifecycle/dist/lifecycle.mjs' ).then(getDefault) diff --git a/src/routes/_utils/asyncPolyfills.js b/src/routes/_utils/asyncPolyfills.js new file mode 100644 index 0000000..8716947 --- /dev/null +++ b/src/routes/_utils/asyncPolyfills.js @@ -0,0 +1,19 @@ +export const importIntersectionObserver = () => import( + /* webpackChunkName: '$polyfill$-intersection-observer' */ 'intersection-observer' + ) + +export const importRequestIdleCallback = () => import( + /* webpackChunkName: '$polyfill$-requestidlecallback' */ 'requestidlecallback' + ) + +export const importWebAnimationPolyfill = () => import( + /* webpackChunkName: '$polyfill$-web-animations-js' */ 'web-animations-js' + ) + +export const importIndexedDBGetAllShim = () => import( + /* webpackChunkName: '$polyfill$-indexeddb-getall-shim' */ 'indexeddb-getall-shim' + ) + +export const importCustomElementsPolyfill = () => import( + /* webpackChunkName: '$polyfill$-@webcomponents/custom-elements' */ '@webcomponents/custom-elements' + ) diff --git a/src/routes/_utils/loadPolyfills.js b/src/routes/_utils/loadPolyfills.js index 8a510b0..81bbcaa 100644 --- a/src/routes/_utils/loadPolyfills.js +++ b/src/routes/_utils/loadPolyfills.js @@ -4,7 +4,7 @@ import { importIntersectionObserver, importRequestIdleCallback, importWebAnimationPolyfill -} from './asyncModules' +} from './asyncPolyfills' export function loadPolyfills () { return Promise.all([ diff --git a/src/routes/_utils/serviceWorkerClient.js b/src/routes/_utils/serviceWorkerClient.js index 27fc972..b1e995d 100644 --- a/src/routes/_utils/serviceWorkerClient.js +++ b/src/routes/_utils/serviceWorkerClient.js @@ -10,7 +10,7 @@ function onUpdateFound (registration) { }) } -if (!location.origin.match('localhost') && 'serviceWorker' in navigator) { +if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js').then(registration => { registration.addEventListener('updatefound', () => onUpdateFound(registration)) }) diff --git a/src/service-worker.js b/src/service-worker.js index 145442d..4cf9da6 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -4,6 +4,11 @@ import { routes as __routes__ } from '../__sapper__/service-worker.js' +import { + get, + post +} from './routes/_utils/ajax' + const timestamp = process.env.SAPPER_TIMESTAMP const ASSETS = `assets_${timestamp}` const WEBPACK_ASSETS = `webpack_assets_${timestamp}` @@ -17,7 +22,8 @@ const assets = __assets__ // `shell` is an array of all the files generated by webpack // also contains '/index.html' for some reason const webpackAssets = __shell__ - .filter(filename => !filename.endsWith('.map')) + .filter(filename => !filename.endsWith('.map')) // don't bother with sourcemaps + .filter(filename => !filename.includes('$polyfill$')) // polyfills are cached on-demand // `routes` is an array of `{ pattern: RegExp }` objects that // match the pages in your src @@ -93,6 +99,13 @@ self.addEventListener('fetch', event => { return response } } + // for polyfills, cache them on-the-fly + if (url.pathname.includes('$polyfill$')) { + let response = await fetch(req) + // cache asynchronously, don't wait + caches.open(WEBPACK_ASSETS).then(cache => cache.put(req, response)) + return response.clone() + } } // for everything else, go network-only @@ -247,62 +260,3 @@ self.addEventListener('notificationclick', event => { } })()) }) - -// Copy-paste from ajax.js -async function get (url, headers, options) { - return _fetch(url, makeFetchOptions('GET', headers), options) -} - -async function post (url, body, headers, options) { - return _putOrPostOrPatch('POST', url, body, headers, options) -} - -async function _putOrPostOrPatch (method, url, body, headers, options) { - let fetchOptions = makeFetchOptions(method, headers) - if (body) { - if (body instanceof FormData) { - fetchOptions.body = body - } else { - fetchOptions.body = JSON.stringify(body) - fetchOptions.headers['Content-Type'] = 'application/json' - } - } - return _fetch(url, fetchOptions, options) -} - -async function _fetch (url, fetchOptions, options) { - let response - if (options && options.timeout) { - response = await fetchWithTimeout(url, fetchOptions, options.timeout) - } else { - response = await fetch(url, fetchOptions) - } - return throwErrorIfInvalidResponse(response) -} - -async function throwErrorIfInvalidResponse (response) { - let json = await response.json() - if (response.status >= 200 && response.status < 300) { - return json - } - if (json && json.error) { - throw new Error(response.status + ': ' + json.error) - } - throw new Error('Request failed: ' + response.status) -} - -function fetchWithTimeout (url, fetchOptions, timeout) { - return new Promise((resolve, reject) => { - fetch(url, fetchOptions).then(resolve, reject) - setTimeout(() => reject(new Error(`Timed out after ${timeout / 1000} seconds`)), timeout) - }) -} - -function makeFetchOptions (method, headers) { - return { - method, - headers: Object.assign(headers || {}, { - 'Accept': 'application/json' - }) - } -}