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

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.
@@ -45,8 +45,8 @@ async function invalidate_(resource) {
45
45
  /**
46
46
  * @type {import('$app/navigation').prefetch}
47
47
  */
48
- function prefetch_(href) {
49
- return router.prefetch(new URL(href, get_base_uri(document)));
48
+ async function prefetch_(href) {
49
+ await router.prefetch(new URL(href, get_base_uri(document)));
50
50
  }
51
51
 
52
52
  /**
@@ -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,
@@ -71,6 +86,7 @@ class Router {
71
86
  renderer.router = this;
72
87
 
73
88
  this.enabled = true;
89
+ this.initialized = false;
74
90
 
75
91
  // make it possible to reset focus
76
92
  document.body.setAttribute('tabindex', '-1');
@@ -83,6 +99,11 @@ class Router {
83
99
  history.replaceState({ ...history.state, 'sveltekit:index': 0 }, '', location.href);
84
100
  }
85
101
 
102
+ // if we reload the page, or Cmd-Shift-T back to it,
103
+ // recover scroll position
104
+ const scroll = scroll_positions[this.current_history_index];
105
+ if (scroll) scrollTo(scroll.x, scroll.y);
106
+
86
107
  this.hash_navigating = false;
87
108
 
88
109
  this.callbacks = {
@@ -95,9 +116,7 @@ class Router {
95
116
  }
96
117
 
97
118
  init_listeners() {
98
- if ('scrollRestoration' in history) {
99
- history.scrollRestoration = 'manual';
100
- }
119
+ history.scrollRestoration = 'manual';
101
120
 
102
121
  // Adopted from Nuxt.js
103
122
  // Reset scrollRestoration to auto when leaving page, allowing page reload
@@ -122,28 +141,16 @@ class Router {
122
141
  }
123
142
  });
124
143
 
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
144
+ addEventListener('visibilitychange', () => {
145
+ if (document.visibilityState === 'hidden') {
146
+ this.#update_scroll_positions();
132
147
 
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);
148
+ try {
149
+ sessionStorage[SCROLL_KEY] = JSON.stringify(scroll_positions);
150
+ } catch {
151
+ // do nothing
152
+ }
153
+ }
147
154
  });
148
155
 
149
156
  /** @param {Event} event */
@@ -216,10 +223,9 @@ class Router {
216
223
  // clicking a hash link and those triggered by popstate
217
224
  this.hash_navigating = true;
218
225
 
219
- const info = this.parse(url);
220
- if (info) {
221
- return this.renderer.update(info, [], false);
222
- }
226
+ this.#update_scroll_positions();
227
+ this.renderer.update_page_store(new URL(url.href));
228
+
223
229
  return;
224
230
  }
225
231
 
@@ -245,7 +251,7 @@ class Router {
245
251
 
246
252
  this._navigate({
247
253
  url: new URL(location.href),
248
- scroll: event.state['sveltekit:scroll'],
254
+ scroll: scroll_positions[event.state['sveltekit:index']],
249
255
  keepfocus: false,
250
256
  chain: [],
251
257
  details: null,
@@ -272,6 +278,12 @@ class Router {
272
278
  );
273
279
  }
274
280
  });
281
+
282
+ this.initialized = true;
283
+ }
284
+
285
+ #update_scroll_positions() {
286
+ scroll_positions[this.current_history_index] = scroll_state();
275
287
  }
276
288
 
277
289
  /**
@@ -294,7 +306,8 @@ class Router {
294
306
  id: url.pathname + url.search,
295
307
  routes: this.routes.filter(([pattern]) => pattern.test(path)),
296
308
  url,
297
- path
309
+ path,
310
+ initial: !this.initialized
298
311
  };
299
312
  }
300
313
  }
@@ -344,7 +357,7 @@ class Router {
344
357
 
345
358
  /**
346
359
  * @param {URL} url
347
- * @returns {Promise<import('./types').NavigationResult>}
360
+ * @returns {Promise<import('./types').NavigationResult | undefined>}
348
361
  */
