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

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.
@@ -1282,7 +1282,7 @@ class Renderer {
1282
1282
  `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json`,
1283
1283
  {
1284
1284
  headers: {
1285
- 'x-sveltekit-noredirect': 'true'
1285
+ 'x-sveltekit-load': 'true'
1286
1286
  }
1287
1287
  }
1288
1288
  );
@@ -88,6 +88,14 @@ function is_pojo(body) {
88
88
 
89
89
  return true;
90
90
  }
91
+ /**
92
+ * @param {import('types/hooks').RequestEvent} event
93
+ * @returns string
94
+ */
95
+ function normalize_request_method(event) {
96
+ const method = event.request.method.toLowerCase();
97
+ return method === 'delete' ? 'del' : method; // 'delete' is a reserved word
98
+ }
91
99
 
92
100
  /** @param {string} body */
93
101
  function error(body) {
@@ -127,9 +135,14 @@ function is_text(content_type) {
127
135
  * @returns {Promise<Response | undefined>}
128
136
  */
129
137
  async function render_endpoint(event, mod) {
138
+ const method = normalize_request_method(event);
139
+
130
140
  /** @type {import('types/endpoint').RequestHandler} */
131
- const handler = mod[event.request.method.toLowerCase().replace('delete', 'del')]; // 'delete' is a reserved word
141
+ let handler = mod[method];
132
142
 
143
+ if (!handler && method === 'head') {
144
+ handler = mod.get;
145
+ }
133
146
  if (!handler) {
134
147
  return;
135
148
  }
@@ -179,7 +192,7 @@ async function render_endpoint(event, mod) {
179
192
  }
180
193
  }
181
194
 
182
- return new Response(normalized_body, {
195
+ return new Response(method !== 'head' ? normalized_body : undefined, {
183
196
  status,
184
197
  headers
185
198
  });
@@ -485,52 +498,29 @@ function coalesce_to_error(err) {
485
498
  : new Error(JSON.stringify(err));
486
499
  }
487
500
 
501
+ // dict from https://github.com/yahoo/serialize-javascript/blob/183c18a776e4635a379fdc620f81771f219832bb/index.js#L25
488
502
  /** @type {Record<string, string>} */
489
503
  const escape_json_in_html_dict = {
490
- '&': '\\u0026',
491
- '>': '\\u003e',
492
- '<': '\\u003c',
493
- '\u2028': '\\u2028',
494
- '\u2029': '\\u2029'
495
- };
496
-
497
- /** @type {Record<string, string>} */
498
- const escape_json_value_in_html_dict = {
499
- '"': '\\"',
500
504
  '<': '\\u003C',
501
505
  '>': '\\u003E',
502
506
  '/': '\\u002F',
503
- '\\': '\\\\',
504
- '\b': '\\b',
505
- '\f': '\\f',
506
- '\n': '\\n',
507
- '\r': '\\r',
508
- '\t': '\\t',
509
- '\0': '\\0',
510
507
  '\u2028': '\\u2028',
511
508
  '\u2029': '\\u2029'
512
509
  };
513
510
 
514
- /**
515
- * Escape a stringified JSON object that's going to be embedded in a `<script>` tag
516
- * @param {string} str
517
- */
518
- function escape_json_in_html(str) {
519
- // adapted from https://github.com/vercel/next.js/blob/694407450638b037673c6d714bfe4126aeded740/packages/next/server/htmlescape.ts
520
- // based on https://github.com/zertosh/htmlescape
521
- // License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
522
- return str.replace(/[&><\u2028\u2029]/g, (match) => escape_json_in_html_dict[match]);
523
- }
511
+ const escape_json_in_html_regex = new RegExp(
512
+ `[${Object.keys(escape_json_in_html_dict).join('')}]`,
513
+ 'g'
514
+ );
524
515
 
525
516
  /**
526
- * Escape a string JSON value to be embedded into a `<script>` tag
527
- * @param {string} str
517
+ * Escape a JSONValue that's going to be embedded in a `<script>` tag
518
+ * @param {import("@sveltejs/kit/types/helper").JSONValue} val
528
519
  */
529
- function escape_json_value_in_html(str) {
530
- return escape(
531
- str,
532
- escape_json_value_in_html_dict,
533
- (code) => `\\u${code.toString(16).toUpperCase()}`
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]
534
524
  );
535
525
  }
536
526
 
@@ -1303,7 +1293,7 @@ async function render_response({
1303
1293
 
1304
1294
  if (shadow_props) {
1305
1295
  // prettier-ignore
1306
- body += `<script type="application/json" data-type="svelte-props">${escape_json_in_html(s(shadow_props))}</script>`;
1296
+ body += `<script type="application/json" data-type="svelte-props">${escape_json_in_html(shadow_props)}</script>`;
1307
1297
  }
1308
1298
  }
1309
1299
 
@@ -1573,6 +1563,7 @@ async function load_node({
1573
1563
  ? await load_shadow_data(
1574
1564
  /** @type {import('types/internal').SSRPage} */ (route),
1575
1565
  event,
1566
+ options,
1576
1567
  !!state.prerender
1577
1568
  )
1578
1569
  : {};
@@ -1758,11 +1749,21 @@ async function load_node({
1758
1749
  }
1759
1750
 
1760
1751
  if (!opts.body || typeof opts.body === 'string') {
1752
+ // the json constructed below is later added to the dom in a script tag
1753
+ // make sure the used values are safe
1754
+ const status_number = Number(response.status);
1755
+ if (isNaN(status_number)) {
1756
+ throw new Error(
1757
+ `response.status is not a number. value: "${
1758
+ response.status
1759
+ }" type: ${typeof response.status}`
1760
+ );
1761
+ }
1761
1762
  // prettier-ignore
1762
1763
  fetched.push({
1763
1764
  url: requested,
1764
1765
  body: /** @type {string} */ (opts.body),
1765
- json: `{"status":${response.status},"statusText":${s(response.statusText)},"headers":${s(headers)},"body":"${escape_json_value_in_html(body)}"}`
1766
+ json: `{"status":${status_number},"statusText":${s(response.statusText)},"headers":${s(headers)},"body":${escape_json_in_html(body)}}`
1766
1767
  });
1767
1768
  }
1768
1769
 
@@ -1867,10 +1868,11 @@ async function load_node({
1867
1868
  *
1868
1869
  * @param {import('types/internal').SSRPage} route
1869
1870
  * @param {import('types/hooks').RequestEvent} event
1871
+ * @param {import('types/internal').SSROptions} options
1870
1872
  * @param {boolean} prerender
1871
1873
  * @returns {Promise<import('types/endpoint').ShadowData>}
1872
1874
  */
1873
- async function load_shadow_data(route, event, prerender) {
1875
+ async function load_shadow_data(route, event, options, prerender) {
1874
1876
  if (!route.shadow) return {};
1875
1877
 
1876
1878
  try {
@@ -1880,10 +1882,11 @@ async function load_shadow_data(route, event, prerender) {
1880
1882
  throw new Error('Cannot prerender pages that have shadow endpoints with mutative methods');
1881
1883
  }
1882
1884
 
1883
- const method = event.request.method.toLowerCase().replace('delete', 'del');
1884
- const handler = mod[method];
1885
+ const method = normalize_request_method(event);
1886
+ const is_get = method === 'head' || method === 'get';
1887
+ const handler = method === 'head' ? mod.head || mod.get : mod[method];
1885
1888
 
1886
- if (!handler) {
1889
+ if (!handler && !is_get) {
1887
1890
  return {
1888
1891
  status: 405,
1889
1892
  error: new Error(`${method} method not allowed`)
@@ -1897,7 +1900,7 @@ async function load_shadow_data(route, event, prerender) {
1897
1900
  body: {}
1898
1901
  };
1899
1902
 
1900
- if (method !== 'get') {
1903
+ if (!is_get) {
1901
1904
  const result = await handler(event);
1902
1905
 
1903
1906
  if (result.fallthrough) return result;
@@ -1921,8 +1924,9 @@ async function load_shadow_data(route, event, prerender) {
1921
1924
  data.body = body;
1922
1925
  }
1923
1926
 
1924
- if (mod.get) {
1925
- const result = await mod.get.call(null, event);
1927
+ const get = (method === 'head' && mod.head) || mod.get;
1928
+ if (get) {
1929
+ const result = await get(event);
1926
1930
 
1927
1931
  if (result.fallthrough) return result;
1928
1932
 
@@ -1947,9 +1951,12 @@ async function load_shadow_data(route, event, prerender) {
1947
1951
 
1948
1952
  return data;
1949
1953
  } catch (e) {
1954
+ const error = coalesce_to_error(e);
1955
+ options.handle_error(error, event);
1956
+
1950
1957
  return {
1951
1958
  status: 500,
1952
- error: coalesce_to_error(e)
1959
+ error
1953
1960
  };
1954
1961
  }
1955
1962
  }
@@ -2584,22 +2591,30 @@ async function respond(request, options, state = {}) {
2584
2591
  if (is_data_request && route.type === 'page' && route.shadow) {
2585
2592
  response = await render_endpoint(event, await route.shadow());
2586
2593
 
2587
- // since redirects are opaque to the browser, we need to repackage
2588
- // 3xx responses as 200s with a custom header
2589
- if (
2590
- response &&
2591
- response.status >= 300 &&
2592
- response.status < 400 &&
2593
- request.headers.get('x-sveltekit-noredirect') === 'true'
2594
- ) {
2595
- const location = response.headers.get('location');
2596
-
2597
- if (location) {
2598
- const headers = new Headers(response.headers);
2599
- headers.set('x-sveltekit-location', location);
2600
- response = new Response(undefined, {
2601
- status: 204,
2602
- headers
2594
+ // loading data for a client-side transition is a special case
2595
+ if (request.headers.get('x-sveltekit-load') === 'true') {
2596
+ if (response) {
2597
+ // since redirects are opaque to the browser, we need to repackage
2598
+ // 3xx responses as 200s with a custom header
2599
+ if (response.status >= 300 && response.status < 400) {
2600
+ const location = response.headers.get('location');
2601
+
2602
+ if (location) {
2603
+ const headers = new Headers(response.headers);
2604
+ headers.set('x-sveltekit-location', location);
2605
+ response = new Response(undefined, {
2606
+ status: 204,
2607
+ headers
2608
+ });
2609
+ }
2610
+ }
2611
+ } else {
2612
+ // TODO ideally, the client wouldn't request this data
2613
+ // in the first place (at least in production)
2614
+ response = new Response('{}', {
2615
+ headers: {
2616
+ 'content-type': 'application/json'
2617
+ }
2603
2618
  });
2604
2619
  }
2605
2620
  }
@@ -347,7 +347,20 @@ function crawl(html) {
347
347
  return hrefs;
348
348
  }
349
349
 
350
+ // dict from https://github.com/yahoo/serialize-javascript/blob/183c18a776e4635a379fdc620f81771f219832bb/index.js#L25
350
351
  /** @type {Record<string, string>} */
352
+ const escape_json_in_html_dict = {
353
+ '<': '\\u003C',
354
+ '>': '\\u003E',
355
+ '/': '\\u002F',
356
+ '\u2028': '\\u2028',
357
+ '\u2029': '\\u2029'
358
+ };
359
+
360
+ new RegExp(
361
+ `[${Object.keys(escape_json_in_html_dict).join('')}]`,
362
+ 'g'
363
+ );
351
364
 
352
365
  /**
353
366
  * @param str {string} string to escape
package/dist/cli.js CHANGED
@@ -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.267');
1001
+ const prog = sade('svelte-kit').version('1.0.0-next.270');
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.267'}\n`));
1159
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.270'}\n`));
1160
1160
 
1161
1161
  const protocol = https ? 'https:' : 'http:';
1162
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.267",
3
+ "version": "1.0.0-next.270",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",