@sveltejs/kit 1.25.1 → 1.26.0

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.25.1",
3
+ "version": "1.26.0",
4
4
  "description": "The fastest way to build Svelte apps",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,17 +18,16 @@
18
18
  "esm-env": "^1.0.0",
19
19
  "kleur": "^4.1.5",
20
20
  "magic-string": "^0.30.0",
21
- "mime": "^3.0.0",
21
+ "mrmime": "^1.0.1",
22
22
  "sade": "^1.8.1",
23
23
  "set-cookie-parser": "^2.6.0",
24
24
  "sirv": "^2.0.2",
25
25
  "tiny-glob": "^0.2.9",
26
- "undici": "~5.25.0"
26
+ "undici": "~5.26.2"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@playwright/test": "1.30.0",
30
30
  "@types/connect": "^3.4.35",
31
- "@types/mime": "^3.0.1",
32
31
  "@types/node": "^16.18.6",
33
32
  "@types/sade": "^1.7.4",
34
33
  "@types/set-cookie-parser": "^2.4.2",
@@ -38,8 +37,8 @@
38
37
  "svelte": "^4.0.5",
39
38
  "svelte-preprocess": "^5.0.4",
40
39
  "typescript": "^4.9.4",
41
- "vite": "^4.4.2",
42
- "vitest": "^0.34.0"
40
+ "vite": "^4.4.9",
41
+ "vitest": "^0.34.5"
43
42
  },
