summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/funny-poems-learn.md5
-rw-r--r--packages/astro/src/core/messages.ts4
-rw-r--r--packages/astro/src/vite-plugin-astro/hmr.ts52
-rw-r--r--packages/astro/src/vite-plugin-astro/index.ts19
4 files changed, 70 insertions, 10 deletions
diff --git a/.changeset/funny-poems-learn.md b/.changeset/funny-poems-learn.md
new file mode 100644
index 000000000..eea866401
--- /dev/null
+++ b/.changeset/funny-poems-learn.md
@@ -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.
diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts
index 8f268052a..855db55fc 100644
--- a/packages/astro/src/core/messages.ts
+++ b/packages/astro/src/core/messages.ts
@@ -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 */
diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts
index d4b72a061..472862344 100644
--- a/packages/astro/src/vite-plugin-astro/hmr.ts
+++ b/packages/astro/src/vite-plugin-astro/hmr.ts
@@ -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 {
diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts
index f38d5e5ca..63eae758e 100644
--- a/packages/astro/src/vite-plugin-astro/index.ts
+++ b/packages/astro/src/vite-plugin-astro/index.ts
@@ -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 });
},
};
}