Skip to content

Commit

Permalink
Improve style HMR (#4125)
Browse files Browse the repository at this point in the history
* feat: improve style HMR

* chore: add inline comments

* Update hmr.ts

Co-authored-by: Nate Moore <[email protected]>
  • Loading branch information
natemoo-re and natemoo-re authored Aug 3, 2022
1 parent 09eca9b commit 5f3b3b4
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/funny-poems-learn.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions packages/astro/src/core/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
52 changes: 46 additions & 6 deletions packages/astro/src/vite-plugin-astro/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down
19 changes: 17 additions & 2 deletions packages/astro/src/vite-plugin-astro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu

return {
code,
meta: {
vite: {
isSelfAccepting: true,
},
},
};
}
case 'script': {
Expand Down Expand Up @@ -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 });
},
};
}
Expand Down

0 comments on commit 5f3b3b4

Please sign in to comment.