@sveltejs/kit 2.50.2 → 2.52.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": "2.50.2",
3
+ "version": "2.52.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@opentelemetry/api": "^1.0.0",
37
- "@playwright/test": "1.56.0",
37
+ "@playwright/test": "1.58.2",
38
38
  "@sveltejs/vite-plugin-svelte": "^6.0.0-next.3",
39
39
  "@types/connect": "^3.4.38",
40
40
  "@types/node": "^18.19.119",
@@ -65,21 +65,8 @@ async function analyse({
65
65
  internal.set_manifest(manifest);
66
66
  internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`));
67
67
 
68
- /** @type {Map<string, { page_options: Record<string, any> | null, children: string[] }>} */
69
- const static_exports = new Map();
70
-
71
68
  // first, build server nodes without the client manifest so we can analyse it
72
- await build_server_nodes(
73
- out,
74
- config,
75
- manifest_data,
76
- server_manifest,
77
- null,
78
- null,
79
- null,
80
- output_config,
81
- static_exports
82
- );
69
+ build_server_nodes(out, config, manifest_data, server_manifest, null, null, null, output_config);
83
70
 
84
71
  /** @type {import('types').ServerMetadata} */
85
72
  const metadata = {
@@ -188,7 +175,7 @@ async function analyse({
188
175
  metadata.remotes.set(remote.hash, exports);
189
176
  }
190
177
 
191
- return { metadata, static_exports };
178
+ return { metadata };
192
179
  }
193
180
 
194
181
  /**
@@ -8,6 +8,10 @@ import { posixify, resolve_entry } from '../../../utils/filesystem.js';
8
8
  import { parse_route_id } from '../../../utils/routing.js';
9
9
  import { sort_routes } from './sort.js';
10
10
  import { isSvelte5Plus } from '../utils.js';
11
+ import {
12
+ create_node_analyser,
13
+ get_page_options
14
+ } from '../../../exports/vite/static_analysis/index.js';
11
15
 
12
16
  /**
13
17
  * Generates the manifest data used for the client-side manifest and types generation.
@@ -342,7 +346,8 @@ function create_routes_and_nodes(cwd, config, fallback) {
342
346
  }
343
347
 
344
348
  route.endpoint = {
345
- file: project_relative
349
+ file: project_relative,
350
+ page_options: null // will be filled later
346
351
  };
347
352
  }
348
353
  }
@@ -415,6 +420,8 @@ function create_routes_and_nodes(cwd, config, fallback) {
415
420
 
416
421
  const indexes = new Map(nodes.map((node, i) => [node, i]));
417
422
 
423
+ const node_analyser = create_node_analyser();
424
+
418
425
  for (const route of routes) {
419
426
  if (!route.leaf) continue;
420
427
 
@@ -459,6 +466,16 @@ function create_routes_and_nodes(cwd, config, fallback) {
459
466
  }
460
467
  }
461
468
 
469
+ for (const node of nodes) {
470
+ node.page_options = node_analyser.get_page_options(node);
471
+ }
472
+
473
+ for (const route of routes) {
474
+ if (route.endpoint) {
475
+ route.endpoint.page_options = get_page_options(route.endpoint.file);
476
+ }
477
+ }
478
+
462
479
  return {
463
480
  nodes,
464
481
  routes: sort_routes(routes)
@@ -7,6 +7,10 @@ import { write_types, write_all_types } from './write_types/index.js';
7
7
  import { write_ambient } from './write_ambient.js';
8
8
  import { write_non_ambient } from './write_non_ambient.js';
9
9
  import { write_server } from './write_server.js';
10
+ import {
11
+ create_node_analyser,
12
+ get_page_options
13
+ } from '../../exports/vite/static_analysis/index.js';
10
14
 
11
15
  /**
12
16
  * Initialize SvelteKit's generated files that only depend on the config and mode.
@@ -45,7 +49,20 @@ export function create(config) {
45
49
  * @param {string} file
46
50
  */
47
51
  export function update(config, manifest_data, file) {
52
+ const node_analyser = create_node_analyser();
53
+
54
+ for (const node of manifest_data.nodes) {
55
+ node.page_options = node_analyser.get_page_options(node);
56
+ }
57
+
58
+ for (const route of manifest_data.routes) {
59
+ if (route.endpoint) {
60
+ route.endpoint.page_options = get_page_options(route.endpoint.file);
61
+ }
62
+ }
63
+
48
64
  write_types(config, manifest_data, file);
65
+ write_non_ambient(config.kit, manifest_data);
49
66
  }
50
67
 
51
68
  /**
@@ -13,6 +13,40 @@ const remove_group_segments = (/** @type {string} */ id) => {
13
13
  return '/' + get_route_segments(id).join('/');
14
14
  };
15
15
 
16
+ /**
17
+ * Get pathnames to add based on trailingSlash settings
18
+ * @param {string} pathname
19
+ * @param {import('types').RouteData} route
20
+ * @returns {string[]}
21
+ */
22
+ function get_pathnames_for_trailing_slash(pathname, route) {
23
+ if (pathname === '/') {
24
+ return [pathname];
25
+ }
26
+
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
+ /** @type {Set<string>} */
34
+ const pathnames = new Set();
35
+
36
+ for (const page_options of routes) {
37
+ if (page_options === null || page_options.trailingSlash === 'ignore') {
38
+ pathnames.add(pathname);
39
+ pathnames.add(pathname + '/');
40
+ } else if (page_options.trailingSlash === 'always') {
41
+ pathnames.add(pathname + '/');
42
+ } else {
43
+ pathnames.add(pathname);
44
+ }
45
+ }
46
+
47
+ return Array.from(pathnames);
48
+ }
49
+
16
50
  // `declare module "svelte/elements"` needs to happen in a non-ambient module, and dts-buddy generates one big ambient module,
17
51
  // so we can't add it there - therefore generate the typings ourselves here.
18
52
  // We're not using the `declare namespace svelteHTML` variant because that one doesn't augment the HTMLAttributes interface
@@ -67,19 +101,14 @@ function generate_app_types(manifest_data) {
67
101
 
68
102
  const pathname = remove_group_segments(route.id);
69
103
  const replaced_pathname = replace_required_params(replace_optional_params(pathname));
70
- pathnames.add(`\`${replaced_pathname}\` & {}`);
71
104
 
72
- if (pathname !== '/') {
73
- // Support trailing slash
74
- pathnames.add(`\`${replaced_pathname + '/'}\` & {}`);
105
+ for (const p of get_pathnames_for_trailing_slash(replaced_pathname, route)) {
106
+ pathnames.add(`\`${p}\` & {}`);
75
107
  }
76
108
  } else {
77
109
  const pathname = remove_group_segments(route.id);
78
- pathnames.add(s(pathname));
79
-
80
- if (pathname !== '/') {
81
- // Support trailing slash
82
- pathnames.add(s(pathname + '/'));
110
+ for (const p of get_pathnames_for_trailing_slash(pathname, route)) {
111
+ pathnames.add(s(p));
83
112
  }
84
113
  }
85
114
 
@@ -62,10 +62,8 @@ export function get_tsconfig(kit) {
62
62
  config_relative('vite.config.js'),
63
63
  config_relative('vite.config.ts')
64
64
  ]);
65
- // TODO(v2): find a better way to include all src files. We can't just use routes/lib only because
66
- // people might have other folders/files in src that they want included.
67
- const src_includes = [kit.files.routes, kit.files.lib, path.resolve('src')].filter((dir) => {
68
- const relative = path.relative(path.resolve('src'), dir);
65
+ const src_includes = [kit.files.routes, kit.files.lib, kit.files.src].filter((dir) => {
66
+ const relative = path.relative(kit.files.src, dir);
69
67
  return !relative || relative.startsWith('..');
70
68
  });
71
69
  for (const dir of src_includes) {
@@ -76,10 +74,14 @@ export function get_tsconfig(kit) {
76
74
 
77
75
  // Test folder is a special case - we advocate putting tests in a top-level test folder
78
76
  // and it's not configurable (should we make it?)
79
- const test_folder = project_relative('tests');
77
+ const test_folder = project_relative('test');
80
78
  include.add(config_relative(`${test_folder}/**/*.js`));
81
79
  include.add(config_relative(`${test_folder}/**/*.ts`));
82
80
  include.add(config_relative(`${test_folder}/**/*.svelte`));
81
+ const tests_folder = project_relative('tests');
82
+ include.add(config_relative(`${tests_folder}/**/*.js`));
83
+ include.add(config_relative(`${tests_folder}/**/*.ts`));
84
+ include.add(config_relative(`${tests_folder}/**/*.svelte`));
83
85
 
84
86
  const exclude = [config_relative('node_modules/**')];
85
87
  // Add service worker to exclude list so that worker types references in it don't spill over into the rest of the app
@@ -1200,6 +1200,19 @@ export interface NavigationTarget<
1200
1200
  * The URL that is navigated to
1201
1201
  */
1202
1202
  url: URL;
1203
+ /**
1204
+ * The scroll position associated with this navigation.
1205
+ *
1206
+ * For the `from` target, this is the scroll position at the moment of navigation.
1207
+ *
1208
+ * For the `to` target, this represents the scroll position that will be or was restored:
1209
+ * - In `beforeNavigate` and `onNavigate`, this is only available for `popstate` navigations (back/forward button)
1210
+ * and will be `null` for other navigation types, since the final scroll position isn't known
1211
+ * ahead of time.
1212
+ * - In `afterNavigate`, this is always the scroll position that was applied after the navigation
1213
+ * completed.
1214
+ */
1215
+ scroll: { x: number; y: number } | null;
1203
1216
  }
1204
1217
 
1205
1218
  /**
@@ -1249,7 +1262,7 @@ export interface NavigationEnter extends NavigationBase {
1249
1262
  delta?: undefined;
1250
1263
 
1251
1264
  /**
1252
- * Dispatched `Event` object when navigation occured by `popstate` or `link`.
1265
+ * Dispatched `Event` object when navigation occurred by `popstate` or `link`.
1253
1266
  */
1254
1267
  event?: undefined;
1255
1268
  }
@@ -3,38 +3,9 @@ import { mkdirp } from '../../../utils/filesystem.js';
3
3
  import { filter_fonts, find_deps, resolve_symlinks } from './utils.js';
4
4
  import { s } from '../../../utils/misc.js';
5
5
  import { normalizePath } from 'vite';
6
- import { basename, join } from 'node:path';
7
- import { create_node_analyser } from '../static_analysis/index.js';
6
+ import { basename } from 'node:path';
8
7
  import { fix_css_urls } from '../../../utils/css.js';
9
8
 
10
- /**
11
- * Regenerate server nodes after acquiring client manifest
12
- * @overload
13
- * @param {string} out
14
- * @param {import('types').ValidatedKitConfig} kit
15
- * @param {import('types').ManifestData} manifest_data
16
- * @param {import('vite').Manifest} server_manifest
17
- * @param {import('vite').Manifest} client_manifest
18
- * @param {string} assets_path
19
- * @param {import('vite').Rollup.RollupOutput['output']} client_chunks
20
- * @param {import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']>} output_config
21
- * @param {Map<string, { page_options: Record<string, any> | null, children: string[] }>} static_exports
22
- * @returns {Promise<void>}
23
- */
24
- /**
25
- * Build server nodes without client manifest for analysis phase
26
- * @overload
27
- * @param {string} out
28
- * @param {import('types').ValidatedKitConfig} kit
29
- * @param {import('types').ManifestData} manifest_data
30
- * @param {import('vite').Manifest} server_manifest
31
- * @param {null} client_manifest
32
- * @param {null} assets_path
33
- * @param {null} client_chunks
34
- * @param {import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']>} output_config
35
- * @param {Map<string, { page_options: Record<string, any> | null, children: string[] }>} static_exports
36
- * @returns {Promise<void>}
37
- */
38
9
  /**
39
10
  * @param {string} out
40
11
  * @param {import('types').ValidatedKitConfig} kit
@@ -44,9 +15,8 @@ import { fix_css_urls } from '../../../utils/css.js';
44
15
  * @param {string | null} assets_path
45
16
  * @param {import('vite').Rollup.RollupOutput['output'] | null} client_chunks
46
17
  * @param {import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']>} output_config
47
- * @param {Map<string, { page_options: Record<string, any> | null, children: string[] }>} static_exports
48
18
  */
49
- export async function build_server_nodes(
19
+ export function build_server_nodes(
50
20
  out,
51
21
  kit,
52
22
  manifest_data,
@@ -54,8 +24,7 @@ export async function build_server_nodes(
54
24
  client_manifest,
55
25
  assets_path,
56
26
  client_chunks,
57
- output_config,
58
- static_exports
27
+ output_config
59
28
  ) {
60
29
  mkdirp(`${out}/server/nodes`);
61
30
  mkdirp(`${out}/server/stylesheets`);
@@ -116,16 +85,6 @@ export async function build_server_nodes(
116
85
  }
117
86
  }
118
87
 
119
- const { get_page_options } = create_node_analyser({
120
- resolve: (server_node) => {
121
- // Windows needs the file:// protocol for absolute path dynamic imports
122
- return import(
123
- `file://${join(out, 'server', resolve_symlinks(server_manifest, server_node).chunk.file)}`
124
- );
125
- },
126
- static_exports
127
- });
128
-
129
88
  for (let i = 0; i < manifest_data.nodes.length; i++) {
130
89
  const node = manifest_data.nodes[i];
131
90
 
@@ -159,9 +118,8 @@ export async function build_server_nodes(
159
118
  }
160
119
 
161
120
  if (node.universal) {
162
- const page_options = await get_page_options(node);
163
- if (!!page_options && page_options.ssr === false) {
164
- exports.push(`export const universal = ${s(page_options, null, 2)};`);
121
+ if (!!node.page_options && node.page_options.ssr === false) {
122
+ exports.push(`export const universal = ${s(node.page_options, null, 2)};`);
165
123
  } else {
166
124
  imports.push(
167
125
  `import * as universal from '../${resolve_symlinks(server_manifest, node.universal).chunk.file}';`
@@ -19,7 +19,6 @@ import { not_found } from '../utils.js';
19
19
  import { SCHEME } from '../../../utils/url.js';
20
20
  import { check_feature } from '../../../utils/features.js';
21
21
  import { escape_html } from '../../../utils/escape.js';
22
- import { create_node_analyser } from '../static_analysis/index.js';
23
22
 
24
23
  const cwd = process.cwd();
25
24
  // vite-specifc queries that we should skip handling for css urls
@@ -103,9 +102,6 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
103
102
  return { module, module_node, url };
104
103
  }
105
104
 
106
- /** @type {(file: string) => void} */
107
- let invalidate_page_options;
108
-
109
105
  function update_manifest() {
110
106
  try {
111
107
  ({ manifest_data } = sync.create(svelte_config));
@@ -129,14 +125,6 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
129
125
  return;
130
126
  }
131
127
 
132
- const node_analyser = create_node_analyser({
133
- resolve: async (server_node) => {
134
- const { module } = await resolve(server_node);
135
- return module;
136
- }
137
- });
138
- invalidate_page_options = node_analyser.invalidate_page_options;
139
-
140
128
  manifest = {
141
129
  appDir: svelte_config.kit.appDir,
142
130
  appPath: svelte_config.kit.appDir,
@@ -215,9 +203,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
215
203
  }
216
204
 
217
205
  if (node.universal) {
218
- const page_options = await node_analyser.get_page_options(node);
219
- if (page_options?.ssr === false) {
220
- result.universal = page_options;
206
+ if (node.page_options?.ssr === false) {
207
+ result.universal = node.page_options;
221
208
  } else {
222
209
  // TODO: explain why the file was loaded on the server if we fail to load it
223
210
  const { module, module_node } = await resolve(node.universal);
@@ -370,12 +357,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
370
357
  watch('unlink', () => debounce(update_manifest));
371
358
  watch('change', (file) => {
372
359
  // Don't run for a single file if the whole manifest is about to get updated
373
- if (timeout || restarting) return;
374
-
375
- if (/\+(page|layout).*$/.test(file)) {
376
- invalidate_page_options(path.relative(cwd, file));
377
- }
378
-
360
+ // Unless it's a file where the trailing slash page option might have changed
361
+ if (timeout || restarting || !/\+(page|layout|server).*$/.test(file)) return;
379
362
  sync.update(svelte_config, manifest_data, file);
380
363
  });
381
364
 
@@ -40,7 +40,7 @@ import {
40
40
  } from './module_ids.js';
41
41
  import { import_peer } from '../../utils/import.js';
42
42
  import { compact } from '../../utils/array.js';
43
- import { should_ignore } from './static_analysis/utils.js';
43
+ import { should_ignore, has_children } from './static_analysis/utils.js';
44
44
 
45
45
  const cwd = posixify(process.cwd());
46
46
 
@@ -112,10 +112,8 @@ const warning_preprocessor = {
112
112
  if (!filename) return;
113
113
 
114
114
  const basename = path.basename(filename);
115
- const has_children =
116
- content.includes('<slot') || (isSvelte5Plus() && content.includes('{@render'));
117
115
 
118
- if (basename.startsWith('+layout.') && !has_children) {
116
+ if (basename.startsWith('+layout.') && !has_children(content, isSvelte5Plus())) {
119
117
  const message =
120
118
  `\n${colors.bold().red(path.relative('.', filename))}\n` +
121
119
  `\`<slot />\`${isSvelte5Plus() ? ' or `{@render ...}` tag' : ''}` +
@@ -1065,7 +1063,7 @@ async function kit({ svelte_config }) {
1065
1063
 
1066
1064
  log.info('Analysing routes');
1067
1065
 
1068
- const { metadata, static_exports } = await analyse({
1066
+ const { metadata } = await analyse({
1069
1067
  hash: kit.router.type === 'hash',
1070
1068
  manifest_path,
1071
1069
  manifest_data,
@@ -1265,7 +1263,7 @@ async function kit({ svelte_config }) {
1265
1263
  );
1266
1264
 
1267
1265
  // regenerate nodes with the client manifest...
1268
- await build_server_nodes(
1266
+ build_server_nodes(
1269
1267
  out,
1270
1268
  kit,
1271
1269
  manifest_data,
@@ -1273,8 +1271,7 @@ async function kit({ svelte_config }) {
1273
1271
  client_manifest,
1274
1272
  assets_path,
1275
1273
  client_chunks,
1276
- svelte_config.kit.output,
1277
- static_exports
1274
+ svelte_config.kit.output
1278
1275
  );
1279
1276
 
1280
1277
  // ...and prerender
@@ -2,9 +2,21 @@ import { tsPlugin } from '@sveltejs/acorn-typescript';
2
2
  import { Parser } from 'acorn';
3
3
  import { read } from '../../../utils/filesystem.js';
4
4
 
5
- const inheritable_page_options = new Set(['ssr', 'prerender', 'csr', 'trailingSlash', 'config']);
6
-
7
- const valid_page_options = new Set([...inheritable_page_options, 'entries', 'load']);
5
+ const valid_page_options_array = /** @type {const} */ ([
6
+ 'ssr',
7
+ 'prerender',
8
+ 'csr',
9
+ 'trailingSlash',
10
+ 'config',
11
+ 'entries',
12
+ 'load'
13
+ ]);
14
+
15
+ /** @type {Set<string>} */
16
+ const valid_page_options = new Set(valid_page_options_array);
17
+
18
+ /** @typedef {typeof valid_page_options_array[number]} ValidPageOption */
19
+ /** @typedef {Partial<Record<ValidPageOption, any>>} PageOptions */
8
20
 
9
21
  const skip_parsing_regex = new RegExp(
10
22
  `${Array.from(valid_page_options).join('|')}|(?:export[\\s\\n]+\\*[\\s\\n]+from)`
@@ -18,11 +30,11 @@ const parser = Parser.extend(tsPlugin());
18
30
  * Returns `null` if any export is too difficult to analyse.
19
31
  * @param {string} filename The name of the file to report when an error occurs
20
32
  * @param {string} input
21
- * @returns {Record<string, any> | null}
33
+ * @returns {PageOptions | null}
22
34
  */
23
35
  export function statically_analyse_page_options(filename, input) {
24
- // if there's a chance there are no page exports or export all declaration,
25
- // then we can skip the AST parsing which is expensive
36
+ // if there's a chance there are no page exports or an unparseable
37
+ // export all declaration, then we can skip the AST parsing which is expensive
26
38
  if (!skip_parsing_regex.test(input)) {
27
39
  return {};
28
40
  }
@@ -194,33 +206,56 @@ export function statically_analyse_page_options(filename, input) {
194
206
  * @param {import('acorn').Identifier | import('acorn').Literal} node
195
207
  * @returns {string}
196
208
  */
197
- export function get_name(node) {
209
+ function get_name(node) {
198
210
  return node.type === 'Identifier' ? node.name : /** @type {string} */ (node.value);
199
211
  }
200
212
 
201
213
  /**
202
- * @param {{
203
- * resolve: (file: string) => Promise<Record<string, any>>;
204
- * static_exports?: Map<string, { page_options: Record<string, any> | null, children: string[] }>;
205
- * }} opts
214
+ * Reads and statically analyses a file for page options
215
+ * @param {string} filepath
216
+ * @returns {PageOptions | null} Returns the page options for the file or `null` if unanalysable
206
217
  */
207
- export function create_node_analyser({ resolve, static_exports = new Map() }) {
218
+ export function get_page_options(filepath) {
219
+ try {
220
+ const input = read(filepath);
221
+ const page_options = statically_analyse_page_options(filepath, input);
222
+ if (page_options === null) {
223
+ return null;
224
+ }
225
+
226
+ return page_options;
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+
232
+ export function create_node_analyser() {
233
+ const static_exports = new Map();
234
+
235
+ /**
236
+ * @param {string | undefined} key
237
+ * @param {PageOptions | null} page_options
238
+ */
239
+ const cache = (key, page_options) => {
240
+ if (key) static_exports.set(key, { page_options, children: [] });
241
+ };
242
+
208
243
  /**
209
244
  * Computes the final page options (may include load function as `load: null`; special case) for a node (if possible). Otherwise, returns `null`.
210
245
  * @param {import('types').PageNode} node
211
- * @returns {Promise<Record<string, any> | null>}
246
+ * @returns {PageOptions | null}
212
247
  */
213
- const get_page_options = async (node) => {
248
+ const crawl = (node) => {
214
249
  const key = node.universal || node.server;
215
250
  if (key && static_exports.has(key)) {
216
251
  return { ...static_exports.get(key)?.page_options };
217
252
  }
218
253
 
219
- /** @type {Record<string, any>} */
254
+ /** @type {PageOptions} */
220
255
  let page_options = {};
221
256
 
222
257
  if (node.parent) {
223
- const parent_options = await get_page_options(node.parent);
258
+ const parent_options = crawl(node.parent);
224
259
 
225
260
  const parent_key = node.parent.universal || node.parent.server;
226
261
  if (key && parent_key) {
@@ -230,9 +265,7 @@ export function create_node_analyser({ resolve, static_exports = new Map() }) {
230
265
  if (parent_options === null) {
231
266
  // if the parent cannot be analysed, we can't know what page options
232
267
  // the child node inherits, so we also mark it as unanalysable
233
- if (key) {
234
- static_exports.set(key, { page_options: null, children: [] });
235
- }
268
+ cache(key, null);
236
269
  return null;
237
270
  }
238
271
 
@@ -240,43 +273,29 @@ export function create_node_analyser({ resolve, static_exports = new Map() }) {
240
273
  }
241
274
 
242
275
  if (node.server) {
243
- const module = await resolve(node.server);
244
- for (const page_option in inheritable_page_options) {
245
- if (page_option in module) {
246
- page_options[page_option] = module[page_option];
247
- }
276
+ const server_page_options = get_page_options(node.server);
277
+ if (server_page_options === null) {
278
+ cache(key, null);
279
+ return null;
248
280
  }
281
+ page_options = { ...page_options, ...server_page_options };
249
282
  }
250
283
 
251
284
  if (node.universal) {
252
- const input = read(node.universal);
253
- const universal_page_options = statically_analyse_page_options(node.universal, input);
254
-
285
+ const universal_page_options = get_page_options(node.universal);
255
286
  if (universal_page_options === null) {
256
- static_exports.set(node.universal, { page_options: null, children: [] });
287
+ cache(key, null);
257
288
  return null;
258
289
  }
259
-
260
290
  page_options = { ...page_options, ...universal_page_options };
261
291
  }
262
292
 
263
- if (key) {
264
- static_exports.set(key, { page_options, children: [] });
265
- }
293
+ cache(key, page_options);
266
294
 
267
295
  return page_options;
268
296
  };
269
297
 
270
- /**
271
- * @param {string} file
272
- */
273
- const invalidate_page_options = (file) => {
274
- static_exports.get(file)?.children.forEach((child) => static_exports.delete(child));
275
- static_exports.delete(file);
276
- };
277
-
278
298
  return {
279
- get_page_options,
280
- invalidate_page_options
299
+ get_page_options: crawl
281
300
  };
282
301
  }
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Check if content has children rendering (slot, @render, or children prop forwarding)
3
+ * @param {string} content - The markup content
4
+ * @param {boolean} is_svelte_5_plus - Whether the project uses Svelte 5+
5
+ * @returns {boolean}
6
+ */
7
+ export function has_children(content, is_svelte_5_plus) {
8
+ return (
9
+ content.includes('<slot') ||
10
+ (is_svelte_5_plus &&
11
+ (content.includes('{@render') ||
12
+ // children may be forwarded to a child component as a prop
13
+ content.includes('{children}') ||
14
+ content.includes('children={')))
15
+ );
16
+ }
17
+
1
18
  /**
2
19
  * Check if a match position is within a comment or a string
3
20
  * @param {string} content - The full content