@sveltejs/kit 2.27.3 → 2.29.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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/src/core/config/index.js +11 -0
  3. package/src/core/config/options.js +30 -11
  4. package/src/core/postbuild/prerender.js +5 -0
  5. package/src/exports/index.js +3 -4
  6. package/src/exports/public.d.ts +40 -11
  7. package/src/exports/vite/build/build_remote.js +4 -4
  8. package/src/exports/vite/index.js +121 -70
  9. package/src/exports/vite/static_analysis/utils.js +101 -0
  10. package/src/exports/vite/utils.js +16 -0
  11. package/src/runtime/app/server/event.js +1 -0
  12. package/src/runtime/app/server/index.js +3 -2
  13. package/src/runtime/app/server/remote/command.js +5 -0
  14. package/src/runtime/app/server/remote/form.js +10 -0
  15. package/src/runtime/client/client.js +2 -2
  16. package/src/runtime/client/fetcher.js +3 -2
  17. package/src/runtime/client/remote-functions/command.svelte.js +91 -0
  18. package/src/runtime/client/remote-functions/form.svelte.js +29 -2
  19. package/src/runtime/client/remote-functions/index.js +1 -1
  20. package/src/runtime/client/remote-functions/shared.svelte.js +11 -14
  21. package/src/runtime/server/cookie.js +2 -1
  22. package/src/runtime/server/data/index.js +3 -4
  23. package/src/runtime/server/page/crypto.js +3 -58
  24. package/src/runtime/server/page/csp.js +2 -2
  25. package/src/runtime/server/page/load_data.js +6 -5
  26. package/src/runtime/server/page/render.js +3 -4
  27. package/src/runtime/server/remote.js +29 -33
  28. package/src/runtime/shared.js +8 -12
  29. package/src/runtime/utils.js +43 -33
  30. package/src/types/internal.d.ts +1 -1
  31. package/src/version.js +1 -1
  32. package/types/index.d.ts +42 -15
  33. package/types/index.d.ts.map +1 -1
  34. package/src/exports/vite/graph_analysis/index.js +0 -87
  35. package/src/exports/vite/graph_analysis/types.d.ts +0 -5
  36. package/src/exports/vite/graph_analysis/utils.js +0 -6
  37. package/src/runtime/client/remote-functions/command.js +0 -71
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.27.3",
3
+ "version": "2.29.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -126,6 +126,17 @@ export function validate_config(config) {
126
126
  }
127
127
 
128
128
  const validated = options(config, 'config');
129
+ const files = validated.kit.files;
130
+
131
+ files.hooks.client ??= path.join(files.src, 'hooks.client');
132
+ files.hooks.server ??= path.join(files.src, 'hooks.server');
133
+ files.hooks.universal ??= path.join(files.src, 'hooks');
134
+ files.lib ??= path.join(files.src, 'lib');
135
+ files.params ??= path.join(files.src, 'params');
136
+ files.routes ??= path.join(files.src, 'routes');
137
+ files.serviceWorker ??= path.join(files.src, 'service-worker');
138
+ files.appTemplate ??= path.join(files.src, 'app.html');
139
+ files.errorTemplate ??= path.join(files.src, 'error.html');
129
140
 
