UNPKG

@sveltejs/kit

Version:

SvelteKit is the fastest way to build Svelte apps

201 lines (167 loc) • 5.78 kB
import fs from 'node:fs'; import path from 'node:path'; import { normalizePath } from 'vite'; /** * Adds transitive JS and CSS dependencies to the js and css inputs. * @param {import('vite').Manifest} manifest * @param {string} entry * @param {boolean} add_dynamic_css * @param {string} root * @returns {import('types').AssetDependencies} */ export function find_deps(manifest, entry, add_dynamic_css, root) { /** @type {Set<string>} */ const seen = new Set(); /** @type {Set<string>} */ const imports = new Set(); /** @type {Set<string>} */ const stylesheets = new Set(); /** @type {Set<string>} */ const imported_assets = new Set(); /** @type {Map<string, { css: Set<string>; assets: Set<string> }>} */ const stylesheet_map = new Map(); /** * @param {string} current * @param {boolean} add_js * @param {string} initial_importer * @param {number} dynamic_import_depth */ function traverse(current, add_js, initial_importer, dynamic_import_depth) { if (seen.has(current)) return; seen.add(current); const { chunk } = resolve_symlinks(manifest, current, root); if (add_js) imports.add(chunk.file); if (chunk.assets) { chunk.assets.forEach((asset) => imported_assets.add(asset)); } if (chunk.css) { chunk.css.forEach((file) => stylesheets.add(file)); } if (chunk.imports) { chunk.imports.forEach((file) => traverse(file, add_js, initial_importer, dynamic_import_depth) ); } if (!add_dynamic_css) return; if ((chunk.css || chunk.assets) && dynamic_import_depth <= 1) { // group files based on the initial importer because if a file is only ever // a transitive dependency, it doesn't have a suitable name we can map back to // the server manifest if (stylesheet_map.has(initial_importer)) { const { css, assets } = /** @type {{ css: Set<string>; assets: Set<string> }} */ ( stylesheet_map.get(initial_importer) ); if (chunk.css) chunk.css.forEach((file) => css.add(file)); if (chunk.assets) chunk.assets.forEach((file) => assets.add(file)); } else { stylesheet_map.set(initial_importer, { css: new Set(chunk.css), assets: new Set(chunk.assets) }); } } if (chunk.dynamicImports) { dynamic_import_depth++; chunk.dynamicImports.forEach((file) => { traverse(file, false, file, dynamic_import_depth); }); } } const { chunk, file } = resolve_symlinks(manifest, entry, root); traverse(file, true, entry, 0); const assets = Array.from(imported_assets); return { assets, file: chunk.file, imports: Array.from(imports), stylesheets: Array.from(stylesheets), // TODO do we need this separately? fonts: filter_fonts(assets, manifest, root), stylesheet_map }; } /** * @param {import('vite').Manifest} manifest * @param {string} file * @param {string} root */ export function resolve_symlinks(manifest, file, root) { while (!manifest[file]) { const next = normalizePath(path.relative(root, fs.realpathSync(file))); if (next === file) throw new Error(`Could not find file "${file}" in Vite manifest`); file = next; } const chunk = manifest[file]; return { chunk, file }; } /** @type {WeakMap<import('vite').Manifest, Map<string, string>>} */ const source_maps = new WeakMap(); /** * @param {string[]} assets * @param {import('vite').Manifest} manifest * @param {string} root * @returns {import('types').FontDependency[]} */ export function filter_fonts(assets, manifest, root) { let sources = source_maps.get(manifest); if (!sources) { sources = new Map(); source_maps.set(manifest, sources); // identical files are emitted once but can have several sources — keep the first for (const key of Object.keys(manifest).sort()) { const { file, src } = manifest[key]; if (src && !sources.has(file)) { sources.set(file, src); } } } return assets .filter((asset) => /\.(woff2?|ttf|otf)$/.test(asset)) .map((file) => { const src = sources.get(file) ?? file; const marker = '/node_modules/'; const index = src.lastIndexOf(marker); // Vite follows package manager symlinks when generating the manifest. Prefer the // project-local node_modules path when it points to the same file. if (index !== -1) { const filename = `node_modules/${src.slice(index + marker.length)}`; const source = path.resolve(root, src); const unresolved = path.resolve(root, filename); if ( fs.existsSync(source) && fs.existsSync(unresolved) && fs.realpathSync(unresolved) === fs.realpathSync(source) ) { return { file, filename }; } } return { file, filename: normalizePath(path.relative(root, path.resolve(root, src))) }; }); } /** * Writes a function with arguments used by a template literal. * This helps us store strings in a module and inject values at runtime. * @param {string} name The name of the function * @param {string[]} placeholder_names The names of the placeholders in the string * @param {string} str A string with placeholders such as "Hello ${arg0}". * It must have backslashes, backticks and dollar signs already escaped. * @returns {string} The function written as a string */ export function create_function_as_string(name, placeholder_names, str) { const args = placeholder_names ? placeholder_names.join(', ') : ''; return `function ${name}(${args}) { return \`${str}\`; }`; } /** * Guarantees that the generated placeholder is not already present in the content. * @param {string} content * @param {string} key * @returns {string} */ export function generate_placeholder(content, key) { let id = 1; let placeholder = `__SVELTEKIT_${key}_${id}__`; while (content.includes(placeholder)) { placeholder = `__SVELTEKIT_${key}_${++id}__`; } return placeholder; }