@sveltejs/kit 1.0.0-next.530 → 1.0.0-next.531
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 +26 -9
- package/src/core/prerender/crawl.js +9 -2
- package/src/core/prerender/prerender.js +74 -34
- package/src/utils/url.js +1 -0
- package/types/index.d.ts +4 -2
- package/types/private.d.ts +7 -2
package/package.json
CHANGED
|
@@ -258,22 +258,39 @@ const options = object(
|
|
|
258
258
|
// TODO: remove this for the 1.0 release
|
|
259
259
|
force: validate(undefined, (input, keypath) => {
|
|
260
260
|
if (typeof input !== 'undefined') {
|
|
261
|
-
const
|
|
262
|
-
const
|
|
261
|
+
const new_input = input ? 'warn' : 'fail';
|
|
262
|
+
const needs_option = new_input === 'warn';
|
|
263
263
|
throw new Error(
|
|
264
|
-
`${keypath} has been removed in favor of \`
|
|
265
|
-
|
|
264
|
+
`${keypath} has been removed in favor of \`handleHttpError\`. In your case, set \`handleHttpError\` to "${new_input}"${
|
|
265
|
+
needs_option ? '' : ' (or leave it undefined)'
|
|
266
266
|
} to get the same behavior as you would with \`force: ${JSON.stringify(input)}\``
|
|
267
267
|
);
|
|
268
268
|
}
|
|
269
269
|
}),
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
handleHttpError: validate('fail', (input, keypath) => {
|
|
272
272
|
if (typeof input === 'function') return input;
|
|
273
|
-
if (['
|
|
274
|
-
throw new Error(
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
if (['fail', 'warn', 'ignore'].includes(input)) return input;
|
|
274
|
+
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
|
|
275
|
+
}),
|
|
276
|
+
|
|
277
|
+
handleMissingId: validate('fail', (input, keypath) => {
|
|
278
|
+
if (typeof input === 'function') return input;
|
|
279
|
+
if (['fail', 'warn', 'ignore'].includes(input)) return input;
|
|
280
|
+
throw new Error(`${keypath} should be "fail", "warn", "ignore" or a custom function`);
|
|
281
|
+
}),
|
|
282
|
+
|
|
283
|
+
// TODO: remove this for the 1.0 release
|
|
284
|
+
onError: validate(undefined, (input, keypath) => {
|
|
285
|
+
if (typeof input !== 'undefined') {
|
|
286
|
+
let message = `${keypath} has been renamed to \`handleHttpError\``;
|
|
287
|
+
|
|
288
|
+
if (input === 'continue') {
|
|
289
|
+
message += ', and "continue" has been renamed to "warn"';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
throw new Error(message);
|
|
293
|
+
}
|
|
277
294
|
}),
|
|
278
295
|
|
|
279
296
|
origin: validate('http://sveltekit-prerender', (input, keypath) => {
|
|
@@ -14,6 +14,9 @@ const WHITESPACE = /[\s\n\r]/;
|
|
|
14
14
|
|
|
15
15
|
/** @param {string} html */
|
|
16
16
|
export function crawl(html) {
|
|
17
|
+
/** @type {string[]} */
|
|
18
|
+
const ids = [];
|
|
19
|
+
|
|
17
20
|
/** @type {string[]} */
|
|
18
21
|
const hrefs = [];
|
|
19
22
|
|
|
@@ -125,7 +128,9 @@ export function crawl(html) {
|
|
|
125
128
|
let escaped = false;
|
|
126
129
|
|
|
127
130
|
while (i < html.length) {
|
|
128
|
-
if (
|
|
131
|
+
if (escaped) {
|
|
132
|
+
escaped = false;
|
|
133
|
+
} else {
|
|
129
134
|
const char = html[i];
|
|
130
135
|
|
|
131
136
|
if (html[i] === quote) {
|
|
@@ -153,6 +158,8 @@ export function crawl(html) {
|
|
|
153
158
|
|
|
154
159
|
if (name === 'href') {
|
|
155
160
|
href = value;
|
|
161
|
+
} else if (name === 'id') {
|
|
162
|
+
ids.push(value);
|
|
156
163
|
} else if (name === 'rel') {
|
|
157
164
|
rel = value;
|
|
158
165
|
} else if (name === 'src') {
|
|
@@ -194,5 +201,5 @@ export function crawl(html) {
|
|
|
194
201
|
i += 1;
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
return hrefs;
|
|
204
|
+
return { ids, hrefs };
|
|
198
205
|
}
|
|
@@ -12,45 +12,31 @@ import { load_config } from '../config/index.js';
|
|
|
12
12
|
import { get_route_segments } from '../../utils/routing.js';
|
|
13
13
|
import { get_option } from '../../runtime/server/utils.js';
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {import('types').PrerenderErrorHandler} PrerenderErrorHandler
|
|
17
|
-
* @typedef {import('types').Logger} Logger
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
15
|
const [, , client_out_dir, results_path, verbose, env] = process.argv;
|
|
21
16
|
|
|
22
17
|
prerender();
|
|
23
18
|
|
|
24
19
|
/**
|
|
25
|
-
* @
|
|
26
|
-
* @param {import('types').
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
status === 404 && !path.startsWith(config.paths.base)
|
|
31
|
-
? `${path} does not begin with \`base\`, which is configured in \`paths.base\` and can be imported from \`$app/paths\` - see https://kit.svelte.dev/docs/configuration#paths for more info`
|
|
32
|
-
: path;
|
|
33
|
-
|
|
34
|
-
return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @param {Logger} log
|
|
39
|
-
* @param {import('types').ValidatedKitConfig} config
|
|
40
|
-
* @returns {PrerenderErrorHandler}
|
|
20
|
+
* @template T
|
|
21
|
+
* @param {import('types').Logger} log
|
|
22
|
+
* @param {'fail' | 'warn' | 'ignore' | ((details: T) => void)} input
|
|
23
|
+
* @param {(details: T) => string} format
|
|
24
|
+
* @returns {(details: T) => void}
|
|
41
25
|
*/
|
|
42
|
-
function normalise_error_handler(log,
|
|
43
|
-
switch (
|
|
44
|
-
case '
|
|
26
|
+
function normalise_error_handler(log, input, format) {
|
|
27
|
+
switch (input) {
|
|
28
|
+
case 'fail':
|
|
45
29
|
return (details) => {
|
|
46
|
-
|
|
30
|
+
throw new Error(format(details));
|
|
47
31
|
};
|
|
48
|
-
case '
|
|
32
|
+
case 'warn':
|
|
49
33
|
return (details) => {
|
|
50
|
-
|
|
34
|
+
log.error(format(details));
|
|
51
35
|
};
|
|
36
|
+
case 'ignore':
|
|
37
|
+
return () => {};
|
|
52
38
|
default:
|
|
53
|
-
return
|
|
39
|
+
return (details) => input({ ...details, message: format(details) });
|
|
54
40
|
}
|
|
55
41
|
}
|
|
56
42
|
|
|
@@ -133,7 +119,29 @@ export async function prerender() {
|
|
|
133
119
|
const server = new Server(manifest);
|
|
134
120
|
await server.init({ env: JSON.parse(env) });
|
|
135
121
|
|
|
136
|
-
const
|
|
122
|
+
const handle_http_error = normalise_error_handler(
|
|
123
|
+
log,
|
|
124
|
+
config.prerender.handleHttpError,
|
|
125
|
+
({ status, path, referrer, referenceType }) => {
|
|
126
|
+
const message =
|
|
127
|
+
status === 404 && !path.startsWith(config.paths.base)
|
|
128
|
+
? `${path} does not begin with \`base\`, which is configured in \`paths.base\` and can be imported from \`$app/paths\` - see https://kit.svelte.dev/docs/configuration#paths for more info`
|
|
129
|
+
: path;
|
|
130
|
+
|
|
131
|
+
return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const handle_missing_id = normalise_error_handler(
|
|
136
|
+
log,
|
|
137
|
+
config.prerender.handleMissingId,
|
|
138
|
+
({ path, id, referrers }) => {
|
|
139
|
+
return (
|
|
140
|
+
`The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path}:` +
|
|
141
|
+
referrers.map((l) => `\n - ${l}`).join('')
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
137
145
|
|
|
138
146
|
const q = queue(config.prerender.concurrency);
|
|
139
147
|
|
|
@@ -155,6 +163,12 @@ export async function prerender() {
|
|
|
155
163
|
const seen = new Set();
|
|
156
164
|
const written = new Set();
|
|
157
165
|
|
|
166
|
+
/** @type {Map<string, Set<string>>} */
|
|
167
|
+
const expected_hashlinks = new Map();
|
|
168
|
+
|
|
169
|
+
/** @type {Map<string, string[]>} */
|
|
170
|
+
const actual_hashlinks = new Map();
|
|
171
|
+
|
|
158
172
|
/**
|
|
159
173
|
* @param {string | null} referrer
|
|
160
174
|
* @param {string} decoded
|
|
@@ -177,7 +191,7 @@ export async function prerender() {
|
|
|
177
191
|
*/
|
|
178
192
|
async function visit(decoded, encoded, referrer) {
|
|
179
193
|
if (!decoded.startsWith(config.paths.base)) {
|
|
180
|
-
|
|
194
|
+
handle_http_error({ status: 404, path: decoded, referrer, referenceType: 'linked' });
|
|
181
195
|
return;
|
|
182
196
|
}
|
|
183
197
|
|
|
@@ -231,18 +245,30 @@ export async function prerender() {
|
|
|
231
245
|
const headers = Object.fromEntries(response.headers);
|
|
232
246
|
|
|
233
247
|
if (config.prerender.crawl && headers['content-type'] === 'text/html') {
|
|
234
|
-
|
|
235
|
-
|
|
248
|
+
const { ids, hrefs } = crawl(body.toString());
|
|
249
|
+
|
|
250
|
+
actual_hashlinks.set(decoded, ids);
|
|
251
|
+
|
|
252
|
+
for (const href of hrefs) {
|
|
253
|
+
if (href.startsWith('data:')) continue;
|
|
236
254
|
|
|
237
255
|
const resolved = resolve(encoded, href);
|
|
238
256
|
if (!is_root_relative(resolved)) continue;
|
|
239
257
|
|
|
240
|
-
const { pathname, search } = new URL(resolved, 'http://localhost');
|
|
258
|
+
const { pathname, search, hash } = new URL(resolved, 'http://localhost');
|
|
241
259
|
|
|
242
260
|
if (search) {
|
|
243
261
|
// TODO warn that query strings have no effect on statically-exported pages
|
|
244
262
|
}
|
|
245
263
|
|
|
264
|
+
if (hash) {
|
|
265
|
+
if (!expected_hashlinks.has(pathname + hash)) {
|
|
266
|
+
expected_hashlinks.set(pathname + hash, new Set());
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** @type {Set<string>} */ (expected_hashlinks.get(pathname + hash)).add(decoded);
|
|
270
|
+
}
|
|
271
|
+
|
|
246
272
|
enqueue(decoded, decodeURI(pathname), pathname);
|
|
247
273
|
}
|
|
248
274
|
}
|
|
@@ -329,7 +355,7 @@ export async function prerender() {
|
|
|
329
355
|
|
|
330
356
|
prerendered.paths.push(decoded);
|
|
331
357
|
} else if (response_type !== OK) {
|
|
332
|
-
|
|
358
|
+
handle_http_error({ status: response.status, path: decoded, referrer, referenceType });
|
|
333
359
|
}
|
|
334
360
|
}
|
|
335
361
|
|
|
@@ -381,6 +407,20 @@ export async function prerender() {
|
|
|
381
407
|
|
|
382
408
|
await q.done();
|
|
383
409
|
|
|
410
|
+
// handle invalid fragment links
|
|
411
|
+
for (const [key, referrers] of expected_hashlinks) {
|
|
412
|
+
const index = key.indexOf('#');
|
|
413
|
+
const path = key.slice(0, index);
|
|
414
|
+
const id = key.slice(index + 1);
|
|
415
|
+
|
|
416
|
+
// ignore fragment links to pages that were not prerendered
|
|
417
|
+
if (!actual_hashlinks.has(path)) continue;
|
|
418
|
+
|
|
419
|
+
if (!actual_hashlinks.get(path).includes(id)) {
|
|
420
|
+
handle_missing_id({ id, path, referrers: Array.from(referrers) });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
384
424
|
/** @type {string[]} */
|
|
385
425
|
const not_prerendered = [];
|
|
386
426
|
|
package/src/utils/url.js
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
Logger,
|
|
11
11
|
MaybePromise,
|
|
12
12
|
Prerendered,
|
|
13
|
-
|
|
13
|
+
PrerenderHttpErrorHandlerValue,
|
|
14
|
+
PrerenderMissingIdHandlerValue,
|
|
14
15
|
RequestOptions,
|
|
15
16
|
RouteDefinition,
|
|
16
17
|
TrailingSlash,
|
|
@@ -222,7 +223,8 @@ export interface KitConfig {
|
|
|
222
223
|
default?: boolean;
|
|
223
224
|
enabled?: boolean;
|
|
224
225
|
entries?: Array<'*' | `/${string}`>;
|
|
225
|
-
|
|
226
|
+
handleHttpError?: PrerenderHttpErrorHandlerValue;
|
|
227
|
+
handleMissingId?: PrerenderMissingIdHandlerValue;
|
|
226
228
|
origin?: string;
|
|
227
229
|
};
|
|
228
230
|
serviceWorker?: {
|
package/types/private.d.ts
CHANGED
|
@@ -179,7 +179,7 @@ export interface Prerendered {
|
|
|
179
179
|
paths: string[];
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
export interface
|
|
182
|
+
export interface PrerenderHttpErrorHandler {
|
|
183
183
|
(details: {
|
|
184
184
|
status: number;
|
|
185
185
|
path: string;
|
|
@@ -188,7 +188,12 @@ export interface PrerenderErrorHandler {
|
|
|
188
188
|
}): void;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
export
|
|
191
|
+
export interface PrerenderMissingIdHandler {
|
|
192
|
+
(details: { path: string; id: string; referrers: string[] }): void;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export type PrerenderHttpErrorHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderHttpErrorHandler;
|
|
196
|
+
export type PrerenderMissingIdHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderMissingIdHandler;
|
|
192
197
|
|
|
193
198
|
export type PrerenderOption = boolean | 'auto';
|
|
194
199
|
|