@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.530",
3
+ "version": "1.0.0-next.531",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -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 newSetting = input ? 'continue' : 'fail';
262
- const needsSetting = newSetting === 'continue';
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 \`onError\`. In your case, set \`onError\` to "${newSetting}"${
265
- needsSetting ? '' : ' (or leave it undefined)'
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
- onError: validate('fail', (input, keypath) => {
271
+ handleHttpError: validate('fail', (input, keypath) => {
272
272
  if (typeof input === 'function') return input;
273
- if (['continue', 'fail'].includes(input)) return input;
274
- throw new Error(
275
- `${keypath} should be either a custom function or one of "continue" or "fail"`
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 (!escaped) {
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
- * @param {Parameters<PrerenderErrorHandler>[0]} details
26
- * @param {import('types').ValidatedKitConfig} config
27
- */
28
- function format_error({ status, path, referrer, referenceType }, config) {
29
- const message =
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, config) {
43
- switch (config.prerender.onError) {
44
- case 'continue':
26
+ function normalise_error_handler(log, input, format) {
27
+ switch (input) {
28
+ case 'fail':
45
29
  return (details) => {
46
- log.error(format_error(details, config));
30
+ throw new Error(format(details));
47
31
  };
48
- case 'fail':
32
+ case 'warn':
49
33
  return (details) => {
50
- throw new Error(format_error(details, config));
34
+ log.error(format(details));
51
35
  };
36
+ case 'ignore':
37
+ return () => {};
52
38
  default:
53
- return config.prerender.onError;
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 error = normalise_error_handler(log, config);
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
- error({ status: 404, path: decoded, referrer, referenceType: 'linked' });
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
- for (const href of crawl(body.toString())) {
235
- if (href.startsWith('data:') || href.startsWith('#')) continue;
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
- error({ status: response.status, path: decoded, referrer, referenceType });
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
@@ -7,6 +7,7 @@ const scheme = /^[a-z]+:/;
7
7
  */
8
8
  export function resolve(base, path) {
9
9
  if (scheme.test(path)) return path;
10
+ if (path[0] === '#') return base + path;
10
11
 
11
12
  const base_match = absolute.exec(base);
12
13
  const path_match = absolute.exec(path);
package/types/index.d.ts CHANGED
@@ -10,7 +10,8 @@ import {
10
10
  Logger,
11
11
  MaybePromise,
12
12
  Prerendered,
13
- PrerenderOnErrorValue,
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
- onError?: PrerenderOnErrorValue;
226
+ handleHttpError?: PrerenderHttpErrorHandlerValue;
227
+ handleMissingId?: PrerenderMissingIdHandlerValue;
226
228
  origin?: string;
227
229
  };
228
230
  serviceWorker?: {
@@ -179,7 +179,7 @@ export interface Prerendered {
179
179
  paths: string[];
180
180
  }
181
181
 
182
- export interface PrerenderErrorHandler {
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 type PrerenderOnErrorValue = 'fail' | 'continue' | PrerenderErrorHandler;
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