Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved HMR #3138

Merged
merged 3 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cyan-dots-admire.md
Original file line number Diff line number Diff line change
@@ -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)
30 changes: 24 additions & 6 deletions packages/astro/src/runtime/client/hmr.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
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();
import.meta.hot.on('astro:update', async ({ file }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom HMR events are gone! Everything is handled by Vite now.

async function updatePage() {
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 doc = parser.parseFromString(html, 'text/html');

Expand All @@ -17,6 +14,27 @@ if (import.meta.hot) {
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()
}
Comment on lines +33 to +35
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an .astro file has been updated, we trigger our custom HMR here. This is the same event as before, just handled through Vite's built-in logic.

}
import.meta.hot.on('vite:beforeUpdate', async (event) => {
await updateAll(event.updates);
});
}
15 changes: 7 additions & 8 deletions packages/astro/src/vite-plugin-astro/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,17 @@ export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logg
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('='));
Comment on lines +85 to +87
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an edge case that was causing full reloads when they weren't called for. Sometimes ?astro&type=astro&index=0&lang.css files would be duplicated in the module graph but represented as ?astro=&type=astro&index=0&lang.css= which would trigger a full reload. We're removing these from the hot update handler since they're always irrelevant.

const isSelfAccepting = mods.every(m => m.isSelfAccepting || m.url.endsWith('.svelte'));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check all modules for isSelfAccepting, not just the file that triggered HMR. For some reason Svelte files aren't marked as self-accepting, but they are.


// 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, '/');
if (ctx.file.endsWith('.astro')) {
ctx.server.ws.send({ type: 'custom', event: 'astro:update', data: { file } });
}
if (mod?.isSelfAccepting) {
if (isSelfAccepting) {
info(logging, 'astro', msg.hmr({ file }));
} else {
info(logging, 'astro', msg.reload({ file }));
}
return Array.from(filtered);

return mods
}
17 changes: 15 additions & 2 deletions packages/astro/src/vite-plugin-astro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,20 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
let SUFFIX = '';
// Add HMR handling in dev mode.
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);
}`;
Comment on lines +177 to +190
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than trigger HMR for only updates to this Astro file, we want to catch HMR updates for any dependencies. This is a bit hacky and could be cleaned up in the compiler, but it works for now.

}
// Add handling to inject scripts into each page JS bundle, if needed.
if (isPage) {
Expand Down Expand Up @@ -242,7 +255,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
}
}

throw err;
throw err;
}
},
async handleHotUpdate(context) {
Expand Down