Improved HMR (#3138)

* WIP: improved HMR

* fix(hmr): improve hmr filtering to avoid full reloads

* chore: add changeset
This commit is contained in:
Nate Moore 2022-04-20 16:46:40 -05:00 committed by GitHub
parent e621c2f7d3
commit 37a7a8347c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 51 additions and 16 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
General HMR Improvements, including new HMR support for framework components that are only server-side rendered (do not have a `client:*` directive)

View file

@ -1,11 +1,8 @@
if (import.meta.hot) { if (import.meta.hot) {
// signal to Vite that we accept HMR import.meta.hot.accept(mod => mod);
import.meta.hot.accept((mod) => mod);
const parser = new DOMParser(); const parser = new DOMParser();
import.meta.hot.on('astro:update', async ({ file }) => { async function updatePage() {
const { default: diff } = await import('micromorph'); const { default: diff } = await import('micromorph');
// eslint-disable-next-line no-console
console.log(`[vite] hot updated: ${file}`);
const html = await fetch(`${window.location}`).then((res) => res.text()); const html = await fetch(`${window.location}`).then((res) => res.text());
const doc = parser.parseFromString(html, 'text/html'); const doc = parser.parseFromString(html, 'text/html');
@ -17,6 +14,27 @@ if (import.meta.hot) {
root.innerHTML = current?.innerHTML; root.innerHTML = current?.innerHTML;
} }
} }
diff(document, doc); return diff(document, doc);
}
async function updateAll(files: any[]) {
let hasAstroUpdate = false;
for (const file of files) {
if (file.acceptedPath.endsWith('.astro')) {
hasAstroUpdate = true;
continue;
}
if (file.acceptedPath.includes('vue&type=style')) {
const link = document.querySelector(`link[href="${file.acceptedPath}"]`);
if (link) {
link.replaceWith(link.cloneNode(true));
}
}
}
if (hasAstroUpdate) {
return updatePage()
}
}
import.meta.hot.on('vite:beforeUpdate', async (event) => {
await updateAll(event.updates);
}); });
} }

View file

@ -82,18 +82,17 @@ export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logg
invalidateCompilation(config, file); invalidateCompilation(config, file);
} }
const mod = ctx.modules.find((m) => m.file === ctx.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 isSelfAccepting = mods.every(m => m.isSelfAccepting || m.url.endsWith('.svelte'));
// Note: this intentionally ONLY applies to Astro components
// HMR is handled for other file types by their respective plugins
const file = ctx.file.replace(config.root.pathname, '/'); const file = ctx.file.replace(config.root.pathname, '/');
if (ctx.file.endsWith('.astro')) { if (isSelfAccepting) {
ctx.server.ws.send({ type: 'custom', event: 'astro:update', data: { file } });
}
if (mod?.isSelfAccepting) {
info(logging, 'astro', msg.hmr({ file })); info(logging, 'astro', msg.hmr({ file }));
} else { } else {
info(logging, 'astro', msg.reload({ file })); info(logging, 'astro', msg.reload({ file }));
} }
return Array.from(filtered);
return mods
} }

View file

@ -174,7 +174,20 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
let SUFFIX = ''; let SUFFIX = '';
// Add HMR handling in dev mode. // Add HMR handling in dev mode.
if (!resolvedConfig.isProduction) { if (!resolvedConfig.isProduction) {
SUFFIX += `\nif (import.meta.hot) import.meta.hot.accept((mod) => mod);`; // HACK: extract dependencies from metadata until compiler static extraction handles them
const metadata = transformResult.code.split('$$createMetadata(')[1].split('});\n')[0]
const pattern = /specifier:\s*'([^']*)'/g;
const deps = new Set();
let match;
while (match = pattern.exec(metadata)?.[1]) {
deps.add(match);
}
// // import.meta.hot.accept(["${id}", "${Array.from(deps).join('","')}"], (...mods) => mods);
// We need to be self-accepting, AND
// we need an explicity array of deps to track changes for SSR-only components
SUFFIX += `\nif (import.meta.hot) {
import.meta.hot.accept(mod => mod);
}`;
} }
// Add handling to inject scripts into each page JS bundle, if needed. // Add handling to inject scripts into each page JS bundle, if needed.
if (isPage) { if (isPage) {
@ -242,7 +255,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
} }
} }
throw err; throw err;
} }
}, },
async handleHotUpdate(context) { async handleHotUpdate(context) {