@sveltejs/kit 1.0.0-next.55 → 1.0.0-next.550
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/package.json +93 -67
- package/postinstall.js +47 -0
- package/scripts/special-types/$env+dynamic+private.md +10 -0
- package/scripts/special-types/$env+dynamic+public.md +8 -0
- package/scripts/special-types/$env+static+private.md +19 -0
- package/scripts/special-types/$env+static+public.md +7 -0
- package/scripts/special-types/$lib.md +5 -0
- package/src/cli.js +108 -0
- package/src/constants.js +5 -0
- package/src/core/adapt/builder.js +212 -0
- package/src/core/adapt/index.js +31 -0
- package/src/core/config/default-error.html +56 -0
- package/src/core/config/index.js +110 -0
- package/src/core/config/options.js +516 -0
- package/src/core/config/types.d.ts +1 -0
- package/src/core/env.js +121 -0
- package/src/core/generate_manifest/index.js +125 -0
- package/src/core/prerender/crawl.js +207 -0
- package/src/core/prerender/entities.js +2252 -0
- package/src/core/prerender/prerender.js +460 -0
- package/src/core/prerender/queue.js +80 -0
- package/src/core/sync/create_manifest_data/conflict.js +0 -0
- package/src/core/sync/create_manifest_data/index.js +513 -0
- package/src/core/sync/create_manifest_data/sort.js +161 -0
- package/src/core/sync/create_manifest_data/types.d.ts +37 -0
- package/src/core/sync/sync.js +78 -0
- package/src/core/sync/utils.js +33 -0
- package/src/core/sync/write_ambient.js +53 -0
- package/src/core/sync/write_client_manifest.js +106 -0
- package/src/core/sync/write_matchers.js +25 -0
- package/src/core/sync/write_root.js +91 -0
- package/src/core/sync/write_tsconfig.js +195 -0
- package/src/core/sync/write_types/index.js +809 -0
- package/src/core/utils.js +67 -0
- package/src/exports/hooks/index.js +1 -0
- package/src/exports/hooks/sequence.js +44 -0
- package/src/exports/index.js +45 -0
- package/src/exports/node/index.js +172 -0
- package/src/exports/node/polyfills.js +28 -0
- package/src/exports/vite/build/build_server.js +384 -0
- package/src/exports/vite/build/build_service_worker.js +92 -0
- package/src/exports/vite/build/utils.js +195 -0
- package/src/exports/vite/dev/index.js +588 -0
- package/src/exports/vite/graph_analysis/index.js +107 -0
- package/src/exports/vite/graph_analysis/types.d.ts +5 -0
- package/src/exports/vite/graph_analysis/utils.js +6 -0
- package/src/exports/vite/index.js +651 -0
- package/src/exports/vite/preview/index.js +193 -0
- package/src/exports/vite/types.d.ts +3 -0
- package/src/exports/vite/utils.js +171 -0
- package/src/runtime/app/env.js +1 -0
- package/src/runtime/app/environment.js +11 -0
- package/src/runtime/app/forms.js +141 -0
- package/src/runtime/app/navigation.js +23 -0
- package/src/runtime/app/paths.js +1 -0
- package/src/runtime/app/stores.js +102 -0
- package/src/runtime/client/ambient.d.ts +30 -0
- package/src/runtime/client/client.js +1726 -0
- package/src/runtime/client/fetcher.js +121 -0
- package/src/runtime/client/parse.js +60 -0
- package/src/runtime/client/singletons.js +21 -0
- package/src/runtime/client/start.js +43 -0
- package/src/runtime/client/types.d.ts +84 -0
- package/src/runtime/client/utils.js +166 -0
- package/src/runtime/components/error.svelte +16 -0
- package/{assets → src/runtime}/components/layout.svelte +0 -0
- package/src/runtime/control.js +98 -0
- package/src/runtime/env/dynamic/private.js +1 -0
- package/src/runtime/env/dynamic/public.js +1 -0
- package/src/runtime/env-private.js +6 -0
- package/src/runtime/env-public.js +6 -0
- package/src/runtime/env.js +6 -0
- package/src/runtime/hash.js +20 -0
- package/src/runtime/paths.js +11 -0
- package/src/runtime/server/cookie.js +231 -0
- package/src/runtime/server/data/index.js +144 -0
- package/src/runtime/server/endpoint.js +89 -0
- package/src/runtime/server/fetch.js +164 -0
- package/src/runtime/server/index.js +375 -0
- package/src/runtime/server/page/actions.js +258 -0
- package/src/runtime/server/page/crypto.js +239 -0
- package/src/runtime/server/page/csp.js +250 -0
- package/src/runtime/server/page/index.js +303 -0
- package/src/runtime/server/page/load_data.js +258 -0
- package/src/runtime/server/page/render.js +391 -0
- package/src/runtime/server/page/respond_with_error.js +102 -0
- package/src/runtime/server/page/serialize_data.js +87 -0
- package/src/runtime/server/page/types.d.ts +35 -0
- package/src/runtime/server/utils.js +205 -0
- package/src/utils/array.js +9 -0
- package/src/utils/error.js +22 -0
- package/src/utils/escape.js +46 -0
- package/src/utils/filesystem.js +166 -0
- package/src/utils/functions.js +16 -0
- package/src/utils/http.js +72 -0
- package/src/utils/misc.js +1 -0
- package/src/utils/promises.js +17 -0
- package/src/utils/routing.js +168 -0
- package/src/utils/unit_test.js +11 -0
- package/src/utils/url.js +159 -0
- package/svelte-kit.js +1 -1
- package/types/ambient.d.ts +469 -0
- package/types/index.d.ts +775 -0
- package/types/internal.d.ts +381 -0
- package/types/private.d.ts +229 -0
- package/CHANGELOG.md +0 -519
- package/assets/components/error.svelte +0 -13
- package/assets/runtime/app/env.js +0 -5
- package/assets/runtime/app/navigation.js +0 -48
- package/assets/runtime/app/paths.js +0 -1
- package/assets/runtime/app/stores.js +0 -93
- package/assets/runtime/chunks/utils.js +0 -22
- package/assets/runtime/internal/singletons.js +0 -23
- package/assets/runtime/internal/start.js +0 -823
- package/assets/runtime/paths.js +0 -12
- package/dist/chunks/index.js +0 -3544
- package/dist/chunks/index2.js +0 -572
- package/dist/chunks/index3.js +0 -246
- package/dist/chunks/index4.js +0 -569
- package/dist/chunks/index5.js +0 -751
- package/dist/chunks/index6.js +0 -323
- package/dist/chunks/standard.js +0 -99
- package/dist/chunks/utils.js +0 -83
- package/dist/cli.js +0 -558
- package/dist/ssr.js +0 -2620
- package/types.d.ts +0 -74
- package/types.internal.d.ts +0 -237
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import * as devalue from 'devalue';
|
|
2
|
+
import { readable, writable } from 'svelte/store';
|
|
3
|
+
import { hash } from '../../hash.js';
|
|
4
|
+
import { serialize_data } from './serialize_data.js';
|
|
5
|
+
import { s } from '../../../utils/misc.js';
|
|
6
|
+
import { Csp } from './csp.js';
|
|
7
|
+
import { uneval_action_response } from './actions.js';
|
|
8
|
+
import { clarify_devalue_error } from '../utils.js';
|
|
9
|
+
|
|
10
|
+
// TODO rename this function/module
|
|
11
|
+
|
|
12
|
+
const updated = {
|
|
13
|
+
...readable(false),
|
|
14
|
+
check: () => false
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates the HTML response.
|
|
19
|
+
* @param {{
|
|
20
|
+
* branch: Array<import('./types').Loaded>;
|
|
21
|
+
* fetched: Array<import('./types').Fetched>;
|
|
22
|
+
* options: import('types').SSROptions;
|
|
23
|
+
* state: import('types').SSRState;
|
|
24
|
+
* page_config: { ssr: boolean; csr: boolean };
|
|
25
|
+
* status: number;
|
|
26
|
+
* error: App.Error | null;
|
|
27
|
+
* event: import('types').RequestEvent;
|
|
28
|
+
* resolve_opts: import('types').RequiredResolveOptions;
|
|
29
|
+
* action_result?: import('types').ActionResult;
|
|
30
|
+
* }} opts
|
|
31
|
+
*/
|
|
32
|
+
export async function render_response({
|
|
33
|
+
branch,
|
|
34
|
+
fetched,
|
|
35
|
+
options,
|
|
36
|
+
state,
|
|
37
|
+
page_config,
|
|
38
|
+
status,
|
|
39
|
+
error = null,
|
|
40
|
+
event,
|
|
41
|
+
resolve_opts,
|
|
42
|
+
action_result
|
|
43
|
+
}) {
|
|
44
|
+
if (state.prerendering) {
|
|
45
|
+
if (options.csp.mode === 'nonce') {
|
|
46
|
+
throw new Error('Cannot use prerendering if config.kit.csp.mode === "nonce"');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (options.app_template_contains_nonce) {
|
|
50
|
+
throw new Error('Cannot use prerendering if page template contains %sveltekit.nonce%');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { entry } = options.manifest._;
|
|
55
|
+
|
|
56
|
+
const stylesheets = new Set(entry.stylesheets);
|
|
57
|
+
const modulepreloads = new Set(entry.imports);
|
|
58
|
+
const fonts = new Set(options.manifest._.entry.fonts);
|
|
59
|
+
|
|
60
|
+
/** @type {Set<string>} */
|
|
61
|
+
const link_header_preloads = new Set();
|
|
62
|
+
|
|
63
|
+
/** @type {Map<string, string>} */
|
|
64
|
+
// TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
|
|
65
|
+
const inline_styles = new Map();
|
|
66
|
+
|
|
67
|
+
let rendered;
|
|
68
|
+
|
|
69
|
+
const form_value =
|
|
70
|
+
action_result?.type === 'success' || action_result?.type === 'invalid'
|
|
71
|
+
? action_result.data ?? null
|
|
72
|
+
: null;
|
|
73
|
+
|
|
74
|
+
if (page_config.ssr) {
|
|
75
|
+
/** @type {Record<string, any>} */
|
|
76
|
+
const props = {
|
|
77
|
+
stores: {
|
|
78
|
+
page: writable(null),
|
|
79
|
+
navigating: writable(null),
|
|
80
|
+
updated
|
|
81
|
+
},
|
|
82
|
+
components: await Promise.all(branch.map(({ node }) => node.component())),
|
|
83
|
+
form: form_value
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let data = {};
|
|
87
|
+
|
|
88
|
+
// props_n (instead of props[n]) makes it easy to avoid
|
|
89
|
+
// unnecessary updates for layout components
|
|
90
|
+
for (let i = 0; i < branch.length; i += 1) {
|
|
91
|
+
data = { ...data, ...branch[i].data };
|
|
92
|
+
props[`data_${i}`] = data;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
props.page = {
|
|
96
|
+
error,
|
|
97
|
+
params: /** @type {Record<string, any>} */ (event.params),
|
|
98
|
+
route: event.route,
|
|
99
|
+
status,
|
|
100
|
+
url: event.url,
|
|
101
|
+
data,
|
|
102
|
+
form: form_value
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// TODO remove this for 1.0
|
|
106
|
+
/**
|
|
107
|
+
* @param {string} property
|
|
108
|
+
* @param {string} replacement
|
|
109
|
+
*/
|
|
110
|
+
const print_error = (property, replacement) => {
|
|
111
|
+
Object.defineProperty(props.page, property, {
|
|
112
|
+
get: () => {
|
|
113
|
+
throw new Error(`$page.${property} has been replaced by $page.url.${replacement}`);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
print_error('origin', 'origin');
|
|
119
|
+
print_error('path', 'pathname');
|
|
120
|
+
print_error('query', 'searchParams');
|
|
121
|
+
|
|
122
|
+
rendered = options.root.render(props);
|
|
123
|
+
|
|
124
|
+
for (const { node } of branch) {
|
|
125
|
+
if (node.imports) {
|
|
126
|
+
node.imports.forEach((url) => modulepreloads.add(url));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (node.stylesheets) {
|
|
130
|
+
node.stylesheets.forEach((url) => stylesheets.add(url));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (node.fonts) {
|
|
134
|
+
node.fonts.forEach((url) => fonts.add(url));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (node.inline_styles) {
|
|
138
|
+
Object.entries(await node.inline_styles()).forEach(([k, v]) => inline_styles.set(k, v));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
rendered = { head: '', html: '', css: { code: '', map: null } };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let head = '';
|
|
146
|
+
let body = rendered.html;
|
|
147
|
+
|
|
148
|
+
const csp = new Csp(options.csp, {
|
|
149
|
+
dev: options.dev,
|
|
150
|
+
prerender: !!state.prerendering
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const target = hash(body);
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template
|
|
157
|
+
* @type {string}
|
|
158
|
+
*/
|
|
159
|
+
let assets;
|
|
160
|
+
|
|
161
|
+
if (options.paths.assets) {
|
|
162
|
+
// if an asset path is specified, use it
|
|
163
|
+
assets = options.paths.assets;
|
|
164
|
+
} else if (state.prerendering?.fallback) {
|
|
165
|
+
// if we're creating a fallback page, asset paths need to be root-relative
|
|
166
|
+
assets = options.paths.base;
|
|
167
|
+
} else {
|
|
168
|
+
// otherwise we want asset paths to be relative to the page, so that they
|
|
169
|
+
// will work in odd contexts like IPFS, the internet archive, and so on
|
|
170
|
+
const segments = event.url.pathname.slice(options.paths.base.length).split('/').slice(2);
|
|
171
|
+
assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @param {string} path */
|
|
175
|
+
const prefixed = (path) => (path.startsWith('/') ? path : `${assets}/${path}`);
|
|
176
|
+
|
|
177
|
+
const serialized = { data: '', form: 'null' };
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
serialized.data = `[${branch
|
|
181
|
+
.map(({ server_data }) => {
|
|
182
|
+
if (server_data?.type === 'data') {
|
|
183
|
+
const data = devalue.uneval(server_data.data);
|
|
184
|
+
|
|
185
|
+
const uses = [];
|
|
186
|
+
if (server_data.uses.dependencies.size > 0) {
|
|
187
|
+
uses.push(`dependencies:${s(Array.from(server_data.uses.dependencies))}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (server_data.uses.params.size > 0) {
|
|
191
|
+
uses.push(`params:${s(Array.from(server_data.uses.params))}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (server_data.uses.parent) uses.push(`parent:1`);
|
|
195
|
+
if (server_data.uses.route) uses.push(`route:1`);
|
|
196
|
+
if (server_data.uses.url) uses.push(`url:1`);
|
|
197
|
+
|
|
198
|
+
return `{type:"data",data:${data},uses:{${uses.join(',')}}}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return s(server_data);
|
|
202
|
+
})
|
|
203
|
+
.join(',')}]`;
|
|
204
|
+
} catch (e) {
|
|
205
|
+
const error = /** @type {any} */ (e);
|
|
206
|
+
throw new Error(clarify_devalue_error(event, error));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (form_value) {
|
|
210
|
+
serialized.form = uneval_action_response(form_value, /** @type {string} */ (event.route.id));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (inline_styles.size > 0) {
|
|
214
|
+
const content = Array.from(inline_styles.values()).join('\n');
|
|
215
|
+
|
|
216
|
+
const attributes = [];
|
|
217
|
+
if (options.dev) attributes.push(' data-sveltekit');
|
|
218
|
+
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
|
|
219
|
+
|
|
220
|
+
csp.add_style(content);
|
|
221
|
+
|
|
222
|
+
head += `\n\t<style${attributes.join('')}>${content}</style>`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const dep of stylesheets) {
|
|
226
|
+
const path = prefixed(dep);
|
|
227
|
+
|
|
228
|
+
if (resolve_opts.preload({ type: 'css', path })) {
|
|
229
|
+
const attributes = [];
|
|
230
|
+
|
|
231
|
+
if (csp.style_needs_nonce) {
|
|
232
|
+
attributes.push(`nonce="${csp.nonce}"`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (inline_styles.has(dep)) {
|
|
236
|
+
// don't load stylesheets that are already inlined
|
|
237
|
+
// include them in disabled state so that Vite can detect them and doesn't try to add them
|
|
238
|
+
attributes.push('disabled', 'media="(max-width: 0)"');
|
|
239
|
+
} else {
|
|
240
|
+
const preload_atts = ['rel="preload"', 'as="style"'].concat(attributes);
|
|
241
|
+
link_header_preloads.add(`<${encodeURI(path)}>; ${preload_atts.join(';')}; nopush`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
attributes.unshift('rel="stylesheet"');
|
|
245
|
+
head += `\n\t\t<link href="${path}" ${attributes.join(' ')}>`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const dep of fonts) {
|
|
250
|
+
const path = prefixed(dep);
|
|
251
|
+
|
|
252
|
+
if (resolve_opts.preload({ type: 'font', path })) {
|
|
253
|
+
const ext = dep.slice(dep.lastIndexOf('.') + 1);
|
|
254
|
+
const attributes = [
|
|
255
|
+
'rel="preload"',
|
|
256
|
+
'as="font"',
|
|
257
|
+
`type="font/${ext}"`,
|
|
258
|
+
`href="${path}"`,
|
|
259
|
+
'crossorigin'
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
head += `\n\t\t<link ${attributes.join(' ')}>`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (page_config.csr) {
|
|
267
|
+
// prettier-ignore
|
|
268
|
+
const init_app = `
|
|
269
|
+
import { start } from ${s(prefixed(entry.file))};
|
|
270
|
+
|
|
271
|
+
start({
|
|
272
|
+
env: ${s(options.public_env)},
|
|
273
|
+
hydrate: ${page_config.ssr ? `{
|
|
274
|
+
status: ${status},
|
|
275
|
+
error: ${devalue.uneval(error)},
|
|
276
|
+
node_ids: [${branch.map(({ node }) => node.index).join(', ')}],
|
|
277
|
+
params: ${devalue.uneval(event.params)},
|
|
278
|
+
route: ${s(event.route)},
|
|
279
|
+
data: ${serialized.data},
|
|
280
|
+
form: ${serialized.form}
|
|
281
|
+
}` : 'null'},
|
|
282
|
+
paths: ${s(options.paths)},
|
|
283
|
+
target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode,
|
|
284
|
+
trailing_slash: ${s(options.trailing_slash)}
|
|
285
|
+
});
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
for (const dep of modulepreloads) {
|
|
289
|
+
const path = prefixed(dep);
|
|
290
|
+
|
|
291
|
+
if (resolve_opts.preload({ type: 'js', path })) {
|
|
292
|
+
link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
|
|
293
|
+
if (state.prerendering) {
|
|
294
|
+
head += `\n\t\t<link rel="modulepreload" href="${path}">`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const attributes = ['type="module"', `data-sveltekit-hydrate="${target}"`];
|
|
300
|
+
|
|
301
|
+
csp.add_script(init_app);
|
|
302
|
+
|
|
303
|
+
if (csp.script_needs_nonce) {
|
|
304
|
+
attributes.push(`nonce="${csp.nonce}"`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
body += `\n\t\t<script ${attributes.join(' ')}>${init_app}</script>`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (page_config.ssr && page_config.csr) {
|
|
311
|
+
body += `\n\t${fetched
|
|
312
|
+
.map((item) =>
|
|
313
|
+
serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
|
|
314
|
+
)
|
|
315
|
+
.join('\n\t')}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (options.service_worker) {
|
|
319
|
+
const opts = options.dev ? `, { type: 'module' }` : '';
|
|
320
|
+
|
|
321
|
+
// we use an anonymous function instead of an arrow function to support
|
|
322
|
+
// older browsers (https://github.com/sveltejs/kit/pull/5417)
|
|
323
|
+
const init_service_worker = `
|
|
324
|
+
if ('serviceWorker' in navigator) {
|
|
325
|
+
addEventListener('load', function () {
|
|
326
|
+
navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
`;
|
|
330
|
+
|
|
331
|
+
// always include service worker unless it's turned off explicitly
|
|
332
|
+
csp.add_script(init_service_worker);
|
|
333
|
+
|
|
334
|
+
head += `
|
|
335
|
+
<script${csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''}>${init_service_worker}</script>`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (state.prerendering) {
|
|
339
|
+
// TODO read headers set with setHeaders and convert into http-equiv where possible
|
|
340
|
+
const http_equiv = [];
|
|
341
|
+
|
|
342
|
+
const csp_headers = csp.csp_provider.get_meta();
|
|
343
|
+
if (csp_headers) {
|
|
344
|
+
http_equiv.push(csp_headers);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (state.prerendering.cache) {
|
|
348
|
+
http_equiv.push(`<meta http-equiv="cache-control" content="${state.prerendering.cache}">`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (http_equiv.length > 0) {
|
|
352
|
+
head = http_equiv.join('\n') + head;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// add the content after the script/css links so the link elements are parsed first
|
|
357
|
+
head += rendered.head;
|
|
358
|
+
|
|
359
|
+
// TODO flush chunks as early as we can
|
|
360
|
+
const html =
|
|
361
|
+
(await resolve_opts.transformPageChunk({
|
|
362
|
+
html: options.app_template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) }),
|
|
363
|
+
done: true
|
|
364
|
+
})) || '';
|
|
365
|
+
|
|
366
|
+
const headers = new Headers({
|
|
367
|
+
'x-sveltekit-page': 'true',
|
|
368
|
+
'content-type': 'text/html',
|
|
369
|
+
etag: `"${hash(html)}"`
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (!state.prerendering) {
|
|
373
|
+
const csp_header = csp.csp_provider.get_header();
|
|
374
|
+
if (csp_header) {
|
|
375
|
+
headers.set('content-security-policy', csp_header);
|
|
376
|
+
}
|
|
377
|
+
const report_only_header = csp.report_only_provider.get_header();
|
|
378
|
+
if (report_only_header) {
|
|
379
|
+
headers.set('content-security-policy-report-only', report_only_header);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (link_header_preloads.size) {
|
|
383
|
+
headers.set('link', Array.from(link_header_preloads).join(', '));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return new Response(html, {
|
|
388
|
+
status,
|
|
389
|
+
headers
|
|
390
|
+
});
|
|
391
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { render_response } from './render.js';
|
|
2
|
+
import { load_data, load_server_data } from './load_data.js';
|
|
3
|
+
import {
|
|
4
|
+
handle_error_and_jsonify,
|
|
5
|
+
get_option,
|
|
6
|
+
static_error_page,
|
|
7
|
+
redirect_response,
|
|
8
|
+
GENERIC_ERROR
|
|
9
|
+
} from '../utils.js';
|
|
10
|
+
import { HttpError, Redirect } from '../../control.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('./types.js').Loaded} Loaded
|
|
14
|
+
* @typedef {import('types').SSROptions} SSROptions
|
|
15
|
+
* @typedef {import('types').SSRState} SSRState
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{
|
|
20
|
+
* event: import('types').RequestEvent;
|
|
21
|
+
* options: SSROptions;
|
|
22
|
+
* state: SSRState;
|
|
23
|
+
* status: number;
|
|
24
|
+
* error: unknown;
|
|
25
|
+
* resolve_opts: import('types').RequiredResolveOptions;
|
|
26
|
+
* }} opts
|
|
27
|
+
*/
|
|
28
|
+
export async function respond_with_error({ event, options, state, status, error, resolve_opts }) {
|
|
29
|
+
/** @type {import('./types').Fetched[]} */
|
|
30
|
+
const fetched = [];
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const branch = [];
|
|
34
|
+
const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
|
|
35
|
+
const ssr = get_option([default_layout], 'ssr') ?? true;
|
|
36
|
+
const csr = get_option([default_layout], 'csr') ?? true;
|
|
37
|
+
|
|
38
|
+
if (ssr) {
|
|
39
|
+
state.initiator = GENERIC_ERROR;
|
|
40
|
+
|
|
41
|
+
const server_data_promise = load_server_data({
|
|
42
|
+
event,
|
|
43
|
+
state,
|
|
44
|
+
node: default_layout,
|
|
45
|
+
parent: async () => ({})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const server_data = await server_data_promise;
|
|
49
|
+
|
|
50
|
+
const data = await load_data({
|
|
51
|
+
event,
|
|
52
|
+
fetched,
|
|
53
|
+
node: default_layout,
|
|
54
|
+
parent: async () => ({}),
|
|
55
|
+
resolve_opts,
|
|
56
|
+
server_data_promise,
|
|
57
|
+
state,
|
|
58
|
+
csr
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
branch.push(
|
|
62
|
+
{
|
|
63
|
+
node: default_layout,
|
|
64
|
+
server_data,
|
|
65
|
+
data
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
node: await options.manifest._.nodes[1](), // 1 is always the root error
|
|
69
|
+
data: null,
|
|
70
|
+
server_data: null
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return await render_response({
|
|
76
|
+
options,
|
|
77
|
+
state,
|
|
78
|
+
page_config: {
|
|
79
|
+
ssr,
|
|
80
|
+
csr: get_option([default_layout], 'csr') ?? true
|
|
81
|
+
},
|
|
82
|
+
status,
|
|
83
|
+
error: handle_error_and_jsonify(event, options, error),
|
|
84
|
+
branch,
|
|
85
|
+
fetched,
|
|
86
|
+
event,
|
|
87
|
+
resolve_opts
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
|
|
91
|
+
// we end up here.
|
|
92
|
+
if (error instanceof Redirect) {
|
|
93
|
+
return redirect_response(error.status, error.location);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return static_error_page(
|
|
97
|
+
options,
|
|
98
|
+
error instanceof HttpError ? error.status : 500,
|
|
99
|
+
handle_error_and_jsonify(event, options, error).message
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { escape_html_attr } from '../../../utils/escape.js';
|
|
2
|
+
import { hash } from '../../hash.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
|
|
6
|
+
*
|
|
7
|
+
* The first closes the script element, so everything after is treated as raw HTML.
|
|
8
|
+
* The second disables further parsing until `-->`, so the script element might be unexpectedly
|
|
9
|
+
* kept open until until an unrelated HTML comment in the page.
|
|
10
|
+
*
|
|
11
|
+
* U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
|
|
12
|
+
* browsers.
|
|
13
|
+
*
|
|
14
|
+
* @see tests for unsafe parsing examples.
|
|
15
|
+
* @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
|
|
16
|
+
* @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
|
|
17
|
+
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
|
|
18
|
+
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
|
|
19
|
+
* @see https://github.com/tc39/proposal-json-superset
|
|
20
|
+
* @type {Record<string, string>}
|
|
21
|
+
*/
|
|
22
|
+
const replacements = {
|
|
23
|
+
'<': '\\u003C',
|
|
24
|
+
'\u2028': '\\u2028',
|
|
25
|
+
'\u2029': '\\u2029'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const pattern = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generates a raw HTML string containing a safe script element carrying data and associated attributes.
|
|
32
|
+
*
|
|
33
|
+
* It escapes all the special characters needed to guarantee the element is unbroken, but care must
|
|
34
|
+
* be taken to ensure it is inserted in the document at an acceptable position for a script element,
|
|
35
|
+
* and that the resulting string isn't further modified.
|
|
36
|
+
*
|
|
37
|
+
* @param {import('./types.js').Fetched} fetched
|
|
38
|
+
* @param {(name: string, value: string) => boolean} filter
|
|
39
|
+
* @param {boolean} [prerendering]
|
|
40
|
+
* @returns {string} The raw HTML of a script element carrying the JSON payload.
|
|
41
|
+
* @example const html = serialize_data('/data.json', null, { foo: 'bar' });
|
|
42
|
+
*/
|
|
43
|
+
export function serialize_data(fetched, filter, prerendering = false) {
|
|
44
|
+
/** @type {Record<string, string>} */
|
|
45
|
+
const headers = {};
|
|
46
|
+
|
|
47
|
+
let cache_control = null;
|
|
48
|
+
let age = null;
|
|
49
|
+
|
|
50
|
+
for (const [key, value] of fetched.response.headers) {
|
|
51
|
+
if (filter(key, value)) {
|
|
52
|
+
headers[key] = value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (key === 'cache-control') cache_control = value;
|
|
56
|
+
if (key === 'age') age = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const payload = {
|
|
60
|
+
status: fetched.response.status,
|
|
61
|
+
statusText: fetched.response.statusText,
|
|
62
|
+
headers,
|
|
63
|
+
body: fetched.response_body
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const safe_payload = JSON.stringify(payload).replace(pattern, (match) => replacements[match]);
|
|
67
|
+
|
|
68
|
+
const attrs = [
|
|
69
|
+
'type="application/json"',
|
|
70
|
+
'data-sveltekit-fetched',
|
|
71
|
+
`data-url=${escape_html_attr(fetched.url)}`
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
if (fetched.request_body) {
|
|
75
|
+
attrs.push(`data-hash=${escape_html_attr(hash(fetched.request_body))}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!prerendering && fetched.method === 'GET' && cache_control) {
|
|
79
|
+
const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control);
|
|
80
|
+
if (match) {
|
|
81
|
+
const ttl = +match[1] - +(age ?? '0');
|
|
82
|
+
attrs.push(`data-ttl="${ttl}"`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return `<script ${attrs.join(' ')}>${safe_payload}</script>`;
|
|
87
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CookieSerializeOptions } from 'cookie';
|
|
2
|
+
import { SSRNode, CspDirectives } from 'types';
|
|
3
|
+
|
|
4
|
+
export interface Fetched {
|
|
5
|
+
url: string;
|
|
6
|
+
method: string;
|
|
7
|
+
request_body?: string | ArrayBufferView | null;
|
|
8
|
+
response_body: string;
|
|
9
|
+
response: Response;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type Loaded = {
|
|
13
|
+
node: SSRNode;
|
|
14
|
+
data: Record<string, any> | null;
|
|
15
|
+
server_data: any;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type CspMode = 'hash' | 'nonce' | 'auto';
|
|
19
|
+
|
|
20
|
+
export interface CspConfig {
|
|
21
|
+
mode: CspMode;
|
|
22
|
+
directives: CspDirectives;
|
|
23
|
+
reportOnly: CspDirectives;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CspOpts {
|
|
27
|
+
dev: boolean;
|
|
28
|
+
prerender: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Cookie {
|
|
32
|
+
name: string;
|
|
33
|
+
value: string;
|
|
34
|
+
options: CookieSerializeOptions;
|
|
35
|
+
}
|