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

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.
@@ -22,6 +22,21 @@ function normalize_path(path, trailing_slash) {
22
22
  return path;
23
23
  }
24
24
 
25
+ // We track the scroll position associated with each history entry in sessionStorage,
26
+ // rather than on history.state itself, because when navigation is driven by
27
+ // popstate it's too late to update the scroll position associated with the
28
+ // state we're navigating from
29
+ const SCROLL_KEY = 'sveltekit:scroll';
30
+
31
+ /** @typedef {{ x: number, y: number }} ScrollPosition */
32
+ /** @type {Record<number, ScrollPosition>} */
33
+ let scroll_positions = {};
34
+ try {
35
+ scroll_positions = JSON.parse(sessionStorage[SCROLL_KEY]);
36
+ } catch {
37
+ // do nothing
38
+ }
39
+
25
40
  function scroll_state() {
26
41
  return {
27
42
  x: pageXOffset,
@@ -83,6 +98,11 @@ class Router {
83
98
  history.replaceState({ ...history.state, 'sveltekit:index': 0 }, '', location.href);
84
99
  }
85
100
 
101
+ // if we reload the page, or Cmd-Shift-T back to it,
102
+ // recover scroll position
103
+ const scroll = scroll_positions[this.current_history_index];
104
+ if (scroll) scrollTo(scroll.x, scroll.y);
105
+
86
106
  this.hash_navigating = false;
87
107
 
88
108
  this.callbacks = {
@@ -95,9 +115,7 @@ class Router {
95
115
  }
96
116
 
97
117
  init_listeners() {
98
- if ('scrollRestoration' in history) {
99
- history.scrollRestoration = 'manual';
100
- }
118
+ history.scrollRestoration = 'manual';
101
119
 
102
120
  // Adopted from Nuxt.js
103
121
  // Reset scrollRestoration to auto when leaving page, allowing page reload
@@ -122,28 +140,16 @@ class Router {
122
140
  }
123
141
  });
124
142
 
125
- // Setting scrollRestoration to manual again when returning to this page.
126
- addEventListener('load', () => {
127
- history.scrollRestoration = 'manual';
128
- });
129
-
130
- // There's no API to capture the scroll location right before the user
131
- // hits the back/forward button, so we listen for scroll events
143
+ addEventListener('visibilitychange', () => {
144
+ if (document.visibilityState === 'hidden') {
145
+ this.#update_scroll_positions();
132
146
 
133
- /** @type {NodeJS.Timeout} */
134
- let scroll_timer;
135
- addEventListener('scroll', () => {
136
- clearTimeout(scroll_timer);
137
- scroll_timer = setTimeout(() => {
138
- // Store the scroll location in the history
139
- // This will persist even if we navigate away from the site and come back
140
- const new_state = {
141
- ...(history.state || {}),
142
- 'sveltekit:scroll': scroll_state()
143
- };
144
- history.replaceState(new_state, document.title, window.location.href);
145
- // iOS scroll event intervals happen between 30-150ms, sometimes around 200ms
146
- }, 200);
147
+ try {
148
+ sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
149
+ } catch {
150
+ // do nothing
151
+ }
152
+ }
147
153
  });
148
154
 
149
155
  /** @param {Event} event */
@@ -212,8 +218,12 @@ class Router {
212
218
  // Removing the hash does a full page navigation in the browser, so make sure a hash is present
213
219
  const [base, hash] = url.href.split('#');
214
220
  if (hash !== undefined && base === location.href.split('#')[0]) {
221
+ // set this flag to distinguish between navigations triggered by
222
+ // clicking a hash link and those triggered by popstate
215
223
  this.hash_navigating = true;
216
224
 
225
+ this.#update_scroll_positions();
226
+
217
227
  const info = this.parse(url);
218
228
  if (info) {
219
229
  return this.renderer.update(info, [], false);
@@ -243,7 +253,7 @@ class Router {
243
253
 
244
254
  this._navigate({
245
255
  url: new URL(location.href),
246
- scroll: event.state['sveltekit:scroll'],
256
+ scroll: scroll_positions[event.state['sveltekit:index']],
247
257
  keepfocus: false,
248
258
  chain: [],
249
259
  details: null,
@@ -259,6 +269,8 @@ class Router {
259
269
  });
260
270
 
261
271
  addEventListener('hashchange', () => {
272
+ // if the hashchange happened as a result of clicking on a link,
273
+ // we need to update history, otherwise we have to leave it alone
262
274
  if (this.hash_navigating) {
263
275
  this.hash_navigating = false;
264
276
  history.replaceState(
@@ -270,6 +282,10 @@ class Router {
270
282
  });
271
283
  }
272
284
 
285
+ #update_scroll_positions() {
286
+ scroll_positions[this.current_history_index] = scroll_state();
287
+ }
288
+
273
289
  /**
274
290
  * Returns true if `url` has the same origin and basepath as the app
275
291
  * @param {URL} url
@@ -417,6 +433,8 @@ class Router {
417
433
  });
418
434
  }
419
435
 
436
+ this.#update_scroll_positions();
437
+
420
438
  accepted();
421
439
 
422
440
  if (!this.navigating) {
@@ -732,11 +750,12 @@ class Renderer {
732
750
  * status: number;
733
751
  * error: Error;
734
752
  * nodes: Array<Promise<CSRComponent>>;
735
- * url: URL;
736
753
  * params: Record<string, string>;
737
754
  * }} selected
738
755
  */
739
- async start({ status, error, nodes, url, params }) {
756
+ async start({ status, error, nodes, params }) {
757
+ const url = new URL(location.href);
758
+
740
759
  /** @type {Array<import('./types').BranchNode | undefined>} */
741
760
  const branch = [];
742
761
 
@@ -748,9 +767,6 @@ class Renderer {
748
767
 
749
768
  let error_args;
750
769
 
751
- // url.hash is empty when coming from the server
752
- url.hash = window.location.hash;
753
-
754
770
  try {
755
771
  for (let i = 0; i < nodes.length; i += 1) {
756
772
  const is_leaf = i === nodes.length - 1;
@@ -776,6 +792,7 @@ class Renderer {
776
792
 
777
793
  if (props) {
778
794
  node.uses.dependencies.add(url.href);
795
+ node.uses.url = true;
779
796
  }
780
797
 
781
798
  branch.push(node);
@@ -1289,7 +1306,9 @@ class Renderer {
1289
1306
  /** @type {Record<string, any>} */
1290
1307
  let props = {};
1291
1308
 
1292
- if (has_shadow && i === a.length - 1) {
1309
+ const is_shadow_page = has_shadow && i === a.length - 1;
1310
+
1311
+ if (is_shadow_page) {
1293
1312
  const res = await fetch(
1294
1313
  `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`,
1295
1314
  {
@@ -1327,25 +1346,31 @@ class Renderer {
1327
1346
  });
1328
1347
  }
1329
1348
 
1330
- if (node && node.loaded) {
1331
- if (node.loaded.fallthrough) {
1332
- return;
1333
- }
1334
- if (node.loaded.error) {
1335
- status = node.loaded.status;
1336
- error = node.loaded.error;
1349
+ if (node) {
1350
+ if (is_shadow_page) {
1351
+ node.uses.url = true;
1337
1352
  }
1338
1353
 
1339
- if (node.loaded.redirect) {
1340
- return {
1341
- redirect: node.loaded.redirect,
1342
- props: {},
1343
- state: this.current
1344
- };
1345
- }
1354
+ if (node.loaded) {
1355
+ if (node.loaded.fallthrough) {
1356
+ return;
1357
+ }
1358
+ if (node.loaded.error) {
1359
+ status = node.loaded.status;
1360
+ error = node.loaded.error;
1361
+ }
1346
1362
 
1347
- if (node.loaded.stuff) {
1348
- stuff_changed = true;
1363
+ if (node.loaded.redirect) {
1364
+ return {
1365
+ redirect: node.loaded.redirect,
1366
+ props: {},
1367
+ state: this.current
1368
+ };
1369
+ }
1370
+
1371
+ if (node.loaded.stuff) {
1372
+ stuff_changed = true;
1373
+ }
1349
1374
  }
1350
1375
  }
1351
1376
  } else {
@@ -1479,7 +1504,6 @@ class Renderer {
1479
1504
  * status: number;
1480
1505
  * error: Error;
1481
1506
  * nodes: Array<Promise<import('types/internal').CSRComponent>>;
1482
- * url: URL;
1483
1507
  * params: Record<string, string>;
1484
1508
  * };
1485
1509
  * }} 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;
@@ -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
  });
@@ -1881,7 +1880,7 @@ async function load_shadow_data(route, event, options, prerender) {
1881
1880
  const mod = await route.shadow();
1882
1881
 
1883
1882
  if (prerender && (mod.post || mod.put || mod.del || mod.patch)) {
1884
- 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');
1885
1884
  }
1886
1885
 
1887
1886
  const method = normalize_request_method(event);
@@ -1988,7 +1987,7 @@ function validate_shadow_output(result) {
1988
1987
  if (headers instanceof Headers) {
1989
1988
  if (headers.has('set-cookie')) {
1990
1989
  throw new Error(
1991
- 'Shadow endpoint request handler cannot use Headers interface with Set-Cookie headers'
1990
+ 'Endpoint request handler cannot use Headers interface with Set-Cookie headers'
1992
1991
  );
1993
1992
  }
1994
1993
  } else {
@@ -1996,7 +1995,7 @@ function validate_shadow_output(result) {
1996
1995
  }
1997
1996
 
1998
1997
  if (!is_pojo(body)) {
1999
- 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');
2000
1999
  }
2001
2000
 
2002
2001
  return { status, headers, body };
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.272');
1001
+ const prog = sade('svelte-kit').version('1.0.0-next.276');
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.272'}\n`));
1159
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.276'}\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.272",
3
+ "version": "1.0.0-next.276",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",