@sveltejs/kit 1.0.0-next.286 → 1.0.0-next.289

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.
@@ -76,7 +76,7 @@ class Router {
76
76
  * base: string;
77
77
  * routes: import('types').CSRRoute[];
78
78
  * trailing_slash: import('types').TrailingSlash;
79
- * renderer: import('./renderer').Renderer
79
+ * renderer: import('./renderer').Renderer;
80
80
  * }} opts
81
81
  */
82
82
  constructor({ base, routes, trailing_slash, renderer }) {
@@ -93,9 +93,6 @@ class Router {
93
93
  this.enabled = true;
94
94
  this.initialized = false;
95
95
 
96
- // make it possible to reset focus
97
- document.body.setAttribute('tabindex', '-1');
98
-
99
96
  // keeping track of the history index in order to prevent popstate navigation events if needed
100
97
  this.current_history_index = history.state?.['sveltekit:index'] ?? 0;
101
98
 
@@ -313,8 +310,7 @@ class Router {
313
310
  id: url.pathname + url.search,
314
311
  routes: this.routes.filter(([pattern]) => pattern.test(path)),
315
312
  url,
316
- path,
317
- initial: !this.initialized
313
+ path
318
314
  };
319
315
  }
320
316
  }
@@ -658,12 +654,12 @@ function create_updated_store() {
658
654
  * @param {RequestInit} [opts]
659
655
  */
660
656
  function initial_fetch(resource, opts) {
661
- const url = typeof resource === 'string' ? resource : resource.url;
657
+ const url = JSON.stringify(typeof resource === 'string' ? resource : resource.url);
662
658
 
663
- let selector = `script[data-type="svelte-data"][data-url=${JSON.stringify(url)}]`;
659
+ let selector = `script[sveltekit\\:data-type="data"][sveltekit\\:data-url=${url}]`;
664
660
 
665
661
  if (opts && typeof opts.body === 'string') {
666
- selector += `[data-body="${hash(opts.body)}"]`;
662
+ selector += `[sveltekit\\:data-body="${hash(opts.body)}"]`;
667
663
  }
668
664
 
669
665
  const script = document.querySelector(selector);
@@ -782,7 +778,7 @@ class Renderer {
782
778
  let props;
783
779
 
784
780
  if (is_leaf) {
785
- const serialized = document.querySelector('[data-type="svelte-props"]');
781
+ const serialized = document.querySelector('script[sveltekit\\:data-type="props"]');
786
782
  if (serialized) {
787
783
  props = JSON.parse(/** @type {string} */ (serialized.textContent));
788
784
  }
@@ -879,6 +875,14 @@ class Renderer {
879
875
  const token = (this.token = {});
880
876
  let navigation_result = await this._get_navigation_result(info, no_cache);
881
877
 
878
+ if (!navigation_result && info.url.pathname === location.pathname) {
879
+ navigation_result = await this._load_error({
880
+ status: 404,
881
+ error: new Error(`Not found: ${info.url.pathname}`),
882
+ url: info.url
883
+ });
884
+ }
885
+
882
886
  if (!navigation_result) {
883
887
  location.href = info.url.href;
884
888
  return;
@@ -932,8 +936,24 @@ class Renderer {
932
936
  const { scroll, keepfocus } = opts;
933
937
 
934
938
  if (!keepfocus) {
939
+ // Reset page selection and focus
940
+ // We try to mimick browsers' behaviour as closely as possible by targeting the
941
+ // viewport, but unfortunately it's not a perfect match — e.g. shift-tabbing won't
942
+ // immediately cycle from the end of the page
943
+ // See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
944
+ const root = document.documentElement;
945
+ const tabindex = root.getAttribute('tabindex');
946
+
935
947
  getSelection()?.removeAllRanges();
936
- document.body.focus();
948
+ root.tabIndex = -1;
949
+ root.focus();
950
+
951
+ // restore `tabindex` as to prevent the document from stealing input from elements
952
+ if (tabindex !== null) {
953
+ root.setAttribute('tabindex', tabindex);
954
+ } else {
955
+ root.removeAttribute('tabindex');
956
+ }
937
957
  }
938
958
 
939
959
  // need to render the DOM before we can scroll to the rendered elements
@@ -1070,14 +1090,6 @@ class Renderer {
1070
1090
  );
1071
1091
  if (result) return result;
1072
1092
  }
1073
-
1074
- if (info.initial) {
1075
- return await this._load_error({
1076
- status: 404,
1077
- error: new Error(`Not found: ${info.url.pathname}`),
1078
- url: info.url
1079
- });
1080
- }
1081
1093
  }
1082
1094
 
1083
1095
  /**
@@ -498,30 +498,62 @@ function coalesce_to_error(err) {
498
498
  : new Error(JSON.stringify(err));
499
499
  }
500
500
 
501
- // dict from https://github.com/yahoo/serialize-javascript/blob/183c18a776e4635a379fdc620f81771f219832bb/index.js#L25
502
- /** @type {Record<string, string>} */
503
- const escape_json_in_html_dict = {
501
+ /**
502
+ * Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
503
+ *
504
+ * The first closes the script element, so everything after is treated as raw HTML.
505
+ * The second disables further parsing until `-->`, so the script element might be unexpectedly
506
+ * kept open until until an unrelated HTML comment in the page.
507
+ *
508
+ * U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
509
+ * browsers.
510
+ *
511
+ * @see tests for unsafe parsing examples.
512
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
513
+ * @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
514
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
515
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
516
+ * @see https://github.com/tc39/proposal-json-superset
517
+ * @type {Record<string, string>}
518
+ */
519
+ const render_json_payload_script_dict = {
504
520
  '<': '\\u003C',
505
- '>': '\\u003E',
506
- '/': '\\u002F',
507
521
  '\u2028': '\\u2028',
508
522
  '\u2029': '\\u2029'
509
523
  };
510
524
 
511
- const escape_json_in_html_regex = new RegExp(
512
- `[${Object.keys(escape_json_in_html_dict).join('')}]`,
525
+ const render_json_payload_script_regex = new RegExp(
526
+ `[${Object.keys(render_json_payload_script_dict).join('')}]`,
513
527
  'g'
514
528
  );
515
529
 
516
530
  /**
517
- * Escape a JSONValue that's going to be embedded in a `<script>` tag
518
- * @param {import('types').JSONValue} val
531
+ * Generates a raw HTML string containing a safe script element carrying JSON data and associated attributes.
532
+ *
533
+ * It escapes all the special characters needed to guarantee the element is unbroken, but care must
534
+ * be taken to ensure it is inserted in the document at an acceptable position for a script element,
535
+ * and that the resulting string isn't further modified.
536
+ *
537
+ * Attribute names must be type-checked so we don't need to escape them.
538
+ *
539
+ * @param {import('types').PayloadScriptAttributes} attrs A list of attributes to be added to the element.
540
+ * @param {import('types').JSONValue} payload The data to be carried by the element. Must be serializable to JSON.
541
+ * @returns {string} The raw HTML of a script element carrying the JSON payload.
542
+ * @example const html = render_json_payload_script({ type: 'data', url: '/data.json' }, { foo: 'bar' });
519
543
  */
520
- function escape_json_in_html(val) {
521
- return JSON.stringify(val).replace(
522
- escape_json_in_html_regex,
523
- (match) => escape_json_in_html_dict[match]
544
+ function render_json_payload_script(attrs, payload) {
545
+ const safe_payload = JSON.stringify(payload).replace(
546
+ render_json_payload_script_regex,
547
+ (match) => render_json_payload_script_dict[match]
524
548
  );
549
+
550
+ let safe_attrs = '';
551
+ for (const [key, value] of Object.entries(attrs)) {
552
+ if (value === undefined) continue;
553
+ safe_attrs += ` sveltekit:data-${key}=${escape_html_attr(value)}`;
554
+ }
555
+
556
+ return `<script type="application/json"${safe_attrs}>${safe_payload}</script>`;
525
557
  }
526
558
 
527
559
  /**
@@ -1084,7 +1116,7 @@ async function render_response({
1084
1116
  /** @type {Map<string, string>} */
1085
1117
  const styles = new Map();
1086
1118
 
1087
- /** @type {Array<{ url: string, body: string, json: string }>} */
1119
+ /** @type {Array<import('./types').Fetched>} */
1088
1120
  const serialized_data = [];
1089
1121
 
1090
1122
  let shadow_props;
@@ -1280,19 +1312,17 @@ async function render_response({
1280
1312
 
1281
1313
  body += `\n\t\t<script ${attributes.join(' ')}>${init_app}</script>`;
1282
1314
 
1283
- // prettier-ignore
1284
1315
  body += serialized_data
1285
- .map(({ url, body, json }) => {
1286
- let attributes = `type="application/json" data-type="svelte-data" data-url=${escape_html_attr(url)}`;
1287
- if (body) attributes += ` data-body="${hash(body)}"`;
1288
-
1289
- return `<script ${attributes}>${json}</script>`;
1290
- })
1316
+ .map(({ url, body, response }) =>
1317
+ render_json_payload_script(
1318
+ { type: 'data', url, body: typeof body === 'string' ? hash(body) : undefined },
1319
+ response
1320
+ )
1321
+ )
1291
1322
  .join('\n\t');
1292
1323
 
1293
1324
  if (shadow_props) {
1294
- // prettier-ignore
1295
- body += `<script type="application/json" data-type="svelte-props">${escape_json_in_html(shadow_props)}</script>`;
1325
+ body += render_json_payload_script({ type: 'props' }, shadow_props);
1296
1326
  }
1297
1327
  }
1298
1328
 
@@ -1542,13 +1572,7 @@ async function load_node({
1542
1572
 
1543
1573
  let uses_credentials = false;
1544
1574
 
1545
- /**
1546
- * @type {Array<{
1547
- * url: string;
1548
- * body: string;
1549
- * json: string;
1550
- * }>}
1551
- */
1575
+ /** @type {Array<import('./types').Fetched>} */
1552
1576
  const fetched = [];
1553
1577
 
1554
1578
  /**
@@ -1748,8 +1772,6 @@ async function load_node({
1748
1772
  }
1749
1773
 
1750
1774
  if (!opts.body || typeof opts.body === 'string') {
1751
- // the json constructed below is later added to the dom in a script tag
1752
- // make sure the used values are safe
1753
1775
  const status_number = Number(response.status);
1754
1776
  if (isNaN(status_number)) {
1755
1777
  throw new Error(
@@ -1758,11 +1780,16 @@ async function load_node({
1758
1780
  }" type: ${typeof response.status}`
1759
1781
  );
1760
1782
  }
1761
- // prettier-ignore
1783
+
1762
1784
  fetched.push({
1763
1785
  url: requested,
1764
- body: /** @type {string} */ (opts.body),
1765
- json: `{"status":${status_number},"statusText":${s(response.statusText)},"headers":${s(headers)},"body":${escape_json_in_html(body)}}`
1786
+ body: opts.body,
1787
+ response: {
1788
+ status: status_number,
1789
+ statusText: response.statusText,
1790
+ headers,
1791
+ body
1792
+ }
1766
1793
  });
1767
1794
  }
1768
1795
 
@@ -2516,7 +2543,6 @@ async function respond(request, options, state = {}) {
2516
2543
  request,
2517
2544
  url,
2518
2545
  params: {},
2519
- // @ts-expect-error this picks up types that belong to the tests
2520
2546
  locals: {},
2521
2547
  platform: state.platform
2522
2548
  };
@@ -1,5 +1,5 @@
1
1
  /** @type {import('amphtml-validator').Validator} */
2
- const amp = await (await import('./index8.js').then(function (n) { return n.i; })).getInstance();
2
+ const amp = await (await import('./index7.js').then(function (n) { return n.i; })).getInstance();
3
3
 
4
4
  /** @type {import('types').Handle} */
5
5
  async function handle({ event, resolve }) {
@@ -1,12 +1,12 @@
1
1
  import path__default from 'path';
2
2
  import { svelte } from '@sveltejs/vite-plugin-svelte';
3
3
  import vite from 'vite';
4
- import { c as create_manifest_data, a as create_app, d as deep_merge } from './index2.js';
4
+ import { c as create_manifest_data, a as create_app, g as generate_tsconfig, d as deep_merge } from './tsconfig.js';
5
5
  import { r as runtime, S as SVELTE_KIT_ASSETS, a as resolve_entry, $, b as SVELTE_KIT, l as load_template, c as coalesce_to_error, g as get_mime_lookup, d as copy_assets, e as get_aliases, p as print_config_conflicts } from '../cli.js';
6
6
  import fs__default from 'fs';
7
7
  import { URL } from 'url';
8
8
  import { s as sirv } from './build.js';
9
- import { __fetch_polyfill } from '../install-fetch.js';
9
+ import { installFetch } from '../install-fetch.js';
10
10
  import { getRequest, setResponse } from '../node.js';
11
11
  import { sequence } from '../hooks.js';
12
12
  import './misc.js';
@@ -46,7 +46,7 @@ async function create_plugin(config, cwd) {
46
46
  name: 'vite-plugin-svelte-kit',
47
47
 
48
48
  configureServer(vite) {
49
- __fetch_polyfill();
49
+ installFetch();
50
50
 
51
51
  /** @type {import('types').SSRManifest} */
52
52
  let manifest;
@@ -54,7 +54,7 @@ async function create_plugin(config, cwd) {
54
54
  function update_manifest() {
55
55
  const manifest_data = create_manifest_data({ config, cwd });
56
56
 
57
- create_app({ manifest_data, output: `${SVELTE_KIT}/generated`, cwd });
57
+ create_app({ config, manifest_data, cwd });
58
58
 
59
59
  manifest = {
60
60
  appDir: config.kit.appDir,
@@ -210,7 +210,6 @@ async function create_plugin(config, cwd) {
210
210
 
211
211
  /** @type {import('types').Hooks} */
212
212
  const hooks = {
213
- // @ts-expect-error this picks up types that belong to the tests
214
213
  getSession: user_hooks.getSession || (() => ({})),
215
214
  handle: amp ? sequence(amp, handle) : handle,
216
215
  handleError:
@@ -408,6 +407,8 @@ function find_deps(node, deps) {
408
407
  async function dev({ cwd, port, host, https, config }) {
409
408
  copy_assets(`${SVELTE_KIT}/runtime`);
410
409
 
410
+ generate_tsconfig(config);
411
+
411
412
  const [vite_config] = deep_merge(
412
413
  {
413
414
  server: {