UNPKG

@sveltejs/vite-plugin-svelte

Version:

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

197 lines (179 loc) 5.34 kB
import { log } from '../utils/log.js'; import { setupWatchers } from '../utils/watch.js'; import { SVELTE_VIRTUAL_STYLE_ID_REGEX } from '../utils/constants.js'; /** * @param {import('../types/plugin-api.d.ts').PluginAPI} api * @returns {import('vite').Plugin} */ export function hotUpdate(api) { /** * @type {import("../types/options.js").ResolvedOptions} */ let options; /** * @type {import('../types/id.d.ts').IdParser} */ let idParser; /** * * @type {Map<string|null,string>} */ const transformResultCache = new Map(); /** @type {import('vite').Plugin} */ const plugin = { name: 'vite-plugin-svelte:hot-update', enforce: 'post', configResolved() { options = api.options; idParser = api.idParser; // @ts-expect-error plugin.transform.filter = { id: { // reinclude virtual styles to get their output include: [...api.filter.id.include, SVELTE_VIRTUAL_STYLE_ID_REGEX], exclude: [ // ignore files in node_modules, we don't hot update them /\/node_modules\//, // remove style exclusion ...api.filter.id.exclude.filter((filter) => filter !== SVELTE_VIRTUAL_STYLE_ID_REGEX) ] } }; }, applyToEnvironment(env) { // we only handle updates for client components // ssr frameworks have to handle updating/reloading themselves as v-p-s can't know what they prefer const hmrEnabled = options.compilerOptions.hmr && options.emitCss; return hmrEnabled && env.config.consumer === 'client'; }, configureServer(server) { const clientEnvironment = Object.values(server.environments).find( (e) => e.config.consumer === 'client' ); if (clientEnvironment) { setupWatchers(options); } else { log.warn( 'No client environment found, not adding watchers for svelte config and preprocessor dependencies' ); } }, buildStart() { transformResultCache.clear(); }, transform: { order: 'post', handler(code, id) { transformResultCache.set(id, code); } }, hotUpdate: { order: 'post', async handler(ctx) { const svelteRequest = idParser(ctx.file, false, ctx.timestamp); if (svelteRequest) { const { modules } = ctx; const svelteModules = []; const nonSvelteModules = []; for (const mod of modules) { if (transformResultCache.has(mod.id)) { svelteModules.push(mod); } else { nonSvelteModules.push(mod); } } if (svelteModules.length === 0) { return; // nothing to do for us } const affectedModules = []; const prevResults = svelteModules.map((m) => transformResultCache.get(m.id)); /** @type {Set<string>} */ const seen = new Set(); /** @param {string} url */ const transformRequest = async (url) => { if (!seen.has(url)) { seen.add(url); await this.environment.transformRequest(url); } }; // Transform the Svelte component itself first, so that the // CSS cache always gets updated. await transformRequest(svelteRequest.filename); for (let i = 0; i < svelteModules.length; i++) { const mod = svelteModules[i]; const prev = prevResults[i]; await transformRequest(mod.url); const next = transformResultCache.get(mod.id); if (hasCodeChanged(prev, next, mod.id)) { affectedModules.push(mod); } else { log.debug( `skipping hot update for ${mod.id} because result is unchanged`, undefined, 'hmr' ); } } log.debug( `hotUpdate for ${svelteRequest.id} result: [${affectedModules.map((m) => m.id).join(', ')}]`, undefined, 'hmr' ); return [...affectedModules, ...nonSvelteModules]; } } } }; return plugin; } /** * @param {string | undefined | null} prev * @param {string | undefined | null} next * @param {string | null} id * @returns {boolean} */ function hasCodeChanged(prev, next, id) { const isStrictEqual = nullSafeEqual(prev, next); if (isStrictEqual) { return false; } const isLooseEqual = nullSafeEqual(normalize(prev), normalize(next)); if (!isStrictEqual && isLooseEqual) { log.debug( `ignoring compiler output change for ${id} as it is equal to previous output after normalization`, undefined, 'hmr' ); } return !isLooseEqual; } /** * @param {string | null | undefined} prev * @param {string | null | undefined} next * @returns {boolean} */ function nullSafeEqual(prev, next) { return (prev == null && next == null) || (prev != null && next != null && prev === next); } /** * remove code that only changes metadata and does not require a js update for the component to keep working * * 1) location numbers argument from $.add_locations calls in svelte output eg [[1,2],[3,4]] * 2) timestamp queries added to imports by vite eg ?t=0123456789123 * * @param {string | null | undefined } code * @returns {string | null | undefined} */ function normalize(code) { if (code == null) { return code; } return ( code // svelte5 $.add_locations line numbers argument [[1,2],[3,4]] // uses matching group replace to keep the template argument intact .replace(/(\$\.add_locations\(.*), (\[\[[\d, [\]]+]])\)/g, '$1, []') // vite import analysis timestamp queries, ?t=0123456789123& .replace(/[?&]t=\d{13}\b/g, '') ); }