You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.7 KiB
TypeScript

import { ModuleNode, HmrContext } from 'vite';
import { Code, CompileData } from './utils/compile';
import { log, logCompilerWarnings } from './utils/log';
import { SvelteRequest } from './utils/id';
import { VitePluginSvelteCache } from './utils/vite-plugin-svelte-cache';
import { ResolvedOptions } from './utils/options';
import { toRollupError } from './utils/error';
/**
* Vite-specific HMR handling
*/
export async function handleHotUpdate(
compileSvelte: Function,
ctx: HmrContext,
svelteRequest: SvelteRequest,
cache: VitePluginSvelteCache,
options: ResolvedOptions
): Promise<ModuleNode[] | void> {
if (!cache.has(svelteRequest)) {
// file hasn't been requested yet (e.g. async component)
log.debug(`handleHotUpdate called before initial transform for ${svelteRequest.id}`);
return;
}
const { read, server, modules } = ctx;
const cachedJS = cache.getJS(svelteRequest);
const cachedCss = cache.getCSS(svelteRequest);
const content = await read();
let compileData: CompileData;
try {
compileData = await compileSvelte(svelteRequest, content, options);
cache.update(compileData);
} catch (e) {
cache.setError(svelteRequest, e);
throw toRollupError(e, options);
}
const affectedModules = [...modules];
const cssIdx = modules.findIndex((m) => m.id === svelteRequest.cssId);
if (cssIdx > -1) {
const cssUpdated = cssChanged(cachedCss, compileData.compiled.css);
if (!cssUpdated) {
log.debug(`skipping unchanged css for ${svelteRequest.cssId}`);
affectedModules.splice(cssIdx, 1);
}
}
const jsIdx = modules.findIndex((m) => m.id === svelteRequest.id);
if (jsIdx > -1) {
const jsUpdated = jsChanged(cachedJS, compileData.compiled.js, svelteRequest.filename);
if (!jsUpdated) {
log.debug(`skipping unchanged js for ${svelteRequest.id}`);
affectedModules.splice(jsIdx, 1);
// transform won't be called, log warnings here
logCompilerWarnings(svelteRequest, compileData.compiled.warnings, options);
}
}
// TODO is this enough? see also: https://github.com/vitejs/vite/issues/2274
const ssrModulesToInvalidate = affectedModules.filter((m) => !!m.ssrTransformResult);
if (ssrModulesToInvalidate.length > 0) {
log.debug(`invalidating modules ${ssrModulesToInvalidate.map((m) => m.id).join(', ')}`);
ssrModulesToInvalidate.forEach((moduleNode) => server.moduleGraph.invalidateModule(moduleNode));
}
if (affectedModules.length > 0) {
log.debug(
`handleHotUpdate for ${svelteRequest.id} result: ${affectedModules
.map((m) => m.id)
.join(', ')}`
);
}
return affectedModules;
}
function cssChanged(prev?: Code, next?: Code): boolean {
return !isCodeEqual(prev?.code, next?.code);
}
function jsChanged(prev?: Code, next?: Code, filename?: string): boolean {
const prevJs = prev?.code;
const nextJs = next?.code;
const isStrictEqual = isCodeEqual(prevJs, nextJs);
if (isStrictEqual) {
return false;
}
const isLooseEqual = isCodeEqual(normalizeJsCode(prevJs), normalizeJsCode(nextJs));
if (!isStrictEqual && isLooseEqual) {
log.warn(
`ignoring compiler output js change for ${filename} as it is equal to previous output after normalization`
);
}
return !isLooseEqual;
}
function isCodeEqual(prev?: string, next?: string): boolean {
if (!prev && !next) {
return true;
}
if ((!prev && next) || (prev && !next)) {
return false;
}
return prev === next;
}
/**
* remove code that only changes metadata and does not require a js update for the component to keep working
*
* 1) add_location() calls. These add location metadata to elements, only used by some dev tools
* 2) ... maybe more (or less) in the future
* @param code
*/
function normalizeJsCode(code?: string): string | undefined {
if (!code) {
return code;
}
return code.replace(/\s*\badd_location\s*\([^)]*\)\s*;?/g, '');
}