@sveltejs/kit 1.0.0-next.224 → 1.0.0-next.229

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.
@@ -1,7 +1,7 @@
1
1
  import Root from '../../generated/root.svelte';
2
2
  import { fallback, routes } from '../../generated/manifest.js';
3
+ import { onMount, tick } from 'svelte';
3
4
  import { g as get_base_uri } from '../chunks/utils.js';
4
- import { tick } from 'svelte';
5
5
  import { writable } from 'svelte/store';
6
6
  import { init } from './singletons.js';
7
7
  import { set_paths } from '../paths.js';
@@ -59,8 +59,21 @@ class Router {
59
59
  // make it possible to reset focus
60
60
  document.body.setAttribute('tabindex', '-1');
61
61
 
62
- // create initial history entry, so we can return here
63
- history.replaceState(history.state || {}, '', location.href);
62
+ // keeping track of the history index in order to prevent popstate navigation events if needed
63
+ this.current_history_index = history.state?.['sveltekit:index'] ?? 0;
64
+
65
+ if (this.current_history_index === 0) {
66
+ // create initial history entry, so we can return here
67
+ history.replaceState({ ...history.state, 'sveltekit:index': 0 }, '', location.href);
68
+ }
69
+
70
+ this.callbacks = {
71
+ /** @type {Array<({ from, to, cancel }: { from: URL, to: URL | null, cancel: () => void }) => void>} */
72
+ before_navigate: [],
73
+
74
+ /** @type {Array<({ from, to }: { from: URL | null, to: URL }) => void>} */
75
+ after_navigate: []
76
+ };
64
77
  }
65
78
 
