Skip to content

Commit 5af1b92

Browse files
authored
[chore] refactor AMP validation (#3554)
1 parent d4eed43 commit 5af1b92

File tree

3 files changed

+73
-55
lines changed

3 files changed

+73
-55
lines changed

.changeset/poor-lizards-punch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[chore] refactor AMP validation

packages/kit/src/core/dev/amp_hook.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/** @type {import('amphtml-validator').Validator} */
2+
const amp = await (await import('amphtml-validator')).getInstance();
3+
4+
/** @type {import('types/hooks').Handle} */
5+
export async function handle({ event, resolve }) {
6+
const response = await resolve(event);
7+
if (response.headers.get('content-type') !== 'text/html') {
8+
return response;
9+
}
10+
11+
let rendered = await response.text();
12+
const result = amp.validateString(rendered);
13+
14+
if (result.status !== 'PASS') {
15+
const lines = rendered.split('\n');
16+
17+
/** @param {string} str */
18+
const escape = (str) => str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
19+
20+
rendered = `<!doctype html>
21+
<head>
22+
<meta charset="utf-8" />
23+
<meta name="viewport" content="width=device-width, initial-scale=1" />
24+
<style>
25+
body {
26+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
27+
color: #333;
28+
}
29+
30+
pre {
31+
background: #f4f4f4;
32+
padding: 1em;
33+
overflow-x: auto;
34+
}
35+
</style>
36+
</head>
37+
<h1>AMP validation failed</h1>
38+
39+
${result.errors
40+
.map(
41+
(error) => `
42+
<h2>${error.severity}</h2>
43+
<p>Line ${error.line}, column ${error.col}: ${error.message} (<a href="${error.specUrl}">${
44+
error.code
45+
}</a>)</p>
46+
<pre>${escape(lines[error.line - 1])}</pre>
47+
`
48+
)
49+
.join('\n\n')}
50+
`;
51+
}
52+
53+
return new Response(rendered, { status: response.status, headers: response.headers });
54+
}

packages/kit/src/core/dev/plugin.js

Lines changed: 14 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ import { SVELTE_KIT, SVELTE_KIT_ASSETS } from '../constants.js';
1212
import { get_mime_lookup, resolve_entry, runtime } from '../utils.js';
1313
import { coalesce_to_error } from '../../utils/error.js';
1414
import { load_template } from '../config/index.js';
15+
import { sequence } from '../../hooks.js';
1516

1617
/**
1718
* @param {import('types/config').ValidatedConfig} config
1819
* @param {string} cwd
1920
* @returns {Promise<import('vite').Plugin>}
2021
*/
2122
export async function create_plugin(config, cwd) {
22-
/** @type {import('amphtml-validator').Validator} */
23+
/** @type {import('types/hooks').Handle} */
2324
let amp;
2425

2526
if (config.kit.amp) {
2627
process.env.VITE_SVELTEKIT_AMP = 'true';
27-
amp = await (await import('amphtml-validator')).getInstance();
28+
amp = (await import('./amp_hook')).handle;
2829
}
2930

3031
return {
@@ -166,10 +167,12 @@ export async function create_plugin(config, cwd) {
166167
? await vite.ssrLoadModule(`/${config.kit.files.hooks}`)
167168
: {};
168169

170+
const handle = user_hooks.handle || (({ event, resolve }) => resolve(event));
171+
169172
/** @type {import('types/internal').Hooks} */
170173
const hooks = {
171174
getSession: user_hooks.getSession || (() => ({})),
172-
handle: user_hooks.handle || (({ event, resolve }) => resolve(event)),
175+
handle: amp ? sequence(amp, handle) : handle,
173176
handleError:
174177
user_hooks.handleError ||
175178
(({ /** @type {Error & { frame?: string }} */ error }) => {
@@ -256,58 +259,14 @@ export async function create_plugin(config, cwd) {
256259
router: config.kit.router,
257260
target: config.kit.target,
258261
template: ({ head, body, assets, nonce }) => {
259-
let rendered = template
260-
.replace(/%svelte\.assets%/g, assets)
261-
.replace(/%svelte\.nonce%/g, nonce)
262-
// head and body must be replaced last, in case someone tries to sneak in %svelte.assets% etc
263-
.replace('%svelte.head%', () => head)
264-
.replace('%svelte.body%', () => body);
265-
266-
if (amp) {
267-
const result = amp.validateString(rendered);
268-
269-
if (result.status !== 'PASS') {
270-
const lines = rendered.split('\n');
271-
272-
/** @param {string} str */
273-
const escape = (str) =>
274-
str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
275-
276-
rendered = `<!doctype html>
277-
<head>
278-
<meta charset="utf-8" />
279-
<meta name="viewport" content="width=device-width, initial-scale=1" />
280-
<style>
281-
body {
282-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
283-
color: #333;
284-
}
285-
286-
pre {
287-
background: #f4f4f4;
288-
padding: 1em;
289-
overflow-x: auto;
290-
}
291-
</style>
292-
</head>
293-
<h1>AMP validation failed</h1>
294-
295-
${result.errors
296-
.map(
297-
(error) => `
298-
<h2>${error.severity}</h2>
299-
<p>Line ${error.line}, column ${error.col}: ${error.message} (<a href="${error.specUrl}">${
300-
error.code
301-
}</a>)</p>
302-
<pre>${escape(lines[error.line - 1])}</pre>
303-
`
304-
)
305-
.join('\n\n')}
306-
`;
307-
}
308-
}
309-
310-
return rendered;
262+
return (
263+
template
264+
.replace(/%svelte\.assets%/g, assets)
265+
.replace(/%svelte\.nonce%/g, nonce)
266+
// head and body must be replaced last, in case someone tries to sneak in %svelte.assets% etc
267+
.replace('%svelte.head%', () => head)
268+
.replace('%svelte.body%', () => body)
269+
);
311270
},
312271
template_contains_nonce: template.includes('%svelte.nonce%'),
313272
trailing_slash: config.kit.trailingSlash

0 commit comments

Comments
 (0)