Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

Commit f2cb02c

Browse files
committed
let users control replacements / page body
A "replacer" is a reducer which is called with the page content and returns the page content. A replacer can: - alter the base href for certain user-agents - replace text in the body template - omitting script when a header is set - etc. For example, omitting javascript for Internet Explorer: ```js // handler before sapper middleware (req, res, next) => { if (req.get('user-agent').toLowerCase().includes("msie")) { res.replacers = res.replacers || [] res.replacers.push((ctx) => (ctx.script = "", ctx)) } next() } ``` or inserting a footer notice on unsupported browsers: ```js (req, res, next) => { // matchesUA: https://github.com/browserslist/browserslist-useragent if (!matchesUA(req.get('user-agent'))) { res.replacers = res.replacers || [] res.replacers.push((ctx) => (ctx.body.replace(/<\/body>/, `${unsupportedBrowserNotice}</body>`), ctx)) } next() } ``` Replacers are run in the order they are defined. System replacers will run last in order: → base href → head → styles → html → script.
1 parent b0f342a commit f2cb02c

File tree

2 files changed

+35
-9
lines changed

2 files changed

+35
-9
lines changed

runtime/src/server/middleware/get_page_handler.ts

+22-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import cookie from 'cookie';
55
import devalue from 'devalue';
66
import fetch from 'node-fetch';
77
import URL from 'url';
8-
import { Manifest, Page, Req, Res } from './types';
8+
import { Manifest, Page, Req, Res, PageContentReducer, PageContent } from './types';
99
import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server';
1010
import App from '@sapper/internal/App.svelte';
1111

@@ -320,15 +320,29 @@ export function get_page_handler(
320320
// users can set a CSP nonce using res.locals.nonce
321321
const nonce_attr = (res.locals && res.locals.nonce) ? ` nonce="${res.locals.nonce}"` : '';
322322

323-
const body = template()
324-
.replace('%sapper.base%', () => `<base href="${req.baseUrl}/">`)
325-
.replace('%sapper.scripts%', () => `<script${nonce_attr}>${script}</script>`)
326-
.replace('%sapper.html%', () => html)
327-
.replace('%sapper.head%', () => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
328-
.replace('%sapper.styles%', () => styles);
323+
let replacers: PageContentReducer[] = res.replacers || [];
324+
replacers = replacers.concat([
325+
(ctx) => (ctx.body = ctx.body.replace('%sapper.base%', `<base href="${ctx.baseUrl}/">`), ctx),
326+
(ctx) => (ctx.body = ctx.body.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${ctx.head}<noscript id='sapper-head-end'></noscript>`), ctx),
327+
(ctx) => (ctx.body = ctx.body.replace('%sapper.styles%', ctx.styles), ctx),
328+
(ctx) => (ctx.body = ctx.body.replace('%sapper.html%', ctx.html), ctx),
329+
(ctx) => (ctx.body = ctx.body.replace('%sapper.scripts%', `<script${ctx.nonce_attr}>${ctx.script}</script>`), ctx),
330+
]);
331+
332+
const pageContent = replacers.reduce((ctx: PageContent, replace: PageContentReducer) => replace(ctx),
333+
{
334+
body: template(),
335+
baseUrl: req.baseUrl,
336+
head,
337+
html,
338+
nonce_attr,
339+
script,
340+
styles
341+
}
342+
)
329343

330344
res.statusCode = status;
331-
res.end(body);
345+
res.end(pageContent.body);
332346
} catch(err) {
333347
if (error) {
334348
bail(req, res, err)

runtime/src/server/middleware/types.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,16 @@ interface Component {
6060
css: { code: string, map: any };
6161
html: string
6262
}
63-
}
63+
}
64+
65+
export interface PageContent {
66+
body: string,
67+
baseUrl: string,
68+
head: string,
69+
styles: string,
70+
html: string,
71+
nonce_attr: string,
72+
script: string,
73+
}
74+
75+
export type PageContentReducer = (context: PageContent) => PageContent;

0 commit comments

Comments
 (0)