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

[breaking] add builder.generateFallback(fallback) API #8013

Merged
merged 1 commit into from Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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