diff --git a/package-lock.json b/package-lock.json index 17ff26b5f..b4fa0be0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2073,8 +2073,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2095,14 +2094,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2117,20 +2114,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2247,8 +2241,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2260,7 +2253,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2275,7 +2267,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2283,14 +2274,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2309,7 +2298,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2390,8 +2378,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2403,7 +2390,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2489,8 +2475,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2526,7 +2511,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2546,7 +2530,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2590,14 +2573,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/runtime/src/server/middleware/get_page_handler.ts b/runtime/src/server/middleware/get_page_handler.ts index 53f413640..b5936beba 100644 --- a/runtime/src/server/middleware/get_page_handler.ts +++ b/runtime/src/server/middleware/get_page_handler.ts @@ -11,7 +11,8 @@ import App from '@sapper/internal/App.svelte'; export function get_page_handler( manifest: Manifest, - session_getter: (req: Req, res: Res) => any + session_getter: (req: Req, res: Res) => any, + preprocess: (body: String, req: Req, res: Res) => any ) { const get_build_info = dev ? () => JSON.parse(fs.readFileSync(path.join(build_dir, 'build.json'), 'utf-8')) @@ -320,13 +321,15 @@ export function get_page_handler( // users can set a CSP nonce using res.locals.nonce const nonce_attr = (res.locals && res.locals.nonce) ? ` nonce="${res.locals.nonce}"` : ''; - const body = template() + let body = template() .replace('%sapper.base%', () => ``) .replace('%sapper.scripts%', () => `${script}`) .replace('%sapper.html%', () => html) .replace('%sapper.head%', () => `${head}`) .replace('%sapper.styles%', () => styles); + body = preprocess(body, req, res) || body; + res.statusCode = status; res.end(body); } catch(err) { diff --git a/runtime/src/server/middleware/index.ts b/runtime/src/server/middleware/index.ts index f7bbea4ff..336d43770 100644 --- a/runtime/src/server/middleware/index.ts +++ b/runtime/src/server/middleware/index.ts @@ -10,7 +10,7 @@ export default function middleware(opts: { session?: (req: Req, res: Res) => any, ignore?: any } = {}) { - const { session, ignore } = opts; + const { session, ignore, preprocess } = opts; let emitted_basepath = false; @@ -61,7 +61,7 @@ export default function middleware(opts: { get_server_route_handler(manifest.server_routes), - get_page_handler(manifest, session || noop) + get_page_handler(manifest, session || noop, preprocess || noop) ].filter(Boolean)); } diff --git a/test/apps/preprocess/rollup.config.js b/test/apps/preprocess/rollup.config.js new file mode 100644 index 000000000..66d3e5913 --- /dev/null +++ b/test/apps/preprocess/rollup.config.js @@ -0,0 +1,58 @@ +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: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ] + }, + + 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'] + }, + + 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/preprocess/src/client.js b/test/apps/preprocess/src/client.js new file mode 100644 index 000000000..6cce7e658 --- /dev/null +++ b/test/apps/preprocess/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '@sapper/app'; + +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/preprocess/src/routes/_error.svelte b/test/apps/preprocess/src/routes/_error.svelte new file mode 100644 index 000000000..bec41f19f --- /dev/null +++ b/test/apps/preprocess/src/routes/_error.svelte @@ -0,0 +1,8 @@ + + +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/preprocess/src/routes/index.svelte b/test/apps/preprocess/src/routes/index.svelte new file mode 100644 index 000000000..a5c6174a4 --- /dev/null +++ b/test/apps/preprocess/src/routes/index.svelte @@ -0,0 +1 @@ +

Great success!

diff --git a/test/apps/preprocess/src/server.js b/test/apps/preprocess/src/server.js new file mode 100644 index 000000000..243171f34 --- /dev/null +++ b/test/apps/preprocess/src/server.js @@ -0,0 +1,15 @@ +import polka from 'polka'; +import * as sapper from '@sapper/server'; + +import { start } from '../../common.js'; + +const app = polka() + .use( + sapper.middleware({ + preprocess: (body) => { + return body.replace('%custom.data%', '
works!
'); + } + }) + ); + +start(app); diff --git a/test/apps/preprocess/src/service-worker.js b/test/apps/preprocess/src/service-worker.js new file mode 100644 index 000000000..8adb97a43 --- /dev/null +++ b/test/apps/preprocess/src/service-worker.js @@ -0,0 +1,82 @@ +import * as sapper from '@sapper/service-worker'; + +const ASSETS = `cache${sapper.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 = sapper.shell.concat(sapper.files); +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${sapper.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/preprocess/src/template.html b/test/apps/preprocess/src/template.html new file mode 100644 index 000000000..1786f84be --- /dev/null +++ b/test/apps/preprocess/src/template.html @@ -0,0 +1,15 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + + %custom.data% +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/preprocess/test.ts b/test/apps/preprocess/test.ts new file mode 100644 index 000000000..d71bdafd4 --- /dev/null +++ b/test/apps/preprocess/test.ts @@ -0,0 +1,40 @@ +import * as assert from 'assert'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; + +describe('preprocess', function() { + this.timeout(10000); + + let r: AppRunner; + + // hooks + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); + }); + + after(() => r && r.end()); + + // tests + it('respects `options.preprocess` data replacement', async () => { + await r.load('/'); + + assert.equal( + await r.text('#test'), + 'works!' + ); + }); + + it('respects `options.preprocess` data replacement on error page', async () => { + await r.load('/error-page'); + + assert.equal( + await r.text('#test'), + 'works!' + ); + }); + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +});