Skip to content

Commit

Permalink
[breaking] add builder.generateFallback(fallback) API (#8013)
Browse files Browse the repository at this point in the history
closes #7899
  • Loading branch information
Rich-Harris committed Dec 9, 2022
1 parent 3d68052 commit 154ee74
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 31 deletions.
6 changes: 6 additions & 0 deletions .changeset/shaggy-bikes-wink.md
@@ -0,0 +1,6 @@
---
'@sveltejs/adapter-static': patch
'@sveltejs/kit': patch
---

[breaking] replace automatic fallback generation with `builder.generateFallback(fallback)`
6 changes: 5 additions & 1 deletion packages/adapter-static/index.js
Expand Up @@ -79,7 +79,11 @@ See https://kit.svelte.dev/docs/page-options#prerender for more details`
builder.rimraf(pages);

builder.writeClient(assets);
builder.writePrerendered(pages, { fallback });
builder.writePrerendered(pages);

if (fallback) {
builder.generateFallback(path.join(pages, fallback));
}

if (precompress) {
builder.log.minor('Compressing assets and pages');
Expand Down
53 changes: 42 additions & 11 deletions packages/kit/src/core/adapt/builder.js
@@ -1,11 +1,14 @@
import { existsSync, statSync, createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';
import { fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import glob from 'tiny-glob';
import zlib from 'zlib';
import { existsSync, statSync, createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream';
import { promisify } from 'util';
import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
import { generate_manifest } from '../generate_manifest/index.js';
import { get_route_segments } from '../../utils/routing.js';
import { get_env } from '../../exports/vite/utils.js';

const pipe = promisify(pipeline);

Expand Down Expand Up @@ -104,6 +107,33 @@ export function create_builder({ config, build_data, routes, prerendered, log })
}
},

generateFallback(dest) {
// do prerendering in a subprocess so any dangling stuff gets killed upon completion
const script = fileURLToPath(new URL('../prerender/fallback.js', import.meta.url));

const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`;

const env = get_env(config.kit.env, 'production');

return new Promise((fulfil, reject) => {
const child = fork(
script,
[dest, manifest_path, JSON.stringify({ ...env.private, ...env.public })],
{
stdio: 'inherit'
}
);

child.on('exit', (code) => {
if (code) {
reject(new Error(`Could not create a fallback page — failed with code ${code}`));
} else {
fulfil(undefined);
}
});
});
},

generateManifest: ({ relativePath }) => {
return generate_manifest({
build_data,
Expand Down Expand Up @@ -132,16 +162,17 @@ export function create_builder({ config, build_data, routes, prerendered, log })
return [...copy(`${config.kit.outDir}/output/client`, dest)];
},

writePrerendered(dest, { fallback } = {}) {
const source = `${config.kit.outDir}/output/prerendered`;
const files = [...copy(`${source}/pages`, dest), ...copy(`${source}/dependencies`, dest)];

if (fallback) {
files.push(fallback);
copy(`${source}/fallback.html`, `${dest}/${fallback}`);
// @ts-expect-error
writePrerendered(dest, opts) {
// TODO remove for 1.0
if (opts?.fallback) {
throw new Error(
'The fallback option no longer exists — use builder.generateFallback(fallback) instead'
);
}

return files;
const source = `${config.kit.outDir}/output/prerendered`;
return [...copy(`${source}/pages`, dest), ...copy(`${source}/dependencies`, dest)];
},

writeServer(dest) {
Expand Down
43 changes: 43 additions & 0 deletions packages/kit/src/core/prerender/fallback.js
@@ -0,0 +1,43 @@
import { readFileSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';
import { pathToFileURL } from 'url';
import { mkdirp } from '../../utils/filesystem.js';
import { installPolyfills } from '../../exports/node/polyfills.js';
import { load_config } from '../config/index.js';

const [, , dest, manifest_path, env] = process.argv;

/** @type {import('types').ValidatedKitConfig} */
const config = (await load_config()).kit;

installPolyfills();

const server_root = join(config.outDir, 'output');

/** @type {import('types').ServerModule} */
const { Server, override } = await import(pathToFileURL(`${server_root}/server/index.js`).href);

/** @type {import('types').SSRManifest} */
const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;

override({
building: true,
paths: config.paths,
read: (file) => readFileSync(join(config.files.assets, file))
});

const server = new Server(manifest);
await server.init({ env: JSON.parse(env) });

const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
getClientAddress: () => {
throw new Error('Cannot read clientAddress during prerendering');
},
prerendering: {
fallback: true,
dependencies: new Map()
}
});

mkdirp(dirname(dest));
writeFileSync(dest, await rendered.text());
12 changes: 0 additions & 12 deletions packages/kit/src/core/prerender/prerender.js
Expand Up @@ -453,18 +453,6 @@ export async function prerender() {
);
}

const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
getClientAddress,
prerendering: {
fallback: true,
dependencies: new Map()
}
});

const file = `${config.outDir}/output/prerendered/fallback.html`;
mkdirp(dirname(file));
writeFileSync(file, await rendered.text());

output_and_exit({ prerendered, prerender_map });
}

Expand Down
13 changes: 6 additions & 7 deletions packages/kit/types/index.d.ts
Expand Up @@ -90,6 +90,11 @@ export interface Builder {
*/
createEntries(fn: (route: RouteDefinition) => AdapterEntry): Promise<void>;

/**
* Generate a fallback page for a static webserver to use when no route is matched. Useful for single-page apps.
*/
generateFallback(dest: string): Promise<void>;

/**
* Generate a server-side manifest to initialise the SvelteKit [server](https://kit.svelte.dev/docs/types#public-types-server) with.
* @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
Expand Down Expand Up @@ -117,15 +122,9 @@ export interface Builder {
/**
* Write prerendered files to `dest`.
* @param dest the destination folder
* @param opts.fallback the name of a file for fallback responses, like `200.html` or `404.html` depending on where the app is deployed
* @returns an array of files written to `dest`
*/
writePrerendered(
dest: string,
opts?: {
fallback?: string;
}
): string[];
writePrerendered(dest: string): string[];
/**
* Write server-side code to `dest`.
* @param dest the destination folder
Expand Down

0 comments on commit 154ee74

Please sign in to comment.