Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6a71231
wip
teemingc Mar 27, 2025
78b1233
try lazily loading the universal module
teemingc Apr 3, 2025
b58093c
generate types
teemingc Apr 3, 2025
2dc32a5
identify csr only nodes
teemingc Apr 7, 2025
ae7d938
remove unused options
teemingc Apr 7, 2025
9d9b06c
better name
teemingc Apr 7, 2025
9da9b9d
analyse once
teemingc Apr 7, 2025
d357fcc
format
teemingc Apr 7, 2025
62f1efb
generate types
teemingc Apr 7, 2025
824a7da
windows
teemingc Apr 7, 2025
bf06c7d
add test
teemingc Apr 9, 2025
46ee40f
fix root layout not being considered
teemingc Apr 9, 2025
cd98d22
changeset
teemingc Apr 9, 2025
97d1aec
named import
teemingc Apr 9, 2025
7a818c6
add todo
teemingc Apr 9, 2025
0225840
docs
teemingc Apr 9, 2025
1bf75d7
Merge branch 'main' into static-analysis-v3
teemingc Apr 21, 2025
6b37c21
lazily compute page options during dev
teemingc Apr 22, 2025
b1cabf9
format
teemingc Apr 23, 2025
4dd8eea
remove server component removal
teemingc Apr 23, 2025
5bb37b5
Update documentation/docs/20-core-concepts/40-page-options.md
teemingc Apr 24, 2025
3249b19
correctly skip parsing
teemingc Apr 24, 2025
338a0cd
format
teemingc Apr 24, 2025
33f82bb
save
teemingc Apr 24, 2025
b238264
todos
teemingc Apr 24, 2025
ab12a13
allow let exports
teemingc Apr 25, 2025
30810e7
format
teemingc Apr 25, 2025
0f98dcc
name tests
teemingc Apr 25, 2025
d6b216b
format
teemingc Apr 25, 2025
c1c2ef9
Update documentation/docs/20-core-concepts/40-page-options.md
Rich-Harris May 7, 2025
939f72e
add todos
teemingc May 7, 2025
9e0900b
Merge branch 'static-analysis-v3' of https://github.com/sveltejs/kit …
teemingc May 7, 2025
000ef5a
Update packages/kit/src/exports/vite/static_analysis/index.spec.js
teemingc May 7, 2025
3c7d194
Update packages/kit/src/exports/vite/static_analysis/index.spec.js
teemingc May 7, 2025
543073b
remove reassignment cases and fix dev change not reevaluating page op…
teemingc May 7, 2025
fcdb77e
remove zimmerframe dep from kit
teemingc May 7, 2025
a98421c
Merge branch 'main' into static-analysis-v3
teemingc May 7, 2025
c9cad47
add test for file updates
teemingc May 7, 2025
48363d2
run prettier
benmccann May 7, 2025
77111f4
done!
teemingc May 7, 2025
6ffec69
Merge branch 'static-analysis-v3' of https://github.com/sveltejs/kit …
teemingc May 7, 2025
48210cb
format
teemingc May 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/forty-weeks-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

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
5 changes: 5 additions & 0 deletions .changeset/weak-snails-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: remove component code from server nodes that are never used for SSR
2 changes: 1 addition & 1 deletion documentation/docs/20-core-concepts/40-page-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const ssr = false;

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.

> [!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).
> [!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.

## csr

Expand Down
2 changes: 2 additions & 0 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"homepage": "https://svelte.dev",
"type": "module",
"dependencies": {
"@sveltejs/acorn-typescript": "^1.0.5",
"@types/cookie": "^0.6.0",
"acorn": "^8.14.1",
"cookie": "^0.6.0",
"devalue": "^5.1.0",
"esm-env": "^1.2.2",
Expand Down
32 changes: 22 additions & 10 deletions packages/kit/src/exports/vite/build/build_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { mkdirp } from '../../../utils/filesystem.js';
import { filter_fonts, find_deps, resolve_symlinks } from './utils.js';
import { s } from '../../../utils/misc.js';
import { normalizePath } from 'vite';
import { basename } from 'node:path';
import { basename, join } from 'node:path';
import { create_static_analyser } from '../static_analysis/index.js';

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

Expand Down Expand Up @@ -73,7 +74,14 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
}
}

manifest_data.nodes.forEach((node, i) => {
const { get_page_options } = create_static_analyser(async (server_node) => {
// Windows needs the file:// protocol for absolute path dynamic imports
return import(`file://${join(out, 'server', resolve_symlinks(server_manifest, server_node).chunk.file)}`);
});

for (let i = 0; i < manifest_data.nodes.length; i++) {
const node = manifest_data.nodes[i];

/** @type {string[]} */
const imports = [];

Expand Down Expand Up @@ -101,12 +109,16 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
}

if (node.universal) {
imports.push(
`import * as universal from '../${
resolve_symlinks(server_manifest, node.universal).chunk.file
}';`
);
exports.push('export { universal };');
const page_options = await get_page_options(node);
if (!!page_options && page_options.ssr === false) {
exports.push(`export const universal = ${s(page_options, null, 2)};`)
} else {
imports.push(
`import * as universal from '../${resolve_symlinks(server_manifest, node.universal).chunk.file}';`
);
// TODO: when building for analysis, explain why the file was loaded on the server if we fail to load it
exports.push('export { universal };');
}
exports.push(`export const universal_id = ${s(node.universal)};`);
}

Expand Down Expand Up @@ -186,5 +198,5 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
`${out}/server/nodes/${i}.js`,
`${imports.join('\n')}\n\n${exports.join('\n')}\n`
);
});
}
}
23 changes: 20 additions & 3 deletions packages/kit/src/exports/vite/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { not_found } from '../utils.js';
import { SCHEME } from '../../../utils/url.js';
import { check_feature } from '../../../utils/features.js';
import { escape_html } from '../../../utils/escape.js';
import { create_static_analyser } from '../static_analysis/index.js';

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

/** @type {(file: string) => void} */
let invalidate_page_options;

function update_manifest() {
try {
({ manifest_data } = sync.create(svelte_config));
Expand All @@ -124,6 +128,12 @@ export async function dev(vite, vite_config, svelte_config) {
return;
}

const static_analyser = create_static_analyser(async (server_node) => {
const { module } = await resolve(server_node);
return module;
});
invalidate_page_options = static_analyser.invalidate_page_options;

manifest = {
appDir: svelte_config.kit.appDir,
appPath: svelte_config.kit.appDir,
Expand Down Expand Up @@ -202,9 +212,15 @@ export async function dev(vite, vite_config, svelte_config) {
}

if (node.universal) {
const { module, module_node } = await resolve(node.universal);
module_nodes.push(module_node);
result.universal = module;
const page_options = await static_analyser.get_page_options(node);
if (page_options?.ssr === false) {
result.universal = page_options;
} else {
// TODO: explain why the file was loaded on the server if we fail to load it
const { module, module_node } = await resolve(node.universal);
module_nodes.push(module_node);
result.universal = module;
}
}

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

sync.update(svelte_config, manifest_data, file);
invalidate_page_options(path.relative(cwd, file));
});

const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files;
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ Tips:
// first, build server nodes without the client manifest so we can analyse it
log.info('Analysing routes');

build_server_nodes(
await build_server_nodes(
out,
kit,
manifest_data,
Expand Down Expand Up @@ -983,7 +983,7 @@ Tips:
);

// regenerate nodes with the client manifest...
build_server_nodes(
await build_server_nodes(
out,
kit,
manifest_data,
Expand Down
Loading
Loading