Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] SSR never option #2529

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c90fc22
add 'never' option to ssr
JeanJPNM Sep 30, 2021
ba8b648
update options validator
JeanJPNM Sep 30, 2021
7aab5d4
update page renderer
JeanJPNM Sep 30, 2021
dc28d89
fix no ssr response
JeanJPNM Sep 30, 2021
ce61f66
remove ssr union check
JeanJPNM Sep 30, 2021
1e6185e
fix formatting
JeanJPNM Sep 30, 2021
7fd45bd
add 'never' option to ssr
JeanJPNM Sep 30, 2021
4ee0a9c
update options validator
JeanJPNM Sep 30, 2021
bc35a2c
update page renderer
JeanJPNM Sep 30, 2021
a3c0a30
fix no ssr response
JeanJPNM Sep 30, 2021
73ebf0d
remove ssr union check
JeanJPNM Sep 30, 2021
64947d0
fix formatting
JeanJPNM Sep 30, 2021
dede251
Merge branch 'ssr-never-option' of https://github.com/JeanJPNM/kit in…
JeanJPNM Oct 1, 2021
caf1d52
ensure ssr is boolean on get_page_config
JeanJPNM Oct 2, 2021
b78c01b
mention "never" on docs
JeanJPNM Oct 2, 2021
7ecd93e
mention "never" on integrations faq
JeanJPNM Oct 2, 2021
bc144a0
avoid prerendering when ssr = 'never'
JeanJPNM Oct 2, 2021
5701dd8
avoid importing components on server build
JeanJPNM Oct 3, 2021
d2f7b31
clarify "never" option on docs
JeanJPNM Oct 3, 2021
e04e707
fix typo on docs
JeanJPNM Oct 3, 2021
87edb89
add changeset
JeanJPNM Oct 4, 2021
0e83be9
Update changeset
JeanJPNM Oct 4, 2021
25ded6c
update docs
JeanJPNM Oct 5, 2021
edbd994
add spa tests
JeanJPNM Oct 8, 2021
2c86494
fix formatting
JeanJPNM Oct 8, 2021
86d2d19
remove unused prerendering check
JeanJPNM Oct 23, 2021
f5d4645
check for ssr option in respond_with_error
JeanJPNM Oct 23, 2021
9dad824
warn users about using "never"
JeanJPNM Oct 23, 2021
5d9a94a
replace tabs by spaces on page/render.js
JeanJPNM Oct 24, 2021
023ca9b
group module and metadata lookups
JeanJPNM Oct 24, 2021
cfc5eae
more concise spa check
JeanJPNM Oct 24, 2021
1b4720e
type cast formatting
JeanJPNM Oct 24, 2021
be01755
show the file name in the error message
JeanJPNM Oct 26, 2021
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/bright-tips-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Added a "never" value to the config.kit.ssr option that prevents pages from being evaluated on the server on both ssr and prerendering.
4 changes: 3 additions & 1 deletion documentation/docs/11-ssr-and-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ Disabling [server-side rendering](#appendix-ssr) effectively turns your SvelteKi

> In most situations this is not recommended: see [the discussion in the appendix](#appendix-ssr). Consider whether it's truly appropriate to disable and don't simply disable SSR because you've hit an issue with it.

You can disable SSR app-wide with the [`ssr` config option](#configuration-ssr), or a page-level `ssr` export:
You can disable SSR on a page-level with a `ssr` export. Page-level `ssr` exports must be boolean values, if another value is provided it will be cast into a boolean.

```html
<script context="module">
export const ssr = false;
</script>
```

You can also disable SSR app-wide with the [`ssr` config option](#configuration-ssr), using a boolean or `"never"`. In case you use `"never"`, all page-level `ssr` exports will be completely ignored regardless of their value.
JeanJPNM marked this conversation as resolved.
Show resolved Hide resolved

### router

SvelteKit includes a [client-side router](#appendix-routing) that intercepts navigations (from the user clicking on links, or interacting with the back/forward buttons) and updates the page contents, rather than letting the browser handle the navigation by reloading.
Expand Down
3 changes: 2 additions & 1 deletion documentation/docs/14-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ An object containing zero or more of the following values:

### ssr

Enables or disables [server-side rendering](#ssr-and-javascript-ssr) app-wide.
- `true` or `false` — Enables or disables [server-side rendering](#ssr-and-javascript-ssr) app-wide and allows pages to override it locally.
- `"never"` — Prevents all pages from being evaluated on the server, on both [server-side rendering](#ssr-and-javascript-ssr) and [prerendering](#ssr-and-javascript-prerender), can't be overriden by page-level exports. Using this option is recommended when building an SPA.
JeanJPNM marked this conversation as resolved.
Show resolved Hide resolved

### target

Expand Down
18 changes: 18 additions & 0 deletions documentation/faq/80-integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ onMount(() => {
});
```

But if your app doesn't use SSR, you can set the `ssr` option to `'never'`:

```js
export default {
kit: {
// ...
ssr: 'never'
// ...
}
}
```

```js
import { method } from 'some-browser-only-library';

method('hello world!');

```
### How do I use Firebase?

Please use SDK v9 which provides a modular SDK approach that's currently in beta. The old versions are very difficult to get working especially with SSR and also resulted in a much larger client download size. Even with v9, most users need to set `kit.ssr: false` until [vite#4425](https://github.com/vitejs/vite/issues/4425) and [firebase-js-sdk#4846](https://github.com/firebase/firebase-js-sdk/issues/4846) are solved.
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/adapt/prerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const REDIRECT = 3;
* }} opts
*/
export async function prerender({ cwd, out, log, config, build_data, fallback, all }) {
if (!config.kit.prerender.enabled && !fallback) {
if (config.kit.ssr === 'never' || (!config.kit.prerender.enabled && !fallback)) {
return;
}

Expand Down
65 changes: 35 additions & 30 deletions packages/kit/src/core/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,32 +259,35 @@ async function build_server(
}
}

const allow_ssr = config.kit.ssr !== 'never';

/** @type {Record<string, { entry: string, css: string[], js: string[], styles: string[] }>} */
const metadata_lookup = {};

manifest.components.forEach((file) => {
const js_deps = new Set();
const css_deps = new Set();

find_deps(file, js_deps, css_deps);

const js = Array.from(js_deps);
const css = Array.from(css_deps);

const styles = config.kit.amp
? Array.from(css_deps).map((url) => {
const resolved = `${output_dir}/client/${config.kit.appDir}/${url}`;
return fs.readFileSync(resolved, 'utf-8');
})
: [];

metadata_lookup[file] = {
entry: client_manifest[file].file,
css,
js,
styles
};
});
if (allow_ssr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any harm in having metadata_lookup populated even if it's not used? I wonder if it might be simpler to just leave the code as is and not add an extra condition, but want to make sure I'm not missing anything

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, I just thought that it would be better to not populate metadata_lookup when it isn't going to be used.

manifest.components.forEach((file) => {
const js_deps = new Set();
const css_deps = new Set();

find_deps(file, js_deps, css_deps);

const js = Array.from(js_deps);
const css = Array.from(css_deps);

const styles = config.kit.amp
? Array.from(css_deps).map((url) => {
const resolved = `${output_dir}/client/${config.kit.appDir}/${url}`;
return fs.readFileSync(resolved, 'utf-8');
})
: [];

metadata_lookup[file] = {
entry: client_manifest[file].file,
css,
js,
styles
};
});
}

/** @type {Set<string>} */
const entry_js = new Set();
Expand Down Expand Up @@ -412,21 +415,23 @@ async function build_server(
externalFetch: hooks.externalFetch || fetch
});

const module_lookup = {
${manifest.components.map(file => `${s(file)}: () => import(${s(app_relative(file))})`)}
};
${allow_ssr ?
`const module_lookup = {
${manifest.components.map((file) => `${s(file)}: () => import(${s(app_relative(file))})`)}
};` : ''}

const metadata_lookup = ${s(metadata_lookup)};
${allow_ssr ? `const metadata_lookup = ${s(metadata_lookup)};` : ''}
JeanJPNM marked this conversation as resolved.
Show resolved Hide resolved

async function load_component(file) {
const { entry, css, js, styles } = metadata_lookup[file];
${allow_ssr ?
`const { entry, css, js, styles } = metadata_lookup[file];
return {
module: await module_lookup[file](),
entry: assets + ${s(prefix)} + entry,
css: css.map(dep => assets + ${s(prefix)} + dep),
js: js.map(dep => assets + ${s(prefix)} + dep),
styles
};
};` : 'throw new Error(\'Cannot use ssr when config.kit.ssr is "never"\')'}
}

export function render(request, {
Expand Down
17 changes: 11 additions & 6 deletions packages/kit/src/core/config/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ const options = object(
files: fun((filename) => !/\.DS_STORE/.test(filename))
}),

ssr: boolean(true),
ssr: list([true, false, 'never']),

target: string(null),

Expand Down Expand Up @@ -275,16 +275,21 @@ function boolean(fallback) {
}

/**
* @param {string[]} options
* @param {unknown[]} options
* @returns {Validator}
*/
function list(options, fallback = options[0]) {
return validate(fallback, (input, keypath) => {
/** @param {unknown} i */
const stringify = (i) => (typeof i === 'string' ? `"${i}"` : `${i}`);
if (!options.includes(input)) {
// prettier-ignore
const msg = options.length > 2
? `${keypath} should be one of ${options.slice(0, -1).map(input => `"${input}"`).join(', ')} or "${options[options.length - 1]}"`
: `${keypath} should be either "${options[0]}" or "${options[1]}"`;
const msg =
options.length > 2
? `${keypath} should be one of ${options
.slice(0, -1)
.map(stringify)
.join(', ')} or ${stringify(options[options.length - 1])}`
: `${keypath} should be either ${stringify(options[0])} or ${stringify(options[1])}`;

throw new Error(msg);
}
Expand Down
13 changes: 9 additions & 4 deletions packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ const s = JSON.stringify;
* branch: Array<import('./types').Loaded>;
* options: import('types/internal').SSRRenderOptions;
* $session: any;
* page_config: { hydrate: boolean, router: boolean, ssr: boolean };
* page_config: {
* hydrate: boolean,
* router: boolean,
* ssr: import('types/internal').SSROption
* };
JeanJPNM marked this conversation as resolved.
Show resolved Hide resolved
* status: number;
* error?: Error,
* page?: import('types/page').Page
Expand Down Expand Up @@ -43,7 +47,8 @@ export async function render_response({
error.stack = options.get_stack(error);
}

if (page_config.ssr) {
// excludes false and 'never'
if (page_config.ssr === true) {
branch.forEach(({ node, loaded, fetched, uses_credentials }) => {
if (node.css) node.css.forEach((url) => css.add(url));
if (node.js) node.js.forEach((url) => js.add(url));
Expand Down Expand Up @@ -124,9 +129,9 @@ export async function render_response({
})},
host: ${page && page.host ? s(page.host) : 'location.host'},
route: ${!!page_config.router},
spa: ${!page_config.ssr},
spa: ${page_config.ssr === 'never' || !page_config.ssr},
JeanJPNM marked this conversation as resolved.
Show resolved Hide resolved
trailing_slash: ${s(options.trailing_slash)},
hydrate: ${page_config.ssr && page_config.hydrate ? `{
hydrate: ${page_config.ssr === true && page_config.hydrate ? `{
status: ${status},
error: ${serialize_error(error)},
nodes: [
Expand Down
15 changes: 14 additions & 1 deletion packages/kit/src/runtime/server/page/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ import { coalesce_to_error } from '../../../utils/error.js';
*/
export async function respond(opts) {
const { request, options, state, $session, route } = opts;
if (options.ssr === 'never') {
return await render_response({
branch: [],
$session,
options,
page_config: {
hydrate: true,
router: true,
ssr: false
},
status: 200
});
}

/** @type {Array<SSRNode | undefined>} */
let nodes;
Expand Down Expand Up @@ -227,7 +240,7 @@ export async function respond(opts) {
*/
function get_page_config(leaf, options) {
return {
ssr: 'ssr' in leaf ? !!leaf.ssr : options.ssr,
ssr: 'ssr' in leaf ? !!leaf.ssr : options.ssr === true,
router: 'router' in leaf ? !!leaf.router : options.router,
hydrate: 'hydrate' in leaf ? !!leaf.hydrate : options.hydrate
};
Expand Down
16 changes: 16 additions & 0 deletions packages/kit/test/apps/spa/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "test-spa",
"private": true,
"version": "0.0.1",
"scripts": {
"dev": "../../../svelte-kit.js dev",
"build": "../../../svelte-kit.js build",
"preview": "../../../svelte-kit.js preview"
},
"devDependencies": {
"@sveltejs/kit": "workspace:*",
"@sveltejs/adapter-node": "workspace:*",
"svelte": "^3.43.0"
},
"type": "module"
}
13 changes: 13 additions & 0 deletions packages/kit/test/apps/spa/src/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const root = /** @type {HTMLElement}*/ (document.getElementById('svelte'));
JeanJPNM marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 22 additions & 0 deletions packages/kit/test/apps/spa/src/routes/client-code/_tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as assert from 'uvu/assert';

/** @type {import('test').TestMaker} */
export default function (test) {
test('page with client only code', '/client-code', async ({ page, js }) => {
if (js) {
await page.waitForSelector('span');
assert.equal(await page.textContent('span'), 'App root is div#svelte');
} else {
assert.ok(await page.evaluate(() => !document.querySelector('span')));
}
});

test('page with client only dependency', '/client-code/dep', async ({ page, js }) => {
if (js) {
await page.waitForSelector('span');
assert.equal(await page.textContent('span'), 'App root is div#svelte');
} else {
assert.ok(await page.evaluate(() => !document.querySelector('span')));
}
});
}
5 changes: 5 additions & 0 deletions packages/kit/test/apps/spa/src/routes/client-code/dep.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
import { root } from './_client_dep';
</script>

<span>App root is {root.localName}#{root.id}</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script context="module">
const appRoot = /** @type {HTMLElement}*/ (document.getElementById('svelte'));
JeanJPNM marked this conversation as resolved.
Show resolved Hide resolved
</script>

<span>App root is {appRoot.localName}#{appRoot.id}</span>
9 changes: 9 additions & 0 deletions packages/kit/test/apps/spa/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
ssr: 'never',
target: '#svelte'
}
};

export default config;
4 changes: 2 additions & 2 deletions packages/kit/types/config.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UserConfig as ViteConfig } from 'vite';
import { RecursiveRequired } from './helper';
import { Logger, TrailingSlash } from './internal';
import { Logger, SSROption, TrailingSlash } from './internal';

export interface AdapterUtils {
log: Logger;
Expand Down Expand Up @@ -68,7 +68,7 @@ export interface Config {
serviceWorker?: {
files?(filepath: string): boolean;
};
ssr?: boolean;
ssr?: SSROption;
target?: string;
trailingSlash?: TrailingSlash;
vite?: ViteConfig | (() => ViteConfig);
Expand Down
4 changes: 3 additions & 1 deletion packages/kit/types/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface Logger {
info(msg: string): void;
}

export type SSROption = boolean | 'never';

export interface SSRComponent {
ssr?: boolean;
router?: boolean;
Expand Down Expand Up @@ -130,7 +132,7 @@ export interface SSRRenderOptions {
root: SSRComponent['default'];
router: boolean;
service_worker?: string;
ssr: boolean;
ssr: SSROption;
target: string;
template({ head, body }: { head: string; body: string }): string;
trailing_slash: TrailingSlash;
Expand Down