@sveltejs/kit 1.0.0-next.269 → 1.0.0-next.272

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.
@@ -83,6 +83,8 @@ class Router {
83
83
  history.replaceState({ ...history.state, 'sveltekit:index': 0 }, '', location.href);
84
84
  }
85
85
 
86
+ this.hash_navigating = false;
87
+
86
88
  this.callbacks = {
87
89
  /** @type {Array<({ from, to, cancel }: { from: URL, to: URL | null, cancel: () => void }) => void>} */
88
90
  before_navigate: [],
@@ -210,9 +212,8 @@ class Router {
210
212
  // Removing the hash does a full page navigation in the browser, so make sure a hash is present
211
213
  const [base, hash] = url.href.split('#');
212
214
  if (hash !== undefined && base === location.href.split('#')[0]) {
213
- // Call `pushState` to add url to history so going back works.
214
- // Also make a delay, otherwise the browser default behaviour would not kick in
215
- setTimeout(() => history.pushState({}, '', url.href));
215
+ this.hash_navigating = true;
216
+
216
217
  const info = this.parse(url);
217
218
  if (info) {
218
219
  return this.renderer.update(info, [], false);
@@ -256,6 +257,17 @@ class Router {
256
257
  });
257
258
  }
258
259
  });
260
+
261
+ addEventListener('hashchange', () => {
262
+ if (this.hash_navigating) {
263
+ this.hash_navigating = false;
264
+ history.replaceState(
265
+ { ...history.state, 'sveltekit:index': ++this.current_history_index },
266
+ '',
267
+ location.href
268
+ );
269
+ }
270
+ });
259
271
  }
260
272
 
261
273
  /**
@@ -1279,10 +1291,10 @@ class Renderer {
1279
1291
 
1280
1292
  if (has_shadow && i === a.length - 1) {
1281
1293
  const res = await fetch(
1282
- `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json`,
1294
+ `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`,
1283
1295
  {
1284
1296
  headers: {
1285
- 'x-sveltekit-noredirect': 'true'
1297
+ 'x-sveltekit-load': 'true'
1286
1298
  }
1287
1299
  }
1288
1300
  );
@@ -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
  });
@@ -1044,7 +1057,7 @@ const updated = {
1044
1057
  * error?: Error;
1045
1058
  * url: URL;
1046
1059
  * params: Record<string, string>;
1047
- * ssr: boolean;
1060
+ * resolve_opts: import('types/hooks').RequiredResolveOptions;
1048
1061
  * stuff: Record<string, any>;
1049
1062
  * }} opts
1050
1063
  */
