UNPKG

@sveltejs/kit

Version:

SvelteKit is the fastest way to build Svelte apps

231 lines (195 loc) • 7.77 kB
import fs from 'node:fs'; import { mkdirp } from '../../../utils/filesystem.js'; import { filter_fonts, find_deps, resolve_symlinks } from './utils.js'; import { s } from '../../../utils/misc.js'; import { normalizePath } from 'vite'; import { basename, join } from 'node:path'; import { create_node_analyser } from '../static_analysis/index.js'; /** * @param {string} out * @param {import('types').ValidatedKitConfig} kit * @param {import('types').ManifestData} manifest_data * @param {import('vite').Manifest} server_manifest * @param {import('vite').Manifest | null} client_manifest * @param {import('vite').Rollup.OutputBundle | null} server_bundle * @param {import('vite').Rollup.RollupOutput['output'] | null} client_chunks * @param {import('types').RecursiveRequired<import('types').ValidatedConfig['kit']['output']>} output_config * @param {Map<string, { page_options: Record<string, any> | null, children: string[] }>} static_exports */ export async function build_server_nodes(out, kit, manifest_data, server_manifest, client_manifest, server_bundle, client_chunks, output_config, static_exports) { mkdirp(`${out}/server/nodes`); mkdirp(`${out}/server/stylesheets`); /** @type {Map<string, string>} */ const stylesheets_to_inline = new Map(); if (server_bundle && client_chunks && kit.inlineStyleThreshold > 0) { const client = get_stylesheets(client_chunks); const server = get_stylesheets(Object.values(server_bundle)); // map server stylesheet name to the client stylesheet name for (const [id, client_stylesheet] of client.stylesheets_used) { const server_stylesheet = server.stylesheets_used.get(id); if (!server_stylesheet) { continue; } client_stylesheet.forEach((file, i) => { stylesheets_to_inline.set(file, server_stylesheet[i]); }) } // filter out stylesheets that should not be inlined for (const [fileName, content] of client.stylesheet_content) { if (content.length >= kit.inlineStyleThreshold) { stylesheets_to_inline.delete(fileName); } } // map server stylesheet source to the client stylesheet name for (const [client_file, server_file] of stylesheets_to_inline) { const source = server.stylesheet_content.get(server_file); if (!source) { throw new Error(`Server stylesheet source not found for client stylesheet ${client_file}`); } stylesheets_to_inline.set(client_file, source); } } const { get_page_options } = create_node_analyser({ resolve: (server_node) => { // Windows needs the file:// protocol for absolute path dynamic imports return import(`file://${join(out, 'server', resolve_symlinks(server_manifest, server_node).chunk.file)}`); }, static_exports }); for (let i = 0; i < manifest_data.nodes.length; i++) { const node = manifest_data.nodes[i]; /** @type {string[]} */ const imports = []; // String representation of /** @type {import('types').SSRNode} */ /** @type {string[]} */ const exports = [`export const index = ${i};`]; /** @type {string[]} */ let imported = []; /** @type {string[]} */ let stylesheets = []; /** @type {string[]} */ let fonts = []; if (node.component && client_manifest) { exports.push( 'let component_cache;', `export const component = async () => component_cache ??= (await import('../${ resolve_symlinks(server_manifest, node.component).chunk.file }')).default;` ); } if (node.universal) { const page_options = await get_page_options(node); if (!!page_options && page_options.ssr === false) { exports.push(`export const universal = ${s(page_options, null, 2)};`) } else { imports.push( `import * as universal from '../${resolve_symlinks(server_manifest, node.universal).chunk.file}';` ); // TODO: when building for analysis, explain why the file was loaded on the server if we fail to load it exports.push('export { universal };'); } exports.push(`export const universal_id = ${s(node.universal)};`); } if (node.server) { imports.push( `import * as server from '../${resolve_symlinks(server_manifest, node.server).chunk.file}';` ); exports.push('export { server };'); exports.push(`export const server_id = ${s(node.server)};`); } if (client_manifest && (node.universal || node.component) && output_config.bundleStrategy === 'split') { const entry_path = `${normalizePath(kit.outDir)}/generated/client-optimized/nodes/${i}.js`; const entry = find_deps(client_manifest, entry_path, true); // eagerly load client stylesheets and fonts imported by the SSR-ed page to avoid FOUC. // However, if it is not used during SSR (not present in the server manifest), // then it can be lazily loaded in the browser. /** @type {import('types').AssetDependencies | undefined} */ let component; if (node.component) { component = find_deps(server_manifest, node.component, true); } /** @type {import('types').AssetDependencies | undefined} */ let universal; if (node.universal) { universal = find_deps(server_manifest, node.universal, true); } /** @type {Set<string>} */ const eager_css = new Set(); /** @type {Set<string>} */ const eager_assets = new Set(); entry.stylesheet_map.forEach((value, filepath) => { // pages and layouts are renamed to node indexes when optimised for the client // so we use the original filename instead to check against the server manifest if (filepath === entry_path) { filepath = node.component ?? filepath; } if (component?.stylesheet_map.has(filepath) || universal?.stylesheet_map.has(filepath)) { value.css.forEach(file => eager_css.add(file)); value.assets.forEach(file => eager_assets.add(file)); } }); imported = entry.imports; stylesheets = Array.from(eager_css); fonts = filter_fonts(Array.from(eager_assets)); } exports.push( `export const imports = ${s(imported)};`, `export const stylesheets = ${s(stylesheets)};`, `export const fonts = ${s(fonts)};` ); /** @type {string[]} */ const inline_styles = []; stylesheets.forEach((file, i) => { if (stylesheets_to_inline.has(file)) { const filename = basename(file); const dest = `${out}/server/stylesheets/${filename}.js`; const source = stylesheets_to_inline.get(file); if (!source) { throw new Error(`Server stylesheet source not found for client stylesheet ${file}`); } fs.writeFileSync(dest, `// ${filename}\nexport default ${s(source)};`); const name = `stylesheet_${i}`; imports.push(`import ${name} from '../stylesheets/${filename}.js';`); inline_styles.push(`\t${s(file)}: ${name}`); } }); if (inline_styles.length > 0) { exports.push(`export const inline_styles = () => ({\n${inline_styles.join(',\n')}\n});`); } fs.writeFileSync( `${out}/server/nodes/${i}.js`, `${imports.join('\n')}\n\n${exports.join('\n')}\n` ); } } /** * @param {(import('vite').Rollup.OutputAsset | import('vite').Rollup.OutputChunk)[]} chunks */ function get_stylesheets(chunks) { /** * A map of module IDs and the stylesheets they use. * @type {Map<string, string[]>} */ const stylesheets_used = new Map(); /** * A map of stylesheet names and their content. * @type {Map<string, string>} */ const stylesheet_content = new Map(); for (const chunk of chunks) { if (chunk.type === 'asset') { if (chunk.fileName.endsWith('.css')) { stylesheet_content.set(chunk.fileName, chunk.source.toString()); } continue; } if (chunk.viteMetadata?.importedCss.size) { const css = Array.from(chunk.viteMetadata.importedCss); for (const id of chunk.moduleIds) { stylesheets_used.set(id, css ); } } } return { stylesheets_used, stylesheet_content }; }