349
362
  async prefetch(url) {
350
363
  const info = this.parse(url);
@@ -421,6 +434,8 @@ class Router {
421
434
  });
422
435
  }
423
436
 
437
+ this.#update_scroll_positions();
438
+
424
439
  accepted();
425
440
 
426
441
  if (!this.navigating) {
@@ -690,7 +705,7 @@ class Renderer {
690
705
  /** @type {Map<string, import('./types').NavigationResult>} */
691
706
  this.cache = new Map();
692
707
 
693
- /** @type {{id: string | null, promise: Promise<import('./types').NavigationResult> | null}} */
708
+ /** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
694
709
  this.loading = {
695
710
  id: null,
696
711
  promise: null
@@ -778,6 +793,7 @@ class Renderer {
778
793
 
779
794
  if (props) {
780
795
  node.uses.dependencies.add(url.href);
796
+ node.uses.url = true;
781
797
  }
782
798
 
783
799
  branch.push(node);
@@ -856,6 +872,11 @@ class Renderer {
856
872
  const token = (this.token = {});
857
873
  let navigation_result = await this._get_navigation_result(info, no_cache);
858
874
 
875
+ if (!navigation_result) {
876
+ location.href = info.url.href;
877
+ return;
878
+ }
879
+
859
880
  // abort if user navigated during update
860
881
  if (token !== this.token) return;
861
882
 
@@ -935,6 +956,10 @@ class Renderer {
935
956
  this.autoscroll = true;
936
957
  this.updating = false;
937
958
 
959
+ if (navigation_result.props.page) {
960
+ this.page = navigation_result.props.page;
961
+ }
962
+
938
963
  if (!this.router) return;
939
964
 
940
965
  const leaf_node = navigation_result.state.branch[navigation_result.state.branch.length - 1];
@@ -947,7 +972,7 @@ class Renderer {
947
972
 
948
973
  /**
949
974
  * @param {import('./types').NavigationInfo} info
950
- * @returns {Promise<import('./types').NavigationResult>}
975
+ * @returns {Promise<import('./types').NavigationResult | undefined>}
951
976
  */
952
977
  load(info) {
953
978
  this.loading.promise = this._get_navigation_result(info, false);
@@ -972,6 +997,12 @@ class Renderer {
972
997
  return this.invalidating;
973
998
  }
974
999
 
1000
+ /** @param {URL} url */
1001
+ update_page_store(url) {
1002
+ this.stores.page.set({ ...this.page, url });
1003
+ this.stores.page.notify();
1004
+ }
1005
+
975
1006
  /** @param {import('./types').NavigationResult} result */
976
1007
  _init(result) {
977
1008
  this.current = result.state;
@@ -979,6 +1010,8 @@ class Renderer {
979
1010
  const style = document.querySelector('style[data-svelte]');
980
1011
  if (style) style.remove();
981
1012
 
1013
+ this.page = result.props.page;
1014
+
982
1015
  this.root = new this.Root({
983
1016
  target: this.target,
984
1017
  props: {
@@ -999,7 +1032,7 @@ class Renderer {
999
1032
  /**
1000
1033
  * @param {import('./types').NavigationInfo} info
1001
1034
  * @param {boolean} no_cache
1002
- * @returns {Promise<import('./types').NavigationResult>}
1035
+ * @returns {Promise<import('./types').NavigationResult | undefined>}
1003
1036
  */
1004
1037
  async _get_navigation_result(info, no_cache) {
1005
1038
  if (this.loading.id === info.id && this.loading.promise) {
@@ -1032,11 +1065,13 @@ class Renderer {
1032
1065
  if (result) return result;
1033
1066
  }
1034
1067
 
1035
- return await this._load_error({
1036
- status: 404,
1037
- error: new Error(`Not found: ${info.url.pathname}`),
1038
- url: info.url
1039
- });
1068
+ if (info.initial) {
1069
+ return await this._load_error({
1070
+ status: 404,
1071
+ error: new Error(`Not found: ${info.url.pathname}`),
1072
+ url: info.url
1073
+ });
1074
+ }
1040
1075
  }
1041
1076
 
1042
1077
  /**
@@ -1291,7 +1326,9 @@ class Renderer {
1291
1326
  /** @type {Record<string, any>} */
1292
1327
  let props = {};
1293
1328
 
1294
- if (has_shadow && i === a.length - 1) {
1329
+ const is_shadow_page = has_shadow && i === a.length - 1;
1330
+
1331
+ if (is_shadow_page) {
1295
1332
  const res = await fetch(
1296
1333
  `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`,
1297
1334
  {
@@ -1329,25 +1366,31 @@ class Renderer {
1329
1366
  });
1330
1367
  }
1331
1368
 
1332
- if (node && node.loaded) {
1333
- if (node.loaded.fallthrough) {
1334
- return;
1335
- }
1336
- if (node.loaded.error) {
1337
- status = node.loaded.status;
1338
- error = node.loaded.error;
1369
+ if (node) {
1370
+ if (is_shadow_page) {
1371
+ node.uses.url = true;
1339
1372
  }
1340
1373
 
1341
- if (node.loaded.redirect) {
1342
- return {
1343
- redirect: node.loaded.redirect,
1344
- props: {},
1345
- state: this.current
1346
- };
1347
- }
1374
+ if (node.loaded) {
1375
+ if (node.loaded.fallthrough) {
1376
+ return;
1377
+ }
1378
+ if (node.loaded.error) {
1379
+ status = node.loaded.status;
1380
+ error = node.loaded.error;
1381
+ }
1348
1382
 
1349
- if (node.loaded.stuff) {
1350
- stuff_changed = true;
1383
+ if (node.loaded.redirect) {
1384
+ return {
1385
+ redirect: node.loaded.redirect,
1386
+ props: {},
1387
+ state: this.current
1388
+ };
1389
+ }
1390
+
1391
+ if (node.loaded.stuff) {
1392
+ stuff_changed = true;
1393
+ }
1351
1394
  }
1352
1395
  }
1353
1396
  } else {
@@ -1221,12 +1221,15 @@ async function render_response({
1221
1221
  `;
1222
1222
 
1223
1223
  if (options.amp) {
1224
+ // inline_style contains CSS files (i.e. `import './styles.css'`)
1225
+ // rendered.css contains the CSS from `<style>` tags in Svelte components
1226
+ const styles = `${inlined_style}\n${rendered.css.code}`;
1224
1227
  head += `
1225
1228
  <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style>
1226
1229
  <noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
1227
1230
  <script async src="https://cdn.ampproject.org/v0.js"></script>
1228
1231
 
1229
- <style amp-custom>${inlined_style}\n${rendered.css.code}</style>`;
1232
+ <style amp-custom>${styles}</style>`;
1230
1233
 
1231
1234
  if (options.service_worker) {
1232
1235
  head +=
@@ -1258,6 +1261,8 @@ async function render_response({
1258
1261
  }
1259
1262
 
1260
1263
  if (styles.has(dep)) {
1264
+ // don't load stylesheets that are already inlined
1265
+ // include them in disabled state so that Vite can detect them and doesn't try to add them
1261
1266
  attributes.push('disabled', 'media="(max-width: 0)"');
1262
1267
  }
1263
1268
 
@@ -108,6 +108,7 @@ async function create_plugin(config, cwd) {
108
108
  entry: url.endsWith('.svelte') ? url : url + '?import',
109
109
  css: [],
110
110
  js: [],
111
+ // in dev we inline all styles to avoid FOUC
111
112
  styles
112
113
  };
113
114
  };
@@ -422,6 +423,11 @@ async function dev({ cwd, port, host, https, config }) {
422
423
  plugins: [
423
424
  svelte({
424
425
  extensions: config.extensions,
426
+ // In AMP mode, we know that there are no conditional component imports. In that case, we
427
+ // don't need to include CSS for components that are imported but unused, so we can just
428
+ // include rendered CSS.
429
+ // This would also apply if hydrate and router are both false, but we don't know if one
430
+ // has been enabled at the page level, so we don't do anything there.
425
431
  emitCss: !config.kit.amp,
426
432
  compilerOptions: {
427
433
  hydratable: !!config.kit.browser.hydrate
@@ -211,6 +211,11 @@ async function build_client({
211
211
  plugins: [
212
212
  svelte({
213
213
  extensions: config.extensions,
214
+ // In AMP mode, we know that there are no conditional component imports. In that case, we
215
+ // don't need to include CSS for components that are imported but unused, so we can just
216
+ // include rendered CSS.
217
+ // This would also apply if hydrate and router are both false, but we don't know if one
218
+ // has been enabled at the page level, so we don't do anything there.
214
219
  emitCss: !config.kit.amp,
215
220
  compilerOptions: {
216
221
  hydratable: !!config.kit.browser.hydrate
@@ -13,6 +13,8 @@ function generate_manifest(
13
13
  routes = build_data.manifest_data.routes,
14
14
  format = 'esm'
15
15
  ) {
16
+ /** @typedef {{ index: number, path: string }} LookupEntry */
17
+ /** @type {Map<string, LookupEntry>} */
16
18
  const bundled_nodes = new Map();
17
19
 
18
20
  // 0 and 1 are special, they correspond to the root layout and root error nodes
@@ -52,6 +54,9 @@ function generate_manifest(
52
54
  assets.push(build_data.service_worker);
53
55
  }
54
56
 
57
+ /** @param {string} id */
58
+ const get_index = (id) => id && /** @type {LookupEntry} */ (bundled_nodes.get(id)).index;
59
+
55
60
  // prettier-ignore
56
61
  return `{
57
62
  appDir: ${s(build_data.app_dir)},
@@ -71,8 +76,8 @@ function generate_manifest(
71
76
  params: ${get_params(route.params)},
72
77
  path: ${route.path ? s(route.path) : null},
73
78
  shadow: ${route.shadow ? importer(`${relative_path}/${build_data.server.vite_manifest[route.shadow].file}`) : null},
74
- a: ${s(route.a.map(component => component && bundled_nodes.get(component).index))},
75
- b: ${s(route.b.map(component => component && bundled_nodes.get(component).index))}
79
+ a: ${s(route.a.map(get_index))},
80
+ b: ${s(route.b.map(get_index))}
76
81
  }`.replace(/^\t\t/gm, '');
77
82
  } else {
78
83
  if (!build_data.server.vite_manifest[route.file]) {
@@ -160,8 +160,6 @@ const TAG_OPEN = /[a-zA-Z]/;
160
160
  const TAG_CHAR = /[a-zA-Z0-9]/;
161
161
  const ATTRIBUTE_NAME = /[^\t\n\f />"'=]/;
162
162
 
163
- const EXTERNAL = /\bexternal\b/;
164
-
165
163
  const WHITESPACE = /[\s\n\r]/;
166
164
 
167
165
  /** @param {string} html */
@@ -240,7 +238,6 @@ function crawl(html) {
240
238
  }
241
239
  }
242
240
 
243
- let rel = '';
244
241
  let href = '';
245
242
 
246
243
  while (i < html.length) {
@@ -301,9 +298,7 @@ function crawl(html) {
301
298
  i -= 1;
302
299
  }
303
300
 
304
- if (name === 'rel') {
305
- rel = value;
306
- } else if (name === 'href') {
301
+ if (name === 'href') {
307
302
  href = value;
308
303
  } else if (name === 'src') {
309
304
  hrefs.push(value);
@@ -335,7 +330,7 @@ function crawl(html) {
335
330
  i += 1;
336
331
  }
337
332
 
338
- if (href && !EXTERNAL.test(rel)) {
333
+ if (href) {
339
334
  hrefs.push(href);
340
335
  }
341
336
  }
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.274');
1001
+ const prog = sade('svelte-kit').version('1.0.0-next.278');
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.274'}\n`));
1159
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.278'}\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.274",
3
+ "version": "1.0.0-next.278",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",