From 3496be8e0bcbf01eead4b7424b7ab0c91fb9482e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 2 Mar 2022 21:52:32 -0500 Subject: [PATCH 01/10] add config.kit.prerender.default --- .../test/apps/prerendered/svelte.config.js | 6 +++++- packages/kit/src/core/adapt/builder.js | 1 - .../kit/src/core/adapt/prerender/prerender.js | 7 +++---- packages/kit/src/core/config/index.spec.js | 1 + packages/kit/src/core/config/options.js | 1 + packages/kit/src/runtime/server/page/respond.js | 16 ++++++++++------ .../test/prerendering/basics/svelte.config.js | 1 + .../test/prerendering/options/svelte.config.js | 4 ++++ packages/kit/types/index.d.ts | 1 + packages/kit/types/internal.d.ts | 2 +- 10 files changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/adapter-static/test/apps/prerendered/svelte.config.js b/packages/adapter-static/test/apps/prerendered/svelte.config.js index 20cd2b3ff5b8..b2c66c3eeb9e 100644 --- a/packages/adapter-static/test/apps/prerendered/svelte.config.js +++ b/packages/adapter-static/test/apps/prerendered/svelte.config.js @@ -3,7 +3,11 @@ import adapter from '../../../index.js'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { - adapter: adapter() + adapter: adapter(), + + prerender: { + default: true + } } }; diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 2a21be674947..e8266fe1d5ea 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -156,7 +156,6 @@ export function create_builder({ config, build_data, log }) { const prerendered = await prerender({ out: dest, - all, config, build_data, fallback, diff --git a/packages/kit/src/core/adapt/prerender/prerender.js b/packages/kit/src/core/adapt/prerender/prerender.js index 9cdab9b9094d..b6b5f8375112 100644 --- a/packages/kit/src/core/adapt/prerender/prerender.js +++ b/packages/kit/src/core/adapt/prerender/prerender.js @@ -45,10 +45,9 @@ const REDIRECT = 3; * config: import('types').ValidatedConfig; * build_data: import('types').BuildData; * fallback?: string; - * all: boolean; // disregard `export const prerender = true` * }} opts */ -export async function prerender({ out, log, config, build_data, fallback, all }) { +export async function prerender({ out, log, config, build_data, fallback }) { /** @type {import('types').Prerendered} */ const prerendered = { pages: new Map(), @@ -145,7 +144,7 @@ export async function prerender({ out, log, config, build_data, fallback, all }) const response = await server.respond(new Request(`http://sveltekit-prerender${encoded}`), { prerender: { - all, + default: config.kit.prerender.default, dependencies } }); @@ -283,7 +282,7 @@ export async function prerender({ out, log, config, build_data, fallback, all }) const rendered = await server.respond(new Request('http://sveltekit-prerender/[fallback]'), { prerender: { fallback, - all: false, + default: false, dependencies: new Map() } }); diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index b2c0f71a8918..222251f01281 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -87,6 +87,7 @@ const get_defaults = (prefix = '') => ({ concurrency: 1, crawl: true, createIndexFiles: undefined, + default: false, enabled: true, entries: ['*'], force: undefined, diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index b6047884a2d5..bd58e9e2fdee 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -192,6 +192,7 @@ const options = object( (keypath) => `${keypath} has been removed — it is now controlled by the trailingSlash option. See https://kit.svelte.dev/docs/configuration#trailingslash` ), + default: boolean(false), enabled: boolean(true), entries: validate(['*'], (input, keypath) => { if (!Array.isArray(input) || !input.every((page) => typeof page === 'string')) { diff --git a/packages/kit/src/runtime/server/page/respond.js b/packages/kit/src/runtime/server/page/respond.js index 3f610540a871..c02db2ef7842 100644 --- a/packages/kit/src/runtime/server/page/respond.js +++ b/packages/kit/src/runtime/server/page/respond.js @@ -67,12 +67,16 @@ export async function respond(opts) { let page_config = get_page_config(leaf, options); - if (!leaf.prerender && state.prerender && !state.prerender.all) { - // if the page has `export const prerender = true`, continue, - // otherwise bail out at this point - return new Response(undefined, { - status: 204 - }); + if (state.prerender) { + // if the page isn't marked as prerenderable (or is explicitly + // marked NOT prerenderable, if `prerender.default` is `true`, + // then bail out at this point + const should_prerender = leaf.prerender ?? state.prerender.default; + if (!should_prerender) { + return new Response(undefined, { + status: 204 + }); + } } /** @type {Array} */ diff --git a/packages/kit/test/prerendering/basics/svelte.config.js b/packages/kit/test/prerendering/basics/svelte.config.js index e2a911987802..bad007a33f62 100644 --- a/packages/kit/test/prerendering/basics/svelte.config.js +++ b/packages/kit/test/prerendering/basics/svelte.config.js @@ -7,6 +7,7 @@ const config = { adapter: adapter(), prerender: { + default: true, onError: 'continue' }, diff --git a/packages/kit/test/prerendering/options/svelte.config.js b/packages/kit/test/prerendering/options/svelte.config.js index 558a6c06f4e3..d84e1d47641d 100644 --- a/packages/kit/test/prerendering/options/svelte.config.js +++ b/packages/kit/test/prerendering/options/svelte.config.js @@ -17,6 +17,10 @@ const config = { assets: 'https://cdn.example.com/stuff' }, + prerender: { + default: true + }, + trailingSlash: 'always', vite: { diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index ca8065de695f..8e210c422c4f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -69,6 +69,7 @@ export interface Config { prerender?: { concurrency?: number; crawl?: boolean; + default?: boolean; enabled?: boolean; entries?: string[]; onError?: PrerenderOnErrorValue; diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 139f0a950328..7a8a0cfddddc 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -150,7 +150,7 @@ export interface PrerenderDependency { export interface PrerenderOptions { fallback?: string; - all: boolean; + default: boolean; dependencies: Map; } From bab9ed733ec397427543398dbb61fd25fb700c49 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 08:32:24 -0500 Subject: [PATCH 02/10] prerender during build --- packages/adapter-static/index.js | 6 +- packages/kit/src/cli.js | 7 +- packages/kit/src/core/adapt/builder.js | 68 +++++++++---------- packages/kit/src/core/adapt/builder.spec.js | 4 ++ packages/kit/src/core/adapt/index.js | 8 +-- packages/kit/src/core/build/index.js | 59 +++++++++++----- .../core/{adapt => build}/prerender/crawl.js | 0 .../{adapt => build}/prerender/crawl.spec.js | 0 .../prerender/fixtures/basic-href/input.html | 0 .../prerender/fixtures/basic-href/output.json | 0 .../prerender/fixtures/basic-src/input.html | 0 .../prerender/fixtures/basic-src/output.json | 0 .../fixtures/basic-srcset/input.html | 0 .../fixtures/basic-srcset/output.json | 0 .../fixtures/include-rel-external/input.html | 0 .../fixtures/include-rel-external/output.json | 0 .../fixtures/unquoted-attributes/input.html | 0 .../fixtures/unquoted-attributes/output.json | 0 .../{adapt => build}/prerender/prerender.js | 49 ++++++------- .../core/{adapt => build}/prerender/queue.js | 0 .../{adapt => build}/prerender/queue.spec.js | 0 .../kit/src/core/generate_manifest/index.js | 17 ++--- .../kit/test/apps/basics/svelte.config.js | 3 + packages/kit/types/internal.d.ts | 5 +- packages/kit/types/private.d.ts | 18 +++-- 25 files changed, 134 insertions(+), 110 deletions(-) rename packages/kit/src/core/{adapt => build}/prerender/crawl.js (100%) rename packages/kit/src/core/{adapt => build}/prerender/crawl.spec.js (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/basic-href/input.html (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/basic-href/output.json (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/basic-src/input.html (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/basic-src/output.json (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/basic-srcset/input.html (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/basic-srcset/output.json (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/include-rel-external/input.html (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/include-rel-external/output.json (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/unquoted-attributes/input.html (100%) rename packages/kit/src/core/{adapt => build}/prerender/fixtures/unquoted-attributes/output.json (100%) rename packages/kit/src/core/{adapt => build}/prerender/prerender.js (87%) rename packages/kit/src/core/{adapt => build}/prerender/queue.js (100%) rename packages/kit/src/core/{adapt => build}/prerender/queue.spec.js (100%) diff --git a/packages/adapter-static/index.js b/packages/adapter-static/index.js index 58483e74bc46..309b4be59fa9 100644 --- a/packages/adapter-static/index.js +++ b/packages/adapter-static/index.js @@ -18,11 +18,7 @@ export default function ({ pages = 'build', assets = pages, fallback, precompres builder.writeStatic(assets); builder.writeClient(assets); - await builder.prerender({ - fallback, - all: !fallback, - dest: pages - }); + builder.writePrerendered(pages, { fallback }); if (precompress) { if (pages === assets) { diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index a8a7a9f9be28..c2ccc09d1ad7 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -5,6 +5,7 @@ import * as ports from 'port-authority'; import { load_config } from './core/config/index.js'; import { networkInterfaces, release } from 'os'; import { coalesce_to_error } from './utils/error.js'; +import { logger } from './core/utils.js'; /** @param {unknown} e */ function handle_error(e) { @@ -91,8 +92,10 @@ prog process.env.NODE_ENV = process.env.NODE_ENV || 'production'; const config = await load_config(); + const log = logger({ verbose }); + const { build } = await import('./core/build/index.js'); - const build_data = await build(config); + const { build_data, prerendered } = await build(config, { log }); console.log( `\nRun ${colors.bold().cyan('npm run preview')} to preview your production build locally.` @@ -100,7 +103,7 @@ prog if (config.kit.adapter) { const { adapt } = await import('./core/adapt/index.js'); - await adapt(config, build_data, { verbose }); + await adapt(config, build_data, prerendered, { log }); // this is necessary to close any open db connections, etc process.exit(0); diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index e8266fe1d5ea..6c1afb0140ef 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -1,25 +1,22 @@ import { copy, rimraf, mkdirp } from '../../utils/filesystem.js'; -import { prerender } from './prerender/prerender.js'; import { generate_manifest } from '../generate_manifest/index.js'; /** * @param {{ * config: import('types').ValidatedConfig; * build_data: import('types').BuildData; + * prerendered: import('types').Prerendered; * log: import('types').Logger; * }} opts * @returns {import('types').Builder} */ -export function create_builder({ config, build_data, log }) { +export function create_builder({ config, build_data, prerendered, log }) { /** @type {Set} */ - let prerendered_paths; - - let generated_manifest = false; + const prerendered_paths = new Set(prerendered.paths); /** @param {import('types').RouteData} route */ + // TODO routes should come pre-filtered function not_prerendered(route) { - if (!prerendered_paths) return true; - if (route.type === 'page' && route.path) { return !prerendered_paths.has(route.path); } @@ -33,12 +30,10 @@ export function create_builder({ config, build_data, log }) { mkdirp, copy, - appDir: config.kit.appDir, - trailingSlash: config.kit.trailingSlash, + config, + prerendered, createEntries(fn) { - generated_manifest = true; - const { routes } = build_data.manifest_data; /** @type {import('types').RouteDefinition[]} */ @@ -99,20 +94,24 @@ export function create_builder({ config, build_data, log }) { if (filtered.size > 0) { complete({ generateManifest: ({ relativePath, format }) => - generate_manifest(build_data, relativePath, Array.from(filtered), format) + generate_manifest({ + build_data, + relative_path: relativePath, + routes: Array.from(filtered), + format + }) }); } } }, generateManifest: ({ relativePath, format }) => { - generated_manifest = true; - return generate_manifest( + return generate_manifest({ build_data, - relativePath, - build_data.manifest_data.routes.filter(not_prerendered), + relative_path: relativePath, + routes: build_data.manifest_data.routes.filter(not_prerendered), format - ); + }); }, getBuildDirectory(name) { @@ -137,6 +136,18 @@ export function create_builder({ config, build_data, log }) { }); }, + 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}`); + } + + return files; + }, + writeServer(dest) { return copy(`${config.kit.outDir}/output/server`, dest, { filter: (file) => file[0] !== '.' @@ -147,24 +158,11 @@ export function create_builder({ config, build_data, log }) { return copy(config.kit.files.assets, dest); }, - async prerender({ all = false, dest, fallback }) { - if (generated_manifest) { - throw new Error( - 'Adapters must call prerender(...) before createEntries(...) or generateManifest(...)' - ); - } - - const prerendered = await prerender({ - out: dest, - config, - build_data, - fallback, - log - }); - - prerendered_paths = new Set(prerendered.paths); - - return prerendered; + // @ts-expect-error + async prerender() { + throw new Error( + 'builder.prerender() has been removed. Prerendering now takes place in the build phase — see builder.prerender and builder.writePrerendered' + ); } }; } diff --git a/packages/kit/src/core/adapt/builder.spec.js b/packages/kit/src/core/adapt/builder.spec.js index 24370092cbca..2778cff29248 100644 --- a/packages/kit/src/core/adapt/builder.spec.js +++ b/packages/kit/src/core/adapt/builder.spec.js @@ -30,6 +30,10 @@ test('copy files', () => { // @ts-expect-error build_data: {}, // @ts-expect-error + prerendered: { + paths: [] + }, + // @ts-expect-error log: {} }); diff --git a/packages/kit/src/core/adapt/index.js b/packages/kit/src/core/adapt/index.js index 645b55df7212..301afe39bb41 100644 --- a/packages/kit/src/core/adapt/index.js +++ b/packages/kit/src/core/adapt/index.js @@ -5,15 +5,15 @@ import { create_builder } from './builder.js'; /** * @param {import('types').ValidatedConfig} config * @param {import('types').BuildData} build_data - * @param {{ verbose: boolean }} opts + * @param {import('types').Prerendered} prerendered + * @param {{ log: import('types').Logger }} opts */ -export async function adapt(config, build_data, { verbose }) { +export async function adapt(config, build_data, prerendered, { log }) { const { name, adapt } = config.kit.adapter; console.log(colors.bold().cyan(`\n> Using ${name}`)); - const log = logger({ verbose }); - const builder = create_builder({ config, build_data, log }); + const builder = create_builder({ config, build_data, prerendered, log }); await adapt(builder); log.success('done'); diff --git a/packages/kit/src/core/build/index.js b/packages/kit/src/core/build/index.js index 5515f727b6fa..67b07c39e7d0 100644 --- a/packages/kit/src/core/build/index.js +++ b/packages/kit/src/core/build/index.js @@ -7,13 +7,14 @@ import { generate_manifest } from '../generate_manifest/index.js'; import { build_service_worker } from './build_service_worker.js'; import { build_client } from './build_client.js'; import { build_server } from './build_server.js'; +import { prerender } from './prerender/prerender.js'; import { generate_tsconfig } from '../tsconfig.js'; /** * @param {import('types').ValidatedConfig} config - * @returns {Promise} + * @param {{ log: import('types').Logger }} opts */ -export async function build(config) { +export async function build(config, { log }) { const cwd = process.cwd(); // TODO is this necessary? const build_dir = path.join(config.kit.outDir, 'build'); @@ -48,28 +49,54 @@ export async function build(config) { const client = await build_client(options); const server = await build_server(options, client); - if (options.service_worker_entry_file) { - if (config.kit.paths.assets) { - throw new Error('Cannot use service worker alongside config.kit.paths.assets'); - } - - await build_service_worker(options, client.vite_manifest); - } - + /** @type {import('types').BuildData} */ const build_data = { app_dir: config.kit.appDir, manifest_data: options.manifest_data, service_worker: options.service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable? client, - server, - static: options.manifest_data.assets.map((asset) => posixify(asset.file)), + server + }; + + const manifest = `export const manifest = ${generate_manifest({ + build_data, + relative_path: '.', + routes: options.manifest_data.routes + })};\n`; + fs.writeFileSync(`${output_dir}/server/manifest.js`, manifest); + + const static_files = options.manifest_data.assets.map((asset) => posixify(asset.file)); + + const files = new Set([ + ...static_files, + ...client.chunks.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`), + ...client.assets.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`) + ]); + + // TODO is this right? + static_files.forEach((file) => { + if (file.endsWith('/index.html')) { + files.add(file.slice(0, -11)); + } + }); + + const prerendered = await prerender({ + out: path.join(output_dir, 'prerendered'), + log, + config, + files, entries: options.manifest_data.routes .map((route) => (route.type === 'page' ? route.path : '')) .filter(Boolean) - }; + }); - const manifest = `export const manifest = ${generate_manifest(build_data, '.')};\n`; - fs.writeFileSync(`${output_dir}/server/manifest.js`, manifest); + if (options.service_worker_entry_file) { + if (config.kit.paths.assets) { + throw new Error('Cannot use service worker alongside config.kit.paths.assets'); + } + + await build_service_worker(options, client.vite_manifest); + } - return build_data; + return { build_data, prerendered }; } diff --git a/packages/kit/src/core/adapt/prerender/crawl.js b/packages/kit/src/core/build/prerender/crawl.js similarity index 100% rename from packages/kit/src/core/adapt/prerender/crawl.js rename to packages/kit/src/core/build/prerender/crawl.js diff --git a/packages/kit/src/core/adapt/prerender/crawl.spec.js b/packages/kit/src/core/build/prerender/crawl.spec.js similarity index 100% rename from packages/kit/src/core/adapt/prerender/crawl.spec.js rename to packages/kit/src/core/build/prerender/crawl.spec.js diff --git a/packages/kit/src/core/adapt/prerender/fixtures/basic-href/input.html b/packages/kit/src/core/build/prerender/fixtures/basic-href/input.html similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/basic-href/input.html rename to packages/kit/src/core/build/prerender/fixtures/basic-href/input.html diff --git a/packages/kit/src/core/adapt/prerender/fixtures/basic-href/output.json b/packages/kit/src/core/build/prerender/fixtures/basic-href/output.json similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/basic-href/output.json rename to packages/kit/src/core/build/prerender/fixtures/basic-href/output.json diff --git a/packages/kit/src/core/adapt/prerender/fixtures/basic-src/input.html b/packages/kit/src/core/build/prerender/fixtures/basic-src/input.html similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/basic-src/input.html rename to packages/kit/src/core/build/prerender/fixtures/basic-src/input.html diff --git a/packages/kit/src/core/adapt/prerender/fixtures/basic-src/output.json b/packages/kit/src/core/build/prerender/fixtures/basic-src/output.json similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/basic-src/output.json rename to packages/kit/src/core/build/prerender/fixtures/basic-src/output.json diff --git a/packages/kit/src/core/adapt/prerender/fixtures/basic-srcset/input.html b/packages/kit/src/core/build/prerender/fixtures/basic-srcset/input.html similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/basic-srcset/input.html rename to packages/kit/src/core/build/prerender/fixtures/basic-srcset/input.html diff --git a/packages/kit/src/core/adapt/prerender/fixtures/basic-srcset/output.json b/packages/kit/src/core/build/prerender/fixtures/basic-srcset/output.json similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/basic-srcset/output.json rename to packages/kit/src/core/build/prerender/fixtures/basic-srcset/output.json diff --git a/packages/kit/src/core/adapt/prerender/fixtures/include-rel-external/input.html b/packages/kit/src/core/build/prerender/fixtures/include-rel-external/input.html similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/include-rel-external/input.html rename to packages/kit/src/core/build/prerender/fixtures/include-rel-external/input.html diff --git a/packages/kit/src/core/adapt/prerender/fixtures/include-rel-external/output.json b/packages/kit/src/core/build/prerender/fixtures/include-rel-external/output.json similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/include-rel-external/output.json rename to packages/kit/src/core/build/prerender/fixtures/include-rel-external/output.json diff --git a/packages/kit/src/core/adapt/prerender/fixtures/unquoted-attributes/input.html b/packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/input.html similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/unquoted-attributes/input.html rename to packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/input.html diff --git a/packages/kit/src/core/adapt/prerender/fixtures/unquoted-attributes/output.json b/packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/output.json similarity index 100% rename from packages/kit/src/core/adapt/prerender/fixtures/unquoted-attributes/output.json rename to packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/output.json diff --git a/packages/kit/src/core/adapt/prerender/prerender.js b/packages/kit/src/core/build/prerender/prerender.js similarity index 87% rename from packages/kit/src/core/adapt/prerender/prerender.js rename to packages/kit/src/core/build/prerender/prerender.js index b6b5f8375112..2735678ffb1f 100644 --- a/packages/kit/src/core/adapt/prerender/prerender.js +++ b/packages/kit/src/core/build/prerender/prerender.js @@ -43,11 +43,12 @@ const REDIRECT = 3; * out: string; * log: Logger; * config: import('types').ValidatedConfig; - * build_data: import('types').BuildData; + * files: Set; + * entries: string[]; * fallback?: string; * }} opts */ -export async function prerender({ out, log, config, build_data, fallback }) { +export async function prerender({ out, log, config, files, entries, fallback }) { /** @type {import('types').Prerendered} */ const prerendered = { pages: new Map(), @@ -78,18 +79,6 @@ export async function prerender({ out, log, config, build_data, fallback }) { const error = normalise_error_handler(log, config.kit.prerender.onError); - const files = new Set([ - ...build_data.static, - ...build_data.client.chunks.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`), - ...build_data.client.assets.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`) - ]); - - build_data.static.forEach((file) => { - if (file.endsWith('/index.html')) { - files.add(file.slice(0, -11)); - } - }); - const q = queue(config.kit.prerender.concurrency); /** @@ -151,7 +140,7 @@ export async function prerender({ out, log, config, build_data, fallback }) { const text = await response.text(); - save(response, text, decoded, encoded, referrer, 'linked'); + save('pages', response, text, decoded, encoded, referrer, 'linked'); for (const [dependency_path, result] of dependencies) { // this seems circuitous, but using new URL allows us to not care @@ -161,6 +150,7 @@ export async function prerender({ out, log, config, build_data, fallback }) { const body = result.body ?? new Uint8Array(await result.response.arrayBuffer()); save( + 'dependencies', result.response, body, decoded_dependency_path, @@ -190,6 +180,7 @@ export async function prerender({ out, log, config, build_data, fallback }) { } /** + * @param {'pages' | 'dependencies'} category * @param {Response} response * @param {string | Uint8Array} body * @param {string} decoded @@ -197,13 +188,13 @@ export async function prerender({ out, log, config, build_data, fallback }) { * @param {string | null} referrer * @param {'linked' | 'fetched'} referenceType */ - function save(response, body, decoded, encoded, referrer, referenceType) { + function save(category, response, body, decoded, encoded, referrer, referenceType) { const response_type = Math.floor(response.status / 100); const type = /** @type {string} */ (response.headers.get('content-type')); const is_html = response_type === REDIRECT || type === 'text/html'; const file = output_filename(decoded, is_html); - const dest = `${out}/${file}`; + const dest = `${config.kit.outDir}/output/prerendered/${category}/${file}`; if (written.has(file)) return; written.add(file); @@ -267,7 +258,7 @@ export async function prerender({ out, log, config, build_data, fallback }) { if (config.kit.prerender.enabled) { for (const entry of config.kit.prerender.entries) { if (entry === '*') { - for (const entry of build_data.entries) { + for (const entry of entries) { enqueue(null, normalize_path(config.kit.paths.base + entry, config.kit.trailingSlash)); // TODO can we pre-normalize these? } } else { @@ -278,19 +269,17 @@ export async function prerender({ out, log, config, build_data, fallback }) { await q.done(); } - if (fallback) { - const rendered = await server.respond(new Request('http://sveltekit-prerender/[fallback]'), { - prerender: { - fallback, - default: false, - dependencies: new Map() - } - }); + const rendered = await server.respond(new Request('http://sveltekit-prerender/[fallback]'), { + prerender: { + fallback: true, + default: false, + dependencies: new Map() + } + }); - const file = join(out, fallback); - mkdirp(dirname(file)); - writeFileSync(file, await rendered.text()); - } + const file = `${config.kit.outDir}/output/prerendered/fallback.html`; + mkdirp(dirname(file)); + writeFileSync(file, await rendered.text()); return prerendered; } diff --git a/packages/kit/src/core/adapt/prerender/queue.js b/packages/kit/src/core/build/prerender/queue.js similarity index 100% rename from packages/kit/src/core/adapt/prerender/queue.js rename to packages/kit/src/core/build/prerender/queue.js diff --git a/packages/kit/src/core/adapt/prerender/queue.spec.js b/packages/kit/src/core/build/prerender/queue.spec.js similarity index 100% rename from packages/kit/src/core/adapt/prerender/queue.spec.js rename to packages/kit/src/core/build/prerender/queue.spec.js diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index 00c03e029926..10536af3eb16 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -2,17 +2,14 @@ import { s } from '../../utils/misc.js'; import { get_mime_lookup } from '../utils.js'; /** - * @param {import('types').BuildData} build_data; - * @param {string} relative_path; - * @param {import('types').RouteData[]} routes; - * @param {'esm' | 'cjs'} format + * @param {{ + * build_data: import('types').BuildData; + * relative_path: string; + * routes: import('types').RouteData[]; + * format?: 'esm' | 'cjs' + * }} opts */ -export function generate_manifest( - build_data, - relative_path, - routes = build_data.manifest_data.routes, - format = 'esm' -) { +export function generate_manifest({ build_data, relative_path, routes, format = 'esm' }) { /** @typedef {{ index: number, path: string }} LookupEntry */ /** @type {Map} */ const bundled_nodes = new Map(); diff --git a/packages/kit/test/apps/basics/svelte.config.js b/packages/kit/test/apps/basics/svelte.config.js index 857b5c6b7bd6..40df6e81dd84 100644 --- a/packages/kit/test/apps/basics/svelte.config.js +++ b/packages/kit/test/apps/basics/svelte.config.js @@ -3,6 +3,9 @@ import path from 'path'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { + prerender: { + onError: 'continue' + }, vite: { build: { minify: false diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 7a8a0cfddddc..e0e10a0158ab 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -14,6 +14,7 @@ import { HttpMethod, JSONObject, MaybePromise, + Prerendered, RequestEvent, RequestOptions, ResolveOptions, @@ -63,8 +64,6 @@ export interface BuildData { methods: Record; vite_manifest: import('vite').Manifest; }; - static: string[]; - entries: string[]; } export type CSRComponent = any; // TODO @@ -149,7 +148,7 @@ export interface PrerenderDependency { } export interface PrerenderOptions { - fallback?: string; + fallback?: boolean; default: boolean; dependencies: Map; } diff --git a/packages/kit/types/private.d.ts b/packages/kit/types/private.d.ts index 48c0659f0ae0..b4beab62d945 100644 --- a/packages/kit/types/private.d.ts +++ b/packages/kit/types/private.d.ts @@ -2,7 +2,7 @@ // but which cannot be imported from `@sveltejs/kit`. Care should // be taken to avoid breaking changes when editing this file -import { SSRNodeLoader, SSRRoute } from './internal'; +import { SSRNodeLoader, SSRRoute, ValidatedConfig } from './internal'; export interface AdapterEntry { /** @@ -35,8 +35,8 @@ export interface Builder { rimraf(dir: string): void; mkdirp(dir: string): void; - appDir: string; - trailingSlash: TrailingSlash; + config: ValidatedConfig; + prerendered: Prerendered; /** * Create entry points that map to individual functions @@ -56,6 +56,16 @@ export interface Builder { * @returns an array of paths corresponding to the files that have been created by the copy */ writeClient(dest: string): string[]; + /** + * + * @param dest + */ + writePrerendered( + dest: string, + opts?: { + fallback?: string; + } + ): string[]; /** * @param dest the destination folder to which files should be copied * @returns an array of paths corresponding to the files that have been created by the copy @@ -81,8 +91,6 @@ export interface Builder { replace?: Record; } ): string[]; - - prerender(options: { all?: boolean; dest: string; fallback?: string }): Promise; } // Based on /~https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts From 10edb195a635ea4bc80176b9fac3df2743fb179e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 09:41:27 -0500 Subject: [PATCH 03/10] lint --- packages/kit/src/core/adapt/index.js | 1 - packages/kit/src/core/build/index.js | 7 +++---- packages/kit/src/core/build/prerender/prerender.js | 10 ++++------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/kit/src/core/adapt/index.js b/packages/kit/src/core/adapt/index.js index 301afe39bb41..25d02039f065 100644 --- a/packages/kit/src/core/adapt/index.js +++ b/packages/kit/src/core/adapt/index.js @@ -1,5 +1,4 @@ import colors from 'kleur'; -import { logger } from '../utils.js'; import { create_builder } from './builder.js'; /** diff --git a/packages/kit/src/core/build/index.js b/packages/kit/src/core/build/index.js index 67b07c39e7d0..1370673d0e06 100644 --- a/packages/kit/src/core/build/index.js +++ b/packages/kit/src/core/build/index.js @@ -81,13 +81,12 @@ export async function build(config, { log }) { }); const prerendered = await prerender({ - out: path.join(output_dir, 'prerendered'), - log, config, - files, entries: options.manifest_data.routes .map((route) => (route.type === 'page' ? route.path : '')) - .filter(Boolean) + .filter(Boolean), + files, + log }); if (options.service_worker_entry_file) { diff --git a/packages/kit/src/core/build/prerender/prerender.js b/packages/kit/src/core/build/prerender/prerender.js index 2735678ffb1f..c21e4da89635 100644 --- a/packages/kit/src/core/build/prerender/prerender.js +++ b/packages/kit/src/core/build/prerender/prerender.js @@ -40,15 +40,13 @@ const REDIRECT = 3; /** * @param {{ - * out: string; - * log: Logger; * config: import('types').ValidatedConfig; - * files: Set; * entries: string[]; - * fallback?: string; + * files: Set; + * log: Logger; * }} opts */ -export async function prerender({ out, log, config, files, entries, fallback }) { +export async function prerender({ config, entries, files, log }) { /** @type {import('types').Prerendered} */ const prerendered = { pages: new Map(), @@ -57,7 +55,7 @@ export async function prerender({ out, log, config, files, entries, fallback }) paths: [] }; - if (!config.kit.prerender.enabled && !fallback) { + if (!config.kit.prerender.enabled) { return prerendered; } From 059a726bb252b969e1ff8a58a11c25597db657d8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 09:56:49 -0500 Subject: [PATCH 04/10] warn if no fallback and default is false --- packages/adapter-static/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/adapter-static/index.js b/packages/adapter-static/index.js index 309b4be59fa9..cb5a05d1d780 100644 --- a/packages/adapter-static/index.js +++ b/packages/adapter-static/index.js @@ -12,12 +12,17 @@ export default function ({ pages = 'build', assets = pages, fallback, precompres name: '@sveltejs/adapter-static', async adapt(builder) { + if (!fallback && !builder.config.kit.prerender.default) { + builder.log.warn( + 'You should set `config.kit.prerender.default` to `true` if no fallback is specified' + ); + } + builder.rimraf(assets); builder.rimraf(pages); builder.writeStatic(assets); builder.writeClient(assets); - builder.writePrerendered(pages, { fallback }); if (precompress) { From b4b3be409a95bfc8cf7bfddcf86ce08ea7435d84 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 12:15:27 -0500 Subject: [PATCH 05/10] use prerendered pages in svelte-kit preview --- packages/kit/src/core/preview/index.js | 228 +++++++++++++++++-------- 1 file changed, 154 insertions(+), 74 deletions(-) diff --git a/packages/kit/src/core/preview/index.js b/packages/kit/src/core/preview/index.js index 59c5d1329f09..2295f5a71ada 100644 --- a/packages/kit/src/core/preview/index.js +++ b/packages/kit/src/core/preview/index.js @@ -7,13 +7,23 @@ import { pathToFileURL } from 'url'; import { getRequest, setResponse } from '../../node.js'; import { installFetch } from '../../install-fetch.js'; import { SVELTE_KIT_ASSETS } from '../constants.js'; +import { normalize_path } from '../../utils/url.js'; -/** @param {string} dir */ +/** @typedef {import('http').IncomingMessage} Req */ +/** @typedef {import('http').ServerResponse} Res */ +/** @typedef {(req: Req, res: Res, next: () => void) => void} Handler */ + +/** + * @param {string} dir + * @returns {Handler} + */ const mutable = (dir) => - sirv(dir, { - etag: true, - maxAge: 0 - }); + fs.existsSync(dir) + ? sirv(dir, { + etag: true, + maxAge: 0 + }) + : (req, res, next) => next(); /** * @param {{ @@ -27,96 +37,124 @@ const mutable = (dir) => export async function preview({ port, host, config, https: use_https = false }) { installFetch(); + const { paths } = config.kit; + const base = paths.base; + const assets = paths.assets ? SVELTE_KIT_ASSETS : paths.base; + + const etag = `"${Date.now()}"`; + const index_file = join(config.kit.outDir, 'output/server/index.js'); const manifest_file = join(config.kit.outDir, 'output/server/manifest.js'); /** @type {import('types').ServerModule} */ const { Server, override } = await import(pathToFileURL(index_file).href); - const { manifest } = await import(pathToFileURL(manifest_file).href); - /** @type {import('sirv').RequestHandler} */ - const static_handler = fs.existsSync(config.kit.files.assets) - ? mutable(config.kit.files.assets) - : (_req, _res, next) => { - if (!next) throw new Error('No next() handler is available'); - return next(); - }; - - const assets_handler = sirv(join(config.kit.outDir, 'output/client'), { - maxAge: 31536000, - immutable: true - }); - - const has_asset_path = !!config.kit.paths.assets; + const server = new Server(manifest); override({ - paths: { - base: config.kit.paths.base, - assets: has_asset_path ? SVELTE_KIT_ASSETS : config.kit.paths.base - }, + paths: { base, assets }, prerendering: false, protocol: use_https ? 'https' : 'http', read: (file) => fs.readFileSync(join(config.kit.files.assets, file)) }); - const server = new Server(manifest); - - /** @type {import('vite').UserConfig} */ - const vite_config = (config.kit.vite && (await config.kit.vite())) || {}; + const handle = compose([ + // files in `static` + scoped(assets, mutable(config.kit.files.assets)), + + // immutable generated client assets + scoped( + assets, + sirv(join(config.kit.outDir, 'output/client'), { + maxAge: 31536000, + immutable: true + }) + ), + + // prerendered dependencies + scoped(base, mutable(join(config.kit.outDir, 'output/prerendered/dependencies'))), + + // prerendered pages (we can't just use sirv because we need to + // preserve the correct trailingSlash behaviour) + scoped(base, (req, res, next) => { + let if_none_match_value = req.headers['if-none-match']; + + if (if_none_match_value?.startsWith('W/"')) { + if_none_match_value = if_none_match_value.substring(2); + } - const http_server = await get_server(use_https, vite_config, (req, res) => { - if (req.url == null) { - throw new Error('Invalid request url'); - } + if (if_none_match_value === etag) { + res.statusCode = 304; + res.end(); + return; + } - const initial_url = req.url; + const { pathname, search } = new URL(/** @type {string} */ (req.url), 'http://dummy'); - const render_handler = async () => { - if (initial_url.startsWith(config.kit.paths.base)) { - const protocol = use_https ? 'https' : 'http'; - const host = req.headers['host']; + const normalized = normalize_path(pathname, config.kit.trailingSlash); - let request; + if (normalized !== pathname) { + res.writeHead(307, { + location: normalized + search + }); + res.end(); + } - try { - req.url = initial_url; - request = await getRequest(`${protocol}://${host}`, req); - } catch (/** @type {any} */ err) { - res.statusCode = err.status || 400; - return res.end(err.reason || 'Invalid request body'); + // only treat this as a page if it doesn't include an extension + if (pathname === '/' || /\/[^./]+\/?$/.test(pathname)) { + const file = join( + config.kit.outDir, + 'output/prerendered/pages' + pathname + (pathname.endsWith('/') ? 'index.html' : '.html') + ); + + if (fs.existsSync(file)) { + res.writeHead(200, { + 'content-type': 'text/html', + etag + }); + + fs.createReadStream(file).pipe(res); + return; } - - setResponse(res, await server.respond(request)); } else { - res.statusCode = 404; - res.end('Not found'); + next(); } - }; - - if (has_asset_path) { - if (initial_url.startsWith(SVELTE_KIT_ASSETS)) { - // custom assets path - req.url = initial_url.slice(SVELTE_KIT_ASSETS.length); - assets_handler(req, res, () => { - static_handler(req, res, render_handler); - }); - } else { - render_handler(); - } - } else { - if (initial_url.startsWith(config.kit.paths.base)) { - req.url = initial_url.slice(config.kit.paths.base.length); + }), + + // SSR + scoped(base, async (req, res) => { + const protocol = use_https ? 'https' : 'http'; + const host = req.headers['host']; + + let request; + + try { + request = await getRequest(`${protocol}://${host}`, req); + } catch (/** @type {any} */ err) { + res.statusCode = err.status || 400; + return res.end(err.reason || 'Invalid request body'); } - assets_handler(req, res, () => { - static_handler(req, res, render_handler); - }); + + setResponse(res, await server.respond(request)); + }) + ]); + + const vite_config = (config.kit.vite && (await config.kit.vite())) || {}; + + const http_server = await get_server(use_https, vite_config, (req, res) => { + if (req.url == null) { + throw new Error('Invalid request url'); } - }); - await http_server.listen(port, host || '0.0.0.0'); + handle(req, res); + }); - return Promise.resolve(http_server); + return new Promise((fulfil) => { + http_server.listen(port, host || '0.0.0.0', () => { + fulfil(http_server); + }); + }); } /** @@ -142,9 +180,51 @@ async function get_server(use_https, user_config, handler) { } } - return Promise.resolve( - use_https - ? https.createServer(/** @type {https.ServerOptions} */ (https_options), handler) - : http.createServer(handler) - ); + return use_https + ? https.createServer(/** @type {https.ServerOptions} */ (https_options), handler) + : http.createServer(handler); +} + +/** @param {Handler[]} handlers */ +function compose(handlers) { + /** + * @param {Req} req + * @param {Res} res + */ + return (req, res) => { + /** @param {number} i */ + function next(i) { + const handler = handlers[i]; + + if (handler) { + handler(req, res, () => next(i + 1)); + } else { + res.statusCode = 404; + res.end('Not found'); + } + } + + next(0); + }; +} + +/** + * @param {string} scope + * @param {Handler} handler + * @returns {Handler} + */ +function scoped(scope, handler) { + if (scope === '') return handler; + + return (req, res, next) => { + if (req.url?.startsWith(scope)) { + req.url = req.url.slice(scope.length); + handler(req, res, () => { + req.url = scope + req.url; + next(); + }); + } else { + next(); + } + }; } From 46e4b00d55fe44a1c3e0a63d74c8a7c7a0b4ef9b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 12:35:58 -0500 Subject: [PATCH 06/10] make prerendered paths available to service worker --- packages/kit/src/core/build/build_service_worker.js | 13 +++++++++++-- packages/kit/src/core/build/index.js | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/core/build/build_service_worker.js b/packages/kit/src/core/build/build_service_worker.js index 965e91459073..4d4158952c48 100644 --- a/packages/kit/src/core/build/build_service_worker.js +++ b/packages/kit/src/core/build/build_service_worker.js @@ -2,21 +2,24 @@ import fs from 'fs'; import vite from 'vite'; import { s } from '../../utils/misc.js'; import { deep_merge } from '../../utils/object.js'; +import { normalize_path } from '../../utils/url.js'; import { print_config_conflicts } from '../config/index.js'; /** * @param {{ * cwd: string; * assets_base: string; - * config: import('types').ValidatedConfig - * manifest_data: import('types').ManifestData + * config: import('types').ValidatedConfig; + * manifest_data: import('types').ManifestData; * output_dir: string; * service_worker_entry_file: string | null; * }} options + * @param {import('types').Prerendered} prerendered * @param {import('vite').Manifest} client_manifest */ export async function build_service_worker( { cwd, assets_base, config, manifest_data, output_dir, service_worker_entry_file }, + prerendered, client_manifest ) { // TODO add any assets referenced in template .html file, e.g. favicon? @@ -49,6 +52,12 @@ export async function build_service_worker( .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`) .join(',\n\t\t\t\t')} ]; + + export const prerendered = [ + ${prerendered.paths + .map((path) => s(normalize_path(path, config.kit.trailingSlash))) + .join(',\n\t\t\t\t')} + ]; ` .replace(/^\t{3}/gm, '') .trim() diff --git a/packages/kit/src/core/build/index.js b/packages/kit/src/core/build/index.js index 1370673d0e06..7f1682a8020f 100644 --- a/packages/kit/src/core/build/index.js +++ b/packages/kit/src/core/build/index.js @@ -94,7 +94,7 @@ export async function build(config, { log }) { throw new Error('Cannot use service worker alongside config.kit.paths.assets'); } - await build_service_worker(options, client.vite_manifest); + await build_service_worker(options, prerendered, client.vite_manifest); } return { build_data, prerendered }; From d2801e379fe8db4fc53870c8ffc8da4d7f31fe88 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 12:44:40 -0500 Subject: [PATCH 07/10] adapters --- packages/adapter-cloudflare-workers/index.js | 6 ++-- packages/adapter-cloudflare/index.js | 5 ++-- packages/adapter-netlify/index.js | 8 ++---- packages/adapter-node/index.js | 6 +--- packages/adapter-vercel/index.js | 30 +++++++++----------- 5 files changed, 21 insertions(+), 34 deletions(-) diff --git a/packages/adapter-cloudflare-workers/index.js b/packages/adapter-cloudflare-workers/index.js index 745bbf006b11..c4b6aa4c69ba 100644 --- a/packages/adapter-cloudflare-workers/index.js +++ b/packages/adapter-cloudflare-workers/index.js @@ -25,9 +25,6 @@ export default function () { builder.rimraf(bucket); builder.rimraf(entrypoint); - builder.log.info('Prerendering static pages...'); - const prerendered = await builder.prerender({ dest: bucket }); - builder.log.info('Installing worker dependencies...'); builder.copy(`${files}/_package.json`, `${tmp}/package.json`); @@ -49,7 +46,7 @@ export default function () { `${tmp}/manifest.js`, `export const manifest = ${builder.generateManifest({ relativePath - })};\n\nexport const prerendered = new Set(${JSON.stringify(prerendered.paths)});\n` + })};\n\nexport const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` ); await esbuild.build({ @@ -65,6 +62,7 @@ export default function () { builder.log.minor('Copying assets...'); builder.writeClient(bucket); builder.writeStatic(bucket); + builder.writePrerendered(bucket); } }; } diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 9af0566f94ce..ea3005e08da5 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -18,8 +18,7 @@ export default function (options = {}) { builder.writeStatic(dest); builder.writeClient(dest); - - const prerendered = await builder.prerender({ dest }); + builder.writePrerendered(dest); const relativePath = posix.relative(tmp, builder.getServerDirectory()); @@ -27,7 +26,7 @@ export default function (options = {}) { `${tmp}/manifest.js`, `export const manifest = ${builder.generateManifest({ relativePath - })};\n\nexport const prerendered = new Set(${JSON.stringify(prerendered.paths)});\n` + })};\n\nexport const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` ); builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, { diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index a37894a88c75..330071470d6b 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -36,11 +36,6 @@ export default function ({ split = false } = {}) { builder.log.minor(`Publishing to "${publish}"`); - builder.log.minor('Prerendering static pages...'); - await builder.prerender({ - dest: publish - }); - builder.writeServer('.netlify/server'); // for esbuild, use ESM @@ -127,6 +122,7 @@ export default function ({ split = false } = {}) { builder.log.minor('Copying assets...'); builder.writeStatic(publish); builder.writeClient(publish); + builder.writePrerendered(publish); builder.log.minor('Writing redirects...'); const redirect_file = join(publish, '_redirects'); @@ -138,7 +134,7 @@ export default function ({ split = false } = {}) { builder.copy('_headers', headers_file); appendFileSync( headers_file, - `\n\n/${builder.appDir}/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n` + `\n\n/${builder.config.kit.appDir}/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n` ); } }; diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 8fd7059511d8..fc302b17a978 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -34,11 +34,7 @@ export default function ({ builder.writeClient(`${out}/client`); builder.writeServer(`${out}/server`); builder.writeStatic(`${out}/static`); - - builder.log.minor('Prerendering static pages'); - await builder.prerender({ - dest: `${out}/prerendered` - }); + builder.writePrerendered(`${out}/prerendered`); writeFileSync( `${out}/manifest.js`, diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index d9c69de89d2e..67c29e4a60b1 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -100,12 +100,6 @@ export default function ({ external = [] } = {}) { lambda: `${dir}/functions/node/render` }; - builder.log.minor('Prerendering static pages...'); - - const prerendered = await builder.prerender({ - dest: `${dir}/static` - }); - builder.log.minor('Generating serverless function...'); const relativePath = posix.relative(tmp, builder.getServerDirectory()); @@ -139,32 +133,36 @@ export default function ({ external = [] } = {}) { builder.writeStatic(dirs.static); builder.writeClient(dirs.static); + builder.writePrerendered(dirs.static); builder.log.minor('Writing routes...'); builder.mkdirp(`${dir}/config`); - const prerendered_pages = Array.from(prerendered.pages, ([src, page]) => ({ + const prerendered_pages = Array.from(builder.prerendered.pages, ([src, page]) => ({ src, dest: page.file })); - const prerendered_redirects = Array.from(prerendered.redirects, ([src, redirect]) => ({ - src, - headers: { - Location: redirect.location - }, - status: redirect.status - })); + const prerendered_redirects = Array.from( + builder.prerendered.redirects, + ([src, redirect]) => ({ + src, + headers: { + Location: redirect.location + }, + status: redirect.status + }) + ); writeFileSync( `${dir}/config/routes.json`, JSON.stringify([ - ...redirects[builder.trailingSlash], + ...redirects[builder.config.kit.trailingSlash], ...prerendered_pages, ...prerendered_redirects, { - src: `/${builder.appDir}/.+`, + src: `/${builder.config.kit.appDir}/.+`, headers: { 'cache-control': 'public, immutable, max-age=31536000' } From 40cce5ad66580988ff05bf52219e2a558ebc819d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 12:52:35 -0500 Subject: [PATCH 08/10] docs --- documentation/docs/09-adapters.md | 2 +- documentation/docs/10-page-options.md | 14 +++++++++++--- documentation/docs/13-configuration.md | 1 + packages/kit/types/ambient.d.ts | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/documentation/docs/09-adapters.md b/documentation/docs/09-adapters.md index 07957bf1ebae..d17df5ffb7a5 100644 --- a/documentation/docs/09-adapters.md +++ b/documentation/docs/09-adapters.md @@ -99,7 +99,7 @@ The types for `Adapter` and its parameters are available in [types/config.d.ts]( Within the `adapt` method, there are a number of things that an adapter should do: - Clear out the build directory -- Call `builder.prerender({ dest })` to prerender pages +- Write SvelteKit output with `builder.writeClient`, `builder.writePrerendered`, `builder.writeServer`, and `builder.writeStatic` - Output code that: - Imports `App` from `${builder.getServerDirectory()}/app.js` - Instantiates the app with a manifest generated with `builder.generateManifest({ relativePath })` diff --git a/documentation/docs/10-page-options.md b/documentation/docs/10-page-options.md index 12d8398e91e3..77a3a954a3ad 100644 --- a/documentation/docs/10-page-options.md +++ b/documentation/docs/10-page-options.md @@ -38,9 +38,7 @@ Ordinarily, SvelteKit [hydrates](/docs/appendix#hydration) your server-rendered It's likely that at least some pages of your app can be represented as a simple HTML file generated at build time. These pages can be [_prerendered_](/docs/appendix#prerendering) by your [adapter](/docs/adapters). -If your entire app is suitable for prerendering, you could use [`adapter-static`](/~https://github.com/sveltejs/kit/tree/master/packages/adapter-static), which will generate HTML files for every page, plus additional files that are requested by `load` functions in those pages. - -In many cases, you'll only want to prerender specific pages in your app. You'll need to annotate these pages: +Prerendering happens automatically for any page with the `prerender` annotation: ```html ``` +Alternatively, you can set [`confit.kit.prerender.default`](/docs/configuration#prerender) to `true` and prerender everything except pages that are explicitly marked as _not_ prerenderable: + +```html + +``` + +> If your entire app is suitable for prerendering, you can use [`adapter-static`](/~https://github.com/sveltejs/kit/tree/master/packages/adapter-static), which will output files suitable for use with any static webserver. + The prerenderer will start at the root of your app and generate HTML for any prerenderable pages it finds. Each page is scanned for `` elements that point to other pages that are candidates for prerendering — because of this, you generally don't need to specify which pages should be accessed. If you _do_ need to specify which pages should be accessed by the prerenderer, you can do so with the `entries` option in the [prerender configuration](/docs/configuration#prerender). #### When not to prerender diff --git a/documentation/docs/13-configuration.md b/documentation/docs/13-configuration.md index 154a6e15060f..3a0d47e27cc8 100644 --- a/documentation/docs/13-configuration.md +++ b/documentation/docs/13-configuration.md @@ -224,6 +224,7 @@ See [Prerendering](/docs/page-options#prerender). An object containing zero or m - `concurrency` — how many pages can be prerendered simultaneously. JS is single-threaded, but in cases where prerendering performance is network-bound (for example loading content from a remote CMS) this can speed things up by processing other tasks while waiting on the network response - `crawl` — determines whether SvelteKit should find pages to prerender by following links from the seed page(s) +- `default` — set to `true` to prerender every page without `export const prerender = false` - `enabled` — set to `false` to disable prerendering altogether - `entries` — an array of pages to prerender, or start crawling from (if `crawl: true`). The `*` string includes all non-dynamic routes (i.e. pages with no `[parameters]` ) - `onError` diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts index ff8f0fd7f464..c71ae90b40db 100644 --- a/packages/kit/types/ambient.d.ts +++ b/packages/kit/types/ambient.d.ts @@ -228,6 +228,10 @@ declare module '$service-worker' { * An array of URL strings representing the files in your static directory, or whatever directory is specified by `config.kit.files.assets`. You can customize which files are included from `static` directory using [`config.kit.serviceWorker.files`](/docs/configuration) */ export const files: string[]; + /** + * An array of pathnames corresponding to prerendered pages and endpoints. + */ + export const prerendered: string[]; /** * The result of calling `Date.now()` at build time. It's useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches. */ From 31e72783878b5b98894e3a397e36197f46813517 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 12:58:06 -0500 Subject: [PATCH 09/10] changesets --- .changeset/gorgeous-beans-glow.md | 5 +++++ .changeset/khaki-dolls-cough.md | 5 +++++ .changeset/large-berries-exercise.md | 5 +++++ .changeset/modern-toys-appear.md | 5 +++++ .changeset/nine-walls-shake.md | 5 +++++ .changeset/shaggy-days-cheat.md | 5 +++++ packages/kit/src/runtime/server/page/respond.js | 2 +- 7 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .changeset/gorgeous-beans-glow.md create mode 100644 .changeset/khaki-dolls-cough.md create mode 100644 .changeset/large-berries-exercise.md create mode 100644 .changeset/modern-toys-appear.md create mode 100644 .changeset/nine-walls-shake.md create mode 100644 .changeset/shaggy-days-cheat.md diff --git a/.changeset/gorgeous-beans-glow.md b/.changeset/gorgeous-beans-glow.md new file mode 100644 index 000000000000..6ef3bba3be17 --- /dev/null +++ b/.changeset/gorgeous-beans-glow.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[breaking] expose entire config to adapters, rather than just appDir and trailingSlash diff --git a/.changeset/khaki-dolls-cough.md b/.changeset/khaki-dolls-cough.md new file mode 100644 index 000000000000..2e85df32d862 --- /dev/null +++ b/.changeset/khaki-dolls-cough.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[breaking] replace builder.prerender() with builder.writePrerendered() and builder.prerendered diff --git a/.changeset/large-berries-exercise.md b/.changeset/large-berries-exercise.md new file mode 100644 index 000000000000..4e72f454eb83 --- /dev/null +++ b/.changeset/large-berries-exercise.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[breaking] prerender pages during build, regardless of adapter diff --git a/.changeset/modern-toys-appear.md b/.changeset/modern-toys-appear.md new file mode 100644 index 000000000000..3ca8cd419e78 --- /dev/null +++ b/.changeset/modern-toys-appear.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Add config.kit.prerender.default option diff --git a/.changeset/nine-walls-shake.md b/.changeset/nine-walls-shake.md new file mode 100644 index 000000000000..f222efb4a0bf --- /dev/null +++ b/.changeset/nine-walls-shake.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Use prerendered pages in svelte-kit preview diff --git a/.changeset/shaggy-days-cheat.md b/.changeset/shaggy-days-cheat.md new file mode 100644 index 000000000000..bb6f4b5a8d93 --- /dev/null +++ b/.changeset/shaggy-days-cheat.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Make prerendered paths available to service workers diff --git a/packages/kit/src/runtime/server/page/respond.js b/packages/kit/src/runtime/server/page/respond.js index c02db2ef7842..1d5c45475314 100644 --- a/packages/kit/src/runtime/server/page/respond.js +++ b/packages/kit/src/runtime/server/page/respond.js @@ -69,7 +69,7 @@ export async function respond(opts) { if (state.prerender) { // if the page isn't marked as prerenderable (or is explicitly - // marked NOT prerenderable, if `prerender.default` is `true`, + // marked NOT prerenderable, if `prerender.default` is `true`), // then bail out at this point const should_prerender = leaf.prerender ?? state.prerender.default; if (!should_prerender) { From da081a605ff3d2d5a404d08e7cb43c7b6cdc51fc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 3 Mar 2022 13:11:08 -0500 Subject: [PATCH 10/10] lint --- .prettierrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index 5e006e35333d..ff4587b4d023 100644 --- a/.prettierrc +++ b/.prettierrc @@ -13,7 +13,7 @@ { "files": [ "packages/kit/src/packaging/test/fixtures/**/expected/**/*", - "packages/kit/src/core/adapt/prerender/fixtures/**/*" + "packages/kit/src/core/build/prerender/fixtures/**/*" ], "options": { "requirePragma": true