@sveltejs/kit 1.15.11 → 1.16.1
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 +1 -1
- package/src/core/config/options.js +14 -0
- package/src/core/postbuild/analyse.js +14 -5
- package/src/core/postbuild/prerender.js +42 -3
- package/src/core/sync/write_types/index.js +6 -0
- package/src/exports/vite/index.js +6 -0
- package/src/runtime/client/client.js +23 -4
- package/src/runtime/server/respond.js +9 -4
- package/src/utils/exports.js +31 -17
- package/src/utils/options.js +3 -3
- package/src/utils/routing.js +55 -0
- package/types/index.d.ts +13 -1
- package/types/internal.d.ts +6 -0
- package/types/private.d.ts +9 -0
package/package.json
CHANGED
|
@@ -229,6 +229,20 @@ const options = object(
|
|
|
229
229
|
}
|
|
230
230
|
),
|
|
231
231
|
|
|
232
|
+
handleEntryGeneratorMismatch: validate(
|
|
233
|
+
(/** @type {any} */ { message }) => {
|
|
234
|
+
throw new Error(
|
|
235
|
+
message +
|
|
236
|
+
`\nTo suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://kit.svelte.dev/docs/configuration#prerender`
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
(input, keypath) => {
|
|
240
|
+
if (typeof input === 'function') return input;
|
|
241
|
+
if (['fail', 'warn', 'ignore'].includes(input)) return input;
|
|
242
|
+
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
|
|
243
|
+
}
|
|
244
|
+
),
|
|
245
|
+
|
|
232
246
|
origin: validate('http://sveltekit-prerender', (input, keypath) => {
|
|
233
247
|
assert_string(input, keypath);
|
|
234
248
|
|
|
@@ -2,7 +2,9 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { get_option } from '../../utils/options.js';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
validate_layout_exports,
|
|
6
|
+
validate_layout_server_exports,
|
|
7
|
+
validate_page_exports,
|
|
6
8
|
validate_page_server_exports,
|
|
7
9
|
validate_server_exports
|
|
8
10
|
} from '../../utils/exports.js';
|
|
@@ -10,6 +12,7 @@ import { load_config } from '../config/index.js';
|
|
|
10
12
|
import { forked } from '../../utils/fork.js';
|
|
11
13
|
import { should_polyfill } from '../../utils/platform.js';
|
|
12
14
|
import { installPolyfills } from '../../exports/node/polyfills.js';
|
|
15
|
+
import { resolve_entry } from '../../utils/routing.js';
|
|
13
16
|
|
|
14
17
|
export default forked(import.meta.url, analyse);
|
|
15
18
|
|
|
@@ -72,6 +75,8 @@ async function analyse({ manifest_path, env }) {
|
|
|
72
75
|
let prerender = undefined;
|
|
73
76
|
/** @type {any} */
|
|
74
77
|
let config = undefined;
|
|
78
|
+
/** @type {import('types').PrerenderEntryGenerator | undefined} */
|
|
79
|
+
let entries = undefined;
|
|
75
80
|
|
|
76
81
|
if (route.endpoint) {
|
|
77
82
|
const mod = await route.endpoint();
|
|
@@ -95,6 +100,7 @@ async function analyse({ manifest_path, env }) {
|
|
|
95
100
|
if (mod.OPTIONS) api_methods.push('OPTIONS');
|
|
96
101
|
|
|
97
102
|
config = mod.config;
|
|
103
|
+
entries = mod.entries;
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
if (route.page) {
|
|
@@ -109,8 +115,8 @@ async function analyse({ manifest_path, env }) {
|
|
|
109
115
|
|
|
110
116
|
for (const layout of layouts) {
|
|
111
117
|
if (layout) {
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
validate_layout_server_exports(layout.server, layout.server_id);
|
|
119
|
+
validate_layout_exports(layout.universal, layout.universal_id);
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
|
|
@@ -119,12 +125,13 @@ async function analyse({ manifest_path, env }) {
|
|
|
119
125
|
if (page.server?.actions) page_methods.push('POST');
|
|
120
126
|
|
|
121
127
|
validate_page_server_exports(page.server, page.server_id);
|
|
122
|
-
|
|
128
|
+
validate_page_exports(page.universal, page.universal_id);
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
prerender = get_option(nodes, 'prerender') ?? false;
|
|
126
132
|
|
|
127
133
|
config = get_config(nodes);
|
|
134
|
+
entries ??= get_option(nodes, 'entries');
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
metadata.routes.set(route.id, {
|
|
@@ -136,7 +143,9 @@ async function analyse({ manifest_path, env }) {
|
|
|
136
143
|
api: {
|
|
137
144
|
methods: api_methods
|
|
138
145
|
},
|
|
139
|
-
prerender
|
|
146
|
+
prerender,
|
|
147
|
+
entries:
|
|
148
|
+
entries && (await entries()).map((entry_object) => resolve_entry(route.id, entry_object))
|
|
140
149
|
});
|
|
141
150
|
}
|
|
142
151
|
|
|
@@ -127,6 +127,14 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
|
|
|
127
127
|
}
|
|
128
128
|
);
|
|
129
129
|
|
|
130
|
+
const handle_entry_generator_mismatch = normalise_error_handler(
|
|
131
|
+
log,
|
|
132
|
+
config.prerender.handleEntryGeneratorMismatch,
|
|
133
|
+
({ generatedFromId, entry, matchedId }) => {
|
|
134
|
+
return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId} - see the \`handleEntryGeneratorMismatch\` option in https://kit.svelte.dev/docs/configuration#prerender for more info.`;
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
130
138
|
const q = queue(config.prerender.concurrency);
|
|
131
139
|
|
|
132
140
|
/**
|
|
@@ -164,23 +172,25 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
|
|
|
164
172
|
* @param {string | null} referrer
|
|
165
173
|
* @param {string} decoded
|
|
166
174
|
* @param {string} [encoded]
|
|
175
|
+
* @param {string} [generated_from_id]
|
|
167
176
|
*/
|
|
168
|
-
function enqueue(referrer, decoded, encoded) {
|
|
177
|
+
function enqueue(referrer, decoded, encoded, generated_from_id) {
|
|
169
178
|
if (seen.has(decoded)) return;
|
|
170
179
|
seen.add(decoded);
|
|
171
180
|
|
|
172
181
|
const file = decoded.slice(config.paths.base.length + 1);
|
|
173
182
|
if (files.has(file)) return;
|
|
174
183
|
|
|
175
|
-
return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer));
|
|
184
|
+
return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer, generated_from_id));
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
/**
|
|
179
188
|
* @param {string} decoded
|
|
180
189
|
* @param {string} encoded
|
|
181
190
|
* @param {string?} referrer
|
|
191
|
+
* @param {string} [generated_from_id]
|
|
182
192
|
*/
|
|
183
|
-
async function visit(decoded, encoded, referrer) {
|
|
193
|
+
async function visit(decoded, encoded, referrer, generated_from_id) {
|
|
184
194
|
if (!decoded.startsWith(config.paths.base)) {
|
|
185
195
|
handle_http_error({ status: 404, path: decoded, referrer, referenceType: 'linked' });
|
|
186
196
|
return;
|
|
@@ -206,6 +216,20 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
|
|
|
206
216
|
}
|
|
207
217
|
});
|
|
208
218
|
|
|
219
|
+
const encoded_id = response.headers.get('x-sveltekit-routeid');
|
|
220
|
+
const decoded_id = encoded_id && decode_uri(encoded_id);
|
|
221
|
+
if (
|
|
222
|
+
decoded_id !== null &&
|
|
223
|
+
generated_from_id !== undefined &&
|
|
224
|
+
decoded_id !== generated_from_id
|
|
225
|
+
) {
|
|
226
|
+
handle_entry_generator_mismatch({
|
|
227
|
+
generatedFromId: generated_from_id,
|
|
228
|
+
entry: decoded,
|
|
229
|
+
matchedId: decoded_id
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
209
233
|
const body = Buffer.from(await response.arrayBuffer());
|
|
210
234
|
|
|
211
235
|
save('pages', response, body, decoded, encoded, referrer, 'linked');
|
|
@@ -378,9 +402,18 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
|
|
|
378
402
|
saved.set(file, dest);
|
|
379
403
|
}
|
|
380
404
|
|
|
405
|
+
/** @type {Array<{ id: string, entries: Array<string>}>} */
|
|
406
|
+
const route_level_entries = [];
|
|
407
|
+
for (const [id, { entries }] of metadata.routes.entries()) {
|
|
408
|
+
if (entries) {
|
|
409
|
+
route_level_entries.push({ id, entries });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
381
413
|
if (
|
|
382
414
|
config.prerender.entries.length > 1 ||
|
|
383
415
|
config.prerender.entries[0] !== '*' ||
|
|
416
|
+
route_level_entries.length > 0 ||
|
|
384
417
|
prerender_map.size > 0
|
|
385
418
|
) {
|
|
386
419
|
// Only log if we're actually going to do something to not confuse users
|
|
@@ -401,6 +434,12 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
|
|
|
401
434
|
}
|
|
402
435
|
}
|
|
403
436
|
|
|
437
|
+
for (const { id, entries } of route_level_entries) {
|
|
438
|
+
for (const entry of entries) {
|
|
439
|
+
enqueue(null, config.paths.base + entry, undefined, id);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
404
443
|
await q.done();
|
|
405
444
|
|
|
406
445
|
// handle invalid fragment links
|
|
@@ -194,6 +194,12 @@ function update_types(config, routes, route, to_delete = new Set()) {
|
|
|
194
194
|
.join('; ')} }`
|
|
195
195
|
);
|
|
196
196
|
|
|
197
|
+
if (route.params.length > 0) {
|
|
198
|
+
exports.push(
|
|
199
|
+
`export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
197
203
|
declarations.push(`type RouteId = '${route.id}';`);
|
|
198
204
|
|
|
199
205
|
// These could also be placed in our public types, but it would bloat them unnecessarily and we may want to change these in the future
|
|
@@ -525,6 +525,12 @@ function kit({ svelte_config }) {
|
|
|
525
525
|
} else {
|
|
526
526
|
input['entry/start'] = `${runtime_directory}/client/start.js`;
|
|
527
527
|
input['entry/app'] = `${kit.outDir}/generated/client-optimized/app.js`;
|
|
528
|
+
|
|
529
|
+
manifest_data.nodes.forEach((node, i) => {
|
|
530
|
+
if (node.component || node.universal) {
|
|
531
|
+
input[`nodes/${i}`] = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`;
|
|
532
|
+
}
|
|
533
|
+
});
|
|
528
534
|
}
|
|
529
535
|
|
|
530
536
|
// see the kit.output.preloadStrategy option for details on why we have multiple options here
|
|
@@ -31,7 +31,7 @@ import { stores } from './singletons.js';
|
|
|
31
31
|
import { unwrap_promises } from '../../utils/promises.js';
|
|
32
32
|
import * as devalue from 'devalue';
|
|
33
33
|
import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
|
|
34
|
-
import {
|
|
34
|
+
import { validate_page_exports } from '../../utils/exports.js';
|
|
35
35
|
import { compact } from '../../utils/array.js';
|
|
36
36
|
import { INVALIDATED_PARAM, validate_depends } from '../shared.js';
|
|
37
37
|
|
|
@@ -171,6 +171,9 @@ export function create_client(app, target) {
|
|
|
171
171
|
if (navigation_result.type === 'redirect') {
|
|
172
172
|
return goto(new URL(navigation_result.location, url).href, {}, [url.pathname], nav_token);
|
|
173
173
|
} else {
|
|
174
|
+
if (navigation_result.props.page !== undefined) {
|
|
175
|
+
page = navigation_result.props.page;
|
|
176
|
+
}
|
|
174
177
|
root.$set(navigation_result.props);
|
|
175
178
|
}
|
|
176
179
|
}
|
|
@@ -428,7 +431,7 @@ export function create_client(app, target) {
|
|
|
428
431
|
const node = await loader();
|
|
429
432
|
|
|
430
433
|
if (DEV) {
|
|
431
|
-
|
|
434
|
+
validate_page_exports(node.universal);
|
|
432
435
|
}
|
|
433
436
|
|
|
434
437
|
if (node.universal?.load) {
|
|
@@ -1737,14 +1740,30 @@ export function create_client(app, target) {
|
|
|
1737
1740
|
});
|
|
1738
1741
|
});
|
|
1739
1742
|
|
|
1743
|
+
/** @type {Array<import('./types').BranchNode | undefined>} */
|
|
1744
|
+
const branch = await Promise.all(branch_promises);
|
|
1745
|
+
|
|
1746
|
+
const parsed_route = routes.find(({ id }) => id === route.id);
|
|
1747
|
+
|
|
1748
|
+
// server-side will have compacted the branch, reinstate empty slots
|
|
1749
|
+
// so that error boundaries can be lined up correctly
|
|
1750
|
+
if (parsed_route) {
|
|
1751
|
+
const layouts = parsed_route.layouts;
|
|
1752
|
+
for (let i = 0; i < layouts.length; i++) {
|
|
1753
|
+
if (!layouts[i]) {
|
|
1754
|
+
branch.splice(i, 0, undefined);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1740
1759
|
result = await get_navigation_result_from_branch({
|
|
1741
1760
|
url,
|
|
1742
1761
|
params,
|
|
1743
|
-
branch
|
|
1762
|
+
branch,
|
|
1744
1763
|
status,
|
|
1745
1764
|
error,
|
|
1746
1765
|
form,
|
|
1747
|
-
route:
|
|
1766
|
+
route: parsed_route ?? null
|
|
1748
1767
|
});
|
|
1749
1768
|
} catch (error) {
|
|
1750
1769
|
if (error instanceof Redirect) {
|
|
@@ -20,7 +20,9 @@ import { add_cookies_to_headers, get_cookies } from './cookie.js';
|
|
|
20
20
|
import { create_fetch } from './fetch.js';
|
|
21
21
|
import { Redirect } from '../control.js';
|
|
22
22
|
import {
|
|
23
|
-
|
|
23
|
+
validate_layout_exports,
|
|
24
|
+
validate_layout_server_exports,
|
|
25
|
+
validate_page_exports,
|
|
24
26
|
validate_page_server_exports,
|
|
25
27
|
validate_server_exports
|
|
26
28
|
} from '../../utils/exports.js';
|
|
@@ -197,8 +199,11 @@ export async function respond(request, options, manifest, state) {
|
|
|
197
199
|
|
|
198
200
|
for (const layout of layouts) {
|
|
199
201
|
if (layout) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
validate_layout_server_exports(
|
|
203
|
+
layout.server,
|
|
204
|
+
/** @type {string} */ (layout.server_id)
|
|
205
|
+
);
|
|
206
|
+
validate_layout_exports(
|
|
202
207
|
layout.universal,
|
|
203
208
|
/** @type {string} */ (layout.universal_id)
|
|
204
209
|
);
|
|
@@ -207,7 +212,7 @@ export async function respond(request, options, manifest, state) {
|
|
|
207
212
|
|
|
208
213
|
if (page) {
|
|
209
214
|
validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
|
|
210
|
-
|
|
215
|
+
validate_page_exports(page.universal, /** @type {string} */ (page.universal_id));
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
218
|
|
package/src/utils/exports.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param {string
|
|
2
|
+
* @param {Set<string>} expected
|
|
3
3
|
*/
|
|
4
4
|
function validator(expected) {
|
|
5
|
-
const set = new Set(expected);
|
|
6
|
-
|
|
7
5
|
/**
|
|
8
6
|
* @param {any} module
|
|
9
7
|
* @param {string} [file]
|
|
@@ -12,11 +10,13 @@ function validator(expected) {
|
|
|
12
10
|
if (!module) return;
|
|
13
11
|
|
|
14
12
|
for (const key in module) {
|
|
15
|
-
if (key[0] === '_' ||
|
|
13
|
+
if (key[0] === '_' || expected.has(key)) continue; // key is valid in this module
|
|
14
|
+
|
|
15
|
+
const values = [...expected.values()];
|
|
16
16
|
|
|
17
17
|
const hint =
|
|
18
18
|
hint_for_supported_files(key, file?.slice(file.lastIndexOf('.'))) ??
|
|
19
|
-
`valid exports are ${
|
|
19
|
+
`valid exports are ${values.join(', ')}, or anything with a '_' prefix`;
|
|
20
20
|
|
|
21
21
|
throw new Error(`Invalid export '${key}'${file ? ` in ${file}` : ''} (${hint})`);
|
|
22
22
|
}
|
|
@@ -33,34 +33,45 @@ function validator(expected) {
|
|
|
33
33
|
function hint_for_supported_files(key, ext = '.js') {
|
|
34
34
|
let supported_files = [];
|
|
35
35
|
|
|
36
|
-
if (
|
|
36
|
+
if (valid_layout_exports.has(key)) {
|
|
37
|
+
supported_files.push(`+layout${ext}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (valid_page_exports.has(key)) {
|
|
37
41
|
supported_files.push(`+page${ext}`);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
if (
|
|
44
|
+
if (valid_layout_server_exports.has(key)) {
|
|
45
|
+
supported_files.push(`+layout.server${ext}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (valid_page_server_exports.has(key)) {
|
|
41
49
|
supported_files.push(`+page.server${ext}`);
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
if (valid_server_exports.
|
|
52
|
+
if (valid_server_exports.has(key)) {
|
|
45
53
|
supported_files.push(`+server${ext}`);
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
if (supported_files.length > 0) {
|
|
49
|
-
return `'${key}' is a valid export in ${supported_files.join(
|
|
57
|
+
return `'${key}' is a valid export in ${supported_files.slice(0, -1).join(`, `)}${
|
|
58
|
+
supported_files.length > 1 ? ' or ' : ''
|
|
59
|
+
}${supported_files.at(-1)}`;
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
const
|
|
54
|
-
const valid_page_server_exports = [
|
|
63
|
+
const valid_layout_exports = new Set([
|
|
55
64
|
'load',
|
|
56
65
|
'prerender',
|
|
57
66
|
'csr',
|
|
58
67
|
'ssr',
|
|
59
|
-
'actions',
|
|
60
68
|
'trailingSlash',
|
|
61
69
|
'config'
|
|
62
|
-
];
|
|
63
|
-
const
|
|
70
|
+
]);
|
|
71
|
+
const valid_page_exports = new Set([...valid_layout_exports, 'entries']);
|
|
72
|
+
const valid_layout_server_exports = new Set([...valid_layout_exports, 'actions']);
|
|
73
|
+
const valid_page_server_exports = new Set([...valid_layout_server_exports, 'entries']);
|
|
74
|
+
const valid_server_exports = new Set([
|
|
64
75
|
'GET',
|
|
65
76
|
'POST',
|
|
66
77
|
'PATCH',
|
|
@@ -69,9 +80,12 @@ const valid_server_exports = [
|
|
|
69
80
|
'OPTIONS',
|
|
70
81
|
'prerender',
|
|
71
82
|
'trailingSlash',
|
|
72
|
-
'config'
|
|
73
|
-
|
|
83
|
+
'config',
|
|
84
|
+
'entries'
|
|
85
|
+
]);
|
|
74
86
|
|
|
75
|
-
export const
|
|
87
|
+
export const validate_layout_exports = validator(valid_layout_exports);
|
|
88
|
+
export const validate_page_exports = validator(valid_page_exports);
|
|
89
|
+
export const validate_layout_server_exports = validator(valid_layout_server_exports);
|
|
76
90
|
export const validate_page_server_exports = validator(valid_page_server_exports);
|
|
77
91
|
export const validate_server_exports = validator(valid_server_exports);
|
package/src/utils/options.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option
|
|
3
|
-
* @template {
|
|
2
|
+
* @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash' | 'entries'} Option
|
|
3
|
+
* @template {(import('types').SSRNode['universal'] | import('types').SSRNode['server'])[Option]} Value
|
|
4
4
|
*
|
|
5
5
|
* @param {Array<import('types').SSRNode | undefined>} nodes
|
|
6
6
|
* @param {Option} option
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export function get_option(nodes, option) {
|
|
11
11
|
return nodes.reduce((value, node) => {
|
|
12
|
-
return /** @type {
|
|
12
|
+
return /** @type {Value} TypeScript's too dumb to understand this */ (
|
|
13
13
|
node?.universal?.[option] ?? node?.server?.[option] ?? value
|
|
14
14
|
);
|
|
15
15
|
}, /** @type {Value | undefined} */ (undefined));
|
package/src/utils/routing.js
CHANGED
|
@@ -96,6 +96,61 @@ export function parse_route_id(id) {
|
|
|
96
96
|
return { pattern, params };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
const basic_param_pattern = /\[(\[)?(?:\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Parses a route ID, then resolves it to a path by replacing parameters with actual values from `entry`.
|
|
103
|
+
* @param {string} id The route id
|
|
104
|
+
* @param {Record<string, string | undefined>} entry The entry meant to populate the route. For example, if the route is `/blog/[slug]`, the entry would be `{ slug: 'hello-world' }`
|
|
105
|
+
* @example
|
|
106
|
+
* ```js
|
|
107
|
+
* resolve_entry(`/blog/[slug]/[...somethingElse]`, { slug: 'hello-world', somethingElse: 'something/else' }); // `/blog/hello-world/something/else`
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export function resolve_entry(id, entry) {
|
|
111
|
+
const segments = get_route_segments(id);
|
|
112
|
+
return (
|
|
113
|
+
'/' +
|
|
114
|
+
segments
|
|
115
|
+
.map((segment) => {
|
|
116
|
+
const match = basic_param_pattern.exec(segment);
|
|
117
|
+
|
|
118
|
+
// static content -- i.e. not a param
|
|
119
|
+
if (!match) return segment;
|
|
120
|
+
|
|
121
|
+
const optional = !!match[1];
|
|
122
|
+
const name = match[2];
|
|
123
|
+
const param_value = entry[name];
|
|
124
|
+
|
|
125
|
+
// This is nested so TS correctly narrows the type
|
|
126
|
+
if (!param_value) {
|
|
127
|
+
if (optional) return '';
|
|
128
|
+
throw new Error(`Missing parameter '${name}' in route ${id}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (param_value.startsWith('/') || param_value.endsWith('/'))
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return param_value;
|
|
137
|
+
})
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.join('/')
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const optional_param_regex = /\/\[\[\w+?(?:=\w+)?\]\]/;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Removes optional params from a route ID.
|
|
147
|
+
* @param {string} id
|
|
148
|
+
* @returns The route id with optional params removed
|
|
149
|
+
*/
|
|
150
|
+
export function remove_optional_params(id) {
|
|
151
|
+
return id.replace(optional_param_regex, '');
|
|
152
|
+
}
|
|
153
|
+
|
|
99
154
|
/**
|
|
100
155
|
* Returns `false` for `(group)` segments
|
|
101
156
|
* @param {string} segment
|
package/types/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Logger,
|
|
12
12
|
MaybePromise,
|
|
13
13
|
Prerendered,
|
|
14
|
+
PrerenderEntryGeneratorMismatchHandlerValue,
|
|
14
15
|
PrerenderHttpErrorHandlerValue,
|
|
15
16
|
PrerenderMissingIdHandlerValue,
|
|
16
17
|
PrerenderOption,
|
|
@@ -515,7 +516,7 @@ export interface KitConfig {
|
|
|
515
516
|
*/
|
|
516
517
|
handleHttpError?: PrerenderHttpErrorHandlerValue;
|
|
517
518
|
/**
|
|
518
|
-
* How to respond
|
|
519
|
+
* How to respond when hash links from one prerendered page to another don't correspond to an `id` on the destination page.
|
|
519
520
|
*
|
|
520
521
|
* - `'fail'` — fail the build
|
|
521
522
|
* - `'ignore'` - silently ignore the failure and continue
|
|
@@ -525,6 +526,17 @@ export interface KitConfig {
|
|
|
525
526
|
* @default "fail"
|
|
526
527
|
*/
|
|
527
528
|
handleMissingId?: PrerenderMissingIdHandlerValue;
|
|
529
|
+
/**
|
|
530
|
+
* How to respond when an entry generated by the `entries` export doesn't match the route it was generated from.
|
|
531
|
+
*
|
|
532
|
+
* - `'fail'` — fail the build
|
|
533
|
+
* - `'ignore'` - silently ignore the failure and continue
|
|
534
|
+
* - `'warn'` — continue, but print a warning
|
|
535
|
+
* - `(details) => void` — a custom error handler that takes a `details` object with `generatedFromId`, `entry`, `matchedId` and `message` properties. If you `throw` from this function, the build will fail
|
|
536
|
+
*
|
|
537
|
+
* @default "fail"
|
|
538
|
+
*/
|
|
539
|
+
handleEntryGeneratorMismatch?: PrerenderEntryGeneratorMismatchHandlerValue;
|
|
528
540
|
/**
|
|
529
541
|
* The value of `url.origin` during prerendering; useful if it is included in rendered content.
|
|
530
542
|
* @default "http://sveltekit-prerender"
|
package/types/internal.d.ts
CHANGED
|
@@ -266,6 +266,7 @@ export interface ServerMetadataRoute {
|
|
|
266
266
|
};
|
|
267
267
|
methods: HttpMethod[];
|
|
268
268
|
prerender: PrerenderOption | undefined;
|
|
269
|
+
entries: Array<string> | undefined;
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
export interface ServerMetadata {
|
|
@@ -308,6 +309,7 @@ export interface SSRNode {
|
|
|
308
309
|
csr?: boolean;
|
|
309
310
|
trailingSlash?: TrailingSlash;
|
|
310
311
|
config?: any;
|
|
312
|
+
entries?: PrerenderEntryGenerator;
|
|
311
313
|
};
|
|
312
314
|
|
|
313
315
|
server: {
|
|
@@ -318,6 +320,7 @@ export interface SSRNode {
|
|
|
318
320
|
trailingSlash?: TrailingSlash;
|
|
319
321
|
actions?: Actions;
|
|
320
322
|
config?: any;
|
|
323
|
+
entries?: PrerenderEntryGenerator;
|
|
321
324
|
};
|
|
322
325
|
|
|
323
326
|
universal_id: string;
|
|
@@ -355,10 +358,13 @@ export interface PageNodeIndexes {
|
|
|
355
358
|
leaf: number;
|
|
356
359
|
}
|
|
357
360
|
|
|
361
|
+
export type PrerenderEntryGenerator = () => MaybePromise<Array<Record<string, string>>>;
|
|
362
|
+
|
|
358
363
|
export type SSREndpoint = Partial<Record<HttpMethod, RequestHandler>> & {
|
|
359
364
|
prerender?: PrerenderOption;
|
|
360
365
|
trailingSlash?: TrailingSlash;
|
|
361
366
|
config?: any;
|
|
367
|
+
entries?: PrerenderEntryGenerator;
|
|
362
368
|
};
|
|
363
369
|
|
|
364
370
|
export interface SSRRoute {
|
package/types/private.d.ts
CHANGED
|
@@ -205,8 +205,17 @@ export interface PrerenderMissingIdHandler {
|
|
|
205
205
|
(details: { path: string; id: string; referrers: string[]; message: string }): void;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
export interface PrerenderEntryGeneratorMismatchHandler {
|
|
209
|
+
(details: { generatedFromId: string; entry: string; matchedId: string; message: string }): void;
|
|
210
|
+
}
|
|
211
|
+
|
|
208
212
|
export type PrerenderHttpErrorHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderHttpErrorHandler;
|
|
209
213
|
export type PrerenderMissingIdHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderMissingIdHandler;
|
|
214
|
+
export type PrerenderEntryGeneratorMismatchHandlerValue =
|
|
215
|
+
| 'fail'
|
|
216
|
+
| 'warn'
|
|
217
|
+
| 'ignore'
|
|
218
|
+
| PrerenderEntryGeneratorMismatchHandler;
|
|
210
219
|
|
|
211
220
|
export type PrerenderOption = boolean | 'auto';
|
|
212
221
|
|