@@ -1058,7 +1071,7 @@ async function render_response({
1058
1071
  error,
1059
1072
  url,
1060
1073
  params,
1061
- ssr,
1074
+ resolve_opts,
1062
1075
  stuff
1063
1076
  }) {
1064
1077
  if (state.prerender) {
@@ -1090,7 +1103,7 @@ async function render_response({
1090
1103
  error.stack = options.get_stack(error);
1091
1104
  }
1092
1105
 
1093
- if (ssr) {
1106
+ if (resolve_opts.ssr) {
1094
1107
  branch.forEach(({ node, props, loaded, fetched, uses_credentials }) => {
1095
1108
  if (node.css) node.css.forEach((url) => stylesheets.add(url));
1096
1109
  if (node.js) node.js.forEach((url) => modulepreloads.add(url));
@@ -1186,9 +1199,9 @@ async function render_response({
1186
1199
  throw new Error(`Failed to serialize session data: ${error.message}`);
1187
1200
  })},
1188
1201
  route: ${!!page_config.router},
1189
- spa: ${!ssr},
1202
+ spa: ${!resolve_opts.ssr},
1190
1203
  trailing_slash: ${s(options.trailing_slash)},
1191
- hydrate: ${ssr && page_config.hydrate ? `{
1204
+ hydrate: ${resolve_opts.ssr && page_config.hydrate ? `{
1192
1205
  status: ${status},
1193
1206
  error: ${serialize_error(error)},
1194
1207
  nodes: [
@@ -1314,7 +1327,9 @@ async function render_response({
1314
1327
  const assets =
1315
1328
  options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
1316
1329
 
1317
- const html = options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) });
1330
+ const html = resolve_opts.transformPage({
1331
+ html: options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) })
1332
+ });
1318
1333
 
1319
1334
  const headers = new Headers({
1320
1335
  'content-type': 'text/html',
@@ -1869,10 +1884,11 @@ async function load_shadow_data(route, event, options, prerender) {
1869
1884
  throw new Error('Cannot prerender pages that have shadow endpoints with mutative methods');
1870
1885
  }
1871
1886
 
1872
- const method = event.request.method.toLowerCase().replace('delete', 'del');
1873
- const handler = mod[method];
1887
+ const method = normalize_request_method(event);
1888
+ const is_get = method === 'head' || method === 'get';
1889
+ const handler = method === 'head' ? mod.head || mod.get : mod[method];
1874
1890
 
1875
- if (!handler) {
1891
+ if (!handler && !is_get) {
1876
1892
  return {
1877
1893
  status: 405,
1878
1894
  error: new Error(`${method} method not allowed`)
@@ -1886,7 +1902,7 @@ async function load_shadow_data(route, event, options, prerender) {
1886
1902
  body: {}
1887
1903
  };
1888
1904
 
1889
- if (method !== 'get') {
1905
+ if (!is_get) {
1890
1906
  const result = await handler(event);
1891
1907
 
1892
1908
  if (result.fallthrough) return result;
@@ -1910,8 +1926,9 @@ async function load_shadow_data(route, event, options, prerender) {
1910
1926
  data.body = body;
1911
1927
  }
1912
1928
 
1913
- if (mod.get) {
1914
- const result = await mod.get.call(null, event);
1929
+ const get = (method === 'head' && mod.head) || mod.get;
1930
+ if (get) {
1931
+ const result = await get(event);
1915
1932
 
1916
1933
  if (result.fallthrough) return result;
1917
1934
 
@@ -1999,10 +2016,18 @@ function validate_shadow_output(result) {
1999
2016
  * $session: any;
2000
2017
  * status: number;
2001
2018
  * error: Error;
2002
- * ssr: boolean;
2019
+ * resolve_opts: import('types/hooks').RequiredResolveOptions;
2003
2020
  * }} opts
2004
2021
  */
2005
- async function respond_with_error({ event, options, state, $session, status, error, ssr }) {
2022
+ async function respond_with_error({
2023
+ event,
2024
+ options,
2025
+ state,
2026
+ $session,
2027
+ status,
2028
+ error,
2029
+ resolve_opts
2030
+ }) {
2006
2031
  try {
2007
2032
  const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
2008
2033
  const default_error = await options.manifest._.nodes[1](); // 1 is always the root error
@@ -2058,7 +2083,7 @@ async function respond_with_error({ event, options, state, $session, status, err
2058
2083
  branch: [layout_loaded, error_loaded],
2059
2084
  url: event.url,
2060
2085
  params,
2061
- ssr
2086
+ resolve_opts
2062
2087
  });
2063
2088
  } catch (err) {
2064
2089
  const error = coalesce_to_error(err);
@@ -2084,19 +2109,19 @@ async function respond_with_error({ event, options, state, $session, status, err
2084
2109
  * options: SSROptions;
2085
2110
  * state: SSRState;
2086
2111
  * $session: any;
2112
+ * resolve_opts: import('types/hooks').RequiredResolveOptions;
2087
2113
  * route: import('types/internal').SSRPage;
2088
2114
  * params: Record<string, string>;
2089
- * ssr: boolean;
2090
2115
  * }} opts
2091
2116
  * @returns {Promise<Response | undefined>}
2092
2117
  */
2093
2118
  async function respond$1(opts) {
2094
- const { event, options, state, $session, route, ssr } = opts;
2119
+ const { event, options, state, $session, route, resolve_opts } = opts;
2095
2120
 
2096
2121
  /** @type {Array<SSRNode | undefined>} */
2097
2122
  let nodes;
2098
2123
 
2099
- if (!ssr) {
2124
+ if (!resolve_opts.ssr) {
2100
2125
  return await render_response({
2101
2126
  ...opts,
2102
2127
  branch: [],
@@ -2126,7 +2151,7 @@ async function respond$1(opts) {
2126
2151
  $session,
2127
2152
  status: 500,
2128
2153
  error,
2129
- ssr
2154
+ resolve_opts
2130
2155
  });
2131
2156
  }
2132
2157
 
@@ -2157,7 +2182,7 @@ async function respond$1(opts) {
2157
2182
 
2158
2183
  let stuff = {};
2159
2184
 
2160
- ssr: if (ssr) {
2185
+ ssr: if (resolve_opts.ssr) {
2161
2186
  for (let i = 0; i < nodes.length; i += 1) {
2162
2187
  const node = nodes[i];
2163
2188
 
@@ -2262,7 +2287,7 @@ async function respond$1(opts) {
2262
2287
  $session,
2263
2288
  status,
2264
2289
  error,
2265
- ssr
2290
+ resolve_opts
2266
2291
  }),
2267
2292
  set_cookie_headers
2268
2293
  );
@@ -2343,10 +2368,10 @@ function with_cookies(response, set_cookie_headers) {
2343
2368
  * @param {import('types/internal').SSRPage} route
2344
2369
  * @param {import('types/internal').SSROptions} options
2345
2370
  * @param {import('types/internal').SSRState} state
2346
- * @param {boolean} ssr
2371
+ * @param {import('types/hooks').RequiredResolveOptions} resolve_opts
2347
2372
  * @returns {Promise<Response | undefined>}
2348
2373
  */
2349
- async function render_page(event, route, options, state, ssr) {
2374
+ async function render_page(event, route, options, state, resolve_opts) {
2350
2375
  if (state.initiator === route) {
2351
2376
  // infinite request cycle detected
2352
2377
  return new Response(`Not found: ${event.url.pathname}`, {
@@ -2372,9 +2397,9 @@ async function render_page(event, route, options, state, ssr) {
2372
2397
  options,
2373
2398
  state,
2374
2399
  $session,
2400
+ resolve_opts,
2375
2401
  route,
2376
- params: event.params, // TODO this is redundant
2377
- ssr
2402
+ params: event.params // TODO this is redundant
2378
2403
  });
2379
2404
 
2380
2405
  if (response) {
@@ -2446,6 +2471,9 @@ function negotiate(accept, types) {
2446
2471
 
2447
2472
  const DATA_SUFFIX = '/__data.json';
2448
2473
 
2474
+ /** @param {{ html: string }} opts */
2475
+ const default_transform = ({ html }) => html;
2476
+
2449
2477
  /** @type {import('types/internal').Respond} */
2450
2478
  async function respond(request, options, state = {}) {
2451
2479
  const url = new URL(request.url);
@@ -2529,13 +2557,22 @@ async function respond(request, options, state = {}) {
2529
2557
  rawBody: body_getter
2530
2558
  });
2531
2559
 
2532
- let ssr = true;
2560
+ /** @type {import('types/hooks').RequiredResolveOptions} */
2561
+ let resolve_opts = {
2562
+ ssr: true,
2563
+ transformPage: default_transform
2564
+ };
2533
2565
 
2534
2566
  try {
2535
2567
  const response = await options.hooks.handle({
2536
2568
  event,
2537
2569
  resolve: async (event, opts) => {
2538
- if (opts && 'ssr' in opts) ssr = /** @type {boolean} */ (opts.ssr);
2570
+ if (opts) {
2571
+ resolve_opts = {
2572
+ ssr: opts.ssr !== false,
2573
+ transformPage: opts.transformPage || default_transform
2574
+ };
2575
+ }
2539
2576
 
2540
2577
  if (state.prerender && state.prerender.fallback) {
2541
2578
  return await render_response({
@@ -2548,7 +2585,10 @@ async function respond(request, options, state = {}) {
2548
2585
  stuff: {},
2549
2586
  status: 200,
2550
2587
  branch: [],
2551
- ssr: false
2588
+ resolve_opts: {
2589
+ ...resolve_opts,
2590
+ ssr: false
2591
+ }
2552
2592
  });
2553
2593
  }
2554
2594
 
@@ -2562,7 +2602,17 @@ async function respond(request, options, state = {}) {
2562
2602
  }
2563
2603
 
2564
2604
  const is_data_request = decoded.endsWith(DATA_SUFFIX);
2565
- if (is_data_request) decoded = decoded.slice(0, -DATA_SUFFIX.length) || '/';
2605
+
2606
+ if (is_data_request) {
2607
+ decoded = decoded.slice(0, -DATA_SUFFIX.length) || '/';
2608
+
2609
+ const normalized = normalize_path(
2610
+ url.pathname.slice(0, -DATA_SUFFIX.length),
2611
+ options.trailing_slash
2612
+ );
2613
+
2614
+ event.url = new URL(event.url.origin + normalized + event.url.search);
2615
+ }
2566
2616
 
2567
2617
  for (const route of options.manifest._.routes) {
2568
2618
  const match = route.pattern.exec(decoded);
@@ -2576,22 +2626,30 @@ async function respond(request, options, state = {}) {
2576
2626
  if (is_data_request && route.type === 'page' && route.shadow) {
2577
2627
  response = await render_endpoint(event, await route.shadow());
2578
2628
 
2579
- // since redirects are opaque to the browser, we need to repackage
2580
- // 3xx responses as 200s with a custom header
2581
- if (
2582
- response &&
2583
- response.status >= 300 &&
2584
- response.status < 400 &&
2585
- request.headers.get('x-sveltekit-noredirect') === 'true'
2586
- ) {
2587
- const location = response.headers.get('location');
2588
-
2589
- if (location) {
2590
- const headers = new Headers(response.headers);
2591
- headers.set('x-sveltekit-location', location);
2592
- response = new Response(undefined, {
2593
- status: 204,
2594
- headers
2629
+ // loading data for a client-side transition is a special case
2630
+ if (request.headers.get('x-sveltekit-load') === 'true') {
2631
+ if (response) {
2632
+ // since redirects are opaque to the browser, we need to repackage
2633
+ // 3xx responses as 200s with a custom header
2634
+ if (response.status >= 300 && response.status < 400) {
2635
+ const location = response.headers.get('location');
2636
+
2637
+ if (location) {
2638
+ const headers = new Headers(response.headers);
2639
+ headers.set('x-sveltekit-location', location);
2640
+ response = new Response(undefined, {
2641
+ status: 204,
2642
+ headers
2643
+ });
2644
+ }
2645
+ }
2646
+ } else {
2647
+ // TODO ideally, the client wouldn't request this data
2648
+ // in the first place (at least in production)
2649
+ response = new Response('{}', {
2650
+ headers: {
2651
+ 'content-type': 'application/json'
2652
+ }
2595
2653
  });
2596
2654
  }
2597
2655
  }
@@ -2599,7 +2657,7 @@ async function respond(request, options, state = {}) {
2599
2657
  response =
2600
2658
  route.type === 'endpoint'
2601
2659
  ? await render_endpoint(event, await route.load())
2602
- : await render_page(event, route, options, state, ssr);
2660
+ : await render_page(event, route, options, state, resolve_opts);
2603
2661
  }
2604
2662
 
2605
2663
  if (response) {
@@ -2651,7 +2709,7 @@ async function respond(request, options, state = {}) {
2651
2709
  $session,
2652
2710
  status: 404,
2653
2711
  error: new Error(`Not found: ${event.url.pathname}`),
2654
- ssr
2712
+ resolve_opts
2655
2713
  });
2656
2714
  }
2657
2715
 
@@ -2687,7 +2745,7 @@ async function respond(request, options, state = {}) {
2687
2745
  $session,
2688
2746
  status: 500,
2689
2747
  error,
2690
- ssr
2748
+ resolve_opts
2691
2749
  });
2692
2750
  } catch (/** @type {unknown} */ e) {
2693
2751
  const error = coalesce_to_error(e);
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.269');
1001
+ const prog = sade('svelte-kit').version('1.0.0-next.272');
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.269'}\n`));
1159
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.272'}\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.269",
3
+ "version": "1.0.0-next.272",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
package/types/hooks.d.ts CHANGED
@@ -14,14 +14,17 @@ export interface GetSession {
14
14
  (event: RequestEvent): MaybePromise<App.Session>;
15
15
  }
16
16
 
17
- export interface ResolveOpts {
18
- ssr?: boolean;
17
+ export interface RequiredResolveOptions {
18
+ ssr: boolean;
19
+ transformPage: ({ html }: { html: string }) => string;
19
20
  }
20
21
 
22
+ export type ResolveOptions = Partial<RequiredResolveOptions>;
23
+
21
24
  export interface Handle {
22
25
  (input: {
23
26
  event: RequestEvent;
24
- resolve(event: RequestEvent, opts?: ResolveOpts): MaybePromise<Response>;
27
+ resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
25
28
  }): MaybePromise<Response>;
26
29
  }
27
30
 
package/types/index.d.ts CHANGED
@@ -14,4 +14,11 @@ export {
14
14
  } from './config';
15
15
  export { EndpointOutput, RequestHandler } from './endpoint';
16
16
  export { ErrorLoad, ErrorLoadInput, Load, LoadInput, LoadOutput } from './page';
17
- export { ExternalFetch, GetSession, Handle, HandleError, RequestEvent, ResolveOpts } from './hooks';
17
+ export {
18
+ ExternalFetch,
19
+ GetSession,
20
+ Handle,
21
+ HandleError,
22
+ RequestEvent,
23
+ ResolveOptions
24
+ } from './hooks';