Improve style HMR (#4125)
* feat: improve style HMR * chore: add inline comments * Update hmr.ts Co-authored-by: Nate Moore <nate@astro.build>
This commit is contained in:
parent
09eca9be5e
commit
5f3b3b44db
4 changed files with 70 additions and 10 deletions
5
.changeset/funny-poems-learn.md
Normal file
5
.changeset/funny-poems-learn.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix HMR of style blocks in Astro files. Updating a style block should no longer perform a full reload of the page.
|
|
@ -47,8 +47,8 @@ export function reload({ file }: { file: string }): string {
|
|||
return `${green('reload'.padStart(PREFIX_PADDING))} ${file}`;
|
||||
}
|
||||
|
||||
export function hmr({ file }: { file: string }): string {
|
||||
return `${green('update'.padStart(PREFIX_PADDING))} ${file}`;
|
||||
export function hmr({ file, style = false }: { file: string; style?: boolean }): string {
|
||||
return `${green('update'.padStart(PREFIX_PADDING))} ${file}${style ? ` ${dim('style')}` : ''}`;
|
||||
}
|
||||
|
||||
/** Display dev server host and startup time */
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { AstroConfig } from '../@types/astro';
|
|||
import type { LogOptions } from '../core/logger/core.js';
|
||||
import { info } from '../core/logger/core.js';
|
||||
import * as msg from '../core/messages.js';
|
||||
import { invalidateCompilation, isCached } from './compile.js';
|
||||
import { cachedCompilation, invalidateCompilation, isCached } from './compile.js';
|
||||
|
||||
interface TrackCSSDependenciesOptions {
|
||||
viteDevServer: ViteDevServer | null;
|
||||
|
@ -55,9 +55,40 @@ const isPkgFile = (id: string | null) => {
|
|||
return id?.startsWith(fileURLToPath(PKG_PREFIX)) || id?.startsWith(PKG_PREFIX.pathname);
|
||||
};
|
||||
|
||||
export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logging: LogOptions) {
|
||||
// Invalidate the compilation cache so it recompiles
|
||||
invalidateCompilation(config, ctx.file);
|
||||
export interface HandleHotUpdateOptions {
|
||||
config: AstroConfig;
|
||||
logging: LogOptions;
|
||||
compile: () => ReturnType<typeof cachedCompilation>;
|
||||
}
|
||||
|
||||
export async function handleHotUpdate(
|
||||
ctx: HmrContext,
|
||||
{ config, logging, compile }: HandleHotUpdateOptions
|
||||
) {
|
||||
let isStyleOnlyChange = false;
|
||||
if (ctx.file.endsWith('.astro')) {
|
||||
// Get the compiled result from the cache
|
||||
const oldResult = await compile();
|
||||
// But we also need a fresh, uncached result to compare it to
|
||||
invalidateCompilation(config, ctx.file);
|
||||
const newResult = await compile();
|
||||
// If the hashes are identical, we assume only styles have changed
|
||||
if (oldResult.scope === newResult.scope) {
|
||||
isStyleOnlyChange = true;
|
||||
// All styles are the same, we can skip an HMR update
|
||||
const styles = new Set(newResult.css);
|
||||
for (const style of oldResult.css) {
|
||||
if (styles.has(style)) {
|
||||
styles.delete(style);
|
||||
}
|
||||
}
|
||||
if (styles.size === 0) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
invalidateCompilation(config, ctx.file);
|
||||
}
|
||||
|
||||
// Skip monorepo files to avoid console spam
|
||||
if (isPkgFile(ctx.file)) {
|
||||
|
@ -91,12 +122,21 @@ export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logg
|
|||
// Invalidate happens as a separate step because a single .astro file
|
||||
// produces multiple CSS modules and we want to return all of those.
|
||||
for (const file of files) {
|
||||
if (isStyleOnlyChange && file === ctx.file) continue;
|
||||
invalidateCompilation(config, file);
|
||||
}
|
||||
|
||||
// Bugfix: sometimes style URLs get normalized and end with `lang.css=`
|
||||
// These will cause full reloads, so filter them out here
|
||||
const mods = ctx.modules.filter((m) => !m.url.endsWith('='));
|
||||
const file = ctx.file.replace(config.root.pathname, '/');
|
||||
|
||||
// If only styles are changed, remove the component file from the update list
|
||||
if (isStyleOnlyChange) {
|
||||
info(logging, 'astro', msg.hmr({ file, style: true }));
|
||||
// remove base file and hoisted scripts
|
||||
return mods.filter((mod) => mod.id !== ctx.file && !mod.id?.endsWith('.ts'));
|
||||
}
|
||||
|
||||
// Add hoisted scripts so these get invalidated
|
||||
for (const mod of mods) {
|
||||
|
@ -106,9 +146,9 @@ export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logg
|
|||
}
|
||||
}
|
||||
}
|
||||
const isSelfAccepting = mods.every((m) => m.isSelfAccepting || m.url.endsWith('.svelte'));
|
||||
|
||||
const file = ctx.file.replace(config.root.pathname, '/');
|
||||
// TODO: Svelte files should be marked as `isSelfAccepting` but they don't appear to be
|
||||
const isSelfAccepting = mods.every((m) => m.isSelfAccepting || m.url.endsWith('.svelte'));
|
||||
if (isSelfAccepting) {
|
||||
info(logging, 'astro', msg.hmr({ file }));
|
||||
} else {
|
||||
|
|
|
@ -142,6 +142,11 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
|
||||
return {
|
||||
code,
|
||||
meta: {
|
||||
vite: {
|
||||
isSelfAccepting: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'script': {
|
||||
|
@ -342,9 +347,19 @@ ${source}
|
|||
throw err;
|
||||
}
|
||||
},
|
||||
async handleHotUpdate(context) {
|
||||
async handleHotUpdate(this: PluginContext, context) {
|
||||
if (context.server.config.isProduction) return;
|
||||
return handleHotUpdate.call(this, context, config, logging);
|
||||
const compileProps: CompileProps = {
|
||||
config,
|
||||
filename: context.file,
|
||||
moduleId: context.file,
|
||||
source: await context.read(),
|
||||
ssr: true,
|
||||
viteTransform,
|
||||
pluginContext: this,
|
||||
};
|
||||
const compile = () => cachedCompilation(compileProps);
|
||||
return handleHotUpdate.call(this, context, { config, logging, compile });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue