UNPKG

@sveltejs/vite-plugin-svelte

Version:

The official [Svelte](https://svelte.dev) plugin for [Vite](https://vitejs.dev).

178 lines (162 loc) 5.72 kB
import { readFileSync } from 'node:fs'; import * as svelte from 'svelte/compiler'; import { log } from './log.js'; import { toESBuildError } from './error.js'; import { safeBase64Hash } from './hash.js'; import { normalize } from './id.js'; /** * @typedef {NonNullable<import('vite').DepOptimizationOptions['esbuildOptions']>} EsbuildOptions * @typedef {NonNullable<EsbuildOptions['plugins']>[number]} EsbuildPlugin */ export const facadeEsbuildSveltePluginName = 'vite-plugin-svelte:facade'; export const facadeEsbuildSvelteModulePluginName = 'vite-plugin-svelte-module:facade'; /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @returns {EsbuildPlugin} */ export function esbuildSveltePlugin(options) { return { name: 'vite-plugin-svelte:optimize-svelte', setup(build) { // Skip in scanning phase as Vite already handles scanning Svelte files. // Otherwise this would heavily slow down the scanning phase. if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return; const filter = /\.svelte(?:\?.*)?$/; /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ let statsCollection; build.onStart(() => { statsCollection = options.stats?.startCollection('prebundle library components', { logResult: (c) => c.stats.length > 1 }); }); build.onLoad({ filter }, async ({ path: filename }) => { const code = readFileSync(filename, 'utf8'); try { const contents = await compileSvelte(options, { filename, code }, statsCollection); return { contents }; } catch (e) { return { errors: [toESBuildError(e, options)] }; } }); build.onEnd(() => { statsCollection?.finish(); }); } }; } /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @param {{ filename: string, code: string }} input * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection] * @returns {Promise<string>} */ async function compileSvelte(options, { filename, code }, statsCollection) { let css = options.compilerOptions.css; if (css !== 'injected') { // TODO ideally we'd be able to externalize prebundled styles too, but for now always put them in the js css = 'injected'; } /** @type {import('svelte/compiler').CompileOptions} */ const compileOptions = { dev: true, // default to dev: true because prebundling is only used in dev ...options.compilerOptions, css, filename, generate: 'client' }; if (compileOptions.hmr && options.emitCss) { const hash = `s-${safeBase64Hash(normalize(filename, options.root))}`; compileOptions.cssHash = () => hash; } let preprocessed; if (options.preprocess) { try { preprocessed = await svelte.preprocess(code, options.preprocess, { filename }); } catch (e) { e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`; throw e; } if (preprocessed.map) compileOptions.sourcemap = preprocessed.map; } const finalCode = preprocessed ? preprocessed.code : code; const dynamicCompileOptions = await options?.dynamicCompileOptions?.({ filename, code: finalCode, compileOptions }); if (dynamicCompileOptions && log.debug.enabled) { log.debug( `dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`, undefined, 'compile' ); } const finalCompileOptions = dynamicCompileOptions ? { ...compileOptions, ...dynamicCompileOptions } : compileOptions; const endStat = statsCollection?.start(filename); const compiled = svelte.compile(finalCode, finalCompileOptions); if (endStat) { endStat(); } return compiled.js.map ? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl() : compiled.js.code; } /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @returns {EsbuildPlugin} */ export function esbuildSvelteModulePlugin(options) { return { name: 'vite-plugin-svelte-module:optimize-svelte', setup(build) { // Skip in scanning phase as Vite already handles scanning Svelte files. // Otherwise this would heavily slow down the scanning phase. if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return; const filter = /\.svelte\.[jt]s(?:\?.*)?$/; /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ let statsCollection; build.onStart(() => { statsCollection = options.stats?.startCollection('prebundle library modules', { logResult: (c) => c.stats.length > 1 }); }); build.onLoad({ filter }, async ({ path: filename }) => { const code = readFileSync(filename, 'utf8'); try { const contents = await compileSvelteModule(options, { filename, code }, statsCollection); return { contents }; } catch (e) { return { errors: [toESBuildError(e, options)] }; } }); build.onEnd(() => { statsCollection?.finish(); }); } }; } /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @param {{ filename: string; code: string }} input * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection] * @returns {Promise<string>} */ async function compileSvelteModule(options, { filename, code }, statsCollection) { const endStat = statsCollection?.start(filename); const compiled = svelte.compileModule(code, { dev: options.compilerOptions?.dev ?? true, // default to dev: true because prebundling is only used in dev filename, generate: 'client' }); if (endStat) { endStat(); } return compiled.js.map ? compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl() : compiled.js.code; }