From c3285fcf8ddd06ee4c8d367fae8d5e8048f5aec5 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 11 May 2022 09:23:11 -0600 Subject: [PATCH] HMR hoisted scripts (#3336) * HMR hoisted scripts * Add to the dep graph * Remove example change * Adds changeset * Fix markdown test --- src/vite-plugin-astro/hmr.ts | 10 ++++++++++ src/vite-plugin-astro/index.ts | 31 ++++++++++++++++++++++++------- test/astro-markdown-css.test.js | 3 ++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/vite-plugin-astro/hmr.ts b/src/vite-plugin-astro/hmr.ts index 4e194993e72b..2b11585ca484 100644 --- a/src/vite-plugin-astro/hmr.ts +++ b/src/vite-plugin-astro/hmr.ts @@ -68,6 +68,7 @@ export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logg filtered.add(mod); files.add(mod.file); } + for (const imp of mod.importers) { if (imp.file && isCached(config, imp.file)) { filtered.add(imp); @@ -85,6 +86,15 @@ export async function handleHotUpdate(ctx: HmrContext, config: AstroConfig, logg // 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('=')); + + // Add hoisted scripts so these get invalidated + for(const mod of mods) { + for(const imp of mod.importedModules) { + if(imp.id?.includes('?astro&type=script')) { + mods.push(imp); + } + } + } const isSelfAccepting = mods.every((m) => m.isSelfAccepting || m.url.endsWith('.svelte')); const file = ctx.file.replace(config.root.pathname, '/'); diff --git a/src/vite-plugin-astro/index.ts b/src/vite-plugin-astro/index.ts index ea6f17dcdfe8..47ecfee1efbe 100644 --- a/src/vite-plugin-astro/index.ts +++ b/src/vite-plugin-astro/index.ts @@ -126,6 +126,12 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu if (typeof query.index === 'undefined') { throw new Error(`Requests for hoisted scripts must include an index`); } + // HMR hoisted script only exists to make them appear in the module graph. + if(opts?.ssr) { + return { + code: `/* client hoisted script, empty in SSR: ${id} */` + }; + } const transformResult = await cachedCompilation(config, filename, source, viteTransform, { ssr: Boolean(opts?.ssr), @@ -182,17 +188,28 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu 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); - }`; + + let i = 0; + while(i < transformResult.scripts.length) { + deps.add(`${id}?astro&type=script&index=${i}`); + SUFFIX += `import "${id}?astro&type=script&index=${i}";`; + i++; + } + + // We only need to define deps if there are any + if(deps.size > 1) { + SUFFIX += `\nif(import.meta.hot) import.meta.hot.accept(["${id}", "${Array.from(deps).join('","')}"], (...mods) => mods);` + } else { + SUFFIX += `\nif (import.meta.hot) { + import.meta.hot.accept(mod => mod); + }`; + } } // Add handling to inject scripts into each page JS bundle, if needed. if (isPage) { SUFFIX += `\nimport "${PAGE_SSR_SCRIPT_ID}";`; } + return { code: `${code}${SUFFIX}`, map, @@ -260,7 +277,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu }, async handleHotUpdate(context) { if (context.server.config.isProduction) return; - return handleHotUpdate(context, config, logging); + return handleHotUpdate.call(this, context, config, logging); }, }; } diff --git a/test/astro-markdown-css.test.js b/test/astro-markdown-css.test.js index 40710ce8528b..fcb1408f091a 100644 --- a/test/astro-markdown-css.test.js +++ b/test/astro-markdown-css.test.js @@ -34,11 +34,12 @@ describe('Imported markdown CSS', function () { }); describe('dev', () => { let devServer; + let html; let $; before(async () => { devServer = await fixture.startDevServer(); - const html = await fixture.fetch('/').then((res) => res.text()); + html = await fixture.fetch('/').then((res) => res.text()); $ = cheerio.load(html); });