import { $ } from './index.js'; 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 { r as rimraf, m as mkdirp, c as copy } from './filesystem.js'; import { g as generate_manifest } from '../vite.js'; import 'path'; import 'url'; import 'node:child_process'; import 'node:fs'; import 'node:path'; import '@sveltejs/vite-plugin-svelte'; import 'vite'; import './sync.js'; import './utils.js'; import './write_tsconfig.js'; import 'querystring'; import '../node.js'; import '../node/polyfills.js'; import 'assert'; import 'net'; import 'http'; import 'buffer'; import 'stream/web'; import 'perf_hooks'; import 'util/types'; import 'events'; import 'tls'; import 'async_hooks'; import 'console'; import 'node:http'; import 'node:https'; import 'node:zlib'; import 'node:stream'; import 'node:buffer'; import 'node:util'; import 'node:url'; import 'node:net'; import 'crypto'; import './error.js'; /** * Creates the Builder which is passed to adapters for building the application. * @param {{ * config: import('types').ValidatedConfig; * build_data: import('types').BuildData; * prerendered: import('types').Prerendered; * log: import('types').Logger; * }} opts * @returns {import('types').Builder} */ function create_builder({ config, build_data, prerendered, log }) { /** @type {Set} */ const prerendered_paths = new Set(prerendered.paths); /** @param {import('types').RouteData} route */ // TODO routes should come pre-filtered function not_prerendered(route) { if (route.type === 'page' && route.path) { return !prerendered_paths.has(route.path) && !prerendered_paths.has(route.path + '/'); } return true; } const pipe = promisify(pipeline); /** * @param {string} file * @param {'gz' | 'br'} format */ async function compress_file(file, format = 'gz') { const compress = format == 'br' ? zlib.createBrotliCompress({ params: { [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY, [zlib.constants.BROTLI_PARAM_SIZE_HINT]: statSync(file).size } }) : zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION }); const source = createReadStream(file); const destination = createWriteStream(`${file}.${format}`); await pipe(source, compress, destination); } return { log, rimraf, mkdirp, copy, config, prerendered, async createEntries(fn) { const { routes } = build_data.manifest_data; /** @type {import('types').RouteDefinition[]} */ const facades = routes.map((route) => ({ id: route.id, type: route.type, segments: route.id.split('/').map((segment) => ({ dynamic: segment.includes('['), rest: segment.includes('[...'), content: segment })), pattern: route.pattern, methods: route.type === 'page' ? ['GET'] : build_data.server.methods[route.file] })); const seen = new Set(); for (let i = 0; i < routes.length; i += 1) { const route = routes[i]; const { id, filter, complete } = fn(facades[i]); if (seen.has(id)) continue; seen.add(id); const group = [route]; // figure out which lower priority routes should be considered fallbacks for (let j = i + 1; j < routes.length; j += 1) { if (filter(facades[j])) { group.push(routes[j]); } } const filtered = new Set(group.filter(not_prerendered)); // heuristic: if /foo/[bar] is included, /foo/[bar].json should // also be included, since the page likely needs the endpoint filtered.forEach((route) => { if (route.type === 'page') { const endpoint = routes.find((candidate) => candidate.id === route.id + '.json'); if (endpoint) { filtered.add(endpoint); } } }); if (filtered.size > 0) { await complete({ generateManifest: ({ relativePath, format }) => generate_manifest({ build_data, relative_path: relativePath, routes: Array.from(filtered), format }) }); } } }, generateManifest: ({ relativePath, format }) => { return generate_manifest({ build_data, relative_path: relativePath, routes: build_data.manifest_data.routes.filter(not_prerendered), format }); }, getBuildDirectory(name) { return `${config.kit.outDir}/${name}`; }, getClientDirectory() { return `${config.kit.outDir}/output/client`; }, getServerDirectory() { return `${config.kit.outDir}/output/server`; }, getStaticDirectory() { return config.kit.files.assets; }, writeClient(dest) { 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}`); } return files; }, writeServer(dest) { return copy(`${config.kit.outDir}/output/server`, dest); }, // TODO remove these methods for 1.0 // @ts-expect-error writeStatic() { throw new Error( `writeStatic has been removed. Please ensure you are using the latest version of ${ config.kit.adapter.name || 'your adapter' }` ); }, async compress(directory) { if (!existsSync(directory)) { return; } const files = await glob('**/*.{html,js,json,css,svg,xml,wasm}', { cwd: directory, dot: true, absolute: true, filesOnly: true }); await Promise.all( files.map((file) => Promise.all([compress_file(file, 'gz'), compress_file(file, 'br')])) ); }, async prerender() { throw new Error( 'builder.prerender() has been removed. Prerendering now takes place in the build phase — see builder.prerender and builder.writePrerendered' ); } }; } /** * @param {import('types').ValidatedConfig} config * @param {import('types').BuildData} build_data * @param {import('types').Prerendered} prerendered * @param {{ log: import('types').Logger }} opts */ async function adapt(config, build_data, prerendered, { log }) { const { name, adapt } = config.kit.adapter; console.log($.bold().cyan(`\n> Using ${name}`)); const builder = create_builder({ config, build_data, prerendered, log }); await adapt(builder); log.success('done'); } export { adapt };