From f97400caaa5126ff6c4f7dd4495149dce8fdeeff Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 16 Dec 2018 11:46:44 -0800 Subject: [PATCH] Do not include sourcemap files in Service Worker shell Fixes #534 --- src/api/build.ts | 7 +- .../with-sourcemaps-webpack/src/client.js | 9 ++ .../src/routes/_error.html | 3 + .../src/routes/index.html | 3 + .../with-sourcemaps-webpack/src/server.js | 8 ++ .../src/service-worker.js | 82 +++++++++++++++++++ .../with-sourcemaps-webpack/src/template.html | 14 ++++ test/apps/with-sourcemaps-webpack/test.ts | 43 ++++++++++ .../with-sourcemaps-webpack/webpack.config.js | 73 +++++++++++++++++ test/apps/with-sourcemaps/rollup.config.js | 64 +++++++++++++++ test/apps/with-sourcemaps/src/client.js | 9 ++ .../with-sourcemaps/src/routes/_error.html | 3 + .../with-sourcemaps/src/routes/index.html | 3 + test/apps/with-sourcemaps/src/server.js | 8 ++ .../with-sourcemaps/src/service-worker.js | 82 +++++++++++++++++++ test/apps/with-sourcemaps/src/template.html | 14 ++++ test/apps/with-sourcemaps/test.ts | 43 ++++++++++ 17 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 test/apps/with-sourcemaps-webpack/src/client.js create mode 100644 test/apps/with-sourcemaps-webpack/src/routes/_error.html create mode 100644 test/apps/with-sourcemaps-webpack/src/routes/index.html create mode 100644 test/apps/with-sourcemaps-webpack/src/server.js create mode 100644 test/apps/with-sourcemaps-webpack/src/service-worker.js create mode 100644 test/apps/with-sourcemaps-webpack/src/template.html create mode 100644 test/apps/with-sourcemaps-webpack/test.ts create mode 100644 test/apps/with-sourcemaps-webpack/webpack.config.js create mode 100644 test/apps/with-sourcemaps/rollup.config.js create mode 100644 test/apps/with-sourcemaps/src/client.js create mode 100644 test/apps/with-sourcemaps/src/routes/_error.html create mode 100644 test/apps/with-sourcemaps/src/routes/index.html create mode 100644 test/apps/with-sourcemaps/src/server.js create mode 100644 test/apps/with-sourcemaps/src/service-worker.js create mode 100644 test/apps/with-sourcemaps/src/template.html create mode 100644 test/apps/with-sourcemaps/test.ts diff --git a/src/api/build.ts b/src/api/build.ts index ac563de..73680b2 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -116,10 +116,15 @@ export async function build({ let serviceworker_stats; if (serviceworker) { + + const client_files = client_result.chunks + .filter(chunk => !chunk.file.endsWith('.map')) // SW does not need to cache sourcemap files + .map(chunk => `client/${chunk.file}`); + create_serviceworker_manifest({ manifest_data, output, - client_files: client_result.chunks.map(chunk => `client/${chunk.file}`), + client_files, static_files }); diff --git a/test/apps/with-sourcemaps-webpack/src/client.js b/test/apps/with-sourcemaps-webpack/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/src/routes/_error.html b/test/apps/with-sourcemaps-webpack/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/src/routes/index.html b/test/apps/with-sourcemaps-webpack/src/routes/index.html new file mode 100644 index 0000000..abaff72 --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +

Woot!

\ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/src/server.js b/test/apps/with-sourcemaps-webpack/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/with-sourcemaps-webpack/src/service-worker.js b/test/apps/with-sourcemaps-webpack/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/with-sourcemaps-webpack/src/template.html b/test/apps/with-sourcemaps-webpack/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/with-sourcemaps-webpack/test.ts b/test/apps/with-sourcemaps-webpack/test.ts new file mode 100644 index 0000000..cb0a3ca --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/test.ts @@ -0,0 +1,43 @@ +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import * as assert from "assert"; +import { AppRunner } from '../AppRunner'; +import * as fs from "fs"; +import * as path from "path"; + +describe('with-sourcemaps-webpack', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + let prefetch: (href: string) => Promise; + let goto: (href: string) => Promise; + + // hooks + before(async () => { + await build({ cwd: __dirname, bundler: 'webpack' }); + + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes, prefetch, goto } = await runner.start()); + }); + + it('does not put sourcemap files in service worker shell', async () => { + const serviceWorker = await import(`${__dirname}/__sapper__/service-worker.js`); + const shell: string[] = serviceWorker.shell; + + assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0, + 'sourcemap files are not cached in SW'); + + const clientShellDir = path.resolve(`${__dirname}/__sapper__/build`, path.dirname(shell[0])); + const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map')); + assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist'); + }); + + after(() => runner.end()); + +}); \ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/webpack.config.js b/test/apps/with-sourcemaps-webpack/webpack.config.js new file mode 100644 index 0000000..40b612d --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/webpack.config.js @@ -0,0 +1,73 @@ +const webpack = require('webpack'); +const config = require('../../../config/webpack.js'); + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +module.exports = { + client: { + entry: config.client.entry(), + output: config.client.output(), + resolve: { + extensions: ['.js', '.json', '.html'], + mainFields: ['svelte', 'module', 'browser', 'main'] + }, + module: { + rules: [ + { + test: /\.html$/, + use: { + loader: 'svelte-loader', + options: { + dev, + hydratable: true, + hotReload: true + } + } + } + ] + }, + mode, + plugins: [ + dev && new webpack.HotModuleReplacementPlugin(), + new webpack.DefinePlugin({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + ].filter(Boolean), + devtool: dev ? 'inline-source-map' : 'source-map' + }, + + server: { + entry: config.server.entry(), + output: config.server.output(), + target: 'node', + resolve: { + extensions: ['.js', '.json', '.html'], + mainFields: ['svelte', 'module', 'browser', 'main'] + }, + module: { + rules: [ + { + test: /\.html$/, + use: { + loader: 'svelte-loader', + options: { + css: false, + generate: 'ssr', + dev + } + } + } + ] + }, + mode: process.env.NODE_ENV + }, + + serviceworker: { + entry: config.serviceworker.entry(), + output: config.serviceworker.output(), + mode: process.env.NODE_ENV, + devtool: 'sourcemap' + } +}; diff --git a/test/apps/with-sourcemaps/rollup.config.js b/test/apps/with-sourcemaps/rollup.config.js new file mode 100644 index 0000000..c057e2e --- /dev/null +++ b/test/apps/with-sourcemaps/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: Object.assign({}, config.client.output(), { sourcemap: true }), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/client.js b/test/apps/with-sourcemaps/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/with-sourcemaps/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/routes/_error.html b/test/apps/with-sourcemaps/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/with-sourcemaps/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/routes/index.html b/test/apps/with-sourcemaps/src/routes/index.html new file mode 100644 index 0000000..abaff72 --- /dev/null +++ b/test/apps/with-sourcemaps/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +

Woot!

\ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/server.js b/test/apps/with-sourcemaps/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/with-sourcemaps/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/with-sourcemaps/src/service-worker.js b/test/apps/with-sourcemaps/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/with-sourcemaps/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/with-sourcemaps/src/template.html b/test/apps/with-sourcemaps/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/with-sourcemaps/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/with-sourcemaps/test.ts b/test/apps/with-sourcemaps/test.ts new file mode 100644 index 0000000..1b61c28 --- /dev/null +++ b/test/apps/with-sourcemaps/test.ts @@ -0,0 +1,43 @@ +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import * as assert from "assert"; +import { AppRunner } from '../AppRunner'; +import * as fs from 'fs'; +import * as path from "path"; + +describe('with-sourcemaps', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + let prefetch: (href: string) => Promise; + let goto: (href: string) => Promise; + + // hooks + before(async () => { + await build({ cwd: __dirname }); + + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes, prefetch, goto } = await runner.start()); + }); + + it('does not put sourcemap files in service worker shell', async () => { + const serviceWorker = await import(`${__dirname}/__sapper__/service-worker.js`); + const shell: string[] = serviceWorker.shell; + + assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0, + 'sourcemap files are not cached in SW'); + + const clientShellDir = path.resolve(`${__dirname}/__sapper__/build`, path.dirname(shell[0])); + const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map')); + assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist'); + }); + + after(() => runner.end()); + +}); \ No newline at end of file