8000 feat: statically analyse universal pages and layouts v3 (#13684) · sveltejs/kit@09f61ec · GitHub
[go: up one dir, main page]

Skip to content

Commit 09f61ec

Browse files
eltigerchinobenmccannRich-Harris
authored
feat: statically analyse universal pages and layouts v3 (#13684)
* wip * try lazily loading the universal module * generate types * identify csr only nodes * remove unused options * better name * analyse once * format * generate types * windows * add test * fix root layout not being considered * changeset * named import * add todo * docs * lazily compute page options during dev * format * remove server component removal * Update documentation/docs/20-core-concepts/40-page-options.md Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> * correctly skip parsing * format * save * todos * allow let exports * format * name tests * format * Update documentation/docs/20-core-concepts/40-page-options.md * add todos * Update packages/kit/src/exports/vite/static_analysis/index.spec.js Co-authored-by: Rich Harris <richard.a.harris@gmail.com> * Update packages/kit/src/exports/vite/static_analysis/index.spec.js Co-authored-by: Rich Harris <richard.a.harris@gmail.com> * remove reassignment cases and fix dev change not reevaluating page options * remove zimmerframe dep from kit * add test for file updates * run prettier * done! * format --------- Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
1 parent bd1c046 commit 09f61ec

File tree

21 files changed

+600
-27
lines changed

21 files changed

+600
-27
lines changed

.changeset/forty-weeks-arrive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: allow running client-side code at the top-level of universal pages/layouts when SSR is disabled and page options are only boolean or string literals

.changeset/weak-snails-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: remove component code from server nodes that are never used for SSR

documentation/docs/20-core-concepts/40 6DB6 -page-options.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export const ssr = false;
127127

128128
If you add `export const ssr = false` to your root `+layout.js`, your entire app will only be rendered on the client — which essentially means you turn your app into an SPA.
129129

130-
> [!NOTE] Even with `ssr` set to `false`, code that relies on browser APIs should be imported in your `+page.svelte` or `+layout.svelte` file instead. This is because page options can be overriden and need to be evaluated by importing your `+page.js` or `+layout.js` file on the server (if you have a runtime) or at build time (in case of prerendering).
130+
> [!NOTE] If all your page options are boolean or string literal values, SvelteKit will evaluate them statically. If not, it will import your `+page.js` or `+layout.js` file on the server (both at build time, and at runtime if your app isn't fully static) so it can evaluate the options. In the second case, browser-only code must not run when the module is loaded. In practice, this means you should import browser-only code in your `+page.svelte` or `+layout.svelte` file instead.
131131
132132
## csr
133133

packages/kit/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"homepage": "https://svelte.dev",
1919
"type": "module",
2020
"dependencies": {
21+
"@sveltejs/acorn-typescript": "^1.0.5",
2122
"@types/cookie": "^0.6.0",
23+
"acorn": "^8.14.1",
2224
"cookie": "^0.6.0",
2325
"devalue": "^5.1.0",
2426
"esm-env": "^1.2.2",

packages/kit/src/exports/vite/build/build_server.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { mkdirp } from '../../../utils/filesystem.js';
33
import { filter_fonts, find_deps, resolve_symlinks } from './utils.js';
44
import { s } from '../../../utils/misc.js';
55
import { normalizePath } from 'vite';
6-
import { basename } from 'node:path';
6+
import { basename, join } from 'node:path';
7+
import { create_static_analyser } from '../static_analysis/index.js';
78

89
/**
910
* @param {string} out
@@ -14,7 +15,7 @@ import { basename } from 'node:path';
1415
* @param {import('vite').Rollup.OutputAsset[] | null} css
1516
* @param {import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']>} output_config
1617
*/
17-
export function build_server_nodes(out, kit, manifest_data, server_manifest, client_manifest, css, output_config) {
18+
export async function build_server_nodes(out, kit, manifest_data, server_manifest, client_manifest, css, output_config) {
1819
mkdirp(`${out}/server/nodes`);
1920
mkdirp(`${out}/server/stylesheets`);
2021

@@ -73,7 +74,14 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
7374
}
7475
}
7576

76-
manifest_data.nodes.forEach((node, i) => {
77+
const { get_page_options } = create_static_analyser(async (server_node) => {
78+
// Windows needs the file:// protocol for absolute path dynamic imports
79+
return import(`file://${join(out, 'server', resolve_symlinks(server_manifest, server_node).chunk.file)}`);
80+
});
81+
82+
for (let i = 0; i < manifest_data.nodes.length; i++) {
83+
const node = manifest_data.nodes[i];
84+
7785
/** @type {string[]} */
7886
const imports = [];
7987

@@ -101,12 +109,16 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
101109
}
102110

103111
if (node.universal) {
104-
imports.push(
105-
`import * as universal from '../${
106-
resolve_symlinks(server_manifest, node.universal).chunk.file
107-
}';`
108-
);
109-
exports.push('export { universal };');
112+
const page_options = await get_page_options(node);
113+
if (!!page_options && page_options.ssr === false) {
114+
exports.push(`export const universal = ${s(page_options, null, 2)};`)
115+
} else {
116+
imports.push(
117+
`import * as universal from '../${resolve_symlinks(server_manifest, node.universal).chunk.file} 10000 ';`
118+
);
119+
// TODO: when building for analysis, explain why the file was loaded on the server if we fail to load it
120+
exports.push('export { universal };');
121+
}
110122
exports.push(`export const universal_id = ${s(node.universal)};`);
111123
}
112124

@@ -186,5 +198,5 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
186198
`${out}/server/nodes/${i}.js`,
187199
`${imports.join('\n')}\n\n${exports.join('\n')}\n`
188200
);
189-
});
201+
}
190202
}

packages/kit/src/exports/vite/dev/index.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { not_found } from '../utils.js';
1919
import { SCHEME } from '../../../utils/url.js';
2020
import { check_feature } from '../../../utils/features.js';
2121
import { escape_html } from '../../../utils/escape.js';
22+
import { create_static_analyser } from '../static_analysis/index.js';
2223

2324
const cwd = process.cwd();
2425
// vite-specifc queries that we should skip handling for css urls
@@ -101,6 +102,9 @@ export async function dev(vite, vite_config, svelte_config) {
101102
return { module, module_node, url };
102103
}
103104

105+
/** @type {(file: string) => void} */
106+
let invalidate_page_options;
107+
104108
function update_manifest() {
105109
try {
106110
({ manifest_data } = sync.create(svelte_config));
@@ -124,6 +128,12 @@ export async function dev(vite, vite_config, svelte_config) {
124128
return;
125129
}
126130

131+
const static_analyser = create_static_analyser(async (server_node) => {
132+
const { module } = await resolve(server_node);
133+
return module;
134+
});
135+
invalidate_page_options = static_analyser.invalidate_page_options;
136+
127137
manifest = {
128138
appDir: svelte_config.kit.appDir,
129139
appPath: svelte_config.kit.appDir,
@@ -202,9 +212,15 @@ export async function dev(vite, vite_config, svelte_config) {
202212
}
203213

204214
if (node.universal) {
205-
const { module, module_node } = await resolve(node.universal);
206-
module_nodes.push(module_node);
207-
result.universal = module;
215+
const page_options = await static_analyser.get_page_options(node);
216+
if (page_options?.ssr === false) {
217+
result.universal = page_options;
218+
} else {
219+
// TODO: explain why the file was loaded on the server if we fail to load it
220+
const { module, module_node } = await resolve(node.universal);
221+
module_nodes.push(module_node);
222+
result.universal = module;
223+
}
208224
}
209225

210226
if (node.server) {
@@ -344,6 +360,7 @@ export async function dev(vite, vite_config, svelte_config) {
344360
if (timeout || restarting) return;
345361

346362
sync.update(svelte_config, manifest_data, file);
363+
invalidate_page_options(path.relative(cwd, file));
347364
});
348365

349366
const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files;

packages/kit/src/exports/vite/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ Tips:
821821
// first, build server nodes without the client manifest so we can analyse it
822822
log.info('Analysing routes');
823823

824-
build_server_nodes(
824+
await build_server_nodes(
825825
out,
826826
kit,
827827
manifest_data,
@@ -983,7 +983,7 @@ Tips:
983983
);
984984

985985
// regenerate nodes with the client manifest...
986-
build_server_nodes(
986+
await build_server_nodes(
987987
out,
988988
kit,
989989
manifest_data,

0 commit comments

Comments
 (0)
0