Skip to content

Commit 4131467

Browse files
author
Rich Harris
authored
SPA mode (#1181)
* rename ssr to respond, since ssr is sometimes false * add tests to adapter-static * add failing test for #754 * implement fallback rendering * render fallback page * update lockfile * changeset * add readme for adapter-static * formatting * gah * missing full stop * remove ESM export for now, no benefit to it * windows * lockfile shenanigans * argh windows * ugh WHAT NOW windows * try this, you dumb timewaster
1 parent 1a7ce01 commit 4131467

39 files changed

+513
-58
lines changed

.changeset/popular-masks-cheat.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@sveltejs/adapter-static': patch
3+
'@sveltejs/kit': patch
4+
---
5+
6+
Prerender fallback page for SPAs

packages/adapter-static/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
.DS_Store
22
node_modules
3+
.svelte
4+
build

packages/adapter-static/README.md

+60-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,62 @@
1-
# adapter-static
1+
# @sveltejs/adapter-static
22

3-
Adapter for Svelte apps that prerenders your entire site as a collection of static files, which is equivalent to `sapper export`.
3+
[Adapter](https://kit.svelte.dev/docs#adapters) for SvelteKit apps that prerenders your site as a collection of static files.
44

5-
This is very experimental. The adapter API is still in flux and will likely change before 1.0.
5+
```js
6+
// svelte.config.cjs
7+
const adapter = require('@sveltejs/adapter-static');
8+
9+
module.exports = {
10+
kit: {
11+
adapter: adapter({
12+
// default options are shown
13+
pages: 'build',
14+
assets: 'build',
15+
fallback: null
16+
})
17+
}
18+
};
19+
```
20+
21+
Unless you're in [SPA mode](#spa-mode), the adapter will attempt to prerender every page of your app, regardless of whether the [`prerender`](https://kit.svelte.dev/docs#ssr-and-javascript-prerender) option is set.
22+
23+
## Options
24+
25+
### pages
26+
27+
The directory to write prerendered pages to. It defaults to `build`.
28+
29+
### assets
30+
31+
The directory to write static assets (the contents of `static`, plus client-side JS and CSS generated by SvelteKit) to. Ordinarily this should be the same as `pages`, and it will default to whatever the value of `pages` is, but in rare circumstances you might need to output pages and assets to separate locations.
32+
33+
### fallback
34+
35+
Specify a fallback page for SPA mode, e.g. `index.html` or `200.html` or `404.html`.
36+
37+
## SPA mode
38+
39+
You can use `adapter-static` to create a single-page app or SPA by specifying a **fallback page**.
40+
41+
> In most situations this is not recommended: it harms SEO, tends to slow down perceived performance, and makes your app inaccessible to users if JavaScript fails or is disabled (which happens [more often than you probably think](https://kryogenix.org/code/browser/everyonehasjs.html)).
42+
43+
The fallback page is a blank HTML page that loads your SvelteKit app and navigates to the correct route. For example [Surge](https://surge.sh/help/adding-a-200-page-for-client-side-routing), a static web host, lets you add a `200.html` file that will handle any requests that don't otherwise match. We can create that file like so:
44+
45+
```js
46+
// svelte.config.cjs
47+
const adapter = require('@sveltejs/adapter-static');
48+
49+
module.exports = {
50+
kit: {
51+
adapter: adapter({
52+
fallback: '200.html'
53+
})
54+
}
55+
};
56+
```
57+
58+
When operating in SPA mode, only pages that have the [`prerender`](https://kit.svelte.dev/docs#ssr-and-javascript-prerender) option set will be prerendered.
59+
60+
## License
61+
62+
[MIT](LICENSE)

packages/adapter-static/index.js packages/adapter-static/index.cjs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = function ({ pages = 'build', assets = 'build' } = {}) {
1+
module.exports = function ({ pages = 'build', assets = pages, fallback = null } = {}) {
22
/** @type {import('@sveltejs/kit').Adapter} */
33
const adapter = {
44
name: '@sveltejs/adapter-static',
@@ -8,7 +8,8 @@ module.exports = function ({ pages = 'build', assets = 'build' } = {}) {
88
utils.copy_client_files(assets);
99

1010
await utils.prerender({
11-
force: true,
11+
fallback,
12+
all: !fallback,
1213
dest: pages
1314
});
1415
}

packages/adapter-static/package.json

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,21 @@
33
"version": "1.0.0-next.4",
44
"scripts": {
55
"lint": "eslint --ignore-path .gitignore \"**/*.{ts,js,svelte}\" && npm run check-format",
6+
"check": "tsc",
67
"format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
7-
"check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore"
8+
"check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
9+
"test": "uvu test test.js"
810
},
911
"devDependencies": {
1012
"@sveltejs/kit": "workspace:*",
11-
"typescript": "^4.2.3"
13+
"playwright-chromium": "^1.10.0",
14+
"port-authority": "^1.1.2",
15+
"sirv": "^1.0.11",
16+
"typescript": "^4.2.4"
17+
},
18+
"type": "module",
19+
"main": "index.cjs",
20+
"exports": {
21+
"require": "./index.cjs"
1222
}
1323
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
node_modules
3+
/.svelte
4+
/build
5+
/functions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "~TODO~",
3+
"version": "0.0.1",
4+
"scripts": {
5+
"dev": "svelte-kit dev",
6+
"build": "svelte-kit build",
7+
"start": "svelte-kit start"
8+
},
9+
"devDependencies": {
10+
"@sveltejs/kit": "next",
11+
"svelte": "^3.29.0",
12+
"vite": "^2.1.0"
13+
},
14+
"type": "module"
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
%svelte.head%
7+
</head>
8+
<body>
9+
<div id="svelte">%svelte.body%</div>
10+
</body>
11+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/// <reference types="@sveltejs/kit" />
2+
/// <reference types="svelte" />
3+
/// <reference types="vite/client" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>This page was prerendered</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** @type {import('@sveltejs/kit').Config} */
2+
module.exports = {
3+
kit: {
4+
adapter: require('../../../index.cjs')(),
5+
target: '#svelte'
6+
}
7+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
node_modules
3+
/.svelte
4+
/build
5+
/functions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# create-svelte
2+
3+
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
4+
5+
## Creating a project
6+
7+
If you're seeing this, you've probably already done this step. Congrats!
8+
9+
```bash
10+
# create a new project in the current directory
11+
npm init svelte@next
12+
13+
# create a new project in my-app
14+
npm init svelte@next my-app
15+
```
16+
17+
> Note: the `@next` is temporary
18+
19+
## Developing
20+
21+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
22+
23+
```bash
24+
npm run dev
25+
26+
# or start the server and open the app in a new browser tab
27+
npm run dev -- --open
28+
```
29+
30+
## Building
31+
32+
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
33+
34+
```bash
35+
npm run build
36+
```
37+
38+
> You can preview the built app with `npm start`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"paths": {
5+
"$lib/*": ["src/lib/*"]
6+
}
7+
},
8+
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "~TODO~",
3+
"version": "0.0.1",
4+
"scripts": {
5+
"dev": "svelte-kit dev",
6+
"build": "svelte-kit build",
7+
"start": "svelte-kit start"
8+
},
9+
"devDependencies": {
10+
"@sveltejs/adapter-node": "next",
11+
"@sveltejs/kit": "next",
12+
"svelte": "^3.29.0",
13+
"vite": "^2.1.0"
14+
},
15+
"type": "module"
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
%svelte.head%
7+
</head>
8+
<body>
9+
<div id="svelte">%svelte.body%</div>
10+
</body>
11+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/// <reference types="@sveltejs/kit" />
2+
/// <reference types="svelte" />
3+
/// <reference types="vite/client" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<nav>
2+
<a href="/">home</a>
3+
<a href="/about">about</a>
4+
</nav>
5+
6+
<slot></slot>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script context="module">
2+
export const prerender = true;
3+
</script>
4+
5+
<h1>This page was prerendered</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>This page was not prerendered</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** @type {import('@sveltejs/kit').Config} */
2+
module.exports = {
3+
kit: {
4+
adapter: require('../../../index.cjs')({
5+
fallback: '200.html'
6+
}),
7+
target: '#svelte'
8+
}
9+
};

packages/adapter-static/test/test.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import fs from 'fs';
2+
import * as assert from 'uvu/assert';
3+
import { run } from './utils.js';
4+
5+
run('prerendered', (test) => {
6+
test('generates HTML files', ({ cwd }) => {
7+
assert.ok(fs.existsSync(`${cwd}/build/index.html`));
8+
});
9+
10+
test('prerenders content', async ({ base, page }) => {
11+
await page.goto(base);
12+
assert.equal(await page.textContent('h1'), 'This page was prerendered');
13+
});
14+
});
15+
16+
run('spa', (test) => {
17+
test('generates a fallback page', ({ cwd }) => {
18+
assert.ok(fs.existsSync(`${cwd}/build/200.html`));
19+
});
20+
21+
test('does not prerender pages without prerender=true', ({ cwd }) => {
22+
assert.ok(!fs.existsSync(`${cwd}/build/index.html`));
23+
});
24+
25+
test('prerenders page with prerender=true', ({ cwd }) => {
26+
assert.ok(fs.existsSync(`${cwd}/build/about/index.html`));
27+
});
28+
29+
test('renders content in fallback page when JS runs', async ({ base, page }) => {
30+
await page.goto(base);
31+
assert.equal(await page.textContent('h1'), 'This page was not prerendered');
32+
});
33+
});

0 commit comments

Comments
 (0)