summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/heavy-bananas-deny.md5
-rw-r--r--packages/astro/src/core/create-vite.ts2
-rw-r--r--packages/astro/src/vite-plugin-astro/hmr.ts2
-rw-r--r--packages/astro/src/vite-plugin-load-fallback/index.ts55
-rw-r--r--packages/astro/test/units/dev/dev.test.js65
-rw-r--r--packages/astro/test/units/test-utils.js38
6 files changed, 148 insertions, 19 deletions
diff --git a/.changeset/heavy-bananas-deny.md b/.changeset/heavy-bananas-deny.md
new file mode 100644
index 000000000..24c34d9be
--- /dev/null
+++ b/.changeset/heavy-bananas-deny.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Prevent overcaching .astro HMR changes
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 91968affd..47440c372 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -96,6 +96,7 @@ export async function createVite(
},
plugins: [
configAliasVitePlugin({ settings }),
+ astroLoadFallbackPlugin({ fs, settings }),
astroVitePlugin({ settings, logging }),
astroScriptsPlugin({ settings }),
// The server plugin is for dev only and having it run during the build causes
@@ -110,7 +111,6 @@ export async function createVite(
astroPostprocessVitePlugin({ settings }),
astroIntegrationsContainerPlugin({ settings, logging }),
astroScriptsPageSSRPlugin({ settings }),
- astroLoadFallbackPlugin({ fs }),
],
publicDir: fileURLToPath(settings.config.publicDir),
root: fileURLToPath(settings.config.root),
diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts
index 7d45723e6..17ecf1885 100644
--- a/packages/astro/src/vite-plugin-astro/hmr.ts
+++ b/packages/astro/src/vite-plugin-astro/hmr.ts
@@ -24,7 +24,7 @@ export async function handleHotUpdate(
{ config, logging, compile }: HandleHotUpdateOptions
) {
let isStyleOnlyChange = false;
- if (ctx.file.endsWith('.astro')) {
+ if (ctx.file.endsWith('.astro') && isCached(config, ctx.file)) {
// Get the compiled result from the cache
const oldResult = await compile();
// But we also need a fresh, uncached result to compare it to
diff --git a/packages/astro/src/vite-plugin-load-fallback/index.ts b/packages/astro/src/vite-plugin-load-fallback/index.ts
index f0a983993..f77f35152 100644
--- a/packages/astro/src/vite-plugin-load-fallback/index.ts
+++ b/packages/astro/src/vite-plugin-load-fallback/index.ts
@@ -1,34 +1,65 @@
-import nodeFs from 'fs';
+import type { AstroSettings } from '../@types/astro';
import type * as vite from 'vite';
+import nodeFs from 'fs';
+import npath from 'path';
+
type NodeFileSystemModule = typeof nodeFs;
export interface LoadFallbackPluginParams {
fs?: NodeFileSystemModule;
+ settings: AstroSettings;
}
-export default function loadFallbackPlugin({ fs }: LoadFallbackPluginParams): vite.Plugin | false {
+export default function loadFallbackPlugin({ fs, settings }: LoadFallbackPluginParams): vite.Plugin[] | false {
// Only add this plugin if a custom fs implementation is provided.
if (!fs || fs === nodeFs) {
return false;
}
- return {
- name: 'astro:load-fallback',
- enforce: 'post',
- async load(id) {
+ const tryLoadModule = async (id: string) => {
+ try {
+ // await is necessary for the catch
+ return await fs.promises.readFile(cleanUrl(id), 'utf-8');
+ } catch (e) {
try {
- // await is necessary for the catch
- return await fs.promises.readFile(cleanUrl(id), 'utf-8');
- } catch (e) {
+ return await fs.promises.readFile(id, 'utf-8');
+ } catch (e2) {
try {
- return await fs.promises.readFile(id, 'utf-8');
- } catch (e2) {
+ const fullpath = new URL('.' + id, settings.config.root);
+ return await fs.promises.readFile(fullpath, 'utf-8');
+ } catch (e3) {
// Let fall through to the next
}
}
- },
+ }
};
+
+ return [{
+ name: 'astro:load-fallback',
+ enforce: 'post',
+ resolveId(id, parent) {
+ if(id.startsWith('.') && parent && fs.existsSync(parent)) {
+ return npath.posix.join(npath.posix.dirname(parent), id);
+ }
+ },
+ async load(id) {
+ const source = await tryLoadModule(id);
+ return source;
+ }
+ }, {
+ name: 'astro:load-fallback-hmr',
+ enforce: 'pre',
+ handleHotUpdate(context) {
+ // Wrap context.read so it checks our filesystem first.
+ const read = context.read;
+ context.read = async () => {
+ const source = await tryLoadModule(context.file);
+ if(source) return source;
+ return read.call(context);
+ };
+ }
+ }];
}
const queryRE = /\?.*$/s;
diff --git a/packages/astro/test/units/dev/dev.test.js b/packages/astro/test/units/dev/dev.test.js
index d8a65d066..2d45eb354 100644
--- a/packages/astro/test/units/dev/dev.test.js
+++ b/packages/astro/test/units/dev/dev.test.js
@@ -2,7 +2,7 @@ import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { runInContainer } from '../../../dist/core/dev/index.js';
-import { createFs, createRequestAndResponse } from '../test-utils.js';
+import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
@@ -37,4 +37,67 @@ describe('dev container', () => {
expect($('h1')).to.have.a.lengthOf(1);
});
});
+
+ it('HMR only short circuits on previously cached modules', async () => {
+ const fs = createFs(
+ {
+ '/src/components/Header.astro': `
+ <h1>{Astro.props.title}</h1>
+ `,
+ '/src/pages/index.astro': `
+ ---
+ import Header from '../components/Header.astro';
+ const name = 'Testing';
+ ---
+ <html>
+ <head><title>{name}</title></head>
+ <body class="one">
+ <Header title={name} />
+ </body>
+ </html>
+ `,
+ },
+ root
+ );
+
+ await runInContainer({ fs, root }, async (container) => {
+ let r = createRequestAndResponse({
+ method: 'GET',
+ url: '/',
+ });
+ container.handle(r.req, r.res);
+ let html = await r.text();
+ let $ = cheerio.load(html);
+ expect($('body.one')).to.have.a.lengthOf(1);
+
+ fs.writeFileFromRootSync('/src/components/Header.astro', `
+ <h1>{Astro.props.title}</h1>
+ `);
+ triggerFSEvent(container, fs, '/src/components/Header.astro', 'change');
+
+ fs.writeFileFromRootSync('/src/pages/index.astro', `
+ ---
+ import Header from '../components/Header.astro';
+ const name = 'Testing';
+ ---
+ <html>
+ <head><title>{name}</title></head>
+ <body class="two">
+ <Header title={name} />
+ </body>
+ </html>
+ `);
+ triggerFSEvent(container, fs, '/src/pages/index.astro', 'change');
+
+ r = createRequestAndResponse({
+ method: 'GET',
+ url: '/',
+ });
+ container.handle(r.req, r.res);
+ html = await r.text();
+ $ = cheerio.load(html);
+ expect($('body.one')).to.have.a.lengthOf(0);
+ expect($('body.two')).to.have.a.lengthOf(1);
+ });
+ });
});
diff --git a/packages/astro/test/units/test-utils.js b/packages/astro/test/units/test-utils.js
index 45e1e50a9..80e2c150b 100644
--- a/packages/astro/test/units/test-utils.js
+++ b/packages/astro/test/units/test-utils.js
@@ -12,15 +12,23 @@ class MyVolume extends Volume {
this.#root = root;
}
+ #forcePath(p) {
+ if (p instanceof URL) {
+ p = unixify(fileURLToPath(p));
+ }
+ return p;
+ }
+
getFullyResolvedPath(pth) {
return npath.posix.join(this.#root, pth);
}
existsSync(p) {
- if (p instanceof URL) {
- p = fileURLToPath(p);
- }
- return super.existsSync(p);
+ return super.existsSync(this.#forcePath(p));
+ }
+
+ readFile(p, ...args) {
+ return super.readFile(this.#forcePath(p), ...args);
}
writeFileFromRootSync(pth, ...rest) {
@@ -44,6 +52,28 @@ export function createFs(json, root) {
return fs;
}
+/**
+ *
+ * @param {import('../../src/core/dev/container').Container} container
+ * @param {typeof import('fs')} fs
+ * @param {string} shortPath
+ * @param {'change'} eventType
+ */
+export function triggerFSEvent(container, fs, shortPath, eventType) {
+ container.viteServer.watcher.emit(
+ eventType,
+ fs.getFullyResolvedPath(shortPath)
+ );
+
+ if(!fileURLToPath(container.settings.config.root).startsWith('/')) {
+ const drive = fileURLToPath(container.settings.config.root).slice(0, 2);
+ container.viteServer.watcher.emit(
+ eventType,
+ drive + fs.getFullyResolvedPath(shortPath)
+ );
+ }
+}
+
export function createRequestAndResponse(reqOptions = {}) {
const req = httpMocks.createRequest(reqOptions);