@sveltejs/kit 1.0.0-next.463 → 1.0.0-next.466

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.463",
3
+ "version": "1.0.0-next.466",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -38,7 +38,7 @@
38
38
  "svelte-preprocess": "^4.10.6",
39
39
  "typescript": "^4.8.2",
40
40
  "uvu": "^0.5.3",
41
- "vite": "^3.1.0-beta.1"
41
+ "vite": "^3.1.0-beta.2"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "svelte": "^3.44.0",
@@ -60,13 +60,13 @@ export function generate_manifest({ build_data, relative_path, routes, format =
60
60
  if (!route.page && !route.endpoint) return;
61
61
 
62
62
  return `{
63
- id: ${s(route.id)},
64
- pattern: ${route.pattern},
65
- names: ${s(route.names)},
66
- types: ${s(route.types)},
67
- page: ${s(route.page)},
68
- endpoint: ${route.endpoint ? loader(`${relative_path}/${build_data.server.vite_manifest[route.endpoint.file].file}`) : 'null'}
69
- }`;
63
+ id: ${s(route.id)},
64
+ pattern: ${route.pattern},
65
+ names: ${s(route.names)},
66
+ types: ${s(route.types)},
67
+ page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${route.page.leaf} }` : 'null'},
68
+ endpoint: ${route.endpoint ? loader(`${relative_path}/${build_data.server.vite_manifest[route.endpoint.file].file}`) : 'null'}
69
+ }`;
70
70
  }).filter(Boolean).join(',\n\t\t\t\t')}
71
71
  ],
