Improved HMR (#3138)
* WIP: improved HMR * fix(hmr): improve hmr filtering to avoid full reloads * chore: add changeset
This commit is contained in:
parent
e621c2f7d3
commit
37a7a8347c
4 changed files with 51 additions and 16 deletions
5
.changeset/cyan-dots-admire.md
Normal file
5
.changeset/cyan-dots-admire.md
Normal 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)
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue