@sveltejs/kit 2.54.0 → 2.56.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 (45) hide show
  1. package/package.json +3 -4
  2. package/src/core/postbuild/analyse.js +3 -3
  3. package/src/core/postbuild/prerender.js +9 -6
  4. package/src/core/sync/write_non_ambient.js +143 -37
  5. package/src/core/sync/write_tsconfig.js +3 -1
  6. package/src/core/sync/write_types/index.js +1 -5
  7. package/src/exports/internal/remote-functions.js +2 -2
  8. package/src/exports/public.d.ts +38 -12
  9. package/src/exports/vite/build/build_server.js +24 -4
  10. package/src/exports/vite/build/build_service_worker.js +16 -6
  11. package/src/exports/vite/build/utils.js +18 -3
  12. package/src/exports/vite/index.js +336 -327
  13. package/src/runtime/app/paths/server.js +1 -1
  14. package/src/runtime/app/server/index.js +1 -1
  15. package/src/runtime/app/server/remote/command.js +12 -7
  16. package/src/runtime/app/server/remote/form.js +14 -14
  17. package/src/runtime/app/server/remote/index.js +1 -0
  18. package/src/runtime/app/server/remote/prerender.js +8 -7
  19. package/src/runtime/app/server/remote/query.js +141 -66
  20. package/src/runtime/app/server/remote/requested.js +172 -0
  21. package/src/runtime/app/server/remote/shared.js +32 -10
  22. package/src/runtime/app/state/server.js +1 -1
  23. package/src/runtime/client/client.js +45 -20
  24. package/src/runtime/client/remote-functions/command.svelte.js +39 -16
  25. package/src/runtime/client/remote-functions/form.svelte.js +41 -24
  26. package/src/runtime/client/remote-functions/prerender.svelte.js +105 -76
  27. package/src/runtime/client/remote-functions/query.svelte.js +408 -138
  28. package/src/runtime/client/remote-functions/shared.svelte.js +95 -94
  29. package/src/runtime/components/svelte-5/error.svelte +2 -0
  30. package/src/runtime/form-utils.js +3 -7
  31. package/src/runtime/server/endpoint.js +0 -1
  32. package/src/runtime/server/page/actions.js +2 -1
  33. package/src/runtime/server/page/load_data.js +3 -1
  34. package/src/runtime/server/page/render.js +38 -15
  35. package/src/runtime/server/remote.js +65 -50
  36. package/src/runtime/server/respond.js +17 -3
  37. package/src/runtime/server/utils.js +0 -12
  38. package/src/runtime/shared.js +233 -5
  39. package/src/types/global-private.d.ts +4 -4
  40. package/src/types/internal.d.ts +80 -44
  41. package/src/utils/css.js +0 -3
  42. package/src/utils/escape.js +15 -3
  43. package/src/version.js +1 -1
  44. package/types/index.d.ts +67 -13
  45. package/types/index.d.ts.map +6 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.54.0",
3
+ "version": "2.56.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -40,8 +40,7 @@
40
40
  "@types/set-cookie-parser": "^2.4.7",
41
41
  "dts-buddy": "^0.7.0",
42
42
  "rollup": "^4.59.0",
43
- "svelte": "^5.53.5",
44
- "svelte-preprocess": "^6.0.0",
43
+ "svelte": "^5.53.12",
45
44
  "typescript": "^5.3.3",
46
45
  "vite": "^6.3.5",
47
46
  "vitest": "^4.0.0"
@@ -50,7 +49,7 @@
50
49
  "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0",
51
50
  "@opentelemetry/api": "^1.0.0",
52
51
  "svelte": "^4.0.0 || ^5.0.0-next.0",
53
- "typescript": "^5.3.3",
52
+ "typescript": "^5.3.3 || ^6.0.0",
54
53
  "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0"
55
54
  },
56
55
  "peerDependenciesMeta": {
@@ -163,12 +163,12 @@ async function analyse({
163
163
  const exports = new Map();
164
164
 
165
165
  for (const name in functions) {
166
- const info = /** @type {import('types').RemoteInfo} */ (functions[name].__);
167
- const type = info.type;
166
+ const internals = /** @type {import('types').RemoteInternals} */ (functions[name].__);
167
+ const type = internals.type;
168
168
 
169
169
  exports.set(name, {
170
170
  type,
171
- dynamic: type !== 'prerender' || info.dynamic
171
+ dynamic: type !== 'prerender' || internals.dynamic
172
172
  });
173
173
  }
174
174
 
@@ -496,7 +496,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
496
496
  internal.set_manifest(manifest);
497
497
  internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`));
498
498
 
499
- /** @type {Array<import('types').RemoteInfo & { type: 'prerender'}>} */
499
+ /** @type {Array<import('types').RemotePrerenderInternals>} */
500
500
  const prerender_functions = [];
501
501
 
502
502
  for (const loader of Object.values(manifest._.remotes)) {
@@ -549,13 +549,16 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
549
549
  }
550
550
 
551
551
  const transport = (await internal.get_hooks()).transport ?? {};
552
- for (const info of prerender_functions) {
553
- if (info.has_arg) {
554
- for (const arg of (await info.inputs?.()) ?? []) {
555
- void enqueue(null, remote_prefix + info.id + '/' + stringify_remote_arg(arg, transport));
552
+ for (const internals of prerender_functions) {
553
+ if (internals.has_arg) {
554
+ for (const arg of (await internals.inputs?.()) ?? []) {
555
+ void enqueue(
556
+ null,
557
+ remote_prefix + internals.id + '/' + stringify_remote_arg(arg, transport)
558
+ );
556
559
  }
557
560
  } else {
558
- void enqueue(null, remote_prefix + info.id);
561
+ void enqueue(null, remote_prefix + internals.id);
559
562
  }
560
563
  }
561
564
 
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { GENERATED_COMMENT } from '../../constants.js';
3
+ import { posixify } from '../../utils/filesystem.js';
3
4
  import { write_if_changed } from './utils.js';
4
5
  import { s } from '../../utils/misc.js';
5
6
  import { get_route_segments } from '../../utils/routing.js';
@@ -24,25 +25,25 @@ function get_pathnames_for_trailing_slash(pathname, route) {
24
25
  return [pathname];
25
26
  }
26
27
 
27
- /** @type {({ trailingSlash?: import('types').TrailingSlash } | null)[]} */
28
- const routes = [];
29
-
30
- if (route.leaf) routes.push(route.leaf.page_options ?? null);
31
- if (route.endpoint) routes.push(route.endpoint.page_options);
32
-
33
28
  /** @type {Set<string>} */
34
29
  const pathnames = new Set();
35
30
 
36
- for (const page_options of routes) {
37
- if (page_options === null || page_options.trailingSlash === 'ignore') {
31
+ /**
32
+ * @param {{ trailingSlash?: import('types').TrailingSlash } | null | undefined} page_options
33
+ */
34
+ const add_pathnames = (page_options) => {
35
+ if (page_options === null || page_options?.trailingSlash === 'ignore') {
38
36
  pathnames.add(pathname);
39
37
  pathnames.add(pathname + '/');
40
- } else if (page_options.trailingSlash === 'always') {
38
+ } else if (page_options?.trailingSlash === 'always') {
41
39
  pathnames.add(pathname + '/');
42
40
  } else {
43
41
  pathnames.add(pathname);
44
42
  }
45
- }
43
+ };
44
+
45
+ if (route.leaf) add_pathnames(route.leaf.page_options ?? null);
46
+ if (route.endpoint) add_pathnames(route.endpoint.page_options);
46
47
 
47
48
  return Array.from(pathnames);
48
49
  }
@@ -81,8 +82,66 @@ export {};
81
82
  /**
82
83
  * Generate app types interface extension
83
84
  * @param {import('types').ManifestData} manifest_data
85
+ * @param {import('types').ValidatedKitConfig} config
84
86
  */
85
- function generate_app_types(manifest_data) {
87
+ function generate_app_types(manifest_data, config) {
88
+ /** @param {string} matcher */
89
+ const path_to_matcher = (matcher) =>
90
+ posixify(path.relative(config.outDir, path.join(config.files.params, matcher + '.js')));
91
+
92
+ /** @type {Map<string, string>} */
93
+ const matcher_types = new Map();
94
+
95
+ /** @param {string | undefined} matcher */
96
+ const get_matcher_type = (matcher) => {
97
+ if (!matcher) return 'string';
98
+
99
+ let type = matcher_types.get(matcher);
100
+ if (!type) {
101
+ type = `MatcherParam<typeof import('${path_to_matcher(matcher)}').match>`;
102
+ matcher_types.set(matcher, type);
103
+ }
104
+
105
+ return type;
106
+ };
107
+
108
+ /** @param {Set<string> | null} matchers */
109
+ const get_matchers_type = (matchers) => {
110
+ if (matchers === null) return 'string';
111
+
112
+ return Array.from(matchers)
113
+ .map((matcher) => get_matcher_type(matcher))
114
+ .join(' | ');
115
+ };
116
+
117
+ /** @type {Set<string>} */
118
+ const route_ids = new Set(manifest_data.routes.map((route) => route.id));
119
+
120
+ /**
121
+ * @param {string} id
122
+ * @returns {string[]}
123
+ */
124
+ const get_ancestor_route_ids = (id) => {
125
+ /** @type {string[]} */
126
+ const ancestors = [];
127
+
128
+ if (route_ids.has('/')) {
129
+ ancestors.push('/');
130
+ }
131
+
132
+ let current = '';
133
+ for (const segment of id.slice(1).split('/')) {
134
+ if (!segment) continue;
135
+
136
+ current += '/' + segment;
137
+ if (route_ids.has(current)) {
138
+ ancestors.push(current);
139
+ }
140
+ }
141
+
142
+ return ancestors;
143
+ };
144
+
86
145
  /** @type {Set<string>} */
87
146
  const pathnames = new Set();
88
147
 
@@ -92,49 +151,96 @@ function generate_app_types(manifest_data) {
92
151
  /** @type {string[]} */
93
152
  const layouts = [];
94
153
 
154
+ /** @type {Map<string, Map<string, { optional: boolean, matchers: Set<string> | null }>>} */
155
+ const layout_params_by_route = new Map(
156
+ manifest_data.routes.map((route) => [
157
+ route.id,
158
+ new Map(
159
+ route.params.map((p) => [
160
+ p.name,
161
+ { optional: p.optional, matchers: p.matcher ? new Set([p.matcher]) : null }
162
+ ])
163
+ )
164
+ ])
165
+ );
166
+
95
167
  for (const route of manifest_data.routes) {
168
+ const ancestors = get_ancestor_route_ids(route.id);
169
+
170
+ for (const ancestor_id of ancestors) {
171
+ const ancestor_params = layout_params_by_route.get(ancestor_id);
172
+ if (!ancestor_params) continue;
173
+
174
+ for (const p of route.params) {
175
+ const matcher = p.matcher ?? null;
176
+ const entry = ancestor_params.get(p.name);
177
+ if (!entry) {
178
+ ancestor_params.set(p.name, {
179
+ optional: true,
180
+ matchers: matcher === null ? null : new Set([matcher])
181
+ });
182
+ continue;
183
+ }
184
+
185
+ if (entry.matchers === null) continue;
186
+
187
+ if (matcher === null) {
188
+ entry.matchers = null;
189
+ continue;
190
+ }
191
+
192
+ entry.matchers.add(matcher);
193
+ }
194
+ }
195
+ }
196
+
197
+ for (const route of manifest_data.routes) {
198
+ const pathname = remove_group_segments(route.id);
199
+ let normalized_pathname = pathname;
200
+
201
+ /** @type {(path: string) => string} */
202
+ let serialise = s;
203
+
96
204
  if (route.params.length > 0) {
97
- const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`);
205
+ const params = route.params.map((p) => {
206
+ const type = get_matcher_type(p.matcher);
207
+ return `${p.name}${p.optional ? '?:' : ':'} ${type}`;
208
+ });
98
209
  const route_type = `${s(route.id)}: { ${params.join('; ')} }`;
99
210
 
100
211
  dynamic_routes.push(route_type);
101
212
 
102
- const pathname = remove_group_segments(route.id);
103
- const replaced_pathname = replace_required_params(replace_optional_params(pathname));
213
+ normalized_pathname = replace_required_params(replace_optional_params(pathname));
214
+ serialise = (p) => `\`${p}\` & {}`;
215
+ }
104
216
 
105
- for (const p of get_pathnames_for_trailing_slash(replaced_pathname, route)) {
106
- pathnames.add(`\`${p}\` & {}`);
107
- }
108
- } else {
109
- const pathname = remove_group_segments(route.id);
110
- for (const p of get_pathnames_for_trailing_slash(pathname, route)) {
111
- pathnames.add(s(p));
112
- }
217
+ for (const p of get_pathnames_for_trailing_slash(normalized_pathname, route)) {
218
+ pathnames.add(serialise(p));
113
219
  }
114
220
 
115
- /** @type {Map<string, boolean>} */
116
- const child_params = new Map(route.params.map((p) => [p.name, p.optional]));
221
+ let layout_type = 'Record<string, never>';
117
222
 
118
- for (const child of manifest_data.routes.filter((r) => r.id.startsWith(route.id))) {
119
- for (const p of child.params) {
120
- if (!child_params.has(p.name)) {
121
- child_params.set(p.name, true); // always optional
122
- }
123
- }
124
- }
223
+ const layout_params = layout_params_by_route.get(route.id);
224
+ if (layout_params) {
225
+ const params = Array.from(layout_params)
226
+ .map(([name, { optional, matchers }]) => {
227
+ const type = get_matchers_type(matchers);
228
+ return `${name}${optional ? '?:' : ':'} ${type}`;
229
+ })
230
+ .join('; ');
125
231
 
126
- const layout_params = Array.from(child_params)
127
- .map(([name, optional]) => `${name}${optional ? '?:' : ':'} string`)
128
- .join('; ');
232
+ if (params.length > 0) layout_type = `{ ${params} }`;
233
+ }
129
234
 
130
- const layout_type = `${s(route.id)}: ${layout_params.length > 0 ? `{ ${layout_params} }` : 'Record<string, never>'}`;
131
- layouts.push(layout_type);
235
+ layouts.push(`${s(route.id)}: ${layout_type}`);
132
236
  }
133
237
 
134
238
  const assets = manifest_data.assets.map((asset) => s('/' + asset.file));
135
239
 
136
240
  return [
137
241
  'declare module "$app/types" {',
242
+ '\ttype MatcherParam<M> = M extends (param : string) => param is (infer U extends string) ? U : string;',
243
+ '',
138
244
  '\texport interface AppTypes {',
139
245
  `\t\tRouteId(): ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`,
140
246
  `\t\tRouteParams(): {\n\t\t\t${dynamic_routes.join(';\n\t\t\t')}\n\t\t};`,
@@ -153,7 +259,7 @@ function generate_app_types(manifest_data) {
153
259
  * @param {import('types').ManifestData} manifest_data
154
260
  */
155
261
  export function write_non_ambient(config, manifest_data) {
156
- const app_types = generate_app_types(manifest_data);
262
+ const app_types = generate_app_types(manifest_data, config);
157
263
  const content = [template, app_types].join('\n\n');
158
264
 
159
265
  write_if_changed(path.join(config.outDir, 'non-ambient.d.ts'), content);
@@ -121,7 +121,8 @@ export function get_tsconfig(kit) {
121
121
  moduleResolution: 'bundler',
122
122
  module: 'esnext',
123
123
  noEmit: true, // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this
124
- target: 'esnext'
124
+ target: 'esnext',
125
+ types: ['node']
125
126
  },
126
127
  include: [...include],
127
128
  exclude
@@ -165,6 +166,7 @@ function validate_user_config(cwd, out, config) {
165
166
  if (extends_framework_config) {
166
167
  const { paths, baseUrl } = options;
167
168
 
169
+ // TODO: baseUrl will be removed in TypeScript 7.0
168
170
  if (baseUrl || paths) {
169
171
  console.warn(
170
172
  colors
@@ -199,11 +199,7 @@ function update_types(config, routes, route, to_delete = new Set()) {
199
199
 
200
200
  // returns the predicate of a matcher's type guard - or string if there is no type guard
201
201
  declarations.push(
202
- // TS complains on infer U, which seems weird, therefore ts-ignore it
203
- [
204
- '// @ts-ignore',
205
- 'type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;'
206
- ].join('\n')
202
+ 'type MatcherParam<M> = M extends (param : string) => param is (infer U extends string) ? U : string;'
207
203
  );
208
204
 
209
205
  declarations.push(
@@ -1,6 +1,6 @@
1
- /** @import { RemoteInfo } from 'types' */
1
+ /** @import { RemoteInternals } from 'types' */
2
2
 
3
- /** @type {RemoteInfo['type'][]} */
3
+ /** @type {RemoteInternals['type'][]} */
4
4
  const types = ['command', 'form', 'prerender', 'query', 'query_batch'];
5
5
 
6
6
  /**
@@ -1452,6 +1452,22 @@ export interface Page<
1452
1452
  */
1453
1453
  export type ParamMatcher = (param: string) => boolean;
1454
1454
 
1455
+ export type RequestedResult<T> = Iterable<T> &
1456
+ AsyncIterable<T> & {
1457
+ /**
1458
+ * Call `refresh` on all queries selected by this `requested` invocation.
1459
+ * This is identical to:
1460
+ * ```ts
1461
+ * import { requested } from '$app/server';
1462
+ *
1463
+ * for await (const arg of requested(query, ...) {
1464
+ * void query(arg).refresh();
1465
+ * }
1466
+ * ```
1467
+ */
1468
+ refreshAll: () => Promise<void>;
1469
+ };
1470
+
1455
1471
  export interface RequestEvent<
1456
1472
  Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>,
1457
1473
  RouteId extends AppRouteId | null = AppRouteId | null
@@ -1914,10 +1930,12 @@ export type RemoteFormFieldValue = string | string[] | number | boolean | File |
1914
1930
  type AsArgs<Type extends keyof InputTypeMap, Value> = Type extends 'checkbox'
1915
1931
  ? Value extends string[]
1916
1932
  ? [type: Type, value: Value[number] | (string & {})]
1917
- : [type: Type]
1933
+ : [type: Type] | [type: Type, value: Value | (string & {})]
1918
1934
  : Type extends 'radio' | 'submit' | 'hidden'
1919
1935
  ? [type: Type, value: Value | (string & {})]
1920
- : [type: Type];
1936
+ : Type extends 'file' | 'file multiple'
1937
+ ? [type: Type]
1938
+ : [type: Type] | [type: Type, value: Value | (string & {})];
1921
1939
 
1922
1940
  /**
1923
1941
  * Form field accessor type that provides name(), value(), and issues() methods
@@ -2058,7 +2076,7 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
2058
2076
  form: HTMLFormElement;
2059
2077
  data: Input;
2060
2078
  submit: () => Promise<void> & {
2061
- updates: (...queries: Array<RemoteQuery<any> | RemoteQueryOverride>) => Promise<void>;
2079
+ updates: (...updates: RemoteQueryUpdate[]) => Promise<void>;
2062
2080
  };
2063
2081
  }) => void | Promise<void>
2064
2082
  ): {
@@ -2102,14 +2120,19 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
2102
2120
  * The return value of a remote `command` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
2103
2121
  */
2104
2122
  export type RemoteCommand<Input, Output> = {
2105
- (arg: undefined extends Input ? Input | void : Input): Promise<Awaited<Output>> & {
2106
- updates(...queries: Array<RemoteQuery<any> | RemoteQueryOverride>): Promise<Awaited<Output>>;
2123
+ (arg: undefined extends Input ? Input | void : Input): Promise<Output> & {
2124
+ updates(...updates: RemoteQueryUpdate[]): Promise<Output>;
2107
2125
  };
2108
2126
  /** The number of pending command executions */
2109
2127
  get pending(): number;
2110
2128
  };
2111
2129
 
2112
- export type RemoteResource<T> = Promise<Awaited<T>> & {
2130
+ export type RemoteQueryUpdate =
2131
+ | RemoteQuery<any>
2132
+ | RemoteQueryFunction<any, any>
2133
+ | RemoteQueryOverride;
2134
+
2135
+ export type RemoteResource<T> = Promise<T> & {
2113
2136
  /** The error in case the query fails. Most often this is a [`HttpError`](https://svelte.dev/docs/kit/@sveltejs-kit#HttpError) but it isn't guaranteed to be. */
2114
2137
  get error(): any;
2115
2138
  /** `true` before the first result is available and during refreshes */
@@ -2122,12 +2145,18 @@ export type RemoteResource<T> = Promise<Awaited<T>> & {
2122
2145
  }
2123
2146
  | {
2124
2147
  /** The current value of the query. Undefined until `ready` is `true` */
2125
- get current(): Awaited<T>;
2148
+ get current(): T;
2126
2149
  ready: true;
2127
2150
  }
2128
2151
  );
2129
2152
 
2130
2153
  export type RemoteQuery<T> = RemoteResource<T> & {
2154
+ /**
2155
+ * Returns a plain promise with the result.
2156
+ * Unlike awaiting the resource directly, this can only be used _outside_ render
2157
+ * (i.e. in load functions, event handlers and so on)
2158
+ */
2159
+ run(): Promise<T>;
2131
2160
  /**
2132
2161
  * On the client, this function will update the value of the query without re-fetching it.
2133
2162
  *
@@ -2161,13 +2190,10 @@ export type RemoteQuery<T> = RemoteResource<T> & {
2161
2190
  * </form>
2162
2191
  * ```
2163
2192
  */
2164
- withOverride(update: (current: Awaited<T>) => Awaited<T>): RemoteQueryOverride;
2193
+ withOverride(update: (current: T) => T): RemoteQueryOverride;
2165
2194
  };
2166
2195
 
2167
- export interface RemoteQueryOverride {
2168
- _key: string;
2169
- release(): void;
2170
- }
2196
+ export type RemoteQueryOverride = () => void;
2171
2197
 
2172
2198
  /**
2173
2199
  * The return value of a remote `prerender` function. See [Remote functions](https://svelte.dev/docs/kit/remote-functions#prerender) for full documentation.
@@ -1,10 +1,17 @@
1
1
  import fs from 'node:fs';
2
2
  import { mkdirp } from '../../../utils/filesystem.js';
3
- import { create_function_as_string, filter_fonts, find_deps, resolve_symlinks } from './utils.js';
3
+ import {
4
+ create_function_as_string,
5
+ filter_fonts,
6
+ find_deps,
7
+ generate_placeholder,
8
+ resolve_symlinks
9
+ } from './utils.js';
4
10
  import { s } from '../../../utils/misc.js';
5
11
  import { normalizePath } from 'vite';
6
12
  import { basename } from 'node:path';
7
13
  import { fix_css_urls } from '../../../utils/css.js';
14
+ import { escape_for_interpolation } from '../../../utils/escape.js';
8
15
 
9
16
  /**
10
17
  * @param {string} out
@@ -67,18 +74,31 @@ export function build_server_nodes(
67
74
  const static_asset_prefix = segments.map(() => '..').join('/') + '/';
68
75
 
69
76
  prepare_css_for_inlining = (css, eager_assets) => {
77
+ const assets_placeholder = generate_placeholder(css, 'ASSETS');
78
+ const base_placeholder = generate_placeholder(css, 'BASE');
79
+
70
80
  const transformed_css = fix_css_urls({
71
81
  css,
72
82
  vite_assets: eager_assets,
73
83
  static_assets,
74
- paths_assets: '${assets}',
75
- base: '${base}',
84
+ paths_assets: assets_placeholder,
85
+ base: base_placeholder,
76
86
  static_asset_prefix
77
87
  });
78
88
 
79
89
  // only convert to a function if we have adjusted any URLs
80
90
  if (css !== transformed_css) {
81
- return create_function_as_string('css', ['assets', 'base'], transformed_css);
91
+ const escaped = escape_for_interpolation(transformed_css, [
92
+ {
93
+ placeholder: assets_placeholder,
94
+ replacement: '${assets}'
95
+ },
96
+ {
97
+ placeholder: base_placeholder,
98
+ replacement: '${base}'
99
+ }
100
+ ]);
101
+ return create_function_as_string('css', ['assets', 'base'], escaped);
82
102
  }
83
103
 
84
104
  return s(css);
@@ -8,7 +8,7 @@ import { create_static_module } from '../../../core/env.js';
8
8
  import { env_static_public, service_worker } from '../module_ids.js';
9
9
 
10
10
  // @ts-ignore `vite.rolldownVersion` only exists in `rolldown-vite`
11
- const isRolldown = !!vite.rolldownVersion;
11
+ const is_rolldown = !!vite.rolldownVersion;
12
12
 
13
13
  /**
14
14
  * @param {string} out
@@ -98,7 +98,8 @@ export async function build_service_worker(
98
98
  }
99
99
  };
100
100
 
101
- await vite.build({
101
+ /** @type {import('vite').InlineConfig} */
102
+ const config = {
102
103
  build: {
103
104
  modulePreload: false,
104
105
  rollupOptions: {
@@ -107,9 +108,9 @@ export async function build_service_worker(
107
108
  },
108
109
  output: {
109
110
  // .mjs so that esbuild doesn't incorrectly inject `export` https://github.com/vitejs/vite/issues/15379
110
- entryFileNames: `service-worker.${isRolldown ? 'js' : 'mjs'}`,
111
+ entryFileNames: `service-worker.${is_rolldown ? 'js' : 'mjs'}`,
111
112
  assetFileNames: `${kit.appDir}/immutable/assets/[name].[hash][extname]`,
112
- inlineDynamicImports: true
113
+ inlineDynamicImports: !is_rolldown
113
114
  }
114
115
  },
115
116
  outDir: `${out}/client`,
@@ -130,10 +131,19 @@ export async function build_service_worker(
130
131
  };
131
132
  }
132
133
  }
133
- });
134
+ };
135
+
136
+ // we must reference Vite 8 options conditionally. Otherwise, older Vite
137
+ // versions throw an error about unknown config options
138
+ if (is_rolldown && config?.build?.rollupOptions?.output) {
139
+ // @ts-ignore only available in Vite 8
140
+ config.build.rollupOptions.output.codeSplitting = true;
141
+ }
142
+
143
+ await vite.build(config);
134
144
 
135
145
  // rename .mjs to .js to avoid incorrect MIME types with ancient webservers
136
- if (!isRolldown) {
146
+ if (!is_rolldown) {
137
147
  fs.renameSync(`${out}/client/service-worker.mjs`, `${out}/client/service-worker.js`);
138
148
  }
139
149
  }
@@ -1,7 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { normalizePath } from 'vite';
4
- import { s } from '../../../utils/misc.js';
5
4
 
6
5
  /**
7
6
  * Adds transitive JS and CSS dependencies to the js and css inputs.
@@ -137,11 +136,27 @@ export function assets_base(config) {
137
136
  * @param {string} name The name of the function
138
137
  * @param {string[]} placeholder_names The names of the placeholders in the string
139
138
  * @param {string} str A string with placeholders such as "Hello ${arg0}".
140
- * It must have backticks and dollar signs escaped.
139
+ * It must have backslashes, backticks and dollar signs already escaped.
141
140
  * @returns {string} The function written as a string
142
141
  */
143
142
  export function create_function_as_string(name, placeholder_names, str) {
144
- str = s(str).slice(1, -1);
145
143
  const args = placeholder_names ? placeholder_names.join(', ') : '';
146
144
  return `function ${name}(${args}) { return \`${str}\`; }`;
147
145
  }
146
+
147
+ /**
148
+ * Guarantees that the generated placeholder is not already present in the content.
149
+ * @param {string} content
150
+ * @param {string} key
151
+ * @returns {string}
152
+ */
153
+ export function generate_placeholder(content, key) {
154
+ let id = 1;
155
+ let placeholder = `__SVELTEKIT_${key}_${id}__`;
156
+
157
+ while (content.includes(placeholder)) {
158
+ placeholder = `__SVELTEKIT_${key}_${++id}__`;
159
+ }
160
+
161
+ return placeholder;
162
+ }