@sveltejs/kit 1.0.0-next.263 → 1.0.0-next.267

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.
@@ -6,6 +6,22 @@ import { writable } from 'svelte/store';
6
6
  import { base, set_paths } from '../paths.js';
7
7
  import { init } from './singletons.js';
8
8
 
9
+ /**
10
+ * @param {string} path
11
+ * @param {'always' | 'never' | 'ignore'} trailing_slash
12
+ */
13
+ function normalize_path(path, trailing_slash) {
14
+ if (path === '/' || trailing_slash === 'ignore') return path;
15
+
16
+ if (trailing_slash === 'never') {
17
+ return path.endsWith('/') ? path.slice(0, -1) : path;
18
+ } else if (trailing_slash === 'always' && /\/[^./]+$/.test(path)) {
19
+ return path + '/';
20
+ }
21
+
22
+ return path;
23
+ }
24
+
9
25
  function scroll_state() {
10
26
  return {
11
27
  x: pageXOffset,
@@ -396,14 +412,7 @@ class Router {
396
412
  }
397
413
  this.navigating++;
398
414
 
399
- let { pathname } = url;
400
-
401
- if (this.trailing_slash === 'never') {
402
- if (pathname !== '/' && pathname.endsWith('/')) pathname = pathname.slice(0, -1);
403
- } else if (this.trailing_slash === 'always') {
404
- const is_file = /** @type {string} */ (url.pathname.split('/').pop()).includes('.');
405
- if (!is_file && !pathname.endsWith('/')) pathname += '/';
406
- }
415
+ const pathname = normalize_path(url.pathname, this.trailing_slash);
407
416
 
408
417
  info.url = new URL(url.origin + pathname + url.search + url.hash);
409
418
 
@@ -1464,10 +1473,6 @@ class Renderer {
1464
1473
  * }} opts
1465
1474
  */
1466
1475
  async function start({ paths, target, session, route, spa, trailing_slash, hydrate }) {
1467
- if (import.meta.env.DEV && !target) {
1468
- throw new Error('Missing target element. See https://kit.svelte.dev/docs#configuration-target');
1469
- }
1470
-
1471
1476
  const renderer = new Renderer({
1472
1477
  Root,
1473
1478
  fallback,
@@ -147,7 +147,9 @@ async function render_endpoint(event, mod) {
147
147
 
148
148
  const { status = 200, body = {} } = response;
149
149
  const headers =
150
- response.headers instanceof Headers ? response.headers : to_headers(response.headers);
150
+ response.headers instanceof Headers
151
+ ? new Headers(response.headers)
152
+ : to_headers(response.headers);
151
153
 
152
154
  const type = headers.get('content-type');
153
155
 
@@ -1496,6 +1498,22 @@ function is_root_relative(path) {
1496
1498
  return path[0] === '/' && path[1] !== '/';
1497
1499
  }
1498
1500
 
1501
+ /**
1502
+ * @param {string} path
1503
+ * @param {'always' | 'never' | 'ignore'} trailing_slash
1504
+ */
1505
+ function normalize_path(path, trailing_slash) {
1506
+ if (path === '/' || trailing_slash === 'ignore') return path;
1507
+
1508
+ if (trailing_slash === 'never') {
1509
+ return path.endsWith('/') ? path.slice(0, -1) : path;
1510
+ } else if (trailing_slash === 'always' && /\/[^./]+$/.test(path)) {
1511
+ return path + '/';
1512
+ }
1513
+
1514
+ return path;
1515
+ }
1516
+
1499
1517
  /**
1500
1518
  * @param {{
1501
1519
  * event: import('types/hooks').RequestEvent;
@@ -1698,9 +1716,7 @@ async function load_node({
1698
1716
  } else {
1699
1717
  // external
1700
1718
  if (resolved.startsWith('//')) {
1701
- throw new Error(
1702
- `Cannot request protocol-relative URL (${requested}) in server-side fetch`
1703
- );
1719
+ requested = event.url.protocol + requested;
1704
1720
  }
1705
1721
 
1706
1722
  // external fetch
@@ -1887,22 +1903,21 @@ async function load_shadow_data(route, event, prerender) {
1887
1903
  if (result.fallthrough) return result;
1888
1904
 
1889
1905
  const { status, headers, body } = validate_shadow_output(result);
1906
+ data.status = status;
1907
+
1890
1908
  add_cookies(/** @type {string[]} */ (data.cookies), headers);
1891
1909
 
1892
1910
  // Redirects are respected...
1893
1911
  if (status >= 300 && status < 400) {
1894
- return {
1895
- status,
1896
- redirect: /** @type {string} */ (
1897
- headers instanceof Headers ? headers.get('location') : headers.location
1898
- )
1899
- };
1912
+ data.redirect = /** @type {string} */ (
1913
+ headers instanceof Headers ? headers.get('location') : headers.location
1914
+ );
1915
+ return data;
1900
1916
  }
1901
1917
 
1902
1918
  // ...but 4xx and 5xx status codes _don't_ result in the error page
1903
1919
  // rendering for non-GET requests — instead, we allow the page
1904
1920
  // to render with any validation errors etc that were returned
1905
- data.status = status;
1906
1921
  data.body = body;
1907
1922
  }
1908
1923
 
@@ -1913,21 +1928,18 @@ async function load_shadow_data(route, event, prerender) {
1913
1928
 
1914
1929
  const { status, headers, body } = validate_shadow_output(result);
1915
1930
  add_cookies(/** @type {string[]} */ (data.cookies), headers);
1931
+ data.status = status;
1916
1932
 
1917
1933
  if (status >= 400) {
1918
- return {
1919
- status,
1920
- error: new Error('Failed to load data')
1921
- };
1934
+ data.error = new Error('Failed to load data');
1935
+ return data;
1922
1936
  }
1923
1937
 
1924
1938
  if (status >= 300) {
1925
- return {
1926
- status,
1927
- redirect: /** @type {string} */ (
1928
- headers instanceof Headers ? headers.get('location') : headers.location
1929
- )
1930
- };
1939
+ data.redirect = /** @type {string} */ (
1940
+ headers instanceof Headers ? headers.get('location') : headers.location
1941
+ );
1942
+ return data;
1931
1943
  }
1932
1944
 
1933
1945
  data.body = { ...body, ...data.body };
@@ -2311,7 +2323,7 @@ function get_page_config(leaf, options) {
2311
2323
  // TODO remove for 1.0
2312
2324
  if ('ssr' in leaf) {
2313
2325
  throw new Error(
2314
- '`export const ssr` has been removed — use the handle hook instead: https://kit.svelte.dev/docs#hooks-handle'
2326
+ '`export const ssr` has been removed — use the handle hook instead: https://kit.svelte.dev/docs/hooks#handle'
2315
2327
  );
2316
2328
  }
2317
2329
 
@@ -2446,26 +2458,15 @@ const DATA_SUFFIX = '/__data.json';
2446
2458
  async function respond(request, options, state = {}) {
2447
2459
  const url = new URL(request.url);
2448
2460
 
2449
- if (url.pathname !== '/' && options.trailing_slash !== 'ignore') {
2450
- const has_trailing_slash = url.pathname.endsWith('/');
2461
+ const normalized = normalize_path(url.pathname, options.trailing_slash);
2451
2462
 
2452
- if (
2453
- (has_trailing_slash && options.trailing_slash === 'never') ||
2454
- (!has_trailing_slash &&
2455
- options.trailing_slash === 'always' &&
2456
- !(url.pathname.split('/').pop() || '').includes('.'))
2457
- ) {
2458
- url.pathname = has_trailing_slash ? url.pathname.slice(0, -1) : url.pathname + '/';
2459
-
2460
- if (url.search === '?') url.search = '';
2461
-
2462
- return new Response(undefined, {
2463
- status: 301,
2464
- headers: {
2465
- location: url.pathname + url.search
2466
- }
2467
- });
2468
- }
2463
+ if (normalized !== url.pathname) {
2464
+ return new Response(undefined, {
2465
+ status: 301,
2466
+ headers: {
2467
+ location: normalized + (url.search === '?' ? '' : url.search)
2468
+ }
2469
+ });
2469
2470
  }
2470
2471
 
2471
2472
  const { parameter, allowed } = options.method_override;
@@ -2482,7 +2483,7 @@ async function respond(request, options, state = {}) {
2482
2483
  });
2483
2484
  } else {
2484
2485
  const verb = allowed.length === 0 ? 'enabled' : 'allowed';
2485
- const body = `${parameter}=${method_override} is not ${verb}. See https://kit.svelte.dev/docs#configuration-methodoverride`;
2486
+ const body = `${parameter}=${method_override} is not ${verb}. See https://kit.svelte.dev/docs/configuration#methodoverride`;
2486
2487
 
2487
2488
  return new Response(body, {
2488
2489
  status: 400
@@ -2594,11 +2595,11 @@ async function respond(request, options, state = {}) {
2594
2595
  const location = response.headers.get('location');
2595
2596
 
2596
2597
  if (location) {
2598
+ const headers = new Headers(response.headers);
2599
+ headers.set('x-sveltekit-location', location);
2597
2600
  response = new Response(undefined, {
2598
2601
  status: 204,
2599
- headers: {
2600
- 'x-sveltekit-location': location
2601
- }
2602
+ headers
2602
2603
  });
2603
2604
  }
2604
2605
  }
@@ -211,7 +211,7 @@ async function create_plugin(config, cwd) {
211
211
  if (/** @type {any} */ (hooks).getContext) {
212
212
  // TODO remove this for 1.0
213
213
  throw new Error(
214
- 'The getContext hook has been removed. See https://kit.svelte.dev/docs#hooks'
214
+ 'The getContext hook has been removed. See https://kit.svelte.dev/docs/hooks'
215
215
  );
216
216
  }
217
217
 
@@ -1,4 +1,4 @@
1
- import { m as mkdirp, b as SVELTE_KIT, h as rimraf, i as copy, $, j as logger } from '../cli.js';
1
+ import { b as SVELTE_KIT, m as mkdirp, h as rimraf, i as copy, $, j as logger } from '../cli.js';
2
2
  import { readFileSync, writeFileSync } from 'fs';
3
3
  import { resolve as resolve$1, join, dirname } from 'path';
4
4
  import { pathToFileURL, URL } from 'url';
@@ -55,6 +55,22 @@ function is_root_relative(path) {
55
55
  return path[0] === '/' && path[1] !== '/';
56
56
  }
57
57
 
58
+ /**
59
+ * @param {string} path
60
+ * @param {'always' | 'never' | 'ignore'} trailing_slash
61
+ */
62
+ function normalize_path(path, trailing_slash) {
63
+ if (path === '/' || trailing_slash === 'ignore') return path;
64
+
65
+ if (trailing_slash === 'never') {
66
+ return path.endsWith('/') ? path.slice(0, -1) : path;
67
+ } else if (trailing_slash === 'always' && /\/[^./]+$/.test(path)) {
68
+ return path + '/';
69
+ }
70
+
71
+ return path;
72
+ }
73
+
58
74
  /** @typedef {{
59
75
  * fn: () => Promise<any>,
60
76
  * fulfil: (value: any) => void,
@@ -391,21 +407,21 @@ function escape_html_attr(str) {
391
407
  * @typedef {import('types/internal').Logger} Logger
392
408
  */
393
409
 
394
- /** @type {(errorDetails: Parameters<PrerenderErrorHandler>[0] ) => string} */
395
- function errorDetailsToString({ status, path, referrer, referenceType }) {
410
+ /** @type {(details: Parameters<PrerenderErrorHandler>[0] ) => string} */
411
+ function format_error({ status, path, referrer, referenceType }) {
396
412
  return `${status} ${path}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
397
413
  }
398
414
 
399
415
  /** @type {(log: Logger, onError: OnError) => PrerenderErrorHandler} */
400
- function chooseErrorHandler(log, onError) {
416
+ function normalise_error_handler(log, onError) {
401
417
  switch (onError) {
402
418
  case 'continue':
403
- return (errorDetails) => {
404
- log.error(errorDetailsToString(errorDetails));
419
+ return (details) => {
420
+ log.error(format_error(details));
405
421
  };
406
422
  case 'fail':
407
- return (errorDetails) => {
408
- throw new Error(errorDetailsToString(errorDetails));
423
+ return (details) => {
424
+ throw new Error(format_error(details));
409
425
  };
410
426
  default:
411
427
  return onError;
@@ -425,25 +441,27 @@ const REDIRECT = 3;
425
441
  * fallback?: string;
426
442
  * all: boolean; // disregard `export const prerender = true`
427
443
  * }} opts
428
- * @returns {Promise<{ paths: string[] }>} returns a promise that resolves to an array of paths corresponding to the files that have been prerendered.
429
444
  */
430
445
  async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
446
+ /** @type {import('types/config').Prerendered} */
447
+ const prerendered = {
448
+ pages: new Map(),
449
+ assets: new Map(),
450
+ redirects: new Map(),
451
+ paths: []
452
+ };
453
+
431
454
  if (!config.kit.prerender.enabled && !fallback) {
432
- return { paths: [] };
455
+ return prerendered;
433
456
  }
434
457
 
435
458
  __fetch_polyfill();
436
459
 
437
- mkdirp(out);
438
-
439
- const dir = resolve$1(cwd, `${SVELTE_KIT}/output`);
440
-
441
- const seen = new Set();
442
-
443
- const server_root = resolve$1(dir);
460
+ const server_root = resolve$1(cwd, `${SVELTE_KIT}/output`);
444
461
 
445
462
  /** @type {import('types/internal').AppModule} */
446
463
  const { App, override } = await import(pathToFileURL(`${server_root}/server/app.js`).href);
464
+ const { manifest } = await import(pathToFileURL(`${server_root}/server/manifest.js`).href);
447
465
 
448
466
  override({
449
467
  paths: config.kit.paths,
@@ -451,11 +469,9 @@ async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
451
469
  read: (file) => readFileSync(join(config.kit.files.assets, file))
452
470
  });
453
471
 
454
- const { manifest } = await import(pathToFileURL(`${server_root}/server/manifest.js`).href);
455
-
456
472
  const app = new App(manifest);
457
473
 
458
- const error = chooseErrorHandler(log, config.kit.prerender.onError);
474
+ const error = normalise_error_handler(log, config.kit.prerender.onError);
459
475
 
460
476
  const files = new Set([
461
477
  ...build_data.static,
@@ -463,28 +479,12 @@ async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
463
479
  ...build_data.client.assets.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`)
464
480
  ]);
465
481
 
466
- /** @type {string[]} */
467
- const paths = [];
468
-
469
482
  build_data.static.forEach((file) => {
470
483
  if (file.endsWith('/index.html')) {
471
484
  files.add(file.slice(0, -11));
472
485
  }
473
486
  });
474
487
 
475
- /**
476
- * @param {string} path
477
- */
478
- function normalize(path) {
479
- if (config.kit.trailingSlash === 'always') {
480
- return path.endsWith('/') ? path : `${path}/`;
481
- } else if (config.kit.trailingSlash === 'never') {
482
- return !path.endsWith('/') || path === '/' ? path : path.slice(0, -1);
483
- }
484
-
485
- return path;
486
- }
487
-
488
488
  const q = queue(config.kit.prerender.concurrency);
489
489
 
490
490
  /**
@@ -492,148 +492,168 @@ async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
492
492
  * @param {boolean} is_html
493
493
  */
494
494
  function output_filename(path, is_html) {
495
- if (path === '/') {
496
- return '/index.html';
495
+ const file = path.slice(config.kit.paths.base.length + 1);
496
+
497
+ if (file === '') {
498
+ return 'index.html';
497
499
  }
498
- const parts = path.split('/');
499
- if (is_html && parts[parts.length - 1] !== 'index.html') {
500
- if (config.kit.prerender.createIndexFiles) {
501
- parts.push('index.html');
502
- } else {
503
- parts[parts.length - 1] += '.html';
504
- }
500
+
501
+ if (is_html && !file.endsWith('.html')) {
502
+ return file + (config.kit.trailingSlash === 'always' ? 'index.html' : '.html');
505
503
  }
506
- return parts.join('/');
504
+
505
+ return file;
507
506
  }
508
507
 
508
+ const seen = new Set();
509
+ const written = new Set();
510
+
509
511
  /**
510
- * @param {string} decoded_path
511
- * @param {string?} referrer
512
+ * @param {string | null} referrer
513
+ * @param {string} decoded
514
+ * @param {string} [encoded]
512
515
  */
513
- function enqueue(decoded_path, referrer) {
514
- const path = encodeURI(normalize(decoded_path));
516
+ function enqueue(referrer, decoded, encoded) {
517
+ if (seen.has(decoded)) return;
518
+ seen.add(decoded);
515
519
 
516
- if (seen.has(path)) return;
517
- seen.add(path);
520
+ const file = decoded.slice(config.kit.paths.base.length + 1);
521
+ if (files.has(file)) return;
518
522
 
519
- return q.add(() => visit(path, decoded_path, referrer));
523
+ return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer));
520
524
  }
521
525
 
522
526
  /**
523
- * @param {string} path
524
- * @param {string} decoded_path
527
+ * @param {string} decoded
528
+ * @param {string} encoded
525
529
  * @param {string?} referrer
526
530
  */
527
- async function visit(path, decoded_path, referrer) {
531
+ async function visit(decoded, encoded, referrer) {
532
+ if (!decoded.startsWith(config.kit.paths.base)) {
533
+ error({ status: 404, path: decoded, referrer, referenceType: 'linked' });
534
+ return;
535
+ }
536
+
528
537
  /** @type {Map<string, import('types/internal').PrerenderDependency>} */
529
538
  const dependencies = new Map();
530
539
 
531
- const render_path = config.kit.paths?.base
532
- ? `http://sveltekit-prerender${config.kit.paths.base}${path === '/' ? '' : path}`
533
- : `http://sveltekit-prerender${path}`;
534
-
535
- const rendered = await app.render(new Request(render_path), {
540
+ const response = await app.render(new Request(`http://sveltekit-prerender${encoded}`), {
536
541
  prerender: {
537
542
  all,
538
543
  dependencies
539
544
  }
540
545
  });
541
546
 
542
- if (rendered) {
543
- const response_type = Math.floor(rendered.status / 100);
544
- const type = rendered.headers.get('content-type');
545
- const is_html = response_type === REDIRECT || type === 'text/html';
547
+ const text = await response.text();
546
548
 
547
- const file = `${out}${output_filename(decoded_path, is_html)}`;
549
+ save(response, text, decoded, encoded, referrer, 'linked');
548
550
 
549
- if (response_type === REDIRECT) {
550
- const location = rendered.headers.get('location');
551
+ for (const [dependency_path, result] of dependencies) {
552
+ // this seems circuitous, but using new URL allows us to not care
553
+ // whether dependency_path is encoded or not
554
+ const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
555
+ const decoded_dependency_path = decodeURI(encoded_dependency_path);
551
556
 
552
- if (location) {
553
- mkdirp(dirname(file));
557
+ const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
558
+ save(
559
+ result.response,
560
+ body,
561
+ decoded_dependency_path,
562
+ encoded_dependency_path,
563
+ decoded,
564
+ 'fetched'
565
+ );
566
+ }
554
567
 
555
- log.warn(`${rendered.status} ${decoded_path} -> ${location}`);
568
+ if (config.kit.prerender.crawl && response.headers.get('content-type') === 'text/html') {
569
+ for (const href of crawl(text)) {
570
+ if (href.startsWith('data:') || href.startsWith('#')) continue;
556
571
 
557
- writeFileSync(
558
- file,
559
- `<meta http-equiv="refresh" content=${escape_html_attr(`0;url=${location}`)}>`
560
- );
572
+ const resolved = resolve(encoded, href);
573
+ if (!is_root_relative(resolved)) continue;
561
574
 
562
- const resolved = resolve(path, location);
563
- if (is_root_relative(resolved)) {
564
- enqueue(resolved, path);
565
- }
566
- } else {
567
- log.warn(`location header missing on redirect received from ${decoded_path}`);
568
- }
575
+ const parsed = new URL(resolved, 'http://localhost');
569
576
 
570
- return;
571
- }
577
+ if (parsed.search) ;
572
578
 
573
- const text = await rendered.text();
579
+ const pathname = normalize_path(parsed.pathname, config.kit.trailingSlash);
580
+ enqueue(decoded, decodeURI(pathname), pathname);
581
+ }
582
+ }
583
+ }
574
584
 
575
- if (rendered.status === 200) {
576
- mkdirp(dirname(file));
585
+ /**
586
+ * @param {Response} response
587
+ * @param {string | Uint8Array} body
588
+ * @param {string} decoded
589
+ * @param {string} encoded
590
+ * @param {string | null} referrer
591
+ * @param {'linked' | 'fetched'} referenceType
592
+ */
593
+ function save(response, body, decoded, encoded, referrer, referenceType) {
594
+ const response_type = Math.floor(response.status / 100);
595
+ const type = /** @type {string} */ (response.headers.get('content-type'));
596
+ const is_html = response_type === REDIRECT || type === 'text/html';
577
597
 
578
- log.info(`${rendered.status} ${decoded_path}`);
579
- writeFileSync(file, text);
580
- paths.push(normalize(decoded_path));
581
- } else if (response_type !== OK) {
582
- error({ status: rendered.status, path, referrer, referenceType: 'linked' });
583
- }
598
+ const file = output_filename(decoded, is_html);
599
+ const dest = `${out}/${file}`;
584
600
 
585
- for (const [dependency_path, result] of dependencies) {
586
- const { status, headers } = result.response;
601
+ if (written.has(file)) return;
602
+ written.add(file);
587
603
 
588
- const response_type = Math.floor(status / 100);
604
+ if (response_type === REDIRECT) {
605
+ const location = response.headers.get('location');
589
606
 
590
- const is_html = headers.get('content-type') === 'text/html';
607
+ if (location) {
608
+ mkdirp(dirname(dest));
591
609
 
592
- const file = `${out}${output_filename(dependency_path, is_html)}`;
593
- mkdirp(dirname(file));
610
+ log.warn(`${response.status} ${decoded} -> ${location}`);
594
611
 
595
612
  writeFileSync(
596
- file,
597
- result.body === null ? new Uint8Array(await result.response.arrayBuffer()) : result.body
613
+ dest,
614
+ `<meta http-equiv="refresh" content=${escape_html_attr(`0;url=${location}`)}>`
598
615
  );
599
- paths.push(dependency_path);
600
-
601
- if (response_type === OK) {
602
- log.info(`${status} ${dependency_path}`);
603
- } else {
604
- error({
605
- status,
606
- path: dependency_path,
607
- referrer: path,
608
- referenceType: 'fetched'
609
- });
610
- }
611
- }
612
-
613
- if (is_html && config.kit.prerender.crawl) {
614
- for (const href of crawl(text)) {
615
- if (href.startsWith('data:') || href.startsWith('#')) continue;
616
616
 
617
- const resolved = resolve(path, href);
618
- if (!is_root_relative(resolved)) continue;
617
+ let resolved = resolve(encoded, location);
618
+ if (is_root_relative(resolved)) {
619
+ resolved = normalize_path(resolved, config.kit.trailingSlash);
620
+ enqueue(decoded, decodeURI(resolved), resolved);
621
+ }
619
622
 
620
- const parsed = new URL(resolved, 'http://localhost');
623
+ if (!prerendered.redirects.has(decoded)) {
624
+ prerendered.redirects.set(decoded, {
625
+ status: response.status,
626
+ location: resolved
627
+ });
621
628
 
622
- let pathname = decodeURI(parsed.pathname);
629
+ prerendered.paths.push(normalize_path(decoded, 'never'));
630
+ }
631
+ } else {
632
+ log.warn(`location header missing on redirect received from ${decoded}`);
633
+ }
623
634
 
624
- if (config.kit.paths.base) {
625
- if (!pathname.startsWith(config.kit.paths.base)) continue;
626
- pathname = pathname.slice(config.kit.paths.base.length) || '/';
627
- }
635
+ return;
636
+ }
628
637
 
629
- const file = pathname.slice(1);
630
- if (files.has(file)) continue;
638
+ if (response.status === 200) {
639
+ mkdirp(dirname(dest));
631
640
 
632
- if (parsed.search) ;
641
+ log.info(`${response.status} ${decoded}`);
642
+ writeFileSync(dest, body);
633
643
 
634
- enqueue(pathname, path);
635
- }
644
+ if (is_html) {
645
+ prerendered.pages.set(decoded, {
646
+ file
647
+ });
648
+ } else {
649
+ prerendered.assets.set(decoded, {
650
+ type
651
+ });
636
652
  }
653
+
654
+ prerendered.paths.push(normalize_path(decoded, 'never'));
655
+ } else if (response_type !== OK) {
656
+ error({ status: response.status, path: decoded, referrer, referenceType });
637
657
  }
638
658
  }
639
659
 
@@ -641,10 +661,10 @@ async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
641
661
  for (const entry of config.kit.prerender.entries) {
642
662
  if (entry === '*') {
643
663
  for (const entry of build_data.entries) {
644
- enqueue(entry, null);
664
+ enqueue(null, normalize_path(config.kit.paths.base + entry, config.kit.trailingSlash)); // TODO can we pre-normalize these?
645
665
  }
646
666
  } else {
647
- enqueue(entry, null);
667
+ enqueue(null, normalize_path(config.kit.paths.base + entry, config.kit.trailingSlash));
648
668
  }
649
669
  }
650
670
 
@@ -665,9 +685,7 @@ async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
665
685
  writeFileSync(file, await rendered.text());
666
686
  }
667
687
 
668
- return {
669
- paths
670
- };
688
+ return prerendered;
671
689
  }
672
690
 
673
691
  /**
@@ -681,11 +699,14 @@ async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
681
699
  */
682
700
  function create_builder({ cwd, config, build_data, log }) {
683
701
  /** @type {Set<string>} */
684
- const prerendered_paths = new Set();
702
+ let prerendered_paths;
703
+
685
704
  let generated_manifest = false;
686
705
 
687
706
  /** @param {import('types/internal').RouteData} route */
688
707
  function not_prerendered(route) {
708
+ if (!prerendered_paths) return true;
709
+
689
710
  if (route.type === 'page' && route.path) {
690
711
  return !prerendered_paths.has(route.path);
691
712
  }
@@ -700,6 +721,7 @@ function create_builder({ cwd, config, build_data, log }) {
700
721
  copy,
701
722
 
702
723
  appDir: config.kit.appDir,
724
+ trailingSlash: config.kit.trailingSlash,
703
725
 
704
726
  createEntries(fn) {
705
727
  generated_manifest = true;
@@ -829,10 +851,7 @@ function create_builder({ cwd, config, build_data, log }) {
829
851
  log
830
852
  });
831
853
 
832
- prerendered.paths.forEach((path) => {
833
- prerendered_paths.add(path);
834
- prerendered_paths.add(path + '/');
835
- });
854
+ prerendered_paths = new Set(prerendered.paths);
836
855
 
837
856
  return prerendered;
838
857
  }
@@ -15296,14 +15296,16 @@ async function make_package(config, cwd = process.cwd()) {
15296
15296
  const svelte_ext = config.extensions.find((ext) => file.endsWith(ext)); // unlike `ext`, could be e.g. `.svelte.md`
15297
15297
 
15298
15298
  if (!config.kit.package.files(normalized)) {
15299
- const dts_file = (svelte_ext ? file : file.slice(0, -ext.length)) + '.d.ts';
15300
- const dts_path = path.join(package_dir, dts_file);
15301
- if (fs.existsSync(dts_path)) {
15302
- fs.unlinkSync(dts_path);
15303
-
15304
- const dir = path.dirname(dts_path);
15305
- if (fs.readdirSync(dir).length === 0) {
15306
- fs.rmdirSync(dir);
15299
+ const base = svelte_ext ? file : file.slice(0, -ext.length);
15300
+ for (const e of ['.d.ts', '.d.mts', '.d.cts']) {
15301
+ const dts_path = path.join(package_dir, base + e);
15302
+ if (fs.existsSync(dts_path)) {
15303
+ fs.unlinkSync(dts_path);
15304
+
15305
+ const dir = path.dirname(dts_path);
15306
+ if (fs.readdirSync(dir).length === 0) {
15307
+ fs.rmdirSync(dir);
15308
+ }
15307
15309
  }
15308
15310
  }
15309
15311
  continue;
@@ -15573,7 +15575,7 @@ async function try_load_svelte2tsx() {
15573
15575
  return await import('svelte2tsx');
15574
15576
  } catch (e) {
15575
15577
  throw new Error(
15576
- 'You need svelte2tsx and typescript if you want to generate type definitions. Install it through your package manager, or disable generation which is highly discouraged. See https://kit.svelte.dev/docs#packaging'
15578
+ 'You need svelte2tsx and typescript if you want to generate type definitions. Install it through your package manager, or disable generation which is highly discouraged. See https://kit.svelte.dev/docs/packaging'
15577
15579
  );
15578
15580
  }
15579
15581
  }
package/dist/cli.js CHANGED
@@ -466,7 +466,7 @@ const options = object(
466
466
  message += ', rather than the name of an adapter';
467
467
  }
468
468
 
469
- throw new Error(`${message}. See https://kit.svelte.dev/docs#adapters`);
469
+ throw new Error(`${message}. See https://kit.svelte.dev/docs/adapters`);
470
470
  }
471
471
 
472
472
  return input;
@@ -480,7 +480,7 @@ const options = object(
480
480
  if (input) {
481
481
  if (input.startsWith('/') || input.endsWith('/')) {
482
482
  throw new Error(
483
- "config.kit.appDir cannot start or end with '/'. See https://kit.svelte.dev/docs#configuration"
483
+ "config.kit.appDir cannot start or end with '/'. See https://kit.svelte.dev/docs/configuration"
484
484
  );
485
485
  }
486
486
  } else {
@@ -589,7 +589,7 @@ const options = object(
589
589
 
590
590
  if (input !== '' && (input.endsWith('/') || !input.startsWith('/'))) {
591
591
  throw new Error(
592
- `${keypath} option must be a root-relative path that starts but doesn't end with '/'. See https://kit.svelte.dev/docs#configuration-paths`
592
+ `${keypath} option must be a root-relative path that starts but doesn't end with '/'. See https://kit.svelte.dev/docs/configuration#paths`
593
593
  );
594
594
  }
595
595
 
@@ -601,13 +601,13 @@ const options = object(
601
601
  if (input) {
602
602
  if (!/^[a-z]+:\/\//.test(input)) {
603
603
  throw new Error(
604
- `${keypath} option must be an absolute path, if specified. See https://kit.svelte.dev/docs#configuration-paths`
604
+ `${keypath} option must be an absolute path, if specified. See https://kit.svelte.dev/docs/configuration#paths`
605
605
  );
606
606
  }
607
607
 
608
608
  if (input.endsWith('/')) {
609
609
  throw new Error(
610
- `${keypath} option must not end with '/'. See https://kit.svelte.dev/docs#configuration-paths`
610
+ `${keypath} option must not end with '/'. See https://kit.svelte.dev/docs/configuration#paths`
611
611
  );
612
612
  }
613
613
  }
@@ -619,7 +619,10 @@ const options = object(
619
619
  prerender: object({
620
620
  concurrency: number(1),
621
621
  crawl: boolean(true),
622
- createIndexFiles: boolean(true),
622
+ createIndexFiles: error(
623
+ (keypath) =>
624
+ `${keypath} has been removed — it is now controlled by the trailingSlash option. See https://kit.svelte.dev/docs/configuration#trailingslash`
625
+ ),
623
626
  enabled: boolean(true),
624
627
  entries: validate(['*'], (input, keypath) => {
625
628
  if (!Array.isArray(input) || !input.every((page) => typeof page === 'string')) {
@@ -675,13 +678,13 @@ const options = object(
675
678
 
676
679
  serviceWorker: object({
677
680
  register: boolean(true),
678
- files: fun((filename) => !/\.DS_STORE/.test(filename))
681
+ files: fun((filename) => !/\.DS_Store/.test(filename))
679
682
  }),
680
683
 
681
684
  // TODO remove this for 1.0
682
685
  ssr: error(
683
686
  (keypath) =>
684
- `${keypath} has been removed — use the handle hook instead: https://kit.svelte.dev/docs#hooks-handle'`
687
+ `${keypath} has been removed — use the handle hook instead: https://kit.svelte.dev/docs/hooks#handle'`
685
688
  ),
686
689
 
687
690
  // TODO remove this for 1.0
@@ -904,7 +907,7 @@ async function load_config({ cwd = process.cwd() } = {}) {
904
907
 
905
908
  if (!fs__default.existsSync(config_file)) {
906
909
  throw new Error(
907
- 'You need to create a svelte.config.js file. See https://kit.svelte.dev/docs#configuration'
910
+ 'You need to create a svelte.config.js file. See https://kit.svelte.dev/docs/configuration'
908
911
  );
909
912
  }
910
913
 
@@ -929,7 +932,7 @@ async function load_config({ cwd = process.cwd() } = {}) {
929
932
  function validate_config(config) {
930
933
  if (typeof config !== 'object') {
931
934
  throw new Error(
932
- 'svelte.config.js must have a configuration object as its default export. See https://kit.svelte.dev/docs#configuration'
935
+ 'svelte.config.js must have a configuration object as its default export. See https://kit.svelte.dev/docs/configuration'
933
936
  );
934
937
  }
935
938
 
@@ -995,7 +998,7 @@ async function launch(port, https) {
995
998
  exec(`${cmd} ${https ? 'https' : 'http'}://localhost:${port}`);
996
999
  }
997
1000
 
998
- const prog = sade('svelte-kit').version('1.0.0-next.263');
1001
+ const prog = sade('svelte-kit').version('1.0.0-next.267');
999
1002
 
1000
1003
  prog
1001
1004
  .command('dev')
@@ -1066,7 +1069,7 @@ prog
1066
1069
 
1067
1070
  // prettier-ignore
1068
1071
  console.log(
1069
- `See ${$.bold().cyan('https://kit.svelte.dev/docs#adapters')} to learn how to configure your app to run on the platform of your choosing`
1072
+ `See ${$.bold().cyan('https://kit.svelte.dev/docs/adapters')} to learn how to configure your app to run on the platform of your choosing`
1070
1073
  );
1071
1074
  } catch (error) {
1072
1075
  handle_error(error);
@@ -1153,7 +1156,7 @@ async function check_port(port) {
1153
1156
  function welcome({ port, host, https, open, loose, allow, cwd }) {
1154
1157
  if (open) launch(port, https);
1155
1158
 
1156
- console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.263'}\n`));
1159
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.267'}\n`));
1157
1160
 
1158
1161
  const protocol = https ? 'https:' : 'http:';
1159
1162
  const exposed = typeof host !== 'undefined' && host !== 'localhost' && host !== '127.0.0.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.263",
3
+ "version": "1.0.0-next.267",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -84,8 +84,9 @@
84
84
  "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
85
85
  "test": "npm run test:unit && npm run test:packaging && npm run test:prerendering && npm run test:integration",
86
86
  "test:unit": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\" -i packaging",
87
- "test:prerendering": "pnpm test:prerendering:basics",
87
+ "test:prerendering": "pnpm test:prerendering:basics && pnpm test:prerendering:options",
88
88
  "test:prerendering:basics": "cd test/prerendering/basics && pnpm test",
89
+ "test:prerendering:options": "cd test/prerendering/options && pnpm test",
89
90
  "test:packaging": "uvu src/packaging \"(spec\\.js|test[\\\\/]index\\.js)\"",
90
91
  "test:integration": "pnpm test:integration:amp && pnpm test:integration:basics && pnpm test:integration:options && pnpm test:integration:options-2",
91
92
  "test:integration:amp": "cd test/apps/amp && pnpm test",
package/types/config.d.ts CHANGED
@@ -35,12 +35,39 @@ export interface AdapterEntry {
35
35
  }) => void;
36
36
  }
37
37
 
38
+ export interface Prerendered {
39
+ pages: Map<
40
+ string,
41
+ {
42
+ /** The location of the .html file relative to the output directory */
43
+ file: string;
44
+ }
45
+ >;
46
+ assets: Map<
47
+ string,
48
+ {
49
+ /** The MIME type of the asset */
50
+ type: string;
51
+ }
52
+ >;
53
+ redirects: Map<
54
+ string,
55
+ {
56
+ status: number;
57
+ location: string;
58
+ }
59
+ >;
60
+ /** An array of prerendered paths (without trailing slashes, regardless of the trailingSlash config) */
61
+ paths: string[];
62
+ }
63
+
38
64
  export interface Builder {
39
65
  log: Logger;
40
66
  rimraf(dir: string): void;
41
67
  mkdirp(dir: string): void;
42
68
 
43
69
  appDir: string;
70
+ trailingSlash: 'always' | 'never' | 'ignore';
44
71
 
45
72
  /**
46
73
  * Create entry points that map to individual functions
@@ -86,9 +113,7 @@ export interface Builder {
86
113
  }
87
114
  ): string[];
88
115
 
89
- prerender(options: { all?: boolean; dest: string; fallback?: string }): Promise<{
90
- paths: string[];
91
- }>;
116
+ prerender(options: { all?: boolean; dest: string; fallback?: string }): Promise<Prerendered>;
92
117
  }
93
118
 
94
119
  export interface Adapter {
@@ -153,7 +178,6 @@ export interface Config {
153
178
  prerender?: {
154
179
  concurrency?: number;
155
180
  crawl?: boolean;
156
- createIndexFiles?: boolean;
157
181
  enabled?: boolean;
158
182
  entries?: string[];
159
183
  onError?: PrerenderOnErrorValue;
package/types/index.d.ts CHANGED
@@ -4,7 +4,14 @@
4
4
  import './ambient-modules';
5
5
 
6
6
  export { App, SSRManifest } from './app';
7
- export { Adapter, Builder, Config, PrerenderErrorHandler, ValidatedConfig } from './config';
7
+ export {
8
+ Adapter,
9
+ Builder,
10
+ Config,
11
+ Prerendered,
12
+ PrerenderErrorHandler,
13
+ ValidatedConfig
14
+ } from './config';
8
15
  export { EndpointOutput, RequestHandler } from './endpoint';
9
16
  export { ErrorLoad, ErrorLoadInput, Load, LoadInput, LoadOutput } from './page';
10
17
  export { ExternalFetch, GetSession, Handle, HandleError, RequestEvent, ResolveOpts } from './hooks';