@sveltejs/kit 3.0.0-next.4 → 3.0.0-next.7
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/package.json +23 -18
- package/src/core/adapt/builder.js +29 -43
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +135 -71
- package/src/core/config/options.js +291 -244
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +120 -5
- package/src/core/generate_manifest/index.js +12 -15
- package/src/core/postbuild/analyse.js +8 -12
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/prerender.js +40 -23
- package/src/core/sync/create_manifest_data/index.js +29 -49
- package/src/core/sync/write_client_manifest.js +14 -14
- package/src/core/sync/write_non_ambient.js +10 -7
- package/src/core/sync/write_root.js +9 -30
- package/src/core/sync/write_server.js +13 -11
- package/src/core/sync/write_tsconfig.js +2 -4
- package/src/core/sync/write_types/index.js +11 -10
- package/src/exports/index.js +34 -12
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/env.js +1 -1
- package/src/exports/internal/index.js +1 -90
- package/src/exports/internal/{event.js → server/event.js} +1 -2
- package/src/exports/internal/server/index.js +33 -0
- package/src/exports/internal/shared.js +89 -0
- package/src/exports/node/index.js +58 -7
- package/src/exports/params.js +63 -0
- package/src/exports/public.d.ts +299 -145
- package/src/exports/url.js +84 -0
- package/src/exports/vite/build/build_server.js +6 -1
- package/src/exports/vite/dev/index.js +38 -37
- package/src/exports/vite/index.js +498 -353
- package/src/exports/vite/preview/index.js +16 -8
- package/src/exports/vite/utils.js +7 -11
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/forms.js +22 -5
- package/src/runtime/app/paths/client.js +4 -10
- package/src/runtime/app/paths/public.d.ts +0 -28
- package/src/runtime/app/paths/server.js +8 -4
- package/src/runtime/app/server/remote/form.js +10 -3
- package/src/runtime/app/server/remote/query.js +9 -18
- package/src/runtime/app/server/remote/requested.js +8 -4
- package/src/runtime/app/server/remote/shared.js +5 -7
- package/src/runtime/app/state/client.js +1 -2
- package/src/runtime/app/stores.js +13 -76
- package/src/runtime/client/client.js +205 -167
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
- package/src/runtime/client/remote-functions/form.svelte.js +122 -31
- package/src/runtime/client/remote-functions/prerender.svelte.js +17 -4
- package/src/runtime/client/remote-functions/query/index.js +3 -2
- package/src/runtime/client/remote-functions/query/instance.svelte.js +27 -11
- package/src/runtime/client/remote-functions/query-batch.svelte.js +4 -3
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +42 -15
- package/src/runtime/client/remote-functions/shared.svelte.js +18 -9
- package/src/runtime/client/state.svelte.js +66 -49
- package/src/runtime/client/types.d.ts +10 -2
- package/src/runtime/client/utils.js +0 -96
- package/src/runtime/form-utils.js +99 -18
- package/src/runtime/server/cookie.js +22 -33
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +7 -7
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/index.js +2 -2
- package/src/runtime/server/page/actions.js +41 -26
- package/src/runtime/server/page/index.js +2 -1
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +36 -47
- package/src/runtime/server/page/respond_with_error.js +7 -8
- package/src/runtime/server/page/server_routing.js +13 -9
- package/src/runtime/server/remote.js +85 -39
- package/src/runtime/server/respond.js +92 -58
- package/src/runtime/server/utils.js +7 -7
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/types/ambient.d.ts +10 -5
- package/src/types/global-private.d.ts +4 -4
- package/src/types/internal.d.ts +17 -20
- package/src/types/private.d.ts +33 -1
- package/src/types/synthetic/$lib.md +1 -1
- package/src/utils/error.js +12 -4
- package/src/utils/mime.js +9 -0
- package/src/utils/params.js +66 -0
- package/src/utils/routing.js +84 -38
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/streaming.js +14 -4
- package/src/utils/url.js +20 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +434 -269
- package/types/index.d.ts.map +19 -15
- package/src/exports/internal/server.js +0 -22
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
package/src/core/env.js
CHANGED
|
@@ -51,6 +51,10 @@ export async function load_explicit_env(kit, file, root, mode) {
|
|
|
51
51
|
/** @type {Record<string, EnvVarConfig<any>>} */
|
|
52
52
|
let variables;
|
|
53
53
|
|
|
54
|
+
/** @type {import('../runtime/app/env/internal.js')} */ (
|
|
55
|
+
await server.ssrLoadModule(`${runtime_directory}/app/env/internal.js`)
|
|
56
|
+
).set_building();
|
|
57
|
+
|
|
54
58
|
try {
|
|
55
59
|
({ variables } = await server.ssrLoadModule(file));
|
|
56
60
|
|
|
@@ -87,11 +91,12 @@ export async function load_explicit_env(kit, file, root, mode) {
|
|
|
87
91
|
|
|
88
92
|
/**
|
|
89
93
|
* Creates the `__sveltekit/env` module
|
|
90
|
-
* @param {Record<string, EnvVarConfig<any
|
|
94
|
+
* @param {Record<string, EnvVarConfig<any> | undefined> | null} variables
|
|
91
95
|
* @param {Record<string, string>} env
|
|
92
96
|
* @param {string | null} entry
|
|
97
|
+
* @param {boolean} is_dev
|
|
93
98
|
*/
|
|
94
|
-
export function create_sveltekit_env(variables, env, entry) {
|
|
99
|
+
export function create_sveltekit_env(variables, env, entry, is_dev) {
|
|
95
100
|
const imports = entry
|
|
96
101
|
? [
|
|
97
102
|
`import { variables } from ${JSON.stringify(entry)};`,
|
|
@@ -106,7 +111,7 @@ export function create_sveltekit_env(variables, env, entry) {
|
|
|
106
111
|
const issues = {};
|
|
107
112
|
|
|
108
113
|
for (const [name, config] of Object.entries(variables ?? {})) {
|
|
109
|
-
if (config
|
|
114
|
+
if (config?.static) {
|
|
110
115
|
if (config.public) {
|
|
111
116
|
const value = validate(variables ?? {}, env[name], name, issues);
|
|
112
117
|
declarations.push(`explicit_public_env.${name} = ${devalue.uneval(value)};`);
|
|
@@ -116,7 +121,7 @@ export function create_sveltekit_env(variables, env, entry) {
|
|
|
116
121
|
`const ${name} = validate(variables, env.${name}, ${JSON.stringify(name)}, issues);`
|
|
117
122
|
);
|
|
118
123
|
|
|
119
|
-
if (config
|
|
124
|
+
if (config?.public) {
|
|
120
125
|
setters.push(`explicit_public_env.${name} = ${name};`);
|
|
121
126
|
setters.push(`rendered_env.${name} = ${name};`);
|
|
122
127
|
} else {
|
|
@@ -145,6 +150,19 @@ export function create_sveltekit_env(variables, env, entry) {
|
|
|
145
150
|
}`
|
|
146
151
|
];
|
|
147
152
|
|
|
153
|
+
// In dev, initialise the env immediately. Tools like `vite-node` load modules
|
|
154
|
+
// through the Vite config but don't run the SvelteKit dev server, which is what
|
|
155
|
+
// normally calls `set_env`. Without this, dynamic env vars imported from
|
|
156
|
+
// `$app/env/public` and `$app/env/private` would be `undefined` in such contexts.
|
|
157
|
+
if (is_dev) {
|
|
158
|
+
/** @type {Record<string, string>} */
|
|
159
|
+
const dev_env = {};
|
|
160
|
+
for (const name of Object.keys(variables ?? {})) {
|
|
161
|
+
if (name in env) dev_env[name] = env[name];
|
|
162
|
+
}
|
|
163
|
+
blocks.push(`set_env(${devalue.uneval(dev_env)});`);
|
|
164
|
+
}
|
|
165
|
+
|
|
148
166
|
const module = blocks.join('\n\n');
|
|
149
167
|
|
|
150
168
|
return module;
|
|
@@ -213,9 +231,36 @@ export function create_sveltekit_env_public(variables, env, prelude) {
|
|
|
213
231
|
return `${prelude}\n\n${exports.join('')}`;
|
|
214
232
|
}
|
|
215
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Creates the `__sveltekit/env/service-worker` module used in production. When an app uses
|
|
236
|
+
* dynamic public env vars, they're loaded at runtime via an import of the prerendered
|
|
237
|
+
* `env.js`. If there are none, values are inlined.
|
|
238
|
+
* @param {Record<string, EnvVarConfig<any>> | null} variables
|
|
239
|
+
* @param {Record<string, string>} env
|
|
240
|
+
* @param {string} global
|
|
241
|
+
* @param {string} base
|
|
242
|
+
* @param {string} app_dir
|
|
243
|
+
*/
|
|
244
|
+
export function create_sveltekit_env_service_worker(variables, env, global, base, app_dir) {
|
|
245
|
+
const has_dynamic_public_env = Object.values(variables ?? {}).some(
|
|
246
|
+
(config) => config.public && !config.static
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (!has_dynamic_public_env) {
|
|
250
|
+
return create_sveltekit_env_service_worker_dev(variables, env, global);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return dedent`
|
|
254
|
+
import { env } from '${base}/${app_dir}/env.js';
|
|
255
|
+
|
|
256
|
+
globalThis.__SVELTEKIT_EXPERIMENTAL_EXPLICIT_ENVIRONMENT_VARIABLES__ = true;
|
|
257
|
+
|
|
258
|
+
${global} = { env };
|
|
259
|
+
`;
|
|
260
|
+
}
|
|
261
|
+
|
|
216
262
|
/**
|
|
217
263
|
* Creates the `__sveltekit/env/service-worker` module used in development
|
|
218
|
-
* (but not in prod, which goes through build_service_worker instead)
|
|
219
264
|
* @param {Record<string, EnvVarConfig<any>> | null} variables
|
|
220
265
|
* @param {Record<string, string>} env
|
|
221
266
|
* @param {string} global
|
|
@@ -330,3 +375,73 @@ export const reserved = new Set([
|
|
|
330
375
|
]);
|
|
331
376
|
|
|
332
377
|
export const valid_identifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Generates `export const` declarations (and, for reserved-word names that need
|
|
381
|
+
* aliasing, `const` + re-export specifiers) for a set of named exports.
|
|
382
|
+
*
|
|
383
|
+
* For regular names, emits a single efficient `export const name = expr;` statement.
|
|
384
|
+
* For reserved-word names (e.g. `delete`, `class`), emits `const alias = expr;` plus
|
|
385
|
+
* a re-export specifier (`alias as name`), since reserved words can't be `const`
|
|
386
|
+
* binding names but CAN appear in export specifiers.
|
|
387
|
+
*
|
|
388
|
+
* You can do evil things like `export { c as class }`. In order to import/re-export
|
|
389
|
+
* these, you need to alias the binding, then un-alias it when re-exporting:
|
|
390
|
+
*
|
|
391
|
+
* const _0 = ...; // safe binding name
|
|
392
|
+
* export { _0 as class }; // valid — `class` is allowed in export specifiers
|
|
393
|
+
*
|
|
394
|
+
* Aliases are chosen to avoid collisions with any of the supplied names. The
|
|
395
|
+
* namespace binding (used to hold the imported module) is likewise chosen to
|
|
396
|
+
* avoid collisions.
|
|
397
|
+
*
|
|
398
|
+
* @param {Iterable<string>} names — the export names
|
|
399
|
+
* @param {(name: string, namespace: string) => string} build_expression —
|
|
400
|
+
* called for each name to produce the right-hand side of the declaration;
|
|
401
|
+
* receives the chosen namespace binding so it can reference the imported module
|
|
402
|
+
* @param {string} namespace_prefix — the preferred binding name for the namespace
|
|
403
|
+
* (suffixed with a number if it collides with any export name)
|
|
404
|
+
* @returns {{ namespace: string, declarations: string[], reexports: string[] }}
|
|
405
|
+
*/
|
|
406
|
+
export function create_exported_declarations(names, build_expression, namespace_prefix) {
|
|
407
|
+
/** @type {Set<string>} */
|
|
408
|
+
const set = new Set(names);
|
|
409
|
+
|
|
410
|
+
let namespace = namespace_prefix;
|
|
411
|
+
let namespace_index = 0;
|
|
412
|
+
while (set.has(namespace)) {
|
|
413
|
+
namespace = `${namespace_prefix}${namespace_index++}`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let alias_index = 0;
|
|
417
|
+
/** @type {Map<string, string>} */
|
|
418
|
+
const aliases = new Map();
|
|
419
|
+
|
|
420
|
+
for (const name of set) {
|
|
421
|
+
if (!reserved.has(name)) continue;
|
|
422
|
+
|
|
423
|
+
let alias = `_${alias_index++}`;
|
|
424
|
+
while (set.has(alias)) {
|
|
425
|
+
alias = `_${alias_index++}`;
|
|
426
|
+
}
|
|
427
|
+
aliases.set(name, alias);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** @type {string[]} */
|
|
431
|
+
const declarations = [];
|
|
432
|
+
/** @type {string[]} */
|
|
433
|
+
const reexports = [];
|
|
434
|
+
|
|
435
|
+
for (const name of set) {
|
|
436
|
+
const alias = aliases.get(name);
|
|
437
|
+
const expr = build_expression(name, namespace);
|
|
438
|
+
if (alias) {
|
|
439
|
+
declarations.push(`const ${alias} = ${expr};`);
|
|
440
|
+
reexports.push(`${alias} as ${name}`);
|
|
441
|
+
} else {
|
|
442
|
+
declarations.push(`export const ${name} = ${expr};`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return { namespace, declarations, reexports };
|
|
447
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @import { RemoteChunk } from 'types' */
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import
|
|
4
|
+
import { lookup as mime_lookup } from '../../utils/mime.js';
|
|
5
5
|
import { s } from '../../utils/misc.js';
|
|
6
6
|
import { get_mime_lookup } from '../utils.js';
|
|
7
7
|
import { resolve_symlinks } from '../../exports/vite/build/utils.js';
|
|
@@ -69,10 +69,8 @@ export function generate_manifest({
|
|
|
69
69
|
assets.push(build_data.service_worker);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const matchers = new Set(
|
|
75
|
-
build_data.client?.nodes ? Object.keys(build_data.manifest_data.matchers) : undefined
|
|
72
|
+
const uses_matchers = build_data.manifest_data.routes.some((route) =>
|
|
73
|
+
route.params.some((param) => param.matcher)
|
|
76
74
|
);
|
|
77
75
|
|
|
78
76
|
/** @param {Array<number | undefined>} indexes */
|
|
@@ -93,7 +91,7 @@ export function generate_manifest({
|
|
|
93
91
|
files[file] = fs.statSync(path.resolve(build_data.out_dir, 'server', file)).size;
|
|
94
92
|
|
|
95
93
|
const ext = path.extname(file);
|
|
96
|
-
mime_types[ext] ??=
|
|
94
|
+
mime_types[ext] ??= mime_lookup(ext) || '';
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
// prettier-ignore
|
|
@@ -117,10 +115,6 @@ export function generate_manifest({
|
|
|
117
115
|
${routes.map(route => {
|
|
118
116
|
if (!route.page && !route.endpoint) return;
|
|
119
117
|
|
|
120
|
-
route.params.forEach(param => {
|
|
121
|
-
if (param.matcher) matchers.add(param.matcher);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
118
|
return dedent`
|
|
125
119
|
{
|
|
126
120
|
id: ${s(route.id)},
|
|
@@ -134,11 +128,14 @@ export function generate_manifest({
|
|
|
134
128
|
],
|
|
135
129
|
prerendered_routes: new Set(${s(prerendered)}),
|
|
136
130
|
matchers: async () => {
|
|
137
|
-
${
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
${
|
|
132
|
+
uses_matchers && build_data.manifest_data.params
|
|
133
|
+
? dedent`
|
|
134
|
+
const { params } = await import('${join_relative(relative_path, '/entries/params.js')}');
|
|
135
|
+
return params;
|
|
136
|
+
`
|
|
137
|
+
: 'return {};'
|
|
138
|
+
}
|
|
142
139
|
},
|
|
143
140
|
server_assets: ${s(files)}
|
|
144
141
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** @import { Adapter } from '@sveltejs/kit' */
|
|
2
1
|
/** @import { RemoteChunk } from 'types' */
|
|
3
2
|
import { join } from 'node:path';
|
|
4
3
|
import { pathToFileURL } from 'node:url';
|
|
@@ -39,15 +38,7 @@ async function analyse({
|
|
|
39
38
|
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
|
|
40
39
|
|
|
41
40
|
const vite_config = await load_vite_config(vite_config_file);
|
|
42
|
-
|
|
43
41
|
const config = extract_svelte_config(vite_config).kit;
|
|
44
|
-
|
|
45
|
-
// TODO i think this can just be config.adapter?
|
|
46
|
-
/** @type {Adapter | undefined} */
|
|
47
|
-
const adapter = vite_config.plugins.find(
|
|
48
|
-
(plugin) => plugin.name === 'vite-plugin-sveltekit-adapter'
|
|
49
|
-
)?.api?.adapter;
|
|
50
|
-
|
|
51
42
|
const server_root = join(config.outDir, 'output');
|
|
52
43
|
|
|
53
44
|
/** @type {import('types').ServerInternalModule} */
|
|
@@ -57,11 +48,16 @@ async function analyse({
|
|
|
57
48
|
// essential we do this before analysing the code
|
|
58
49
|
internal.set_building();
|
|
59
50
|
|
|
60
|
-
// set
|
|
61
|
-
internal.set_env(env);
|
|
51
|
+
// set `read` and `manifest`, in case they're used in initialisation
|
|
62
52
|
internal.set_manifest(manifest);
|
|
63
53
|
internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`));
|
|
64
54
|
|
|
55
|
+
// `set_env` lives in a separate module that imports the user's `src/env` config. We import it
|
|
56
|
+
// *after* `set_building()` so that `building`-dependent expressions resolve correctly
|
|
57
|
+
/** @type {import('__sveltekit/env')} */
|
|
58
|
+
const { set_env } = await import(pathToFileURL(`${server_root}/server/env.js`).href);
|
|
59
|
+
set_env(env);
|
|
60
|
+
|
|
65
61
|
/** @type {import('types').ServerMetadata} */
|
|
66
62
|
const metadata = {
|
|
67
63
|
nodes: [],
|
|
@@ -126,7 +122,7 @@ async function analyse({
|
|
|
126
122
|
server_manifest,
|
|
127
123
|
tracked_features
|
|
128
124
|
)) {
|
|
129
|
-
check_feature(route.id, route_config, feature, adapter);
|
|
125
|
+
check_feature(route.id, route_config, feature, config.adapter);
|
|
130
126
|
}
|
|
131
127
|
}
|
|
132
128
|
|
|
@@ -38,6 +38,18 @@ export function crawl(html, base) {
|
|
|
38
38
|
/** @type {string[]} */
|
|
39
39
|
const hrefs = [];
|
|
40
40
|
|
|
41
|
+
/** @type {string[]} */
|
|
42
|
+
const invalid = [];
|
|
43
|
+
|
|
44
|
+
/** @param {string} url */
|
|
45
|
+
const push_href = (url) => {
|
|
46
|
+
try {
|
|
47
|
+
hrefs.push(resolve(base, url));
|
|
48
|
+
} catch {
|
|
49
|
+
invalid.push(url);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
41
53
|
let i = 0;
|
|
42
54
|
main: while (i < html.length) {
|
|
43
55
|
const char = html[i];
|
|
@@ -186,9 +198,13 @@ export function crawl(html, base) {
|
|
|
186
198
|
|
|
187
199
|
if (href) {
|
|
188
200
|
if (tag === 'BASE') {
|
|
189
|
-
|
|
201
|
+
try {
|
|
202
|
+
base = resolve(base, href);
|
|
203
|
+
} catch {
|
|
204
|
+
invalid.push(href);
|
|
205
|
+
}
|
|
190
206
|
} else if (!rel || !/\bexternal\b/i.test(rel)) {
|
|
191
|
-
|
|
207
|
+
push_href(href);
|
|
192
208
|
}
|
|
193
209
|
}
|
|
194
210
|
|
|
@@ -201,7 +217,7 @@ export function crawl(html, base) {
|
|
|
201
217
|
}
|
|
202
218
|
|
|
203
219
|
if (src) {
|
|
204
|
-
|
|
220
|
+
push_href(src);
|
|
205
221
|
}
|
|
206
222
|
|
|
207
223
|
if (srcset) {
|
|
@@ -222,7 +238,7 @@ export function crawl(html, base) {
|
|
|
222
238
|
candidates.push(value);
|
|
223
239
|
for (const candidate of candidates) {
|
|
224
240
|
const src = candidate.split(WHITESPACE)[0];
|
|
225
|
-
if (src)
|
|
241
|
+
if (src) push_href(src);
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
244
|
|
|
@@ -230,7 +246,7 @@ export function crawl(html, base) {
|
|
|
230
246
|
const attr = name ?? property;
|
|
231
247
|
|
|
232
248
|
if (attr && CRAWLABLE_META_NAME_ATTRS.has(attr)) {
|
|
233
|
-
|
|
249
|
+
push_href(content);
|
|
234
250
|
}
|
|
235
251
|
}
|
|
236
252
|
}
|
|
@@ -239,5 +255,5 @@ export function crawl(html, base) {
|
|
|
239
255
|
i += 1;
|
|
240
256
|
}
|
|
241
257
|
|
|
242
|
-
return { ids, hrefs };
|
|
258
|
+
return { ids, hrefs, invalid };
|
|
243
259
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** @import { Adapter } from '@sveltejs/kit' */
|
|
2
1
|
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
2
|
import { dirname, join } from 'node:path';
|
|
4
3
|
import { pathToFileURL } from 'node:url';
|
|
@@ -44,14 +43,20 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
44
43
|
/** @type {import('types').ServerInternalModule} */
|
|
45
44
|
const internal = await import(pathToFileURL(`${out}/server/internal.js`).href);
|
|
46
45
|
|
|
47
|
-
/** @type {import('types').ServerModule} */
|
|
48
|
-
const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
|
|
49
|
-
|
|
50
46
|
// configure `import { building } from `$app/env` —
|
|
51
47
|
// essential we do this before analysing the code
|
|
52
48
|
internal.set_building();
|
|
53
49
|
internal.set_prerendering();
|
|
54
50
|
|
|
51
|
+
// `set_env` and `Server` live in modules that import the user's `src/env` config. We import them
|
|
52
|
+
// *after* `set_building()` so that `building`-dependent expressions resolve correctly
|
|
53
|
+
/** @type {import('__sveltekit/env')} */
|
|
54
|
+
const { set_env } = await import(pathToFileURL(`${out}/server/env.js`).href);
|
|
55
|
+
set_env(env);
|
|
56
|
+
|
|
57
|
+
/** @type {import('types').ServerModule} */
|
|
58
|
+
const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href);
|
|
59
|
+
|
|
55
60
|
/**
|
|
56
61
|
* @template {{message: string}} T
|
|
57
62
|
* @template {Omit<T, 'message'>} K
|
|
@@ -105,12 +110,14 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
105
110
|
|
|
106
111
|
const config = extract_svelte_config(vite_config).kit;
|
|
107
112
|
|
|
113
|
+
const prerender_origin = config.paths.origin || 'http://sveltekit-prerender';
|
|
114
|
+
|
|
108
115
|
if (hash) {
|
|
109
116
|
const fallback = await generate_fallback({
|
|
110
117
|
manifest_path,
|
|
111
118
|
env,
|
|
112
119
|
out_dir: config.outDir,
|
|
113
|
-
origin:
|
|
120
|
+
origin: prerender_origin,
|
|
114
121
|
assets: config.files.assets
|
|
115
122
|
});
|
|
116
123
|
|
|
@@ -125,13 +132,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
125
132
|
return { prerendered, prerender_map };
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
|
|
129
|
-
/** @type {Adapter | undefined} */
|
|
130
|
-
const adapter = vite_config.plugins.find(
|
|
131
|
-
(plugin) => plugin.name === 'vite-plugin-sveltekit-adapter'
|
|
132
|
-
)?.api?.adapter;
|
|
133
|
-
|
|
134
|
-
const emulator = await adapter?.emulate?.();
|
|
135
|
+
const emulator = await config.adapter?.emulate?.();
|
|
135
136
|
|
|
136
137
|
/** @type {import('types').Logger} */
|
|
137
138
|
const log = logger({ verbose });
|
|
@@ -145,7 +146,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
145
146
|
({ status, path, referrer, referenceType }) => {
|
|
146
147
|
const message =
|
|
147
148
|
status === 404 && !path.startsWith(config.paths.base)
|
|
148
|
-
? `${path} does not begin with \`base
|
|
149
|
+
? `${path} does not begin with \`base\`. You can fix this by using \`resolve('${path}')\` from \`$app/paths\`. The base path is configurable from \`paths.base\` - see https://svelte.dev/docs/kit/configuration#paths for more info`
|
|
149
150
|
: path;
|
|
150
151
|
|
|
151
152
|
return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
|
|
@@ -180,6 +181,14 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
180
181
|
}
|
|
181
182
|
);
|
|
182
183
|
|
|
184
|
+
const handle_invalid_url = normalise_error_handler(
|
|
185
|
+
log,
|
|
186
|
+
config.prerender.handleInvalidUrl,
|
|
187
|
+
({ href, referrer }) => {
|
|
188
|
+
return `Invalid URL ${href}${referrer ? ` (linked from ${referrer})` : ''}`;
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
|
|
183
192
|
const q = queue(config.prerender.concurrency);
|
|
184
193
|
|
|
185
194
|
/**
|
|
@@ -251,7 +260,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
251
260
|
/** @type {Map<string, import('types').PrerenderDependency>} */
|
|
252
261
|
const dependencies = new Map();
|
|
253
262
|
|
|
254
|
-
const response = await server.respond(new Request(
|
|
263
|
+
const response = await server.respond(new Request(prerender_origin + encoded), {
|
|
255
264
|
getClientAddress() {
|
|
256
265
|
throw new Error('Cannot read clientAddress during prerendering');
|
|
257
266
|
},
|
|
@@ -334,16 +343,20 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
334
343
|
|
|
335
344
|
// if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those
|
|
336
345
|
if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') {
|
|
337
|
-
const { ids, hrefs } = crawl(body.toString(), decoded);
|
|
346
|
+
const { ids, hrefs, invalid } = crawl(body.toString(), decoded);
|
|
347
|
+
|
|
348
|
+
for (const href of invalid) {
|
|
349
|
+
handle_invalid_url({ href, referrer: decoded });
|
|
350
|
+
}
|
|
338
351
|
|
|
339
352
|
actual_hashlinks.set(decoded, ids);
|
|
340
353
|
|
|
341
354
|
/** @param {string} href */
|
|
342
355
|
const removePrerenderOrigin = (href) => {
|
|
343
|
-
if (href.startsWith(
|
|
344
|
-
if (href ===
|
|
345
|
-
if (href.at(
|
|
346
|
-
return href.slice(
|
|
356
|
+
if (href.startsWith(prerender_origin)) {
|
|
357
|
+
if (href === prerender_origin) return '/';
|
|
358
|
+
if (href.at(prerender_origin.length) !== '/') return href;
|
|
359
|
+
return href.slice(prerender_origin.length);
|
|
347
360
|
}
|
|
348
361
|
return href;
|
|
349
362
|
};
|
|
@@ -388,6 +401,12 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
388
401
|
const type = headers['content-type'];
|
|
389
402
|
const is_html = response_type === REDIRECT || type === 'text/html';
|
|
390
403
|
|
|
404
|
+
if (!is_html && response.status === 200 && decoded.slice(config.paths.base.length + 1) === '') {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`Cannot prerender a root +server.js that returns a non-HTML response - static hosts always serve an HTML file for \`${config.paths.base || '/'}\``
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
391
410
|
const file = output_filename(decoded, is_html);
|
|
392
411
|
const dest = `${config.outDir}/output/prerendered/${category}/${file}`;
|
|
393
412
|
|
|
@@ -497,10 +516,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit
|
|
|
497
516
|
}
|
|
498
517
|
}
|
|
499
518
|
|
|
500
|
-
// the user's remote function modules may reference
|
|
501
|
-
//
|
|
502
|
-
// evaluating those modules to avoid potential runtime errors
|
|
503
|
-
internal.set_env(env);
|
|
519
|
+
// the user's remote function modules may reference `read` or the `manifest` at the top-level
|
|
520
|
+
// so we need to set them before evaluating those modules to avoid potential runtime errors
|
|
504
521
|
internal.set_manifest(manifest);
|
|
505
522
|
internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
|
|
506
523
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lookup } from '
|
|
1
|
+
import { lookup } from '../../../utils/mime.js';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { styleText } from 'node:util';
|
|
@@ -29,22 +29,13 @@ export default function create_manifest_data({
|
|
|
29
29
|
}) {
|
|
30
30
|
const assets = create_assets(config);
|
|
31
31
|
const hooks = create_hooks(config, cwd);
|
|
32
|
-
const
|
|
32
|
+
const params = resolve_params(config, cwd);
|
|
33
33
|
const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);
|
|
34
34
|
|
|
35
|
-
// validate matcher names used in parameterised routes
|
|
36
|
-
for (const route of routes) {
|
|
37
|
-
for (const param of route.params) {
|
|
38
|
-
if (param.matcher && !matchers[param.matcher]) {
|
|
39
|
-
throw new Error(`No matcher found for parameter '${param.matcher}' in route ${route.id}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
35
|
return {
|
|
45
36
|
assets,
|
|
46
37
|
hooks,
|
|
47
|
-
|
|
38
|
+
params,
|
|
48
39
|
nodes,
|
|
49
40
|
routes
|
|
50
41
|
};
|
|
@@ -82,38 +73,9 @@ function create_hooks(config, cwd) {
|
|
|
82
73
|
* @param {import('types').ValidatedConfig} config
|
|
83
74
|
* @param {string} cwd
|
|
84
75
|
*/
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
/** @type {Record<string, string>} */
|
|
89
|
-
const matchers = {};
|
|
90
|
-
if (fs.existsSync(config.kit.files.params)) {
|
|
91
|
-
for (const file of fs.readdirSync(config.kit.files.params)) {
|
|
92
|
-
const ext = path.extname(file);
|
|
93
|
-
if (!config.kit.moduleExtensions.includes(ext)) continue;
|
|
94
|
-
const type = file.slice(0, -ext.length);
|
|
95
|
-
|
|
96
|
-
if (/^\w+$/.test(type)) {
|
|
97
|
-
const matcher_file = path.join(params_base, file);
|
|
98
|
-
|
|
99
|
-
// Disallow same matcher with different extensions
|
|
100
|
-
if (matchers[type]) {
|
|
101
|
-
throw new Error(`Duplicate matchers: ${matcher_file} and ${matchers[type]}`);
|
|
102
|
-
} else {
|
|
103
|
-
matchers[type] = matcher_file;
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
// Allow for matcher test collocation
|
|
107
|
-
if (type.endsWith('.test') || type.endsWith('.spec')) continue;
|
|
108
|
-
|
|
109
|
-
throw new Error(
|
|
110
|
-
`Matcher names can only have underscores and alphanumeric characters — "${file}" is invalid`
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return matchers;
|
|
76
|
+
function resolve_params(config, cwd) {
|
|
77
|
+
const params_file = resolve_entry(config.kit.files.params);
|
|
78
|
+
return params_file ? posixify(path.relative(cwd, params_file)) : null;
|
|
117
79
|
}
|
|
118
80
|
|
|
119
81
|
/**
|
|
@@ -221,10 +183,17 @@ function create_routes_and_nodes(cwd, config, fallback) {
|
|
|
221
183
|
|
|
222
184
|
// We can't use withFileTypes because of a NodeJs bug which returns wrong results
|
|
223
185
|
// with isDirectory() in case of symlinks: https://github.com/nodejs/node/issues/30646
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
186
|
+
// We sort the entries because `readdirSync` order is not guaranteed and differs
|
|
187
|
+
// between runtimes (e.g. Node returns entries alphabetically, Bun in directory
|
|
188
|
+
// order). Node indices are assigned from this traversal order, so without sorting
|
|
189
|
+
// the SSR and client manifests can disagree, causing hydration mismatches.
|
|
190
|
+
const files = fs
|
|
191
|
+
.readdirSync(dir)
|
|
192
|
+
.sort()
|
|
193
|
+
.map((name) => ({
|
|
194
|
+
is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
|
|
195
|
+
name
|
|
196
|
+
}));
|
|
228
197
|
|
|
229
198
|
// process files first
|
|
230
199
|
for (const file of files) {
|
|
@@ -368,7 +337,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
|
|
|
368
337
|
const root = routes[0];
|
|
369
338
|
if (!root.leaf && !root.error && !root.layout && !root.endpoint) {
|
|
370
339
|
throw new Error(
|
|
371
|
-
'No routes found. If you are using a custom src/routes directory, make sure it is specified in your
|
|
340
|
+
'No routes found. If you are using a custom src/routes directory, make sure it is specified in your SvelteKit Vite plugin options'
|
|
372
341
|
);
|
|
373
342
|
}
|
|
374
343
|
}
|
|
@@ -495,6 +464,17 @@ function create_routes_and_nodes(cwd, config, fallback) {
|
|
|
495
464
|
if (route.endpoint) {
|
|
496
465
|
route.endpoint.page_options = get_page_options(route.endpoint.file, cwd);
|
|
497
466
|
}
|
|
467
|
+
|
|
468
|
+
if (route.page && route.endpoint) {
|
|
469
|
+
const page = nodes[route.page.leaf];
|
|
470
|
+
if (page.page_options?.prerender || route.endpoint.page_options?.prerender) {
|
|
471
|
+
const endpoint_file = route.endpoint.file.split('/').pop();
|
|
472
|
+
|
|
473
|
+
throw new Error(
|
|
474
|
+
`Cannot prerender a route (${route.id}) with both a \`+page.svelte\` and a \`${endpoint_file}\``
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
498
478
|
}
|
|
499
479
|
|
|
500
480
|
return {
|
|
@@ -139,6 +139,11 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
|
|
|
139
139
|
write_if_changed(
|
|
140
140
|
`${output}/app.js`,
|
|
141
141
|
dedent`
|
|
142
|
+
// in dev, this makes Vite inject its client as this module's first dependency,
|
|
143
|
+
// so that global constant replacements are installed before any other module
|
|
144
|
+
// (including user hooks) evaluates. In build it's inert.
|
|
145
|
+
import.meta.hot;
|
|
146
|
+
|
|
142
147
|
${
|
|
143
148
|
client_hooks_file
|
|
144
149
|
? `import * as client_hooks from '${relative_path(output, client_hooks_file)}';`
|
|
@@ -177,25 +182,20 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
|
|
|
177
182
|
export const decode = (type, value) => decoders[type](value);
|
|
178
183
|
|
|
179
184
|
export { default as root } from '../root.js';
|
|
185
|
+
|
|
186
|
+
export const get_error_template = () => import('../shared/error-template.js').then(m => m.default);
|
|
180
187
|
`
|
|
181
188
|
);
|
|
182
189
|
|
|
183
190
|
if (client_routing) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const matchers = [];
|
|
188
|
-
|
|
189
|
-
for (const key in manifest_data.matchers) {
|
|
190
|
-
const src = manifest_data.matchers[key];
|
|
191
|
-
|
|
192
|
-
imports.push(`import { match as ${key} } from ${s(relative_path(output, src))};`);
|
|
193
|
-
matchers.push(key);
|
|
194
|
-
}
|
|
191
|
+
const uses_matchers = manifest_data.routes.some((route) =>
|
|
192
|
+
route.params.some((param) => param.matcher)
|
|
193
|
+
);
|
|
195
194
|
|
|
196
|
-
const module =
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
const module =
|
|
196
|
+
!manifest_data.params || !uses_matchers
|
|
197
|
+
? 'export const matchers = {};'
|
|
198
|
+
: `import { params as matchers } from ${s(relative_path(output, manifest_data.params))};\n\nexport { matchers };`;
|
|
199
199
|
|
|
200
200
|
write_if_changed(`${output}/matchers.js`, module);
|
|
201
201
|
}
|