Skip to content

Commit 154ee74

Browse files
authored
[breaking] add builder.generateFallback(fallback) API (#8013)
closes #7899
1 parent 3d68052 commit 154ee74

File tree

6 files changed

+102
-31
lines changed

6 files changed

+102
-31
lines changed

.changeset/shaggy-bikes-wink.md

Lines changed: 6 additions & 0 deletions
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+
[breaking] replace automatic fallback generation with `builder.generateFallback(fallback)`

packages/adapter-static/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ See https://kit.svelte.dev/docs/page-options#prerender for more details`
7979
builder.rimraf(pages);
8080

8181
builder.writeClient(assets);
82-
builder.writePrerendered(pages, { fallback });
82+
builder.writePrerendered(pages);
83+
84+
if (fallback) {
85+
builder.generateFallback(path.join(pages, fallback));
86+
}
8387

8488
if (precompress) {
8589
builder.log.minor('Compressing assets and pages');

packages/kit/src/core/adapt/builder.js

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { existsSync, statSync, createReadStream, createWriteStream } from 'node:fs';
2+
import { pipeline } from 'node:stream';
3+
import { promisify } from 'node:util';
4+
import { fork } from 'node:child_process';
5+
import { fileURLToPath } from 'node:url';
16
import glob from 'tiny-glob';
27
import zlib from 'zlib';
3-
import { existsSync, statSync, createReadStream, createWriteStream } from 'fs';
4-
import { pipeline } from 'stream';
5-
import { promisify } from 'util';
68
import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
79
import { generate_manifest } from '../generate_manifest/index.js';
810
import { get_route_segments } from '../../utils/routing.js';
11+
import { get_env } from '../../exports/vite/utils.js';
912

1013
const pipe = promisify(pipeline);
1114

@@ -104,6 +107,33 @@ export function create_builder({ config, build_data, routes, prerendered, log })
104107
}
105108
},
106109

110+
generateFallback(dest) {
111+
// do prerendering in a subprocess so any dangling stuff gets killed upon completion
112+
const script = fileURLToPath(new URL('../prerender/fallback.js', import.meta.url));
113+
114+
const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`;
115+
116+
const env = get_env(config.kit.env, 'production');
117+
118+
return new Promise((fulfil, reject) => {
119+
const child = fork(
120+
script,
121+
[dest, manifest_path, JSON.stringify({ ...env.private, ...env.public })],
122+
{
123+
stdio: 'inherit'
124+
}
125+
);
126+
127+
child.on('exit', (code) => {
128+
if (code) {
129+
reject(new Error(`Could not create a fallback page — failed with code ${code}`));
130+
} else {
131+
fulfil(undefined);
132+
}
133+
});
134+
});
135+
},
136+
107137
generateManifest: ({ relativePath }) => {
108138
return generate_manifest({
109139
build_data,
@@ -132,16 +162,17 @@ export function create_builder({ config, build_data, routes, prerendered, log })
132162
return [...copy(`${config.kit.outDir}/output/client`, dest)];
133163
},
134164

135-
writePrerendered(dest, { fallback } = {}) {
136-
const source = `${config.kit.outDir}/output/prerendered`;
137-
const files = [...copy(`${source}/pages`, dest), ...copy(`${source}/dependencies`, dest)];
138-
139-
if (fallback) {
140-
files.push(fallback);
141-
copy(`${source}/fallback.html`, `${dest}/${fallback}`);
165+
// @ts-expect-error
166+
writePrerendered(dest, opts) {
167+
// TODO remove for 1.0
168+
if (opts?.fallback) {
169+
throw new Error(
170+
'The fallback option no longer exists — use builder.generateFallback(fallback) instead'
171+
);
142172
}
143173

144-
return files;
174+
const source = `${config.kit.outDir}/output/prerendered`;
175+
return [...copy(`${source}/pages`, dest), ...copy(`${source}/dependencies`, dest)];
145176
},
146177

147178
writeServer(dest) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { readFileSync, writeFileSync } from 'fs';
2+
import { dirname, join } from 'path';
3+
import { pathToFileURL } from 'url';
4+
import { mkdirp } from '../../utils/filesystem.js';
5+
import { installPolyfills } from '../../exports/node/polyfills.js';
6+
import { load_config } from '../config/index.js';
7+
8+
const [, , dest, manifest_path, env] = process.argv;
9+
10+
/** @type {import('types').ValidatedKitConfig} */
11+
const config = (await load_config()).kit;
12+
13+
installPolyfills();
14+
15+
const server_root = join(config.outDir, 'output');
16+
17+
/** @type {import('types').ServerModule} */
18+
const { Server, override } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
19+
20+
/** @type {import('types').SSRManifest} */
21+
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
22+
23+
override({
24+
building: true,
25+
paths: config.paths,
26+
read: (file) => readFileSync(join(config.files.assets, file))
27+
});
28+
29+
const server = new Server(manifest);
30+
await server.init({ env: JSON.parse(env) });
31+
32+
const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
33+
getClientAddress: () => {
34+
throw new Error('Cannot read clientAddress during prerendering');
35+
},
36+
prerendering: {
37+
fallback: true,
38+
dependencies: new Map()
39+
}
40+
});
41+
42+
mkdirp(dirname(dest));
43+
writeFileSync(dest, await rendered.text());

packages/kit/src/core/prerender/prerender.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -453,18 +453,6 @@ export async function prerender() {
453453
);
454454
}
455455

456-
const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
457-
getClientAddress,
458-
prerendering: {
459-
fallback: true,
460-
dependencies: new Map()
461-
}
462-
});
463-
464-
const file = `${config.outDir}/output/prerendered/fallback.html`;
465-
mkdirp(dirname(file));
466-
writeFileSync(file, await rendered.text());
467-
468456
output_and_exit({ prerendered, prerender_map });
469457
}
470458

packages/kit/types/index.d.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ export interface Builder {
9090
*/
9191
createEntries(fn: (route: RouteDefinition) => AdapterEntry): Promise<void>;
9292

93+
/**
94+
* Generate a fallback page for a static webserver to use when no route is matched. Useful for single-page apps.
95+
*/
96+
generateFallback(dest: string): Promise<void>;
97+
9398
/**
9499
* Generate a server-side manifest to initialise the SvelteKit [server](https://kit.svelte.dev/docs/types#public-types-server) with.
95100
* @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated
@@ -117,15 +122,9 @@ export interface Builder {
117122
/**
118123
* Write prerendered files to `dest`.
119124
* @param dest the destination folder
120-
* @param opts.fallback the name of a file for fallback responses, like `200.html` or `404.html` depending on where the app is deployed
121125
* @returns an array of files written to `dest`
122126
*/
123-
writePrerendered(
124-
dest: string,
125-
opts?: {
126-
fallback?: string;
127-
}
128-
): string[];
127+
writePrerendered(dest: string): string[];
129128
/**
130129
* Write server-side code to `dest`.
131130
* @param dest the destination folder

0 commit comments

Comments
 (0)