44
43
  "peerDependencies": {
45
44
  "svelte": "^3.54.0 || ^4.0.0-next.0",
@@ -181,7 +181,10 @@ export function create_builder({
181
181
  },
182
182
 
183
183
  writeClient(dest) {
184
- return copy(`${config.kit.outDir}/output/client`, dest);
184
+ return copy(`${config.kit.outDir}/output/client`, dest, {
185
+ // avoid making vite build artefacts public
186
+ filter: (basename) => basename !== '.vite'
187
+ });
185
188
  },
186
189
 
187
190
  writePrerendered(dest) {
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import colors from 'kleur';
4
- import mime from 'mime';
4
+ import { lookup } from 'mrmime';
5
5
  import { list_files, runtime_directory } from '../../utils.js';
6
6
  import { posixify } from '../../../utils/filesystem.js';
7
7
  import { parse_route_id } from '../../../utils/routing.js';
@@ -48,7 +48,7 @@ export function create_assets(config) {
48
48
  return list_files(config.kit.files.assets).map((file) => ({
49
49
  file,
50
50
  size: fs.statSync(path.resolve(config.kit.files.assets, file)).size,
51
- type: mime.getType(file)
51
+ type: lookup(file) || null
52
52
  }));
53
53
  }
54
54
 
@@ -188,10 +188,18 @@ function update_types(config, routes, route, to_delete = new Set()) {
188
188
  // add 'Expand' helper
189
189
  // Makes sure a type is "repackaged" and therefore more readable
190
190
  declarations.push('type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;');
191
+
192
+ // returns the predicate of a matcher's type guard - or string if there is no type guard
191
193
  declarations.push(
192
- `type RouteParams = { ${route.params
193
- .map((param) => `${param.name}${param.optional ? '?' : ''}: string`)
194
- .join('; ')} }`
194
+ // TS complains on infer U, which seems weird, therefore ts-ignore it
195
+ [
196
+ '// @ts-ignore',
197
+ 'type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;'
198
+ ].join('\n')
199
+ );
200
+
201
+ declarations.push(
202
+ 'type RouteParams = ' + generate_params_type(route.params, outdir, config) + ';'
195
203
  );
196
204
 
197
205
  if (route.params.length > 0) {
@@ -265,7 +273,8 @@ function update_types(config, routes, route, to_delete = new Set()) {
265
273
 
266
274
  if (route.layout) {
267
275
  let all_pages_have_load = true;
268
- const layout_params = new Set();
276
+ /** @type {import('types').RouteParam[]} */
277
+ const layout_params = [];
269
278
  const ids = ['RouteId'];
270
279
 
271
280
  route.layout.child_pages?.forEach((page) => {
@@ -274,7 +283,9 @@ function update_types(config, routes, route, to_delete = new Set()) {
274
283
  if (leaf.route.page) ids.push(`"${leaf.route.id}"`);
275
284
 
276
285
  for (const param of leaf.route.params) {
277
- layout_params.add(param.name);
286
+ // skip if already added
287
+ if (layout_params.some((p) => p.name === param.name)) continue;
288
+ layout_params.push({ ...param, optional: true });
278
289
  }
279
290
 
280
291
  ensureProxies(page, leaf.proxies);
@@ -301,9 +312,7 @@ function update_types(config, routes, route, to_delete = new Set()) {
301
312
  declarations.push(`type LayoutRouteId = ${ids.join(' | ')}`);
302
313
 
303
314
  declarations.push(
304
- `type LayoutParams = RouteParams & { ${Array.from(layout_params).map(
305
- (param) => `${param}?: string`
306
- )} }`
315
+ 'type LayoutParams = RouteParams & ' + generate_params_type(layout_params, outdir, config)
307
316
  );
308
317
 
309
318
  const {
@@ -567,6 +576,28 @@ function replace_ext_with_js(file_path) {
567
576
  return file_path.slice(0, -ext.length) + '.js';
568
577
  }
569
578
 
579
+ /**
580
+ * @param {import('types').RouteParam[]} params
581
+ * @param {string} outdir
582
+ * @param {import('types').ValidatedConfig} config
583
+ */
584
+ function generate_params_type(params, outdir, config) {
585
+ /** @param {string} matcher */
586
+ const path_to_matcher = (matcher) =>
587
+ posixify(path.relative(outdir, path.join(config.kit.files.params, matcher)));
588
+
589
+ return `{ ${params
590
+ .map(
591
+ (param) =>
592
+ `${param.name}${param.optional ? '?' : ''}: ${
593
+ param.matcher
594
+ ? `MatcherParam<typeof import('${path_to_matcher(param.matcher)}').match>`
595
+ : 'string'
596
+ }`
597
+ )
598
+ .join('; ')} }`;
599
+ }
600
+
570
601
  /**
571
602
  * @param {string} content
572
603
  * @param {boolean} is_server
@@ -1,7 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import { posixify } from '../../../utils/filesystem.js';
3
+ import { strip_virtual_prefix } from '../utils.js';
3
4
 
4
- const ILLEGAL_IMPORTS = new Set(['\0$env/dynamic/private', '\0$env/static/private']);
5
+ const ILLEGAL_IMPORTS = new Set([
6
+ '\0virtual:$env/dynamic/private',
7
+ '\0virtual:$env/static/private'
8
+ ]);
5
9
  const ILLEGAL_MODULE_NAME_PATTERN = /.*\.server\..+/;
6
10
 
7
11
  /**
@@ -51,10 +55,14 @@ export function module_guard(context, { cwd, lib }) {
51
55
  chain.map(({ id, dynamic }, i) => {
52
56
  id = normalize_id(id, lib, cwd);
53
57
 
54
- return `${' '.repeat(i * 2)}- ${id} ${dynamic ? 'dynamically imports' : 'imports'}\n`;
55
- }) + `${' '.repeat(chain.length)}- ${id}`;
58
+ return `${' '.repeat(i * 2)}- ${strip_virtual_prefix(id)} ${
59
+ dynamic ? 'dynamically imports' : 'imports'
60
+ }\n`;
61
+ }) + `${' '.repeat(chain.length)}- ${strip_virtual_prefix(id)}`;
56
62
 
57
- const message = `Cannot import ${id} into client-side code:\n${pyramid}`;
63
+ const message = `Cannot import ${strip_virtual_prefix(
64
+ id
65
+ )} into client-side code:\n${pyramid}`;
58
66
 
59
67
  throw new Error(message);
60
68
  }
@@ -18,7 +18,7 @@ import { assets_base, find_deps } from './build/utils.js';
18
18
  import { dev } from './dev/index.js';
19
19
  import { is_illegal, module_guard, normalize_id } from './graph_analysis/index.js';
20
20
  import { preview } from './preview/index.js';
21
- import { get_config_aliases, get_env } from './utils.js';
21
+ import { get_config_aliases, get_env, strip_virtual_prefix } from './utils.js';
22
22
  import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
23
23
  import prerender from '../../core/postbuild/prerender.js';
24
24
  import analyse from '../../core/postbuild/analyse.js';
@@ -336,7 +336,7 @@ function kit({ svelte_config }) {
336
336
  async resolveId(id) {
337
337
  // treat $env/static/[public|private] as virtual
338
338
  if (id.startsWith('$env/') || id.startsWith('__sveltekit/') || id === '$service-worker') {
339
- return `\0${id}`;
339
+ return `\0virtual:${id}`;
340
340
  }
341
341
  },
342
342
 
@@ -358,24 +358,24 @@ function kit({ svelte_config }) {
358
358
  })
359
359
  ) {
360
360
  const relative = normalize_id(id, normalized_lib, normalized_cwd);
361
- throw new Error(`Cannot import ${relative} into client-side code`);
361
+ throw new Error(`Cannot import ${strip_virtual_prefix(relative)} into client-side code`);
362
362
  }
363
363
  }
364
364
 
365
365
  switch (id) {
366
- case '\0$env/static/private':
366
+ case '\0virtual:$env/static/private':
367
367
  return create_static_module('$env/static/private', env.private);
368
368
 
369
- case '\0$env/static/public':
369
+ case '\0virtual:$env/static/public':
370
370
  return create_static_module('$env/static/public', env.public);
371
371
 
372
- case '\0$env/dynamic/private':
372
+ case '\0virtual:$env/dynamic/private':
373
373
  return create_dynamic_module(
374
374
  'private',
375
375
  vite_config_env.command === 'serve' ? env.private : undefined
376
376
  );
377
377
 
378
- case '\0$env/dynamic/public':
378
+ case '\0virtual:$env/dynamic/public':
379
379
  // populate `$env/dynamic/public` from `window`
380
380
  if (browser) {
381
381
  return `export const env = ${global}.env;`;
@@ -386,12 +386,12 @@ function kit({ svelte_config }) {
386
386
  vite_config_env.command === 'serve' ? env.public : undefined
387
387
  );
388
388
 
389
- case '\0$service-worker':
389
+ case '\0virtual:$service-worker':
390
390
  return create_service_worker_module(svelte_config);
391
391
 
392
392
  // for internal use only. it's published as $app/paths externally
393
393
  // we use this alias so that we won't collide with user aliases
394
- case '\0__sveltekit/paths': {
394
+ case '\0virtual:__sveltekit/paths': {
395
395
  const { assets, base } = svelte_config.kit.paths;
396
396
 
397
397
  // use the values defined in `global`, but fall back to hard-coded values
@@ -429,7 +429,7 @@ function kit({ svelte_config }) {
429
429
  `;
430
430
  }
431
431
 
432
- case '\0__sveltekit/environment': {
432
+ case '\0virtual:__sveltekit/environment': {
433
433
  const { version } = svelte_config.kit;
434
434
 
435
435
  return dedent`
@@ -555,7 +555,7 @@ function kit({ svelte_config }) {
555
555
  cssCodeSplit: true,
556
556
  cssMinify: initial_config.build?.minify == null ? true : !!initial_config.build.minify,
557
557
  // don't use the default name to avoid collisions with 'static/manifest.json'
558
- manifest: 'vite-manifest.json',
558
+ manifest: '.vite/manifest.json', // TODO: remove this after bumping peer dep to vite 5
559
559
  outDir: `${out}/${ssr ? 'server' : 'client'}`,
560
560
  rollupOptions: {
561
561
  input,
@@ -805,10 +805,6 @@ function kit({ svelte_config }) {
805
805
  .cyan('npm run preview')} to preview your production build locally.`
806
806
  );
807
807
 
808
- // avoid making the manifest available to users
809
- fs.unlinkSync(`${out}/client/${vite_config.build.manifest}`);
810
- fs.unlinkSync(`${out}/server/${vite_config.build.manifest}`);
811
-
812
808
  if (kit.adapter) {
813
809
  const { adapt } = await import('../../core/adapt/index.js');
814
810
  await adapt(svelte_config, build_data, metadata, prerendered, prerender_map, log);
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
+ import { lookup } from 'mrmime';
4
5
  import sirv from 'sirv';
5
6
  import { loadEnv, normalizePath } from 'vite';
6
7
  import { getRequest, setResponse } from '../../../exports/node/index.js';
@@ -141,7 +142,7 @@ export async function preview(vite, vite_config, svelte_config) {
141
142
 
142
143
  if (prerendered) {
143
144
  res.writeHead(200, {
144
- 'content-type': 'text/html',
145
+ 'content-type': lookup(pathname) || 'text/html',
145
146
  etag
146
147
  });
147
148
 
@@ -97,3 +97,5 @@ export function not_found(req, res, base) {
97
97
  );
98
98
  }
99
99
  }
100
+
101
+ export const strip_virtual_prefix = /** @param {string} id */ (id) => id.replace('\0virtual:', '');
@@ -173,7 +173,7 @@ export function create_client(app, target) {
173
173
 
174
174
  if (navigation_result) {
175
175
  if (navigation_result.type === 'redirect') {
176
- return goto(new URL(navigation_result.location, url).href, {}, [url.pathname], nav_token);
176
+ return goto(new URL(navigation_result.location, url).href, {}, 1, nav_token);
177
177
  } else {
178
178
  if (navigation_result.props.page !== undefined) {
179
179
  page = navigation_result.props.page;
@@ -208,7 +208,7 @@ export function create_client(app, target) {
208
208
  /**
209
209
  * @param {string | URL} url
210
210
  * @param {{ noScroll?: boolean; replaceState?: boolean; keepFocus?: boolean; state?: any; invalidateAll?: boolean }} opts
211
- * @param {string[]} redirect_chain
211
+ * @param {number} redirect_count
212
212
  * @param {{}} [nav_token]
213
213
  */
214
214
  async function goto(
@@ -220,7 +220,7 @@ export function create_client(app, target) {
220
220
  state = {},
221
221
  invalidateAll = false
222
222
  },
223
- redirect_chain,
223
+ redirect_count,
224
224
  nav_token
225
225
  ) {
226
226
  if (typeof url === 'string') {
@@ -231,7 +231,7 @@ export function create_client(app, target) {
231
231
  url,
232
232
  scroll: noScroll ? scroll_state() : null,
233
233
  keepfocus: keepFocus,
234
- redirect_chain,
234
+ redirect_count,
235
235
  details: {
236
236
  state,
237
237
  replaceState
@@ -941,7 +941,7 @@ export function create_client(app, target) {
941
941
  * url: URL;
942
942
  * scroll: { x: number, y: number } | null;
943
943
  * keepfocus: boolean;
944
- * redirect_chain: string[];
944
+ * redirect_count: number;
945
945
  * details: {
946
946
  * replaceState: boolean;
947
947
  * state: any;
@@ -957,7 +957,7 @@ export function create_client(app, target) {
957
957
  url,
958
958
  scroll,
959
959
  keepfocus,
960
- redirect_chain,
960
+ redirect_count,
961
961
  details,
962
962
  type,
963
963
  delta,
@@ -1014,7 +1014,8 @@ export function create_client(app, target) {
1014
1014
  }
1015
1015
 
1016
1016
  if (navigation_result.type === 'redirect') {
1017
- if (redirect_chain.length > 10 || redirect_chain.includes(url.pathname)) {
1017
+ // whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
1018
+ if (redirect_count >= 20) {
1018
1019
  navigation_result = await load_root_error_page({
1019
1020
  status: 500,
1020
1021
  error: await handle_error(new Error('Redirect loop'), {
@@ -1026,12 +1027,7 @@ export function create_client(app, target) {
1026
1027
  route: { id: null }
1027
1028
  });
1028
1029
  } else {
1029
- goto(
1030
- new URL(navigation_result.location, url).href,
1031
- {},
1032
- [...redirect_chain, url.pathname],
1033
- nav_token
1034
- );
1030
+ goto(new URL(navigation_result.location, url).href, {}, redirect_count + 1, nav_token);
1035
1031
  return false;
1036
1032
  }
1037
1033
  } else if (/** @type {number} */ (navigation_result.props.page?.status) >= 400) {
@@ -1379,7 +1375,7 @@ export function create_client(app, target) {
1379
1375
  },
1380
1376
 
1381
1377
  goto: (href, opts = {}) => {
1382
- return goto(href, opts, []);
1378
+ return goto(href, opts, 0);
1383
1379
  },
1384
1380
 
1385
1381
  invalidate: (resource) => {
@@ -1440,7 +1436,7 @@ export function create_client(app, target) {
1440
1436
  tick().then(reset_focus);
1441
1437
  }
1442
1438
  } else if (result.type === 'redirect') {
1443
- goto(result.location, { invalidateAll: true }, []);
1439
+ goto(result.location, { invalidateAll: true }, 0);
1444
1440
  } else {
1445
1441
  /** @type {Record<string, any>} */
1446
1442
  root.$set({
@@ -1595,7 +1591,7 @@ export function create_client(app, target) {
1595
1591
  url,
1596
1592
  scroll: options.noscroll ? scroll_state() : null,
1597
1593
  keepfocus: options.keep_focus ?? false,
1598
- redirect_chain: [],
1594
+ redirect_count: 0,
1599
1595
  details: {
1600
1596
  state: {},
1601
1597
  replaceState: options.replace_state ?? url.href === location.href
@@ -1649,7 +1645,7 @@ export function create_client(app, target) {
1649
1645
  url,
1650
1646
  scroll: noscroll ? scroll_state() : null,
1651
1647
  keepfocus: keep_focus ?? false,
1652
- redirect_chain: [],
1648
+ redirect_count: 0,
1653
1649
  details: {
1654
1650
  state: {},
1655
1651
  replaceState: replace_state ?? url.href === location.href
@@ -1662,16 +1658,19 @@ export function create_client(app, target) {
1662
1658
  });
1663
1659
 
1664
1660
  addEventListener('popstate', async (event) => {
1661
+ token = {};
1665
1662
  if (event.state?.[INDEX_KEY]) {
1666
1663
  // if a popstate-driven navigation is cancelled, we need to counteract it
1667
1664
  // with history.go, which means we end up back here, hence this check
1668
1665
  if (event.state[INDEX_KEY] === current_history_index) return;
1669
1666
 
1670
1667
  const scroll = scroll_positions[event.state[INDEX_KEY]];
1668
+ const url = new URL(location.href);
1671
1669
 
1672
1670
  // if the only change is the hash, we don't need to do anything...
1673
1671
  if (current.url.href.split('#')[0] === location.href.split('#')[0]) {
1674
- // ...except handle scroll
1672
+ // ...except update our internal URL tracking and handle scroll
1673
+ update_url(url);
1675
1674
  scroll_positions[current_history_index] = scroll_state();
1676
1675
  current_history_index = event.state[INDEX_KEY];
1677
1676
  scrollTo(scroll.x, scroll.y);
@@ -1681,10 +1680,10 @@ export function create_client(app, target) {
1681
1680
  const delta = event.state[INDEX_KEY] - current_history_index;
1682
1681
 
1683
1682
  await navigate({
1684
- url: new URL(location.href),
1683
+ url,
1685
1684
  scroll,
1686
1685
  keepfocus: false,
1687
- redirect_chain: [],
1686
+ redirect_count: 0,
1688
1687
  details: null,
1689
1688
  accepted: () => {
1690
1689
  current_history_index = event.state[INDEX_KEY];
@@ -1693,7 +1692,8 @@ export function create_client(app, target) {
1693
1692
  history.go(-delta);
1694
1693
  },
1695
1694
  type: 'popstate',
1696
- delta
1695
+ delta,
1696
+ nav_token: token
1697
1697
  });
1698
1698
  } else {
1699
1699
  // since popstate event is also emitted when an anchor referencing the same
@@ -23,6 +23,10 @@ if (DEV) {
23
23
 
24
24
  check_stack_trace();
25
25
 
26
+ /**
27
+ * @param {RequestInfo | URL} input
28
+ * @param {RequestInit & Record<string, any> | undefined} init
29
+ */
26
30
  window.fetch = (input, init) => {
27
31
  // Check if fetch was called via load_node. the lock method only checks if it was called at the
28
32
  // same time, but not necessarily if it was called from `load`.
@@ -36,10 +40,14 @@ if (DEV) {
36
40
  const cutoff = stack_array.findIndex((a) => a.includes('load@') || a.includes('at load'));
37
41
  const stack = stack_array.slice(0, cutoff + 2).join('\n');
38
42
 
39
- const heuristic = can_inspect_stack_trace
43
+ const in_load_heuristic = can_inspect_stack_trace
40
44
  ? stack.includes('src/runtime/client/client.js')
41
45
  : loading;
42
- if (heuristic) {
46
+
47
+ // This flag is set in initial_fetch and subsequent_fetch
48
+ const used_kit_fetch = init?.__sveltekit_fetch__;
49
+
50
+ if (in_load_heuristic && !used_kit_fetch) {
43
51
  console.warn(
44
52
  `Loading ${url} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://kit.svelte.dev/docs/load#making-fetch-requests`
45
53
  );
@@ -86,7 +94,7 @@ export function initial_fetch(resource, opts) {
86
94
  return Promise.resolve(new Response(body, init));
87
95
  }
88
96
 
89
- return native_fetch(resource, opts);
97
+ return DEV ? dev_fetch(resource, opts) : window.fetch(resource, opts);
90
98
  }
91
99
 
92
100
  /**
@@ -112,7 +120,22 @@ export function subsequent_fetch(resource, resolved, opts) {
112
120
  }
113
121
  }
114
122
 
115
- return native_fetch(resolved, opts);
123
+ return DEV ? dev_fetch(resolved, opts) : window.fetch(resolved, opts);
124
+ }
125
+
126
+ /**
127
+ * @param {RequestInfo | URL} resource
128
+ * @param {RequestInit & Record<string, any> | undefined} opts
129
+ */
130
+ function dev_fetch(resource, opts) {
131
+ const patched_opts = { ...opts };
132
+ // This assigns the __sveltekit_fetch__ flag and makes it non-enumerable
133
+ Object.defineProperty(patched_opts, '__sveltekit_fetch__', {
134
+ value: true,
135
+ writable: true,
136
+ configurable: true
137
+ });
138
+ return window.fetch(resource, patched_opts);
116
139
  }
117
140
 
118
141
  /**
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // generated during release, do not modify
2
2
 
3
3
  /** @type {string} */
4
- export const VERSION = '1.25.1';
4
+ export const VERSION = '1.26.0';