@sveltejs/kit 1.0.0-next.285 → 1.0.0-next.288

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.
@@ -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
 
@@ -658,12 +655,12 @@ function create_updated_store() {
658
655
  * @param {RequestInit} [opts]
659
656
  */
660
657
  function initial_fetch(resource, opts) {
661
- const url = typeof resource === 'string' ? resource : resource.url;
658
+ const url = JSON.stringify(typeof resource === 'string' ? resource : resource.url);
662
659
 
663
- let selector = `script[data-type="svelte-data"][data-url=${JSON.stringify(url)}]`;
660
+ let selector = `script[sveltekit\\:data-type="data"][sveltekit\\:data-url=${url}]`;
664
661
 
665
662
  if (opts && typeof opts.body === 'string') {
666
- selector += `[data-body="${hash(opts.body)}"]`;
663
+ selector += `[sveltekit\\:data-body="${hash(opts.body)}"]`;
667
664
  }
668
665
 
669
666
  const script = document.querySelector(selector);
@@ -782,7 +779,7 @@ class Renderer {
782
779
  let props;
783
780
 
784
781
  if (is_leaf) {
785
- const serialized = document.querySelector('[data-type="svelte-props"]');
782
+ const serialized = document.querySelector('script[sveltekit\\:data-type="props"]');
786
783
  if (serialized) {
787
784
  props = JSON.parse(/** @type {string} */ (serialized.textContent));
788
785
  }
@@ -932,8 +929,24 @@ class Renderer {
932
929
  const { scroll, keepfocus } = opts;
933
930
 
934
931
  if (!keepfocus) {
932
+ // Reset page selection and focus
933
+ // We try to mimick browsers' behaviour as closely as possible by targeting the
934
+ // viewport, but unfortunately it's not a perfect match — e.g. shift-tabbing won't
935
+ // immediately cycle from the end of the page
936
+ // See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
937
+ const root = document.documentElement;
938
+ const tabindex = root.getAttribute('tabindex');
939
+
935
940
  getSelection()?.removeAllRanges();
936
- document.body.focus();
941
+ root.tabIndex = -1;
942
+ root.focus();
943
+
944
+ // restore `tabindex` as to prevent the document from stealing input from elements
945
+ if (tabindex !== null) {
946
+ root.setAttribute('tabindex', tabindex);
947
+ } else {
948
+ root.removeAttribute('tabindex');
949
+ }
937
950
  }
938
951
 
939
952
  // need to render the DOM before we can scroll to the rendered elements
@@ -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
 
@@ -6,7 +6,7 @@ import { r as runtime, S as SVELTE_KIT_ASSETS, a as resolve_entry, $, b as SVELT
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;
@@ -144,6 +144,24 @@ async function create_plugin(config, cwd) {
144
144
  };
145
145
  }
146
146
 
147
+ /** @param {Error} error */
148
+ function fix_stack_trace(error) {
149
+ // TODO https://github.com/vitejs/vite/issues/7045
150
+
151
+ // ideally vite would expose ssrRewriteStacktrace, but
152
+ // in lieu of that, we can implement it ourselves. we
153
+ // don't want to mutate the error object, because
154
+ // the stack trace could be 'fixed' multiple times,
155
+ // and Vite will fix stack traces before we even
156
+ // see them if they occur during ssrLoadModule
157
+ const original = error.stack;
158
+ vite.ssrFixStacktrace(error);
159
+ const fixed = error.stack;
160
+ error.stack = original;
161
+
162
+ return fixed;
163
+ }
164
+
147
165
  update_manifest();
148
166
 
149
167
  vite.watcher.on('add', update_manifest);
@@ -248,13 +266,19 @@ async function create_plugin(config, cwd) {
248
266
  dev: true,
249
267
  floc: config.kit.floc,
250
268
  get_stack: (error) => {
251
- vite.ssrFixStacktrace(error);
252
- return error.stack;
269
+ return fix_stack_trace(error);
253
270
  },
254
271
  handle_error: (error, event) => {
255
- vite.ssrFixStacktrace(error);
256
272
  hooks.handleError({
257
- error,
273
+ error: new Proxy(error, {
274
+ get: (target, property) => {
275
+ if (property === 'stack') {
276
+ return fix_stack_trace(error);
277
+ }
278
+
279
+ return Reflect.get(target, property, target);
280
+ }
281
+ }),
258
282
  event,
259
283
 
260
284
  // TODO remove for 1.0
@@ -2,7 +2,7 @@ import { b as SVELTE_KIT, m as mkdirp, h as rimraf, i as copy, $, j as logger }
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';
5
- import { __fetch_polyfill } from '../install-fetch.js';
5
+ import { installFetch } from '../install-fetch.js';
6
6
  import { g as generate_manifest } from './index4.js';
7
7
  import 'sade';
8
8
  import 'child_process';
@@ -342,18 +342,32 @@ function crawl(html) {
342
342
  return hrefs;
343
343
  }
344
344
 
345
- // dict from https://github.com/yahoo/serialize-javascript/blob/183c18a776e4635a379fdc620f81771f219832bb/index.js#L25
346
- /** @type {Record<string, string>} */
347
- const escape_json_in_html_dict = {
345
+ /**
346
+ * Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
347
+ *
348
+ * The first closes the script element, so everything after is treated as raw HTML.
349
+ * The second disables further parsing until `-->`, so the script element might be unexpectedly
350
+ * kept open until until an unrelated HTML comment in the page.
351
+ *
352
+ * U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
353
+ * browsers.
354
+ *
355
+ * @see tests for unsafe parsing examples.
356
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
357
+ * @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
358
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
359
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
360
+ * @see https://github.com/tc39/proposal-json-superset
361
+ * @type {Record<string, string>}
362
+ */
363
+ const render_json_payload_script_dict = {
348
364
  '<': '\\u003C',
349
- '>': '\\u003E',
350
- '/': '\\u002F',
351
365
  '\u2028': '\\u2028',
352
366
  '\u2029': '\\u2029'
353
367
  };
354
368
 
355
369
  new RegExp(
356
- `[${Object.keys(escape_json_in_html_dict).join('')}]`,
370
+ `[${Object.keys(render_json_payload_script_dict).join('')}]`,
357
371
  'g'
358
372
  );
359
373
 
@@ -458,7 +472,7 @@ async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
458
472
  return prerendered;
459
473
  }
460
474
 
461
- __fetch_polyfill();
475
+ installFetch();
462
476
 
463
477
  const server_root = resolve$1(cwd, `${SVELTE_KIT}/output`);
464
478
 
@@ -5,7 +5,7 @@ import { resolve, join } from 'path';
5
5
  import { s as sirv } from './build.js';
6
6
  import { pathToFileURL } from 'url';
7
7
  import { getRequest, setResponse } from '../node.js';
8
- import { __fetch_polyfill } from '../install-fetch.js';
8
+ import { installFetch } from '../install-fetch.js';
9
9
  import { b as SVELTE_KIT, S as SVELTE_KIT_ASSETS } from '../cli.js';
10
10
  import 'querystring';
11
11
  import 'stream';
@@ -43,7 +43,7 @@ async function preview({
43
43
  https: use_https = false,
44
44
  cwd = process.cwd()
45
45
  }) {
46
- __fetch_polyfill();
46
+ installFetch();
47
47
 
48
48
  const index_file = resolve(cwd, `${SVELTE_KIT}/output/server/index.js`);
49
49
  const manifest_file = resolve(cwd, `${SVELTE_KIT}/output/server/manifest.js`);
package/dist/cli.js CHANGED
@@ -422,7 +422,7 @@ function get_mime_lookup(manifest_data) {
422
422
  return mime;
423
423
  }
424
424
 
425
- /** @param {import('@sveltejs/kit').ValidatedConfig} config */
425
+ /** @param {import('types').ValidatedConfig} config */
426
426
  function get_aliases(config) {
427
427
  const alias = {
428
428
  __GENERATED__: path__default.posix.resolve(`${SVELTE_KIT}/generated`),
@@ -998,7 +998,7 @@ async function launch(port, https) {
998
998
  exec(`${cmd} ${https ? 'https' : 'http'}://localhost:${port}`);
999
999
  }
1000
1000
 
1001
- const prog = sade('svelte-kit').version('1.0.0-next.285');
1001
+ const prog = sade('svelte-kit').version('1.0.0-next.288');
1002
1002
 
1003
1003
  prog
1004
1004
  .command('dev')
@@ -1156,7 +1156,7 @@ async function check_port(port) {
1156
1156
  function welcome({ port, host, https, open, loose, allow, cwd }) {
1157
1157
  if (open) launch(port, https);
1158
1158
 
1159
- console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.285'}\n`));
1159
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.288'}\n`));
1160
1160
 
1161
1161
  const protocol = https ? 'https:' : 'http:';
1162
1162
  const exposed = typeof host !== 'undefined' && host !== 'localhost' && host !== '127.0.0.1';
@@ -6490,7 +6490,7 @@ function fixResponseChunkedTransferBadEnding(request, errorCallback) {
6490
6490
  }
6491
6491
 
6492
6492
  // exported for dev/preview and node environments
6493
- function __fetch_polyfill() {
6493
+ function installFetch() {
6494
6494
  Object.defineProperties(globalThis, {
6495
6495
  fetch: {
6496
6496
  enumerable: true,
@@ -6515,4 +6515,4 @@ function __fetch_polyfill() {
6515
6515
  });
6516
6516
  }
6517
6517
 
6518
- export { FormData as F, Headers, Request, Response, __fetch_polyfill, File as a, commonjsGlobal as c, fetch };
6518
+ export { FormData as F, File as a, commonjsGlobal as c, installFetch };
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Readable } from 'stream';
2
2
 
3
- /** @type {import('@sveltejs/kit/node').GetRawBody} */
3
+ /** @param {import('http').IncomingMessage} req */
4
4
  function get_raw_body(req) {
5
5
  return new Promise((fulfil, reject) => {
6
6
  const h = req.headers;
@@ -50,7 +50,7 @@ function get_raw_body(req) {
50
50
  });
51
51
  }
52
52
 
53
- /** @type {import('@sveltejs/kit/node').GetRequest} */
53
+ /** @type {import('@sveltejs/kit/node').getRequest} */
54
54
  async function getRequest(base, req) {
55
55
  let headers = /** @type {Record<string, string>} */ (req.headers);
56
56
  if (req.httpVersionMajor === 2) {
@@ -69,9 +69,8 @@ async function getRequest(base, req) {
69
69
  });
70
70
  }
71
71
 
72
- /** @type {import('@sveltejs/kit/node').SetResponse} */
72
+ /** @type {import('@sveltejs/kit/node').setResponse} */
73
73
  async function setResponse(res, response) {
74
- /** @type {import('types').ResponseHeaders} */
75
74
  const headers = Object.fromEntries(response.headers);
76
75
 
77
76
  if (response.headers.has('set-cookie')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.285",
3
+ "version": "1.0.0-next.288",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -15,7 +15,7 @@
15
15
  "vite": "^2.8.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@playwright/test": "^1.17.1",
18
+ "@playwright/test": "^1.19.1",
19
19
  "@rollup/plugin-replace": "^4.0.0",
20
20
  "@types/amphtml-validator": "^1.0.1",
21
21
  "@types/cookie": "^0.4.1",
@@ -82,8 +82,9 @@
82
82
  "check": "tsc && svelte-check --ignore test/prerendering,src/packaging/test",
83
83
  "format": "npm run check-format -- --write",
84
84
  "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
85
- "test": "npm run test:unit && npm run test:packaging && npm run test:prerendering && npm run test:integration",
85
+ "test": "npm run test:unit && npm run test:typings && 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:typings": "tsc --project test/typings",
87
88
  "test:prerendering": "pnpm test:prerendering:basics && pnpm test:prerendering:options",
88
89
  "test:prerendering:basics": "cd test/prerendering/basics && pnpm test",
89
90
  "test:prerendering:options": "cd test/prerendering/options && pnpm test",