diff --git a/.changeset/witty-avocados-admire.md b/.changeset/witty-avocados-admire.md new file mode 100644 index 000000000000..4f7cf8b081b1 --- /dev/null +++ b/.changeset/witty-avocados-admire.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +prerender in a subprocess diff --git a/packages/kit/rollup.config.js b/packages/kit/rollup.config.js index 7fa7819d249d..1abcaa774219 100644 --- a/packages/kit/rollup.config.js +++ b/packages/kit/rollup.config.js @@ -61,9 +61,10 @@ export default [ { input: { cli: 'src/cli.js', + hooks: 'src/hooks.js', node: 'src/node/index.js', 'node/polyfills': 'src/node/polyfills.js', - hooks: 'src/hooks.js', + prerender: 'src/core/prerender/prerender.js', vite: 'src/vite/index.js' }, output: { diff --git a/packages/kit/src/core/prerender/prerender.js b/packages/kit/src/core/prerender/prerender.js index 6d6b58187f14..80471728d105 100644 --- a/packages/kit/src/core/prerender/prerender.js +++ b/packages/kit/src/core/prerender/prerender.js @@ -7,12 +7,18 @@ import { is_root_relative, resolve } from '../../utils/url.js'; import { queue } from './queue.js'; import { crawl } from './crawl.js'; import { escape_html_attr } from '../../utils/escape.js'; +import { logger } from '../utils.js'; +import { load_config } from '../config/index.js'; /** * @typedef {import('types').PrerenderErrorHandler} PrerenderErrorHandler * @typedef {import('types').Logger} Logger */ +const [, , client_out_dir, results_path, manifest_path, verbose] = process.argv; + +prerender(); + /** * @param {Parameters[0]} details * @param {import('types').ValidatedKitConfig} config @@ -50,14 +56,19 @@ const OK = 2; const REDIRECT = 3; /** - * @param {{ - * config: import('types').ValidatedKitConfig; - * client_out_dir: string; - * manifest_path: string; - * log: Logger; - * }} opts + * @param {import('types').Prerendered} prerendered */ -export async function prerender({ config, client_out_dir, manifest_path, log }) { +const output_and_exit = (prerendered) => { + writeFileSync( + results_path, + JSON.stringify(prerendered, (_key, value) => + value instanceof Map ? Array.from(value.entries()) : value + ) + ); + process.exit(0); +}; + +export async function prerender() { /** @type {import('types').Prerendered} */ const prerendered = { pages: new Map(), @@ -66,10 +77,19 @@ export async function prerender({ config, client_out_dir, manifest_path, log }) paths: [] }; + /** @type {import('types').ValidatedKitConfig} */ + const config = (await load_config()).kit; + if (!config.prerender.enabled) { - return prerendered; + output_and_exit(prerendered); + return; } + /** @type {import('types').Logger} */ + const log = logger({ + verbose: verbose === 'true' + }); + installPolyfills(); const { fetch } = globalThis; globalThis.fetch = async (info, init) => { @@ -349,7 +369,7 @@ export async function prerender({ config, client_out_dir, manifest_path, log }) mkdirp(dirname(file)); writeFileSync(file, await rendered.text()); - return prerendered; + output_and_exit(prerendered); } /** @return {string} */ diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js index ae0bb863cd79..4d9bd942e742 100644 --- a/packages/kit/src/vite/index.js +++ b/packages/kit/src/vite/index.js @@ -1,5 +1,6 @@ -import fs from 'fs'; -import path from 'path'; +import { fork } from 'node:child_process'; +import fs, { existsSync } from 'node:fs'; +import path from 'node:path'; import colors from 'kleur'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import * as vite from 'vite'; @@ -7,7 +8,6 @@ import { mkdirp, posixify, rimraf } from '../utils/filesystem.js'; import * as sync from '../core/sync/sync.js'; import { build_server } from './build/build_server.js'; import { build_service_worker } from './build/build_service_worker.js'; -import { prerender } from '../core/prerender/prerender.js'; import { load_config } from '../core/config/index.js'; import { dev } from './dev/index.js'; import { generate_manifest } from '../core/generate_manifest/index.js'; @@ -15,6 +15,7 @@ import { get_runtime_directory, logger } from '../core/utils.js'; import { find_deps, get_default_config as get_default_build_config } from './build/utils.js'; import { preview } from './preview/index.js'; import { get_aliases, resolve_entry } from './utils.js'; +import { fileURLToPath } from 'node:url'; const cwd = process.cwd(); @@ -273,9 +274,8 @@ function kit() { * then use this hook to kick off builds for the server and service worker. */ async writeBundle(_options, bundle) { - log = logger({ - verbose: vite_config.logLevel === 'info' - }); + const verbose = vite_config.logLevel === 'info'; + log = logger({ verbose }); fs.writeFileSync( `${paths.client_out_dir}/${svelte_config.kit.appDir}/version.json`, @@ -318,14 +318,39 @@ function kit() { })};\n` ); - process.env.SVELTEKIT_SERVER_BUILD_COMPLETED = 'true'; log.info('Prerendering'); + await new Promise((fulfil, reject) => { + const results_path = `${svelte_config.kit.outDir}/generated/prerendered.json`; + + // do prerendering in a subprocess so any dangling stuff gets killed upon completion + const script = fileURLToPath( + new URL( + process.env.BUNDLED ? './prerender.js' : '../core/prerender/prerender.js', + import.meta.url + ) + ); - prerendered = await prerender({ - config: svelte_config.kit, - client_out_dir: vite_config.build.outDir, - manifest_path, - log + const child = fork( + script, + [vite_config.build.outDir, results_path, manifest_path, '' + verbose], + { + stdio: 'inherit' + } + ); + + child.on('exit', (code) => { + if (code) { + reject(new Error(`Prerendering failed with code ${code}`)); + } else { + prerendered = JSON.parse(fs.readFileSync(results_path, 'utf8'), (key, value) => { + if (key === 'pages' || key === 'assets' || key === 'redirects') { + return new Map(value); + } + return value; + }); + fulfil(undefined); + } + }); }); if (options.service_worker_entry_file) { @@ -365,13 +390,6 @@ function kit() { `See ${colors.bold().cyan('https://kit.svelte.dev/docs/adapters')} to learn how to configure your app to run on the platform of your choosing` ); } - - if (svelte_config.kit.prerender.enabled) { - // this is necessary to close any open db connections, etc. - // TODO: prerender in a subprocess so we can exit in isolation and then remove this - // https://github.com/sveltejs/kit/issues/5306 - process.exit(0); - } }, /**