66
79
  init_listeners() {
@@ -72,8 +85,23 @@ class Router {
72
85
  // Reset scrollRestoration to auto when leaving page, allowing page reload
73
86
  // and back-navigation from other pages to use the browser to restore the
74
87
  // scrolling position.
75
- addEventListener('beforeunload', () => {
76
- history.scrollRestoration = 'auto';
88
+ addEventListener('beforeunload', (e) => {
89
+ let should_block = false;
90
+
91
+ const intent = {
92
+ from: this.renderer.current.url,
93
+ to: null,
94
+ cancel: () => (should_block = true)
95
+ };
96
+
97
+ this.callbacks.before_navigate.forEach((fn) => fn(intent));
98
+
99
+ if (should_block) {
100
+ e.preventDefault();
101
+ e.returnValue = '';
102
+ } else {
103
+ history.scrollRestoration = 'auto';
104
+ }
77
105
  });
78
106
 
79
107
  // Setting scrollRestoration to manual again when returning to this page.
@@ -128,7 +156,7 @@ class Router {
128
156
  addEventListener('sveltekit:trigger_prefetch', trigger_prefetch);
129
157
 
130
158
  /** @param {MouseEvent} event */
131
- addEventListener('click', (event) => {
159
+ addEventListener('click', async (event) => {
132
160
  if (!this.enabled) return;
133
161
 
134
162
  // Adapted from https://github.com/visionmedia/page.js
@@ -161,8 +189,6 @@ class Router {
161
189
  // Ignore if <a> has a target
162
190
  if (a instanceof SVGAElement ? a.target.baseVal : a.target) return;
163
191
 
164
- if (!this.owns(url)) return;
165
-
166
192
  // Check if new url only differs by hash
167
193
  if (url.href.split('#')[0] === location.href.split('#')[0]) {
168
194
  // Call `pushState` to add url to history so going back works.
@@ -175,22 +201,48 @@ class Router {
175
201
  return;
176
202
  }
177
203
 
178
- history.pushState({}, '', url.href);
179
-
180
- const noscroll = a.hasAttribute('sveltekit:noscroll');
181
- this._navigate(url, noscroll ? scroll_state() : null, false, [], url.hash);
182
- event.preventDefault();
204
+ this._navigate({
205
+ url,
206
+ scroll: a.hasAttribute('sveltekit:noscroll') ? scroll_state() : null,
207
+ keepfocus: false,
208
+ chain: [],
209
+ details: {
210
+ state: {},
211
+ replaceState: false
212
+ },
213
+ accepted: () => event.preventDefault(),
214
+ blocked: () => event.preventDefault()
215
+ });
183
216
  });
184
217
 
185
218
  addEventListener('popstate', (event) => {
186
219
  if (event.state && this.enabled) {
187
- const url = new URL(location.href);
188
- this._navigate(url, event.state['sveltekit:scroll'], false, []);
220
+ // if a popstate-driven navigation is cancelled, we need to counteract it
221
+ // with history.go, which means we end up back here, hence this check
222
+ if (event.state['sveltekit:index'] === this.current_history_index) return;
223
+
224
+ this._navigate({
225
+ url: new URL(location.href),
226
+ scroll: event.state['sveltekit:scroll'],
227
+ keepfocus: false,
228
+ chain: [],
229
+ details: null,
230
+ accepted: () => {
231
+ this.current_history_index = event.state['sveltekit:index'];
232
+ },
233
+ blocked: () => {
234
+ const delta = this.current_history_index - event.state['sveltekit:index'];
235
+ history.go(delta);
236
+ }
237
+ });
189
238
  }
190
239
  });
191
240
  }
192
241
 
193
- /** @param {URL} url */
242
+ /**
243
+ * Returns true if `url` has the same origin and basepath as the app
244
+ * @param {URL} url
245
+ */
194
246
  owns(url) {
195
247
  return url.origin === location.origin && url.pathname.startsWith(this.base);
196
248
  }
@@ -226,9 +278,19 @@ class Router {
226
278
  ) {
227
279
  const url = new URL(href, get_base_uri(document));
228
280
 
229
- if (this.enabled && this.owns(url)) {
230
- history[replaceState ? 'replaceState' : 'pushState'](state, '', href);
231
- return this._navigate(url, noscroll ? scroll_state() : null, keepfocus, chain, url.hash);
281
+ if (this.enabled) {
282
+ return this._navigate({
283
+ url,
284
+ scroll: noscroll ? scroll_state() : null,
285
+ keepfocus,
286
+ chain,
287
+ details: {
288
+ state,
289
+ replaceState
290
+ },
291
+ accepted: () => {},
292
+ blocked: () => {}
293
+ });
232
294
  }
233
295
 
234
296
  location.href = url.href;
@@ -259,20 +321,73 @@ class Router {
259
321
  return this.renderer.load(info);
260
322
  }
261
323
 
324
+ /** @param {({ from, to }: { from: URL | null, to: URL }) => void} fn */
325
+ after_navigate(fn) {
326
+ onMount(() => {
327
+ this.callbacks.after_navigate.push(fn);
328
+
329
+ return () => {
330
+ const i = this.callbacks.after_navigate.indexOf(fn);
331
+ this.callbacks.after_navigate.splice(i, 1);
332
+ };
333
+ });
334
+ }
335
+
262
336
  /**
263
- * @param {URL} url
264
- * @param {{ x: number, y: number }?} scroll
265
- * @param {boolean} keepfocus
266
- * @param {string[]} chain
267
- * @param {string} [hash]
337
+ * @param {({ from, to, cancel }: { from: URL, to: URL | null, cancel: () => void }) => void} fn
268
338
  */
269
- async _navigate(url, scroll, keepfocus, chain, hash) {
270
- const info = this.parse(url);
339
+ before_navigate(fn) {
340
+ onMount(() => {
341
+ this.callbacks.before_navigate.push(fn);
342
+
343
+ return () => {
344
+ const i = this.callbacks.before_navigate.indexOf(fn);
345
+ this.callbacks.before_navigate.splice(i, 1);
346
+ };
347
+ });
348
+ }
349
+
350
+ /**
351
+ * @param {{
352
+ * url: URL;
353
+ * scroll: { x: number, y: number } | null;
354
+ * keepfocus: boolean;
355
+ * chain: string[];
356
+ * details: {
357
+ * replaceState: boolean;
358
+ * state: any;
359
+ * } | null;
360
+ * accepted: () => void;
361
+ * blocked: () => void;
362
+ * }} opts
363
+ */
364
+ async _navigate({ url, scroll, keepfocus, chain, details, accepted, blocked }) {
365
+ const from = this.renderer.current.url;
366
+ let should_block = false;
367
+
368
+ const intent = {
369
+ from,
370
+ to: url,
371
+ cancel: () => (should_block = true)
372
+ };
373
+
374
+ this.callbacks.before_navigate.forEach((fn) => fn(intent));
271
375
 
376
+ if (should_block) {
377
+ blocked();
378
+ return;
379
+ }
380
+
381
+ const info = this.parse(url);
272
382
  if (!info) {
273
- throw new Error('Attempted to navigate to a URL that does not belong to this app');
383
+ location.href = url.href;
384
+ return new Promise(() => {
385
+ // never resolves
386
+ });
274
387
  }
275
388
 
389
+ accepted();
390
+
276
391
  if (!this.navigating) {
277
392
  dispatchEvent(new CustomEvent('sveltekit:navigation-start'));
278
393
  }
@@ -288,13 +403,24 @@ class Router {
288
403
  }
289
404
 
290
405
  info.url = new URL(url.origin + pathname + url.search + url.hash);
291
- history.replaceState({}, '', info.url);
292
406
 
293
- await this.renderer.handle_navigation(info, chain, false, { hash, scroll, keepfocus });
407
+ if (details) {
408
+ const change = details.replaceState ? 0 : 1;
409
+ details.state['sveltekit:index'] = this.current_history_index += change;
410
+ history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', info.url);
411
+ }
412
+
413
+ await this.renderer.handle_navigation(info, chain, false, {
414
+ scroll,
415
+ keepfocus
416
+ });
294
417
 
295
418
  this.navigating--;
296
419
  if (!this.navigating) {
297
420
  dispatchEvent(new CustomEvent('sveltekit:navigation-end'));
421
+
422
+ const navigation = { from, to: url };
423
+ this.callbacks.after_navigate.forEach((fn) => fn(navigation));
298
424
  }
299
425
  }
300
426
  }
@@ -653,10 +779,11 @@ class Renderer {
653
779
  });
654
780
  } else {
655
781
  if (this.router) {
656
- this.router.goto(navigation_result.redirect, { replaceState: true }, [
657
- ...chain,
658
- info.url.pathname
659
- ]);
782
+ this.router.goto(
783
+ new URL(navigation_result.redirect, info.url).href,
784
+ { replaceState: true },
785
+ [...chain, info.url.pathname]
786
+ );
660
787
  } else {
661
788
  location.href = new URL(navigation_result.redirect, location.href).href;
662
789
  }
@@ -678,7 +805,7 @@ class Renderer {
678
805
 
679
806
  // opts must be passed if we're navigating
680
807
  if (opts) {
681
- const { hash, scroll, keepfocus } = opts;
808
+ const { scroll, keepfocus } = opts;
682
809
 
683
810
  if (!keepfocus) {
684
811
  getSelection()?.removeAllRanges();
@@ -689,7 +816,7 @@ class Renderer {
689
816
  await tick();
690
817
 
691
818
  if (this.autoscroll) {
692
- const deep_linked = hash && document.getElementById(hash.slice(1));
819
+ const deep_linked = info.url.hash && document.getElementById(info.url.hash.slice(1));
693
820
  if (scroll) {
694
821
  scrollTo(scroll.x, scroll.y);
695
822
  } else if (deep_linked) {
@@ -765,6 +892,11 @@ class Renderer {
765
892
  });
766
893
 
767
894
  this.started = true;
895
+
896
+ if (this.router) {
897
+ const navigation = { from: null, to: new URL(location.href) };
898
+ this.router.callbacks.after_navigate.forEach((fn) => fn(navigation));
899
+ }
768
900
  }
769
901
 
770
902
  /**
@@ -76,7 +76,8 @@ async function create_plugin(config, output, cwd) {
76
76
  const deps = new Set();
77
77
  find_deps(node, deps);
78
78
 
79
- const styles = new Set();
79
+ /** @type {Record<string, string>} */
80
+ const styles = {};
80
81
 
81
82
  for (const dep of deps) {
82
83
  const parsed = new URL(dep.url, 'http://localhost/');
@@ -89,7 +90,7 @@ async function create_plugin(config, output, cwd) {
89
90
  ) {
90
91
  try {
91
92
  const mod = await vite.ssrLoadModule(dep.url);
92
- styles.add(mod.default);
93
+ styles[dep.url] = mod.default;
93
94
  } catch {
94
95
  // this can happen with dynamically imported modules, I think
95
96
  // because the Vite module graph doesn't distinguish between
@@ -103,7 +104,7 @@ async function create_plugin(config, output, cwd) {
103
104
  entry: url.endsWith('.svelte') ? url : url + '?import',
104
105
  css: [],
105
106
  js: [],
106
- styles: Array.from(styles)
107
+ styles
107
108
  };
108
109
  };
109
110
  }),
@@ -260,26 +260,12 @@ function generate_app(manifest_data) {
260
260
  ${pyramid.replace(/\n/g, '\n\t\t')}
261
261
 
262
262
  {#if mounted}
263
- <div id="svelte-announcer" aria-live="assertive" aria-atomic="true">
263
+ <div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
264
264
  {#if navigated}
265
265
  {title}
266
266
  {/if}
267
267
  </div>
268
268
  {/if}
269
-
270
- <style>
271
- #svelte-announcer {
272
- position: absolute;
273
- left: 0;
274
- top: 0;
275
- clip: rect(0 0 0 0);
276
- clip-path: inset(50%);
277
- overflow: hidden;
278
- white-space: nowrap;
279
- width: 1px;
280
- height: 1px;
281
- }
282
- </style>
283
269
  `);
284
270
  }
285
271
 
@@ -313,7 +313,7 @@ export class App {
313
313
  prerender: ${config.kit.prerender.enabled},
314
314
  read,
315
315
  root,
316
- service_worker: ${has_service_worker ? "'/service-worker.js'" : 'null'},
316
+ service_worker: ${has_service_worker ? "base + '/service-worker.js'" : 'null'},
317
317
  router: ${s(config.kit.router)},
318
318
  target: ${s(config.kit.target)},
319
319
  template,
@@ -499,16 +499,23 @@ async function build_server(
499
499
  /** @type {import('vite').Manifest} */
500
500
  const vite_manifest = JSON.parse(fs__default.readFileSync(`${output_dir}/server/manifest.json`, 'utf-8'));
501
501
 
502
- const styles_lookup = new Map();
503
- if (config.kit.amp) {
504
- client.assets.forEach((asset) => {
505
- if (asset.fileName.endsWith('.css')) {
506
- styles_lookup.set(asset.fileName, asset.source);
502
+ mkdirp(`${output_dir}/server/nodes`);
503
+ mkdirp(`${output_dir}/server/stylesheets`);
504
+
505
+ const stylesheet_lookup = new Map();
506
+
507
+ client.assets.forEach((asset) => {
508
+ if (asset.fileName.endsWith('.css')) {
509
+ if (config.kit.amp || asset.source.length < config.kit.inlineStyleThreshold) {
510
+ const index = stylesheet_lookup.size;
511
+ const file = `${output_dir}/server/stylesheets/${index}.js`;
512
+
513
+ fs__default.writeFileSync(file, `// ${asset.fileName}\nexport default ${s(asset.source)};`);
514
+ stylesheet_lookup.set(asset.fileName, index);
507
515
  }
508
- });
509
- }
516
+ }
517
+ });
510
518
 
511
- mkdirp(`${output_dir}/server/nodes`);
512
519
  manifest_data.components.forEach((component, i) => {
513
520
  const file = `${output_dir}/server/nodes/${i}.js`;
514
521
 
@@ -516,17 +523,32 @@ async function build_server(
516
523
  const css = new Set();
517
524
  find_deps(component, client.vite_manifest, js, css);
518
525
 
519
- const styles = config.kit.amp && Array.from(css).map((file) => styles_lookup.get(file));
526
+ const imports = [`import * as module from '../${vite_manifest[component].file}';`];
527
+
528
+ const exports = [
529
+ 'export { module };',
530
+ `export const entry = '${client.vite_manifest[component].file}';`,
531
+ `export const js = ${s(Array.from(js))};`,
532
+ `export const css = ${s(Array.from(css))};`
533
+ ];
520
534
 
521
- const node = `import * as module from '../${vite_manifest[component].file}';
522
- export { module };
523
- export const entry = '${client.vite_manifest[component].file}';
524
- export const js = ${JSON.stringify(Array.from(js))};
525
- export const css = ${JSON.stringify(Array.from(css))};
526
- ${styles ? `export const styles = ${s(styles)}` : ''}
527
- `.replace(/^\t\t\t/gm, '');
535
+ /** @type {string[]} */
536
+ const styles = [];
537
+
538
+ css.forEach((file) => {
539
+ if (stylesheet_lookup.has(file)) {
540
+ const index = stylesheet_lookup.get(file);
541
+ const name = `stylesheet_${index}`;
542
+ imports.push(`import ${name} from '../stylesheets/${index}.js';`);
543
+ styles.push(`\t${s(file)}: ${name}`);
544
+ }
545
+ });
546
+
547
+ if (styles.length > 0) {
548
+ exports.push(`export const styles = {\n${styles.join(',\n')}\n};`);
549
+ }
528
550
 
529
- fs__default.writeFileSync(file, node);
551
+ fs__default.writeFileSync(file, `${imports.join('\n')}\n\n${exports.join('\n')}\n`);
530
552
  });
531
553
 
532
554
  return {
@@ -256,9 +256,22 @@ function crawl(html) {
256
256
  } else if (name === 'src') {
257
257
  hrefs.push(value);
258
258
  } else if (name === 'srcset') {
259
- const candidates = value.split(',');
259
+ const candidates = [];
260
+ let insideURL = true;
261
+ value = value.trim();
262
+ for (let i = 0; i < value.length; i++) {
263
+ if (value[i] === ',' && (!insideURL || (insideURL && value[i + 1] === ' '))) {
264
+ candidates.push(value.slice(0, i));
265
+ value = value.substring(i + 1).trim();
266
+ i = 0;
267
+ insideURL = true;
268
+ } else if (value[i] === ' ') {
269
+ insideURL = false;
270
+ }
271
+ }
272
+ candidates.push(value);
260
273
  for (const candidate of candidates) {
261
- const src = candidate.trim().split(WHITESPACE)[0];
274
+ const src = candidate.split(WHITESPACE)[0];
262
275
  hrefs.push(src);
263
276
  }
264
277
  }
package/dist/cli.js CHANGED
@@ -479,6 +479,8 @@ const options = object(
479
479
 
480
480
  hydrate: boolean(true),
481
481
 
482
+ inlineStyleThreshold: number(0),
483
+
482
484
  methodOverride: object({
483
485
  parameter: string('_method'),
484
486
  allowed: validate([], (input, keypath) => {
@@ -868,7 +870,7 @@ async function launch(port, https) {
868
870
  exec(`${cmd} ${https ? 'https' : 'http'}://localhost:${port}`);
869
871
  }
870
872
 
871
- const prog = sade('svelte-kit').version('1.0.0-next.224');
873
+ const prog = sade('svelte-kit').version('1.0.0-next.229');
872
874
 
873
875
  prog
874
876
  .command('dev')
@@ -1020,7 +1022,7 @@ async function check_port(port) {
1020
1022
  function welcome({ port, host, https, open, loose, allow, cwd }) {
1021
1023
  if (open) launch(port, https);
1022
1024
 
1023
- console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.224'}\n`));
1025
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.229'}\n`));
1024
1026
 
1025
1027
  const protocol = https ? 'https:' : 'http:';
1026
1028
  const exposed = typeof host !== 'undefined' && host !== 'localhost' && host !== '127.0.0.1';