72
72
  matchers: async () => {
@@ -76,3 +76,17 @@ export function generate_manifest({ build_data, relative_path, routes, format =
76
76
  }
77
77
  }`.replace(/^\t/gm, '');
78
78
  }
79
+
80
+ /** @param {Array<number | undefined>} indexes */
81
+ function get_nodes(indexes) {
82
+ let string = indexes.map((n) => n ?? '').join(',');
83
+
84
+ if (indexes.at(-1) === undefined) {
85
+ // since JavaScript ignores trailing commas, we need to insert a dummy
86
+ // comma so that the array has the correct length if the last item
87
+ // is undefined
88
+ string += ',';
89
+ }
90
+
91
+ return `[${string}]`;
92
+ }
@@ -80,10 +80,10 @@ export function create_client({ target, base, trailing_slash }) {
80
80
  };
81
81
 
82
82
  const callbacks = {
83
- /** @type {Array<(opts: { from: URL, to: URL | null, cancel: () => void }) => void>} */
83
+ /** @type {Array<(navigation: import('types').Navigation & { cancel: () => void }) => void>} */
84
84
  before_navigate: [],
85
85
 
86
- /** @type {Array<(opts: { from: URL | null, to: URL }) => void>} */
86
+ /** @type {Array<(navigation: import('types').Navigation) => void>} */
87
87
  after_navigate: []
88
88
  };
89
89
 
@@ -142,8 +142,11 @@ export function create_client({ target, base, trailing_slash }) {
142
142
 
143
143
  function invalidate() {
144
144
  if (!invalidating) {
145
+ const url = new URL(location.href);
146
+
145
147
  invalidating = Promise.resolve().then(async () => {
146
- await update(new URL(location.href), []);
148
+ const intent = get_navigation_intent(url);
149
+ await update(intent, url, []);
147
150
 
148
151
  invalidating = null;
149
152
  force_invalidation = false;
@@ -177,7 +180,8 @@ export function create_client({ target, base, trailing_slash }) {
177
180
  replaceState
178
181
  },
179
182
  accepted: () => {},
180
- blocked: () => {}
183
+ blocked: () => {},
184
+ type: 'goto'
181
185
  });
182
186
  }
183
187
 
@@ -197,14 +201,13 @@ export function create_client({ target, base, trailing_slash }) {
197
201
 
198
202
  /**
199
203
  * Returns `true` if update completes, `false` if it is aborted
204
+ * @param {import('./types').NavigationIntent | undefined} intent
200
205
  * @param {URL} url
201
206
  * @param {string[]} redirect_chain
202
207
  * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
203
208
  * @param {() => void} [callback]
204
209
  */
205
- async function update(url, redirect_chain, opts, callback) {
206
- const intent = get_navigation_intent(url);
207
-
210
+ async function update(intent, url, redirect_chain, opts, callback) {
208
211
  const current_token = (token = {});
209
212
  let navigation_result = intent && (await load_route(intent));
210
213
 
@@ -395,7 +398,16 @@ export function create_client({ target, base, trailing_slash }) {
395
398
  });
396
399
  }
397
400
 
398
- const navigation = { from: null, to: new URL(location.href) };
401
+ /** @type {import('types').Navigation} */
402
+ const navigation = {
403
+ from: null,
404
+ to: add_url_properties('to', {
405
+ params: current.params,
406
+ routeId: current.route?.id ?? null,
407
+ url: new URL(location.href)
408
+ }),
409
+ type: 'load'
410
+ };
399
411
  callbacks.after_navigate.forEach((fn) => fn(navigation));
400
412
 
401
413
  started = true;
@@ -409,7 +421,7 @@ export function create_client({ target, base, trailing_slash }) {
409
421
  * branch: Array<import('./types').BranchNode | undefined>;
410
422
  * status: number;
411
423
  * error: HttpError | Error | null;
412
- * routeId: string | null;
424
+ * route: import('types').CSRRoute | null;
413
425
  * validation_errors?: Record<string, any> | null;
414
426
  * }} opts
415
427
  */
@@ -419,7 +431,7 @@ export function create_client({ target, base, trailing_slash }) {
419
431
  branch,
420
432
  status,
421
433
  error,
422
- routeId,
434
+ route,
423
435
  validation_errors
424
436
  }) {
425
437
  const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
@@ -432,6 +444,7 @@ export function create_client({ target, base, trailing_slash }) {
432
444
  params,
433
445
  branch,
434
446
  error,
447
+ route,
435
448
  session_id
436
449
  },
437
450
  props: {
@@ -466,7 +479,7 @@ export function create_client({ target, base, trailing_slash }) {
466
479
  result.props.page = {
467
480
  error,
468
481
  params,
469
- routeId,
482
+ routeId: route && route.id,
470
483
  status,
471
484
  url,
472
485
  // The whole page store is updated, but this way the object reference stays the same
@@ -842,7 +855,7 @@ export function create_client({ target, base, trailing_slash }) {
842
855
  branch: branch.slice(0, j + 1).concat(error_loaded),
843
856
  status,
844
857
  error,
845
- routeId: route.id
858
+ route
846
859
  });
847
860
  } catch (e) {
848
861
  continue;
@@ -868,7 +881,7 @@ export function create_client({ target, base, trailing_slash }) {
868
881
  branch,
869
882
  status: 200,
870
883
  error: null,
871
- routeId: route.id
884
+ route
872
885
  });
873
886
  }
874
887
 
@@ -937,13 +950,13 @@ export function create_client({ target, base, trailing_slash }) {
937
950
  branch: [root_layout, root_error],
938
951
  status,
939
952
  error,
940
- routeId
953
+ route: null
941
954
  });
942
955
  }
943
956
 
944
957
  /** @param {URL} url */
945
958
  function get_navigation_intent(url) {
946
- if (url.origin !== location.origin || !url.pathname.startsWith(base)) return;
959
+ if (is_external_url(url)) return;
947
960
 
948
961
  const path = decodeURI(url.pathname.slice(base.length) || '/');
949
962
 
@@ -962,6 +975,11 @@ export function create_client({ target, base, trailing_slash }) {
962
975
  }
963
976
  }
964
977
 
978
+ /** @param {URL} url */
979
+ function is_external_url(url) {
980
+ return url.origin !== location.origin || !url.pathname.startsWith(base);
981
+ }
982
+
965
983
  /**
966
984
  * @param {{
967
985
  * url: URL;
@@ -972,21 +990,54 @@ export function create_client({ target, base, trailing_slash }) {
972
990
  * replaceState: boolean;
973
991
  * state: any;
974
992
  * } | null;
993
+ * type: import('types').NavigationType;
994
+ * delta?: number;
975
995
  * accepted: () => void;
976
996
  * blocked: () => void;
977
997
  * }} opts
978
998
  */
979
- async function navigate({ url, scroll, keepfocus, redirect_chain, details, accepted, blocked }) {
980
- const from = current.url;
999
+ async function navigate({
1000
+ url,
1001
+ scroll,
1002
+ keepfocus,
1003
+ redirect_chain,
1004
+ details,
1005
+ type,
1006
+ delta,
1007
+ accepted,
1008
+ blocked
1009
+ }) {
981
1010
  let should_block = false;
982
1011
 
1012
+ const intent = get_navigation_intent(url);
1013
+
1014
+ /** @type {import('types').Navigation} */
983
1015
  const navigation = {
984
- from,
985
- to: url,
986
- cancel: () => (should_block = true)
1016
+ from: add_url_properties('from', {
1017
+ params: current.params,
1018
+ routeId: current.route?.id ?? null,
1019
+ url: current.url
1020
+ }),
1021
+ to: add_url_properties('to', {
1022
+ params: intent?.params ?? null,
1023
+ routeId: intent?.route.id ?? null,
1024
+ url
1025
+ }),
1026
+ type
987
1027
  };
988
1028
 
989
- callbacks.before_navigate.forEach((fn) => fn(navigation));
1029
+ if (delta !== undefined) {
1030
+ navigation.delta = delta;
1031
+ }
1032
+
1033
+ const cancellable = {
1034
+ ...navigation,
1035
+ cancel: () => {
1036
+ should_block = true;
1037
+ }
1038
+ };
1039
+
1040
+ callbacks.before_navigate.forEach((fn) => fn(cancellable));
990
1041
 
991
1042
  if (should_block) {
992
1043
  blocked();
@@ -998,13 +1049,11 @@ export function create_client({ target, base, trailing_slash }) {
998
1049
  accepted();
999
1050
 
1000
1051
  if (started) {
1001
- stores.navigating.set({
1002
- from: current.url,
1003
- to: url
1004
- });
1052
+ stores.navigating.set(navigation);
1005
1053
  }
1006
1054
 
1007
1055
  await update(
1056
+ intent,
1008
1057
  url,
1009
1058
  redirect_chain,
1010
1059
  {
@@ -1013,9 +1062,7 @@ export function create_client({ target, base, trailing_slash }) {
1013
1062
  details
1014
1063
  },
1015
1064
  () => {
1016
- const navigation = { from, to: url };
1017
1065
  callbacks.after_navigate.forEach((fn) => fn(navigation));
1018
-
1019
1066
  stores.navigating.set(null);
1020
1067
  }
1021
1068
  );
@@ -1124,9 +1171,15 @@ export function create_client({ target, base, trailing_slash }) {
1124
1171
  addEventListener('beforeunload', (e) => {
1125
1172
  let should_block = false;
1126
1173
 
1174
+ /** @type {import('types').Navigation & { cancel: () => void }} */
1127
1175
  const navigation = {
1128
- from: current.url,
1176
+ from: add_url_properties('from', {
1177
+ params: current.params,
1178
+ routeId: current.route?.id ?? null,
1179
+ url: current.url
1180
+ }),
1129
1181
  to: null,
1182
+ type: 'unload',
1130
1183
  cancel: () => (should_block = true)
1131
1184
  };
1132
1185
 
@@ -1155,7 +1208,8 @@ export function create_client({ target, base, trailing_slash }) {
1155
1208
  /** @param {Event} event */
1156
1209
  const trigger_prefetch = (event) => {
1157
1210
  const { url, options } = find_anchor(event);
1158
- if (url && options.prefetch === '') {
1211
+ if (url && options.prefetch) {
1212
+ if (is_external_url(url)) return;
1159
1213
  prefetch(url);
1160
1214
  }
1161
1215
  };
@@ -1204,7 +1258,7 @@ export function create_client({ target, base, trailing_slash }) {
1204
1258
  // 2. 'rel' attribute includes external
1205
1259
  const rel = (a.getAttribute('rel') || '').split(/\s+/);
1206
1260
 
1207
- if (a.hasAttribute('download') || rel.includes('external') || options.reload === '') {
1261
+ if (a.hasAttribute('download') || rel.includes('external') || options.reload) {
1208
1262
  return;
1209
1263
  }
1210
1264
 
@@ -1230,7 +1284,7 @@ export function create_client({ target, base, trailing_slash }) {
1230
1284
 
1231
1285
  navigate({
1232
1286
  url,
1233
- scroll: options.noscroll === '' ? scroll_state() : null,
1287
+ scroll: options.noscroll ? scroll_state() : null,
1234
1288
  keepfocus: false,
1235
1289
  redirect_chain: [],
1236
1290
  details: {
@@ -1238,7 +1292,8 @@ export function create_client({ target, base, trailing_slash }) {
1238
1292
  replaceState: url.href === location.href
1239
1293
  },
1240
1294
  accepted: () => event.preventDefault(),
1241
- blocked: () => event.preventDefault()
1295
+ blocked: () => event.preventDefault(),
1296
+ type: 'link'
1242
1297
  });
1243
1298
  });
1244
1299
 
@@ -1248,6 +1303,8 @@ export function create_client({ target, base, trailing_slash }) {
1248
1303
  // with history.go, which means we end up back here, hence this check
1249
1304
  if (event.state[INDEX_KEY] === current_history_index) return;
1250
1305
 
1306
+ const delta = event.state[INDEX_KEY] - current_history_index;
1307
+
1251
1308
  navigate({
1252
1309
  url: new URL(location.href),
1253
1310
  scroll: scroll_positions[event.state[INDEX_KEY]],
@@ -1258,9 +1315,10 @@ export function create_client({ target, base, trailing_slash }) {
1258
1315
  current_history_index = event.state[INDEX_KEY];
1259
1316
  },
1260
1317
  blocked: () => {
1261
- const delta = current_history_index - event.state[INDEX_KEY];
1262
- history.go(delta);
1263
- }
1318
+ history.go(-delta);
1319
+ },
1320
+ type: 'popstate',
1321
+ delta
1264
1322
  });
1265
1323
  }
1266
1324
  });
@@ -1345,7 +1403,7 @@ export function create_client({ target, base, trailing_slash }) {
1345
1403
  )
1346
1404
  : original_error,
1347
1405
  validation_errors,
1348
- routeId
1406
+ route: routes.find((route) => route.id === routeId) ?? null
1349
1407
  });
1350
1408
  } catch (e) {
1351
1409
  const error = normalize_error(e);
@@ -1400,3 +1458,37 @@ async function load_data(url, invalid) {
1400
1458
 
1401
1459
  return server_data;
1402
1460
  }
1461
+
1462
+ // TODO remove for 1.0
1463
+ const properties = [
1464
+ 'hash',
1465
+ 'href',
1466
+ 'host',
1467
+ 'hostname',
1468
+ 'origin',
1469
+ 'pathname',
1470
+ 'port',
1471
+ 'protocol',
1472
+ 'search',
1473
+ 'searchParams',
1474
+ 'toString',
1475
+ 'toJSON'
1476
+ ];
1477
+
1478
+ /**
1479
+ * @param {'from' | 'to'} type
1480
+ * @param {import('types').NavigationTarget} target
1481
+ */
1482
+ function add_url_properties(type, target) {
1483
+ for (const prop of properties) {
1484
+ Object.defineProperty(target, prop, {
1485
+ get() {
1486
+ throw new Error(
1487
+ `The navigation shape changed - ${type}.${prop} should now be ${type}.url.${prop}`
1488
+ );
1489
+ }
1490
+ });
1491
+ }
1492
+
1493
+ return target;
1494
+ }
@@ -81,10 +81,11 @@ export interface DataNode {
81
81
  uses: Uses;
82
82
  }
83
83
 
84
- export type NavigationState = {
84
+ export interface NavigationState {
85
85
  branch: Array<BranchNode | undefined>;
86
86
  error: HttpError | Error | null;
87
87
  params: Record<string, string>;
88
+ route: CSRRoute | null;
88
89
  session_id: number;
89
90
  url: URL;
90
- };
91
+ }
@@ -27,16 +27,14 @@ export function find_anchor(event) {
27
27
  /** @type {HTMLAnchorElement | SVGAElement | undefined} */
28
28
  let a;
29
29
 
30
- const options = {
31
- /** @type {string | null} */
32
- noscroll: null,
30
+ /** @type {boolean | null} */
31
+ let noscroll = null;
33
32
 
34
- /** @type {string | null} */
35
- prefetch: null,
33
+ /** @type {boolean | null} */
34
+ let prefetch = null;
36
35
 
37
- /** @type {string | null} */
38
- reload: null
39
- };
36
+ /** @type {boolean | null} */
37
+ let reload = null;
40
38
 
41
39
  for (const element of event.composedPath()) {
42
40
  if (!(element instanceof Element)) continue;
@@ -46,22 +44,43 @@ export function find_anchor(event) {
46
44
  a = /** @type {HTMLAnchorElement | SVGAElement} */ (element);
47
45
  }
48
46
 
49
- if (options.noscroll === null) {
50
- options.noscroll = element.getAttribute('data-sveltekit-noscroll');
51
- }
47
+ if (noscroll === null) noscroll = get_link_option(element, 'data-sveltekit-noscroll');
48
+ if (prefetch === null) prefetch = get_link_option(element, 'data-sveltekit-prefetch');
49
+ if (reload === null) reload = get_link_option(element, 'data-sveltekit-reload');
50
+ }
52
51
 
53
- if (options.prefetch === null) {
54
- options.prefetch = element.getAttribute('data-sveltekit-prefetch');
55
- }
52
+ const url = a && new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
56
53
 
57
- if (options.reload === null) {
58
- options.reload = element.getAttribute('data-sveltekit-reload');
54
+ return {
55
+ a,
56
+ url,
57
+ options: {
58
+ noscroll,
59
+ prefetch,
60
+ reload
59
61
  }
60
- }
62
+ };
63
+ }
61
64
 
62
- const url = a && new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
65
+ const warned = new WeakSet();
66
+
67
+ /**
68
+ * @param {Element} element
69
+ * @param {string} attribute
70
+ */
71
+ function get_link_option(element, attribute) {
72
+ const value = element.getAttribute(attribute);
73
+ if (value === null) return value;
74
+
75
+ if (value === '') return true;
76
+ if (value === 'off') return false;
77
+
78
+ if (__SVELTEKIT_DEV__ && !warned.has(element)) {
79
+ console.error(`Unexpected value for ${attribute} — should be "" or "off"`, element);
80
+ warned.add(element);
81
+ }
63
82
 
64
- return { a, url, options };
83
+ return false;
65
84
  }
66
85
 
67
86
  /** @param {any} value */
@@ -95,6 +95,8 @@ declare module '$app/environment' {
95
95
  * ```
96
96
  */
97
97
  declare module '$app/navigation' {
98
+ import { Navigation } from '@sveltejs/kit';
99
+
98
100
  /**
99
101
  * If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
100
102
  * This is generally discouraged, since it breaks user expectations.
@@ -158,17 +160,23 @@ declare module '$app/navigation' {
158
160
  export function prefetchRoutes(routes?: string[]): Promise<void>;
159
161
 
160
162
  /**
161
- * A navigation interceptor that triggers before we navigate to a new URL (internal or external) whether by clicking a link, calling `goto`, or using the browser back/forward controls.
162
- * This is helpful if we want to conditionally prevent a navigation from completing or lookup the upcoming url.
163
+ * A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
164
+ * Calling `cancel()` will prevent the navigation from completing.
165
+ *
166
+ * When navigating to an external URL, `navigation.to` will be `null`.
167
+ *
168
+ * `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
163
169
  */
164
170
  export function beforeNavigate(
165
- fn: (navigation: { from: URL; to: URL | null; cancel: () => void }) => void
171
+ callback: (navigation: Navigation & { cancel: () => void }) => void
166
172
  ): void;
167
173
 
168
174
  /**
169
- * A lifecycle function that runs when the page mounts, and also whenever SvelteKit navigates to a new URL but stays on this component.
175
+ * A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
176
+ *
177
+ * `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
170
178
  */
171
- export function afterNavigate(fn: (navigation: { from: URL | null; to: URL }) => void): void;
179
+ export function afterNavigate(callback: (navigation: Navigation) => void): void;
172
180
  }
173
181
 
174
182
  /**
@@ -210,7 +218,7 @@ declare module '$app/stores' {
210
218
  export const page: Readable<Page>;
211
219
  /**
212
220
  * A readable store.
213
- * When navigating starts, its value is `{ from: URL, to: URL }`,
221
+ * When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
214
222
  * When navigating finishes, its value reverts to `null`.
215
223
  */
216
224
  export const navigating: Readable<Navigation | null>;
package/types/index.d.ts CHANGED
@@ -219,9 +219,19 @@ export interface LoadEvent<
219
219
  depends: (...deps: string[]) => void;
220
220
  }
221
221
 
222
+ export interface NavigationTarget {
223
+ params: Record<string, string> | null;
224
+ routeId: string | null;
225
+ url: URL;
226
+ }
227
+
228
+ export type NavigationType = 'load' | 'unload' | 'link' | 'goto' | 'popstate';
229
+
222
230
  export interface Navigation {
223
- from: URL;
224
- to: URL;
231
+ from: NavigationTarget | null;
232
+ to: NavigationTarget | null;
233
+ type: NavigationType;
234
+ delta?: number;
225
235
  }
226
236
 
227
237
  export interface Page<Params extends Record<string, string> = Record<string, string>> {