@sveltejs/kit 1.8.3 → 1.8.5

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.8.3",
3
+ "version": "1.8.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -16,7 +16,7 @@
16
16
  "devalue": "^4.3.0",
17
17
  "esm-env": "^1.0.0",
18
18
  "kleur": "^4.1.5",
19
- "magic-string": "^0.29.0",
19
+ "magic-string": "^0.30.0",
20
20
  "mime": "^3.0.0",
21
21
  "sade": "^1.8.1",
22
22
  "set-cookie-parser": "^2.5.1",
@@ -80,7 +80,7 @@
80
80
  "node": "^16.14 || >=18"
81
81
  },
82
82
  "scripts": {
83
- "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore && eslint src/**/*.js",
83
+ "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
84
84
  "check": "tsc",
85
85
  "check:all": "tsc && pnpm -r --filter=\"./**\" check",
86
86
  "format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
package/postinstall.js CHANGED
@@ -1,5 +1,5 @@
1
- import fs from 'fs';
2
- import path from 'path';
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
3
  import glob from 'tiny-glob/sync.js';
4
4
  import { load_config } from './src/core/config/index.js';
5
5
  import * as sync from './src/core/sync/sync.js';
@@ -82,7 +82,7 @@ export function create_builder({
82
82
  return;
83
83
  }
84
84
 
85
- const files = await glob('**/*.{html,js,json,css,svg,xml,wasm}', {
85
+ const files = await glob('**/*.{html,js,mjs,json,css,svg,xml,wasm}', {
86
86
  cwd: directory,
87
87
  dot: true,
88
88
  absolute: true,
@@ -138,6 +138,10 @@ const options = object(
138
138
 
139
139
  outDir: string('.svelte-kit'),
140
140
 
141
+ output: object({
142
+ preloadStrategy: list(['modulepreload', 'preload-js', 'preload-mjs'], 'modulepreload')
143
+ }),
144
+
141
145
  paths: object({
142
146
  base: validate('', (input, keypath) => {
143
147
  assert_string(input, keypath);
@@ -36,6 +36,7 @@ export const options = {
36
36
  embedded: ${config.kit.embedded},
37
37
  env_public_prefix: '${config.kit.env.publicPrefix}',
38
38
  hooks: null, // added lazily, via \`get_hooks\`
39
+ preload_strategy: ${s(config.kit.output.preloadStrategy)},
39
40
  root,
40
41
  service_worker: ${has_service_worker},
41
42
  templates: {
@@ -507,6 +507,9 @@ export function set_building() {
507
507
  }
508
508
  }
509
509
 
510
+ // see the kit.output.preloadStrategy option for details on why we have multiple options here
511
+ const ext = kit.output.preloadStrategy === 'preload-mjs' ? 'mjs' : 'js';
512
+
510
513
  new_config = {
511
514
  base: ssr ? assets_base(kit) : './',
512
515
  build: {
@@ -516,12 +519,8 @@ export function set_building() {
516
519
  input,
517
520
  output: {
518
521
  format: 'esm',
519
- // we use .mjs for client-side modules, because this signals to Chrome (when it
520
- // reads the <link rel="preload">) that it should parse the file as a module
521
- // rather than as a script, preventing a double parse. Ideally we'd just use
522
- // modulepreload, but Safari prevents that
523
- entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].mjs`,
524
- chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].mjs`,
522
+ entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`,
523
+ chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].${ext}`,
525
524
  assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
526
525
  hoistTransitiveImports: false
527
526
  },
@@ -38,8 +38,6 @@ export async function render_data(
38
38
  });
39
39
  }
40
40
 
41
- state.initiator = route;
42
-
43
41
  try {
44
42
  const node_ids = [...route.page.layouts, route.page.leaf];
45
43
  const invalidated = invalidated_data_nodes ?? node_ids.map(() => true);
@@ -4,12 +4,11 @@ import { method_not_allowed } from './utils.js';
4
4
 
5
5
  /**
6
6
  * @param {import('types').RequestEvent} event
7
- * @param {import('types').SSRRoute} route
8
7
  * @param {import('types').SSREndpoint} mod
9
8
  * @param {import('types').SSRState} state
10
9
  * @returns {Promise<Response>}
11
10
  */
12
- export async function render_endpoint(event, route, mod, state) {
11
+ export async function render_endpoint(event, mod, state) {
13
12
  const method = /** @type {import('types').HttpMethod} */ (event.request.method);
14
13
 
15
14
  let handler = mod[method];
@@ -29,7 +28,7 @@ export async function render_endpoint(event, route, mod, state) {
29
28
  }
30
29
 
31
30
  if (state.prerendering && !prerender) {
32
- if (state.initiator) {
31
+ if (state.depth > 0) {
33
32
  // if request came from a prerendered page, bail
34
33
  throw new Error(`${event.route.id} is not prerenderable`);
35
34
  } else {
@@ -39,8 +38,6 @@ export async function render_endpoint(event, route, mod, state) {
39
38
  }
40
39
  }
41
40
 
42
- state.initiator = route;
43
-
44
41
  try {
45
42
  const response = await handler(
46
43
  /** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
@@ -131,7 +131,10 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
131
131
  );
132
132
  }
133
133
 
134
- response = await respond(request, options, manifest, state);
134
+ response = await respond(request, options, manifest, {
135
+ ...state,
136
+ depth: state.depth + 1
137
+ });
135
138
 
136
139
  const set_cookie = response.headers.get('set-cookie');
137
140
  if (set_cookie) {
@@ -58,6 +58,10 @@ export class Server {
58
58
  );
59
59
  }
60
60
 
61
- return respond(request, this.#options, this.#manifest, options);
61
+ return respond(request, this.#options, this.#manifest, {
62
+ ...options,
63
+ error: false,
64
+ depth: 0
65
+ });
62
66
  }
63
67
  }
@@ -16,9 +16,13 @@ import { respond_with_error } from './respond_with_error.js';
16
16
  import { get_option } from '../../../utils/options.js';
17
17
  import { get_data_json } from '../data/index.js';
18
18
 
19
+ /**
20
+ * The maximum request depth permitted before assuming we're stuck in an infinite loop
21
+ */
22
+ const MAX_DEPTH = 10;
23
+
19
24
  /**
20
25
  * @param {import('types').RequestEvent} event
21
- * @param {import('types').SSRRoute} route
22
26
  * @param {import('types').PageNodeIndexes} page
23
27
  * @param {import('types').SSROptions} options
24
28
  * @param {import('types').SSRManifest} manifest
@@ -26,16 +30,14 @@ import { get_data_json } from '../data/index.js';
26
30
  * @param {import('types').RequiredResolveOptions} resolve_opts
27
31
  * @returns {Promise<Response>}
28
32
  */
29
- export async function render_page(event, route, page, options, manifest, state, resolve_opts) {
30
- if (state.initiator === route) {
33
+ export async function render_page(event, page, options, manifest, state, resolve_opts) {
34
+ if (state.depth > MAX_DEPTH) {
31
35
  // infinite request cycle detected
32
36
  return text(`Not found: ${event.url.pathname}`, {
33
- status: 404
37
+ status: 404 // TODO in some cases this should be 500. not sure how to differentiate
34
38
  });
35
39
  }
36
40
 
37
- state.initiator = route;
38
-
39
41
  if (is_action_json_request(event)) {
40
42
  const node = await manifest._.nodes[page.leaf]();
41
43
  return handle_action_json_request(event, options, node?.server);
@@ -322,7 +324,7 @@ export async function render_page(event, route, page, options, manifest, state,
322
324
  fetched
323
325
  });
324
326
  } catch (e) {
325
- // if we end up here, it means the data loaded successfull
327
+ // if we end up here, it means the data loaded successfully
326
328
  // but the page failed to render, or that a prerendering error occurred
327
329
  return await respond_with_error({
328
330
  event,
@@ -273,12 +273,13 @@ export async function render_response({
273
273
  );
274
274
 
275
275
  for (const path of included_modulepreloads) {
276
- // we use modulepreload with the Link header for Chrome, along with
277
- // <link rel="preload"> for Safari. This results in the fastest loading in
278
- // the most used browsers, with no double-loading. Note that we need to use
279
- // .mjs extensions for `preload` to behave like `modulepreload` in Chrome
276
+ // see the kit.output.preloadStrategy option for details on why we have multiple options here
280
277
  link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
281
- head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
278
+ if (options.preload_strategy !== 'modulepreload') {
279
+ head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
280
+ } else if (state.prerendering) {
281
+ head += `\n\t\t<link rel="modulepreload" href="${path}">`;
282
+ }
282
283
  }
283
284
 
284
285
  const blocks = [];
@@ -1,11 +1,6 @@
1
1
  import { render_response } from './render.js';
2
2
  import { load_data, load_server_data } from './load_data.js';
3
- import {
4
- handle_error_and_jsonify,
5
- static_error_page,
6
- redirect_response,
7
- GENERIC_ERROR
8
- } from '../utils.js';
3
+ import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
9
4
  import { get_option } from '../../../utils/options.js';
10
5
  import { HttpError, Redirect } from '../../control.js';
11
6
 
@@ -43,7 +38,7 @@ export async function respond_with_error({
43
38
  const csr = get_option([default_layout], 'csr') ?? true;
44
39
 
45
40
  if (ssr) {
46
- state.initiator = GENERIC_ERROR;
41
+ state.error = true;
47
42
 
48
43
  const server_data_promise = load_server_data({
49
44
  event,
@@ -5,7 +5,7 @@ import { render_page } from './page/index.js';
5
5
  import { render_response } from './page/render.js';
6
6
  import { respond_with_error } from './page/respond_with_error.js';
7
7
  import { is_form_content_type } from '../../utils/http.js';
8
- import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js';
8
+ import { handle_fatal_error, redirect_response } from './utils.js';
9
9
  import {
10
10
  decode_pathname,
11
11
  decode_params,
@@ -38,7 +38,13 @@ const default_filter = () => false;
38
38
  /** @type {import('types').RequiredResolveOptions['preload']} */
39
39
  const default_preload = ({ type }) => type === 'js' || type === 'css';
40
40
 
41
- /** @type {import('types').Respond} */
41
+ /**
42
+ * @param {Request} request
43
+ * @param {import('types').SSROptions} options
44
+ * @param {import('types').SSRManifest} manifest
45
+ * @param {import('types').SSRState} state
46
+ * @returns {Promise<Response>}
47
+ */
42
48
  export async function respond(request, options, manifest, state) {
43
49
  /** URL but stripped from the potential `/__data.json` suffix and its search param */
44
50
  let url = new URL(request.url);
@@ -358,17 +364,9 @@ export async function respond(request, options, manifest, state) {
358
364
  trailing_slash ?? 'never'
359
365
  );
360
366
  } else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
361
- response = await render_endpoint(event, route, await route.endpoint(), state);
367
+ response = await render_endpoint(event, await route.endpoint(), state);
362
368
  } else if (route.page) {
363
- response = await render_page(
364
- event,
365
- route,
366
- route.page,
367
- options,
368
- manifest,
369
- state,
370
- resolve_opts
371
- );
369
+ response = await render_page(event, route.page, options, manifest, state, resolve_opts);
372
370
  } else {
373
371
  // a route will always have a page or an endpoint, but TypeScript
374
372
  // doesn't know that
@@ -378,15 +376,15 @@ export async function respond(request, options, manifest, state) {
378
376
  return response;
379
377
  }
380
378
 
381
- if (state.initiator === GENERIC_ERROR) {
379
+ if (state.error) {
382
380
  return text('Internal Server Error', {
383
381
  status: 500
384
382
  });
385
383
  }
386
384
 
387
385
  // if this request came direct from the user, rather than
388
- // via a `fetch` in a `load`, render a 404 page
389
- if (!state.initiator) {
386
+ // via our own `fetch`, render a 404 page
387
+ if (state.depth === 0) {
390
388
  return await respond_with_error({
391
389
  event,
392
390
  options,
@@ -16,11 +16,6 @@ export function is_pojo(body) {
16
16
  return true;
17
17
  }
18
18
 
19
- /** @type {import('types').SSRErrorPage} */
20
- export const GENERIC_ERROR = {
21
- id: '__error'
22
- };
23
-
24
19
  /**
25
20
  * @param {Partial<Record<import('types').HttpMethod, any>>} mod
26
21
  * @param {import('types').HttpMethod} method
package/types/index.d.ts CHANGED
@@ -422,6 +422,20 @@ export interface KitConfig {
422
422
  * @default ".svelte-kit"
423
423
  */
424
424
  outDir?: string;
425
+ /**
426
+ * Options related to the build output format
427
+ */
428
+ output?: {
429
+ /**
430
+ * SvelteKit will preload the JavaScript modules needed for the initial page to avoid import 'waterfalls', resulting in faster application startup. There
431
+ * are three strategies with different trade-offs:
432
+ * - `modulepreload` - uses `<link rel="modulepreload">`. This delivers the best results in Chromium-based browsers, but is currently ignored by Firefox and Safari (though support is coming to Safari soon).
433
+ * - `preload-js` - uses `<link rel="preload">`. Prevents waterfalls in Chromium and Safari, but Chromium will parse each module twice (once as a script, once as a module). Causes modules to be requested twice in Firefox. This is a good setting if you want to maximise performance for users on iOS devices at the cost of a very slight degradation for Chromium users.
434
+ * - `preload-mjs` - uses `<link rel="preload">` but with the `.mjs` extension which prevents double-parsing in Chromium. Some static webservers will fail to serve .mjs files with a `Content-Type: application/javascript` header, which will cause your application to break. If that doesn't apply to you, this is the option that will deliver the best performance for the largest number of users, until `modulepreload` is more widely supported.
435
+ * @default "modulepreload"
436
+ */
437
+ preloadStrategy?: 'modulepreload' | 'preload-js' | 'preload-mjs';
438
+ };
425
439
  paths?: {
426
440
  /**
427
441
  * An absolute path that your app's files are served from. This is useful if your files are served from a storage bucket of some kind.
@@ -157,15 +157,6 @@ export type RecursiveRequired<T> = {
157
157
 
158
158
  export type RequiredResolveOptions = Required<ResolveOptions>;
159
159
 
160
- export interface Respond {
161
- (
162
- request: Request,
163
- options: SSROptions,
164
- manifest: SSRManifest,
165
- state: SSRState
166
- ): Promise<Response>;
167
- }
168
-
169
160
  export interface RouteParam {
170
161
  name: string;
171
162
  matcher: string;
@@ -337,6 +328,7 @@ export interface SSROptions {
337
328
  embedded: boolean;
338
329
  env_public_prefix: string;
339
330
  hooks: ServerHooks;
331
+ preload_strategy: ValidatedConfig['kit']['output']['preloadStrategy'];
340
332
  root: SSRComponent['default'];
341
333
  service_worker: boolean;
342
334
  templates: {
@@ -352,10 +344,6 @@ export interface SSROptions {
352
344
  version_hash: string;
353
345
  }
354
346
 
355
- export interface SSRErrorPage {
356
- id: '__error';
357
- }
358
-
359
347
  export interface PageNodeIndexes {
360
348
  errors: Array<number | undefined>;
361
349
  layouts: Array<number | undefined>;
@@ -380,7 +368,14 @@ export interface SSRRoute {
380
368
  export interface SSRState {
381
369
  fallback?: string;
382
370
  getClientAddress(): string;
383
- initiator?: SSRRoute | SSRErrorPage;
371
+ /**
372
+ * True if we're currently attempting to render an error page
373
+ */
374
+ error: boolean;
375
+ /**
376
+ * Allows us to prevent `event.fetch` from making infinitely looping internal requests
377
+ */
378
+ depth: number;
384
379
  platform?: any;
385
380
  prerendering?: PrerenderOptions;
386
381
  /**