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

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,10 @@ 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
+ // set this flag to distinguish between navigations triggered by
216
+ // clicking a hash link and those triggered by popstate
217
+ this.hash_navigating = true;
218
+
216
219
  const info = this.parse(url);
217
220
  if (info) {
218
221
  return this.renderer.update(info, [], false);
@@ -256,6 +259,19 @@ class Router {
256
259
  });
257
260
  }
258
261
  });
262
+
263
+ addEventListener('hashchange', () => {
264
+ // if the hashchange happened as a result of clicking on a link,
265
+ // we need to update history, otherwise we have to leave it alone
266
+ if (this.hash_navigating) {
267
+ this.hash_navigating = false;
268
+ history.replaceState(
269
+ { ...history.state, 'sveltekit:index': ++this.current_history_index },
270
+ '',
271
+ location.href
272
+ );
273
+ }
274
+ });
259
275
  }
260
276
 
261
277
  /**
@@ -720,11 +736,12 @@ class Renderer {
720
736
  * status: number;
721
737
  * error: Error;
722
738
  * nodes: Array<Promise<CSRComponent>>;
723
- * url: URL;
724
739
  * params: Record<string, string>;
725
740
  * }} selected
726
741
  */
727
- async start({ status, error, nodes, url, params }) {
742
+ async start({ status, error, nodes, params }) {
743
+ const url = new URL(location.href);
744
+
728
745
  /** @type {Array<import('./types').BranchNode | undefined>} */
729
746
  const branch = [];
730
747
 
@@ -736,9 +753,6 @@ class Renderer {
736
753
 
737
754
  let error_args;
738
755
 
739
- // url.hash is empty when coming from the server
740
- url.hash = window.location.hash;
741
-
742
756
  try {
743
757
  for (let i = 0; i < nodes.length; i += 1) {
744
758
  const is_leaf = i === nodes.length - 1;
@@ -1279,7 +1293,7 @@ class Renderer {
1279
1293
 
1280
1294
  if (has_shadow && i === a.length - 1) {
1281
1295
  const res = await fetch(
1282
- `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json`,
1296
+ `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`,
1283
1297
  {
1284
1298
  headers: {
1285
1299
  'x-sveltekit-load': 'true'
@@ -1467,7 +1481,6 @@ class Renderer {
1467
1481
  * status: number;
1468
1482
  * error: Error;
1469
1483
  * nodes: Array<Promise<import('types/internal').CSRComponent>>;
1470
- * url: URL;
1471
1484
  * params: Record<string, string>;
1472
1485
  * };
1473
1486
  * }} opts
@@ -80,7 +80,7 @@ function is_pojo(body) {
80
80
 
81
81
  // body could be a node Readable, but we don't want to import
82
82
  // node built-ins, so we use duck typing
83
- if (body._readableState && body._writableState && body._events) return false;
83
+ if (body._readableState && typeof body.pipe === 'function') return false;
84
84
 
85
85
  // similarly, it could be a web ReadableStream
86
86
  if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) return false;
@@ -1057,7 +1057,7 @@ const updated = {
1057
1057
  * error?: Error;
1058
1058
  * url: URL;
1059
1059
  * params: Record<string, string>;
1060
- * ssr: boolean;
1060
+ * resolve_opts: import('types/hooks').RequiredResolveOptions;
1061
1061
  * stuff: Record<string, any>;
1062
1062
  * }} opts
1063
1063
  */
@@ -1071,7 +1071,7 @@ async function render_response({
1071
1071
  error,
1072
1072
  url,
1073
1073
  params,
1074
- ssr,
1074
+ resolve_opts,
1075
1075
  stuff
1076
1076
  }) {
1077
1077
  if (state.prerender) {
@@ -1103,7 +1103,7 @@ async function render_response({
1103
1103
  error.stack = options.get_stack(error);
1104
1104
  }
1105
1105
 
1106
- if (ssr) {
1106
+ if (resolve_opts.ssr) {
1107
1107
  branch.forEach(({ node, props, loaded, fetched, uses_credentials }) => {
1108
1108
  if (node.css) node.css.forEach((url) => stylesheets.add(url));
1109
1109
  if (node.js) node.js.forEach((url) => modulepreloads.add(url));
@@ -1199,9 +1199,9 @@ async function render_response({
1199
1199
  throw new Error(`Failed to serialize session data: ${error.message}`);
1200
1200
  })},
1201
1201
  route: ${!!page_config.router},
1202
- spa: ${!ssr},
1202
+ spa: ${!resolve_opts.ssr},
1203
1203
  trailing_slash: ${s(options.trailing_slash)},
1204
- hydrate: ${ssr && page_config.hydrate ? `{
1204
+ hydrate: ${resolve_opts.ssr && page_config.hydrate ? `{
1205
1205
  status: ${status},
1206
1206
  error: ${serialize_error(error)},
1207
1207
  nodes: [
@@ -1209,7 +1209,6 @@ async function render_response({
1209
1209
  .map(({ node }) => `import(${s(options.prefix + node.entry)})`)
1210
1210
  .join(',\n\t\t\t\t\t\t')}
1211
1211
  ],
1212
- url: new URL(${s(url.href)}),
1213
1212
  params: ${devalue(params)}
1214
1213
  }` : 'null'}
1215
1214
  });
@@ -1327,7 +1326,9 @@ async function render_response({
1327
1326
  const assets =
1328
1327
  options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
1329
1328
 
1330
- const html = options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) });
1329
+ const html = resolve_opts.transformPage({
1330
+ html: options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) })
1331
+ });
1331
1332
 
1332
1333
  const headers = new Headers({
1333
1334
  'content-type': 'text/html',
@@ -1879,7 +1880,7 @@ async function load_shadow_data(route, event, options, prerender) {
1879
1880
  const mod = await route.shadow();
1880
1881
 
1881
1882
  if (prerender && (mod.post || mod.put || mod.del || mod.patch)) {
1882
- throw new Error('Cannot prerender pages that have shadow endpoints with mutative methods');
1883
+ throw new Error('Cannot prerender pages that have endpoints with mutative methods');
1883
1884
  }
1884
1885
 
1885
1886
  const method = normalize_request_method(event);
@@ -1986,7 +1987,7 @@ function validate_shadow_output(result) {
1986
1987
  if (headers instanceof Headers) {
1987
1988
  if (headers.has('set-cookie')) {
1988
1989
  throw new Error(
1989
- 'Shadow endpoint request handler cannot use Headers interface with Set-Cookie headers'
1990
+ 'Endpoint request handler cannot use Headers interface with Set-Cookie headers'
1990
1991
  );
1991
1992
  }
1992
1993
  } else {
@@ -1994,7 +1995,7 @@ function validate_shadow_output(result) {
1994
1995
  }
1995
1996
 
1996
1997
  if (!is_pojo(body)) {
1997
- throw new Error('Body returned from shadow endpoint request handler must be a plain object');
1998
+ throw new Error('Body returned from endpoint request handler must be a plain object');
1998
1999
  }
1999
2000
 
2000
2001
  return { status, headers, body };
@@ -2014,10 +2015,18 @@ function validate_shadow_output(result) {
2014
2015
  * $session: any;
2015
2016
  * status: number;
2016
2017
  * error: Error;
2017
- * ssr: boolean;
2018
+ * resolve_opts: import('types/hooks').RequiredResolveOptions;
2018
2019
  * }} opts
2019
2020
  */
2020
- async function respond_with_error({ event, options, state, $session, status, error, ssr }) {
2021
+ async function respond_with_error({
2022
+ event,
2023
+ options,
2024
+ state,
2025
+ $session,
2026
+ status,
2027
+ error,
2028
+ resolve_opts
2029
+ }) {
2021
2030
  try {
2022
2031
  const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
2023
2032
  const default_error = await options.manifest._.nodes[1](); // 1 is always the root error
@@ -2073,7 +2082,7 @@ async function respond_with_error({ event, options, state, $session, status, err
2073
2082
  branch: [layout_loaded, error_loaded],
2074
2083
  url: event.url,
2075
2084
  params,
2076
- ssr
2085
+ resolve_opts
2077
2086
  });
2078
2087
  } catch (err) {
2079
2088
  const error = coalesce_to_error(err);
@@ -2099,19 +2108,19 @@ async function respond_with_error({ event, options, state, $session, status, err
2099
2108
  * options: SSROptions;
2100
2109
  * state: SSRState;
2101
2110
  * $session: any;
2111
+ * resolve_opts: import('types/hooks').RequiredResolveOptions;
2102
2112
  * route: import('types/internal').SSRPage;
2103
2113
  * params: Record<string, string>;
2104
- * ssr: boolean;
2105
2114
  * }} opts
2106
2115
  * @returns {Promise<Response | undefined>}
2107
2116
  */
2108
2117
  async function respond$1(opts) {
2109
- const { event, options, state, $session, route, ssr } = opts;
2118
+ const { event, options, state, $session, route, resolve_opts } = opts;
2110
2119
 
2111
2120
  /** @type {Array<SSRNode | undefined>} */
2112
2121
  let nodes;
2113
2122
 
2114
- if (!ssr) {
2123
+ if (!resolve_opts.ssr) {
2115
2124
  return await render_response({
2116
2125
  ...opts,
2117
2126
  branch: [],
@@ -2141,7 +2150,7 @@ async function respond$1(opts) {
2141
2150
  $session,
2142
2151
  status: 500,
2143
2152
  error,
2144
- ssr
2153
+ resolve_opts
2145
2154
  });
2146
2155
  }
2147
2156
 
@@ -2172,7 +2181,7 @@ async function respond$1(opts) {
2172
2181
 
2173
2182
  let stuff = {};
2174
2183
 
2175
- ssr: if (ssr) {
2184
+ ssr: if (resolve_opts.ssr) {
2176
2185
  for (let i = 0; i < nodes.length; i += 1) {
2177
2186
  const node = nodes[i];
2178
2187
 
@@ -2277,7 +2286,7 @@ async function respond$1(opts) {
2277
2286
  $session,
2278
2287
  status,
2279
2288
  error,
2280
- ssr
2289
+ resolve_opts
2281
2290
  }),
2282
2291
  set_cookie_headers
2283
2292
  );
@@ -2358,10 +2367,10 @@ function with_cookies(response, set_cookie_headers) {
2358
2367
  * @param {import('types/internal').SSRPage} route
2359
2368
  * @param {import('types/internal').SSROptions} options
2360
2369
  * @param {import('types/internal').SSRState} state
2361
- * @param {boolean} ssr
2370
+ * @param {import('types/hooks').RequiredResolveOptions} resolve_opts
2362
2371
  * @returns {Promise<Response | undefined>}
2363
2372
  */
2364
- async function render_page(event, route, options, state, ssr) {
2373
+ async function render_page(event, route, options, state, resolve_opts) {
2365
2374
  if (state.initiator === route) {
2366
2375
  // infinite request cycle detected
2367
2376
  return new Response(`Not found: ${event.url.pathname}`, {
@@ -2387,9 +2396,9 @@ async function render_page(event, route, options, state, ssr) {
2387
2396
  options,
2388
2397
  state,
2389
2398
  $session,
2399
+ resolve_opts,
2390
2400
  route,
2391
- params: event.params, // TODO this is redundant
2392
- ssr
2401
+ params: event.params // TODO this is redundant
2393
2402
  });
2394
2403
 
2395
2404
  if (response) {
@@ -2461,6 +2470,9 @@ function negotiate(accept, types) {
2461
2470
 
2462
2471
  const DATA_SUFFIX = '/__data.json';
2463
2472
 
2473
+ /** @param {{ html: string }} opts */
2474
+ const default_transform = ({ html }) => html;
2475
+
2464
2476
  /** @type {import('types/internal').Respond} */
2465
2477
  async function respond(request, options, state = {}) {
2466
2478
  const url = new URL(request.url);
@@ -2544,13 +2556,22 @@ async function respond(request, options, state = {}) {
2544
2556
  rawBody: body_getter
2545
2557
  });
2546
2558
 
2547
- let ssr = true;
2559
+ /** @type {import('types/hooks').RequiredResolveOptions} */
2560
+ let resolve_opts = {
2561
+ ssr: true,
2562
+ transformPage: default_transform
2563
+ };
2548
2564
 
2549
2565
  try {
2550
2566
  const response = await options.hooks.handle({
2551
2567
  event,
2552
2568
  resolve: async (event, opts) => {
2553
- if (opts && 'ssr' in opts) ssr = /** @type {boolean} */ (opts.ssr);
2569
+ if (opts) {
2570
+ resolve_opts = {
2571
+ ssr: opts.ssr !== false,
2572
+ transformPage: opts.transformPage || default_transform
2573
+ };
2574
+ }
2554
2575
 
2555
2576
  if (state.prerender && state.prerender.fallback) {
2556
2577
  return await render_response({
@@ -2563,7 +2584,10 @@ async function respond(request, options, state = {}) {
2563
2584
  stuff: {},
2564
2585
  status: 200,
2565
2586
  branch: [],
2566
- ssr: false
2587
+ resolve_opts: {
2588
+ ...resolve_opts,
2589
+ ssr: false
2590
+ }
2567
2591
  });
2568
2592
  }
2569
2593
 
@@ -2577,7 +2601,17 @@ async function respond(request, options, state = {}) {
2577
2601
  }
2578
2602
 
2579
2603
  const is_data_request = decoded.endsWith(DATA_SUFFIX);
2580
- if (is_data_request) decoded = decoded.slice(0, -DATA_SUFFIX.length) || '/';
2604
+
2605
+ if (is_data_request) {
2606
+ decoded = decoded.slice(0, -DATA_SUFFIX.length) || '/';
2607
+
2608
+ const normalized = normalize_path(
2609
+ url.pathname.slice(0, -DATA_SUFFIX.length),
2610
+ options.trailing_slash
2611
+ );
2612
+
2613
+ event.url = new URL(event.url.origin + normalized + event.url.search);
2614
+ }
2581
2615
 
2582
2616
  for (const route of options.manifest._.routes) {
2583
2617
  const match = route.pattern.exec(decoded);
@@ -2622,7 +2656,7 @@ async function respond(request, options, state = {}) {
2622
2656
  response =
2623
2657
  route.type === 'endpoint'
2624
2658
  ? await render_endpoint(event, await route.load())
2625
- : await render_page(event, route, options, state, ssr);
2659
+ : await render_page(event, route, options, state, resolve_opts);
2626
2660
  }
2627
2661
 
2628
2662
  if (response) {
@@ -2674,7 +2708,7 @@ async function respond(request, options, state = {}) {
2674
2708
  $session,
2675
2709
  status: 404,
2676
2710
  error: new Error(`Not found: ${event.url.pathname}`),
2677
- ssr
2711
+ resolve_opts
2678
2712
  });
2679
2713
  }
2680
2714
 
@@ -2710,7 +2744,7 @@ async function respond(request, options, state = {}) {
2710
2744
  $session,
2711
2745
  status: 500,
2712
2746
  error,
2713
- ssr
2747
+ resolve_opts
2714
2748
  });
2715
2749
  } catch (/** @type {unknown} */ e) {
2716
2750
  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.270');
1001
+ const prog = sade('svelte-kit').version('1.0.0-next.274');
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.270'}\n`));
1159
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.274'}\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.270",
3
+ "version": "1.0.0-next.274",
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';