130
141
  if (validated.kit.router.resolution === 'server') {
131
142
  if (validated.kit.router.type === 'hash') {
@@ -1,4 +1,3 @@
1
- import { join } from 'node:path';
2
1
  import process from 'node:process';
3
2
 
4
3
  /** @typedef {import('./types.js').Validator} Validator */
@@ -125,18 +124,19 @@ const options = object(
125
124
  }),
126
125
 
127
126
  files: object({
128
- assets: string('static'),
127
+ src: deprecate(string('src')),
128
+ assets: deprecate(string('static')),
129
129
  hooks: object({
130
- client: string(join('src', 'hooks.client')),
131
- server: string(join('src', 'hooks.server')),
132
- universal: string(join('src', 'hooks'))
130
+ client: deprecate(string(null)),
131
+ server: deprecate(string(null)),
132
+ universal: deprecate(string(null))
133
133
  }),
134
- lib: string(join('src', 'lib')),
135
- params: string(join('src', 'params')),
136
- routes: string(join('src', 'routes')),
137
- serviceWorker: string(join('src', 'service-worker')),
138
- appTemplate: string(join('src', 'app.html')),
139
- errorTemplate: string(join('src', 'error.html'))
134
+ lib: deprecate(string(null)),
135
+ params: deprecate(string(null)),
136
+ routes: deprecate(string(null)),
137
+ serviceWorker: deprecate(string(null)),
138
+ appTemplate: deprecate(string(null)),
139
+ errorTemplate: deprecate(string(null))
140
140
  }),
141
141
 
142
142
  inlineStyleThreshold: number(0),
@@ -287,6 +287,25 @@ const options = object(
287
287
  true
288
288
  );
289
289
 
290
+ /**
291
+ * @param {Validator} fn
292
+ * @param {(keypath: string) => string} get_message
293
+ * @returns {Validator}
294
+ */
295
+ function deprecate(
296
+ fn,
297
+ get_message = (keypath) =>
298
+ `The \`${keypath}\` option is deprecated, and will be removed in a future version`
299
+ ) {
300
+ return (input, keypath) => {
301
+ if (input !== undefined) {
302
+ console.warn(get_message(keypath));
303
+ }
304
+
305
+ return fn(input, keypath);
306
+ };
307
+ }
308
+
290
309
  /**
291
310
  * @param {Record<string, Validator>} children
292
311
  * @param {boolean} [allow_unknown]
@@ -242,6 +242,11 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
242
242
  const filepath = saved.get(file);
243
243
  if (filepath) return readFileSync(filepath);
244
244
 
245
+ // Static assets emitted during build
246
+ if (file.startsWith(config.appDir)) {
247
+ return readFileSync(`${out}/server/${file}`);
248
+ }
249
+
245
250
  // stuff in `static`
246
251
  return readFileSync(join(config.files.assets, file));
247
252
  },
@@ -8,6 +8,7 @@ import {
8
8
  strip_data_suffix,
9
9
  strip_resolution_suffix
10
10
  } from '../runtime/pathname.js';
11
+ import { text_encoder } from '../runtime/utils.js';
11
12
 
12
13
  export { VERSION } from '../version.js';
13
14
 
@@ -142,7 +143,7 @@ export function json(data, init) {
142
143
  // means less duplicated work
143
144
  const headers = new Headers(init?.headers);
144
145
  if (!headers.has('content-length')) {
145
- headers.set('content-length', encoder.encode(body).byteLength.toString());
146
+ headers.set('content-length', text_encoder.encode(body).byteLength.toString());
146
147
  }
147
148
 
148
149
  if (!headers.has('content-type')) {
@@ -155,8 +156,6 @@ export function json(data, init) {
155
156
  });
156
157
  }
157
158
 
158
- const encoder = new TextEncoder();
159
-
160
159
  /**
161
160
  * Create a `Response` object from the supplied body.
162
161
  * @param {string} body The value that will be used as-is.
@@ -165,7 +164,7 @@ const encoder = new TextEncoder();
165
164
  export function text(body, init) {
166
165
  const headers = new Headers(init?.headers);
167
166
  if (!headers.has('content-length')) {
168
- const encoded = encoder.encode(body);
167
+ const encoded = text_encoder.encode(body);
169
168
  headers.set('content-length', encoded.byteLength.toString());
170
169
  return new Response(encoded, {
171
170
  ...init,
@@ -420,26 +420,38 @@ export interface KitConfig {
420
420
  };
421
421
  /**
422
422
  * Where to find various files within your project.
423
+ * @deprecated
423
424
  */
424
425
  files?: {
426
+ /**
427
+ * the location of your source code
428
+ * @deprecated
429
+ * @default "src"
430
+ * @since 2.28
431
+ */
432
+ src?: string;
425
433
  /**
426
434
  * a place to put static files that should have stable URLs and undergo no processing, such as `favicon.ico` or `manifest.json`
435
+ * @deprecated
427
436
  * @default "static"
428
437
  */
429
438
  assets?: string;
430
439
  hooks?: {
431
440
  /**
432
441
  * The location of your client [hooks](https://svelte.dev/docs/kit/hooks).
442
+ * @deprecated
433
443
  * @default "src/hooks.client"
434
444
  */
435
445
  client?: string;
436
446
  /**
437
447
  * The location of your server [hooks](https://svelte.dev/docs/kit/hooks).
448
+ * @deprecated
438
449
  * @default "src/hooks.server"
439
450
  */
440
451
  server?: string;
441
452
  /**
442
453
  * The location of your universal [hooks](https://svelte.dev/docs/kit/hooks).
454
+ * @deprecated
443
455
  * @default "src/hooks"
444
456
  * @since 2.3.0
445
457
  */
@@ -447,31 +459,37 @@ export interface KitConfig {
447
459
  };
448
460
  /**
449
461
  * your app's internal library, accessible throughout the codebase as `$lib`
462
+ * @deprecated
450
463
  * @default "src/lib"
451
464
  */
452
465
  lib?: string;
453
466
  /**
454
467
  * a directory containing [parameter matchers](https://svelte.dev/docs/kit/advanced-routing#Matching)
468
+ * @deprecated
455
469
  * @default "src/params"
456
470
  */
457
471
  params?: string;
458
472
  /**
459
473
  * the files that define the structure of your app (see [Routing](https://svelte.dev/docs/kit/routing))
474
+ * @deprecated
460
475
  * @default "src/routes"
461
476
  */
462
477
  routes?: string;
463
478
  /**
464
479
  * the location of your service worker's entry point (see [Service workers](https://svelte.dev/docs/kit/service-workers))
480
+ * @deprecated
465
481
  * @default "src/service-worker"
466
482
  */
467
483
  serviceWorker?: string;
468
484
  /**
469
485
  * the location of the template for HTML responses
486
+ * @deprecated
470
487
  * @default "src/app.html"
471
488
  */
472
489
  appTemplate?: string;
473
490
  /**
474
491
  * the location of the template for fallback error responses
492
+ * @deprecated
475
493
  * @default "src/error.html"
476
494
  */
477
495
  errorTemplate?: string;
@@ -1020,12 +1038,15 @@ export interface NavigationEvent<
1020
1038
  /**
1021
1039
  * Information about the target of a specific navigation.
1022
1040
  */
1023
- export interface NavigationTarget {
1041
+ export interface NavigationTarget<
1042
+ Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>,
1043
+ RouteId extends AppRouteId | null = AppRouteId | null
1044
+ > {
1024
1045
  /**
1025
1046
  * Parameters of the target page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object.
1026
1047
  * Is `null` if the target is not part of the SvelteKit app (could not be resolved to a route).
1027
1048
  */
1028
- params: Record<string, string> | null;
1049
+ params: Params | null;
1029
1050
  /**
1030
1051
  * Info about the target route
1031
1052
  */
@@ -1033,7 +1054,7 @@ export interface NavigationTarget {
1033
1054
  /**
1034
1055
  * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`. It is `null` when no route is matched.
1035
1056
  */
1036
- id: string | null;
1057
+ id: RouteId | null;
1037
1058
  };
1038
1059
  /**
1039
1060
  * The URL that is navigated to
@@ -1043,8 +1064,8 @@ export interface NavigationTarget {
1043
1064
 
1044
1065
  /**
1045
1066
  * - `enter`: The app has hydrated/started
1046
- * - `form`: The user submitted a `<form>` with a GET method
1047
- * - `leave`: The user is leaving the app by closing the tab or using the back/forward buttons to go to a different document
1067
+ * - `form`: The user submitted a `<form method="GET">`
1068
+ * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1048
1069
  * - `link`: Navigation was triggered by a link click
1049
1070
  * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1050
1071
  * - `popstate`: Navigation was triggered by back/forward navigation
@@ -1062,7 +1083,7 @@ export interface Navigation {
1062
1083
  to: NavigationTarget | null;
1063
1084
  /**
1064
1085
  * The type of navigation:
1065
- * - `form`: The user submitted a `<form>`
1086
+ * - `form`: The user submitted a `<form method="GET">`
1066
1087
  * - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
1067
1088
  * - `link`: Navigation was triggered by a link click
1068
1089
  * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
@@ -1100,7 +1121,7 @@ export interface BeforeNavigate extends Navigation {
1100
1121
  export interface OnNavigate extends Navigation {
1101
1122
  /**
1102
1123
  * The type of navigation:
1103
- * - `form`: The user submitted a `<form>`
1124
+ * - `form`: The user submitted a `<form method="GET">`
1104
1125
  * - `link`: Navigation was triggered by a link click
1105
1126
  * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1106
1127
  * - `popstate`: Navigation was triggered by back/forward navigation
@@ -1119,7 +1140,7 @@ export interface AfterNavigate extends Omit<Navigation, 'type'> {
1119
1140
  /**
1120
1141
  * The type of navigation:
1121
1142
  * - `enter`: The app has hydrated/started
1122
- * - `form`: The user submitted a `<form>`
1143
+ * - `form`: The user submitted a `<form method="GET">`
1123
1144
  * - `link`: Navigation was triggered by a link click
1124
1145
  * - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
1125
1146
  * - `popstate`: Navigation was triggered by back/forward navigation
@@ -1565,6 +1586,8 @@ export type RemoteForm<Result> = {
1565
1586
  for(key: string | number | boolean): Omit<RemoteForm<Result>, 'for'>;
1566
1587
  /** The result of the form submission */
1567
1588
  get result(): Result | undefined;
1589
+ /** The number of pending submissions */
1590
+ get pending(): number;
1568
1591
  /** Spread this onto a `<button>` or `<input type="submit">` */
1569
1592
  buttonProps: {
1570
1593
  type: 'submit';
@@ -1586,14 +1609,20 @@ export type RemoteForm<Result> = {
1586
1609
  formaction: string;
1587
1610
  onclick: (event: Event) => void;
1588
1611
  };
1612
+ /** The number of pending submissions */
1613
+ get pending(): number;
1589
1614
  };
1590
1615
  };
1591
1616
 
1592
1617
  /**
1593
1618
  * The return value of a remote `command` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
1594
1619
  */
1595
- export type RemoteCommand<Input, Output> = (arg: Input) => Promise<Awaited<Output>> & {
1596
- updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<Awaited<Output>>;
1620
+ export type RemoteCommand<Input, Output> = {
1621
+ (arg: Input): Promise<Awaited<Output>> & {
1622
+ updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<Awaited<Output>>;
1623
+ };
1624
+ /** The number of pending command executions */
1625
+ get pending(): number;
1597
1626
  };
1598
1627
 
1599
1628
  export type RemoteResource<T> = Promise<Awaited<T>> & {
@@ -1623,7 +1652,7 @@ export type RemoteQuery<T> = RemoteResource<T> & {
1623
1652
  */
1624
1653
  refresh(): Promise<void>;
1625
1654
  /**
1626
- * Temporarily override the value of a query. This is used with the `updates` method of a [command](https://svelte.dev/docs/kit/remote-functions#command-Single-flight-mutations) or [enhanced form submission](https://svelte.dev/docs/kit/remote-functions#form-enhance) to provide optimistic updates.
1655
+ * Temporarily override the value of a query. This is used with the `updates` method of a [command](https://svelte.dev/docs/kit/remote-functions#command-Updating-queries) or [enhanced form submission](https://svelte.dev/docs/kit/remote-functions#form-enhance) to provide optimistic updates.
1627
1656
  *
1628
1657
  * ```svelte
1629
1658
  * <script>
@@ -102,9 +102,10 @@ export async function treeshake_prerendered_remotes(out, manifest_data, metadata
102
102
  input[prefix + remote.hash] = remote_file;
103
103
  }
104
104
 
105
- const bundle = await vite.build({
105
+ const bundle = /** @type {import('vite').Rollup.RollupOutput} */ (await vite.build({
106
106
  configFile: false,
107
107
  build: {
108
+ write: false,
108
109
  ssr: true,
109
110
  rollupOptions: {
110
111
  external: (id) => {
@@ -114,11 +115,10 @@ export async function treeshake_prerendered_remotes(out, manifest_data, metadata
114
115
  input
115
116
  }
116
117
  }
117
- });
118
+ }));
118
119
 
119
- // @ts-expect-error TypeScript doesn't know what type `bundle` is
120
120
  for (const chunk of bundle.output) {
121
- if (chunk.name.startsWith(prefix)) {
121
+ if (chunk.type === 'chunk' && chunk.name.startsWith(prefix)) {
122
122
  fs.writeFileSync(`${dir}/${chunk.fileName.slice(prefix.length)}`, chunk.code);
123
123
  }
124
124
  }
@@ -15,9 +15,8 @@ import { build_server_nodes } from './build/build_server.js';
15
15
  import { build_service_worker } from './build/build_service_worker.js';
16
16
  import { assets_base, find_deps, resolve_symlinks } from './build/utils.js';
17
17
  import { dev } from './dev/index.js';
18
- import { is_illegal, module_guard } from './graph_analysis/index.js';
19
18
  import { preview } from './preview/index.js';
20
- import { get_config_aliases, get_env, normalize_id, strip_virtual_prefix } from './utils.js';
19
+ import { get_config_aliases, get_env, normalize_id, stackless } from './utils.js';
21
20
  import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
22
21
  import prerender from '../../core/postbuild/prerender.js';
23
22
  import analyse from '../../core/postbuild/analyse.js';
@@ -37,6 +36,7 @@ import {
37
36
  import { import_peer } from '../../utils/import.js';
38
37
  import { compact } from '../../utils/array.js';
39
38
  import { build_remotes, treeshake_prerendered_remotes } from './build/build_remote.js';
39
+ import { should_ignore } from './static_analysis/utils.js';
40
40
 
41
41
  const cwd = process.cwd();
42
42
 
@@ -90,7 +90,7 @@ const warning_preprocessor = {
90
90
  const basename = path.basename(filename);
91
91
  if (basename.startsWith('+page.') || basename.startsWith('+layout.')) {
92
92
  const match = content.match(options_regex);
93
- if (match) {
93
+ if (match && match.index !== undefined && !should_ignore(content, match.index)) {
94
94
  const fixed = basename.replace('.svelte', '(.server).js/ts');
95
95
 
96
96
  const message =
@@ -219,6 +219,7 @@ async function kit({ svelte_config }) {
219
219
 
220
220
  const normalized_cwd = vite.normalizePath(cwd);
221
221
  const normalized_lib = vite.normalizePath(kit.files.lib);
222
+ const normalized_node_modules = vite.normalizePath(path.resolve('node_modules'));
222
223
 
223
224
  /**
224
225
  * A map showing which features (such as `$app/server:read`) are defined
@@ -374,9 +375,6 @@ async function kit({ svelte_config }) {
374
375
  }
375
376
  };
376
377
 
377
- /** @type {Map<string, string>} */
378
- const import_map = new Map();
379
-
380
378
  /** @type {import('vite').Plugin} */
381
379
  const plugin_virtual_modules = {
382
380
  name: 'vite-plugin-sveltekit-virtual-modules',
@@ -389,6 +387,7 @@ async function kit({ svelte_config }) {
389
387
  // If importing from a service-worker, only allow $service-worker & $env/static/public, but none of the other virtual modules.
390
388
  // This check won't catch transitive imports, but it will warn when the import comes from a service-worker directly.
391
389
  // Transitive imports will be caught during the build.
390
+ // TODO move this logic to plugin_guard
392
391
  if (importer) {
393
392
  const parsed_importer = path.parse(importer);
394
393
 
@@ -405,8 +404,6 @@ async function kit({ svelte_config }) {
405
404
  )} into service-worker code. Only the modules $service-worker and $env/static/public are available in service workers.`
406
405
  );
407
406
  }
408
-
409
- import_map.set(id, importer);
410
407
  }
411
408
 
412
409
  // treat $env/static/[public|private] as virtual
@@ -414,9 +411,11 @@ async function kit({ svelte_config }) {
414
411
  // ids with :$ don't work with reverse proxies like nginx
415
412
  return `\0virtual:${id.substring(1)}`;
416
413
  }
414
+
417
415
  if (id === '__sveltekit/remote') {
418
416
  return `${runtime_directory}/client/remote-functions/index.js`;
419
417
  }
418
+
420
419
  if (id.startsWith('__sveltekit/')) {
421
420
  return `\0virtual:${id}`;
422
421
  }
@@ -429,37 +428,6 @@ async function kit({ svelte_config }) {
429
428
  ? `globalThis.__sveltekit_${version_hash}`
430
429
  : 'globalThis.__sveltekit_dev';
431
430
 
432
- if (options?.ssr === false && process.env.TEST !== 'true') {
433
- if (
434
- is_illegal(id, {
435
- cwd: normalized_cwd,
436
- node_modules: vite.normalizePath(path.resolve('node_modules')),
437
- server: vite.normalizePath(path.join(normalized_lib, 'server'))
438
- })
439
- ) {
440
- const relative = normalize_id(id, normalized_lib, normalized_cwd);
441
-
442
- const illegal_module = strip_virtual_prefix(relative);
443
-
444
- const error_prefix = `Cannot import ${illegal_module} into client-side code. This could leak sensitive information.`;
445
- const error_suffix = `
446
- Tips:
447
- - To resolve this error, ensure that no exports from ${illegal_module} are used, even transitively, in client-side code.
448
- - If you're only using the import as a type, change it to \`import type\`.
449
- - If you're not sure which module is causing this, try building your app -- it will create a more helpful error.`;
450
-
451
- if (import_map.has(illegal_module)) {
452
- const importer = path.relative(
453
- cwd,
454
- /** @type {string} */ (import_map.get(illegal_module))
455
- );
456
- throw new Error(`${error_prefix}\nImported by: ${importer}.${error_suffix}`);
457
- }
458
-
459
- throw new Error(`${error_prefix}${error_suffix}`);
460
- }
461
- }
462
-
463
431
  switch (id) {
464
432
  case env_static_private:
465
433
  return create_static_module('$env/static/private', env.private);
@@ -566,6 +534,10 @@ Tips:
566
534
  }
567
535
  };
568
536
 
537
+ /** @type {Map<string, Set<string>>} */
538
+ const import_map = new Map();
539
+ const server_only_pattern = /.*\.server\..+/;
540
+
569
541
  /**
570
542
  * Ensures that client-side code can't accidentally import server-side code,
571
543
  * whether in `*.server.js` files, `$app/server`, `$lib/server`, or `$env/[static|dynamic]/private`
@@ -574,23 +546,89 @@ Tips:
574
546
  const plugin_guard = {
575
547
  name: 'vite-plugin-sveltekit-guard',
576
548
 
577
- writeBundle: {
578
- sequential: true,
579
- handler(_options) {
580
- if (vite_config.build.ssr) return;
549
+ // Run this plugin before built-in resolution, so that relative imports
550
+ // are added to the module graph
551
+ enforce: 'pre',
581
552
 
582
- const guard = module_guard(this, {
583
- cwd: vite.normalizePath(process.cwd()),
584
- lib: vite.normalizePath(kit.files.lib)
585
- });
553
+ async resolveId(id, importer) {
554
+ if (importer && !importer.endsWith('index.html')) {
555
+ const resolved = await this.resolve(id, importer, { skipSelf: true });
586
556
 
587
- manifest_data.nodes.forEach((_node, i) => {
588
- const id = vite.normalizePath(
589
- path.resolve(kit.outDir, `generated/client-optimized/nodes/${i}.js`)
590
- );
557
+ if (resolved) {
558
+ const normalized = normalize_id(resolved.id, normalized_lib, normalized_cwd);
591
559
 
592
- guard.check(id);
593
- });
560
+ let importers = import_map.get(normalized);
561
+
562
+ if (!importers) {
563
+ importers = new Set();
564
+ import_map.set(normalized, importers);
565
+ }
566
+
567
+ importers.add(normalize_id(importer, normalized_lib, normalized_cwd));
568
+ }
569
+ }
570
+ },
571
+
572
+ load(id, options) {
573
+ if (options?.ssr === true || process.env.TEST === 'true') {
574
+ return;
575
+ }
576
+
577
+ // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context
578
+ const is_internal = id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules);
579
+
580
+ const normalized = normalize_id(id, normalized_lib, normalized_cwd);
581
+
582
+ const is_server_only =
583
+ normalized === '$env/static/private' ||
584
+ normalized === '$env/dynamic/private' ||
585
+ normalized === '$app/server' ||
586
+ normalized.startsWith('$lib/server/') ||
587
+ (is_internal && server_only_pattern.test(path.basename(id)));
588
+
589
+ if (is_server_only) {
590
+ // in dev, this doesn't exist, so we need to create it
591
+ manifest_data ??= sync.all(svelte_config, vite_config_env.mode).manifest_data;
592
+
593
+ /** @type {Set<string>} */
594
+ const entrypoints = new Set();
595
+ for (const node of manifest_data.nodes) {
596
+ if (node.component) entrypoints.add(node.component);
597
+ if (node.universal) entrypoints.add(node.universal);
598
+ }
599
+
600
+ const normalized = normalize_id(id, normalized_lib, normalized_cwd);
601
+ const chain = [normalized];
602
+
603
+ let current = normalized;
604
+
605
+ while (true) {
606
+ const importers = import_map.get(current);
607
+ if (!importers) break;
608
+
609
+ const candidates = Array.from(importers).filter((importer) => !chain.includes(importer));
610
+ if (candidates.length === 0) break;
611
+
612
+ chain.push((current = candidates[0]));
613
+
614
+ if (entrypoints.has(current)) {
615
+ let message = `Cannot import ${normalized} into code that runs in the browser, as this could leak sensitive information.`;
616
+
617
+ const pyramid = chain
618
+ .reverse()
619
+ .map((id, i) => {
620
+ return `${' '.repeat(i + 1)}${id}`;
621
+ })
622
+ .join(' imports\n');
623
+
624
+ message += `\n\n${pyramid}`;
625
+ message += `\n\nIf you're only using the import as a type, change it to \`import type\`.`;
626
+
627
+ throw stackless(message);
628
+ }
629
+ }
630
+
631
+ throw new Error('An impossible situation occurred');
594
632
  }
595
633
  }
596
634
  };
@@ -956,23 +994,36 @@ Tips:
956
994
 
957
995
  secondary_build_started = true;
958
996
 
959
- const { output: client_chunks } = /** @type {import('vite').Rollup.RollupOutput} */ (
960
- await vite.build({
961
- configFile: vite_config.configFile,
962
- // CLI args
963
- mode: vite_config_env.mode,
964
- logLevel: vite_config.logLevel,
965
- clearScreen: vite_config.clearScreen,
966
- build: {
967
- minify: initial_config.build?.minify,
968
- assetsInlineLimit: vite_config.build.assetsInlineLimit,
969
- sourcemap: vite_config.build.sourcemap
970
- },
971
- optimizeDeps: {
972
- force: vite_config.optimizeDeps.force
973
- }
974
- })
975
- );
997
+ let client_chunks;
998
+
999
+ try {
1000
+ const bundle = /** @type {import('vite').Rollup.RollupOutput} */ (
1001
+ await vite.build({
1002
+ configFile: vite_config.configFile,
1003
+ // CLI args
1004
+ mode: vite_config_env.mode,
1005
+ logLevel: vite_config.logLevel,
1006
+ clearScreen: vite_config.clearScreen,
1007
+ build: {
1008
+ minify: initial_config.build?.minify,
1009
+ assetsInlineLimit: vite_config.build.assetsInlineLimit,
1010
+ sourcemap: vite_config.build.sourcemap
1011
+ },
1012
+ optimizeDeps: {
1013
+ force: vite_config.optimizeDeps.force
1014
+ }
1015
+ })
1016
+ );
1017
+
1018
+ client_chunks = bundle.output;
1019
+ } catch (e) {
1020
+ const error =
1021
+ e instanceof Error ? e : new Error(/** @type {any} */ (e).message ?? e ?? '<unknown>');
1022
+
1023
+ // without this, errors that occur during the secondary build
1024
+ // will be logged twice
1025
+ throw stackless(error.stack ?? error.message);
1026
+ }
976
1027
 
977
1028
  copy(
978
1029
  `${out}/server/${kit.appDir}/immutable/assets`,