@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 +2 -2
- package/src/core/postbuild/analyse.js +2 -15
- package/src/core/sync/create_manifest_data/index.js +18 -1
- package/src/core/sync/sync.js +17 -0
- package/src/core/sync/write_non_ambient.js +38 -9
- package/src/core/sync/write_tsconfig.js +7 -5
- package/src/exports/public.d.ts +14 -1
- package/src/exports/vite/build/build_server.js +5 -47
- package/src/exports/vite/dev/index.js +4 -21
- package/src/exports/vite/index.js +5 -8
- package/src/exports/vite/static_analysis/index.js +61 -42
- package/src/exports/vite/static_analysis/utils.js +17 -0
- package/src/runtime/app/paths/client.js +38 -0
- package/src/runtime/app/paths/public.d.ts +1 -1
- package/src/runtime/app/paths/server.js +42 -1
- package/src/runtime/client/client.js +36 -38
- package/src/runtime/server/fetch.js +6 -2
- package/src/runtime/server/page/csp.js +54 -22
- package/src/runtime/server/page/index.js +7 -2
- package/src/runtime/server/page/render.js +104 -43
- package/src/runtime/server/page/server_routing.js +4 -20
- package/src/runtime/server/respond.js +7 -13
- package/src/types/internal.d.ts +13 -4
- package/src/types/private.d.ts +1 -0
- package/src/utils/routing.js +25 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +49 -2
- package/types/index.d.ts.map +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltejs/kit",
|
|
3
|
-
"version": "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.
|
|
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
|
-
|
|
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
|
|
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)
|
package/src/core/sync/sync.js
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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('
|
|
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
|
package/src/exports/public.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
209
|
+
function get_name(node) {
|
|
198
210
|
return node.type === 'Identifier' ? node.name : /** @type {string} */ (node.value);
|
|
199
211
|
}
|
|
200
212
|
|
|
201
213
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
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
|
|
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 {
|
|
246
|
+
* @returns {PageOptions | null}
|
|
212
247
|
*/
|
|
213
|
-
const
|
|
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 {
|
|
254
|
+
/** @type {PageOptions} */
|
|
220
255
|
let page_options = {};
|
|
221
256
|
|
|
222
257
|
if (node.parent) {
|
|
223
|
-
const parent_options =
|
|
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
|
-
|
|
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
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|