From f4d9cfb8660b9fea220fe37416d9f9a87df2ca15 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 20 Feb 2024 18:04:19 +0800 Subject: [PATCH 1/3] Improve Astro style HMR for imported styles --- .changeset/little-trains-act.md | 5 ++ .../fixtures/hmr/src/pages/css-external.astro | 11 +++ .../fixtures/hmr/src/styles/css-external.css | 3 + packages/astro/e2e/hmr.test.js | 29 +++++++- packages/astro/src/core/compile/compile.ts | 21 ++++-- packages/astro/src/core/compile/style.ts | 13 ++-- packages/astro/src/vite-plugin-astro/hmr.ts | 64 ++++++----------- packages/astro/src/vite-plugin-astro/index.ts | 70 ++++++------------- packages/astro/src/vite-plugin-astro/types.ts | 3 +- packages/astro/src/vite-plugin-astro/utils.ts | 20 ++++++ 10 files changed, 132 insertions(+), 107 deletions(-) create mode 100644 .changeset/little-trains-act.md create mode 100644 packages/astro/e2e/fixtures/hmr/src/pages/css-external.astro create mode 100644 packages/astro/e2e/fixtures/hmr/src/styles/css-external.css diff --git a/.changeset/little-trains-act.md b/.changeset/little-trains-act.md new file mode 100644 index 000000000000..33e3cc64fa30 --- /dev/null +++ b/.changeset/little-trains-act.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Improves Astro style tag HMR when updating imported styles diff --git a/packages/astro/e2e/fixtures/hmr/src/pages/css-external.astro b/packages/astro/e2e/fixtures/hmr/src/pages/css-external.astro new file mode 100644 index 000000000000..26ae3c1a3e6a --- /dev/null +++ b/packages/astro/e2e/fixtures/hmr/src/pages/css-external.astro @@ -0,0 +1,11 @@ + + + Test + + +

This is blue

+ + + diff --git a/packages/astro/e2e/fixtures/hmr/src/styles/css-external.css b/packages/astro/e2e/fixtures/hmr/src/styles/css-external.css new file mode 100644 index 000000000000..d7836b5f0d6e --- /dev/null +++ b/packages/astro/e2e/fixtures/hmr/src/styles/css-external.css @@ -0,0 +1,3 @@ +.css-external { + color: blue; +} diff --git a/packages/astro/e2e/hmr.test.js b/packages/astro/e2e/hmr.test.js index 5e7e232af96f..e6c054300fc4 100644 --- a/packages/astro/e2e/hmr.test.js +++ b/packages/astro/e2e/hmr.test.js @@ -10,10 +10,18 @@ const test = testFactory({ let devServer; +function throwPageShouldNotReload() { + throw new Error('Page should not reload in HMR'); +} + test.beforeAll(async ({ astro }) => { devServer = await astro.startDevServer(); }); +test.afterEach(({ page }) => { + page.off('load', throwPageShouldNotReload); +}); + test.afterAll(async () => { await devServer.stop(); }); @@ -33,10 +41,12 @@ test.describe('Scripts with dependencies', () => { }); }); -test.describe('Styles with dependencies', () => { - test('refresh with HMR', async ({ page, astro }) => { +test.describe('Styles', () => { + test('dependencies cause refresh with HMR', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/css-dep')); + page.once('load', throwPageShouldNotReload); + const h = page.locator('h1'); await expect(h).toHaveCSS('color', 'rgb(0, 0, 255)'); @@ -44,4 +54,19 @@ test.describe('Styles with dependencies', () => { await expect(h).toHaveCSS('color', 'rgb(255, 0, 0)'); }); + + test('external CSS refresh with HMR', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/css-external')); + + page.once('load', throwPageShouldNotReload); + + const h = page.locator('h1'); + await expect(h).toHaveCSS('color', 'rgb(0, 0, 255)'); + + await astro.editFile('./src/styles/css-external.css', (original) => + original.replace('blue', 'red') + ); + + await expect(h).toHaveCSS('color', 'rgb(255, 0, 0)'); + }); }); diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 97625f021a55..832f0a3c6b08 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -20,9 +20,13 @@ export interface CompileProps { source: string; } -export interface CompileResult extends TransformResult { - cssDeps: Set; - source: string; +export interface CompileCssResult { + code: string; + dependencies?: Set; +} + +export interface CompileResult extends Omit { + css: CompileCssResult[]; } export async function compile({ @@ -32,7 +36,10 @@ export async function compile({ filename, source, }: CompileProps): Promise { - const cssDeps = new Set(); + // Because `@astrojs/compiler` can't return the dependencies for each style transformed, + // we need to use an external array to track the dependencies whenever preprocessing is called, + // and we'll rebuild the final `css` result after transformation. + const cssDeps: CompileCssResult['dependencies'][] = []; const cssTransformErrors: AstroError[] = []; let transformResult: TransformResult; @@ -82,8 +89,10 @@ export async function compile({ return { ...transformResult, - cssDeps, - source, + css: transformResult.css.map((code, i) => ({ + code, + dependencies: cssDeps[i], + })), }; } diff --git a/packages/astro/src/core/compile/style.ts b/packages/astro/src/core/compile/style.ts index 176b6b0441bc..b5a13f0bf869 100644 --- a/packages/astro/src/core/compile/style.ts +++ b/packages/astro/src/core/compile/style.ts @@ -2,6 +2,7 @@ import type { TransformOptions } from '@astrojs/compiler'; import fs from 'node:fs'; import { preprocessCSS, type ResolvedConfig } from 'vite'; import { AstroErrorData, CSSError, positionAt } from '../errors/index.js'; +import type { CompileCssResult } from './compile.js'; export function createStylePreprocessor({ filename, @@ -11,18 +12,18 @@ export function createStylePreprocessor({ }: { filename: string; viteConfig: ResolvedConfig; - cssDeps: Set; + cssDeps: CompileCssResult['dependencies'][]; cssTransformErrors: Error[]; }): TransformOptions['preprocessStyle'] { + let processedStylesCount = 0; + return async (content, attrs) => { + const index = processedStylesCount++; const lang = `.${attrs?.lang || 'css'}`.toLowerCase(); - const id = `${filename}?astro&type=style&lang${lang}`; + const id = `${filename}?astro&type=style&index=${index}&lang${lang}`; try { const result = await preprocessCSS(content, id, viteConfig); - - result.deps?.forEach((dep) => { - cssDeps.add(dep); - }); + cssDeps[index] = result.deps; let map: string | undefined; if (result.map) { diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts index 861fccae4b71..14f35df60105 100644 --- a/packages/astro/src/vite-plugin-astro/hmr.ts +++ b/packages/astro/src/vite-plugin-astro/hmr.ts @@ -1,62 +1,44 @@ -import path from 'node:path'; -import { appendForwardSlash } from '@astrojs/internal-helpers/path'; import type { HmrContext } from 'vite'; import type { Logger } from '../core/logger/core.js'; -import type { CompileAstroResult } from './compile.js'; -import type { CompileMetadata } from './types.js'; import { frontmatterRE } from './utils.js'; +import type { CompileMetadata } from './types.js'; export interface HandleHotUpdateOptions { logger: Logger; - compile: (code: string, filename: string) => Promise; - astroFileToCssAstroDeps: Map>; astroFileToCompileMetadata: Map; } export async function handleHotUpdate( ctx: HmrContext, - { logger, compile, astroFileToCssAstroDeps, astroFileToCompileMetadata }: HandleHotUpdateOptions + { logger, astroFileToCompileMetadata }: HandleHotUpdateOptions ) { - const oldCode = astroFileToCompileMetadata.get(ctx.file)?.originalCode; + const compileMetadata = astroFileToCompileMetadata.get(ctx.file); + + // If `ctx.file` is part of a CSS dependency of any Astro file, invalidate its `astroFileToCompileMetadata` + // so the next transform will re-generate it. + for (const [astroFile, compileData] of astroFileToCompileMetadata) { + const isUpdatedFileCssDep = compileData.css.some((css) => css.dependencies?.has(ctx.file)); + if (isUpdatedFileCssDep) { + astroFileToCompileMetadata.delete(astroFile); + } + } + + // Bail if not Astro file, we only handle them below + if (compileMetadata == null || !ctx.file.endsWith('.astro')) return; + + const oldCode = compileMetadata.originalCode; const newCode = await ctx.read(); + // If only the style code has changed, e.g. editing the `color`, then we can directly invalidate // the Astro CSS virtual modules only. The main Astro module's JS result will be the same and doesn't // need to be invalidated. - if (oldCode && isStyleOnlyChanged(oldCode, newCode)) { + if (isStyleOnlyChanged(oldCode, newCode)) { logger.debug('watch', 'style-only change'); - // Re-compile the main Astro component (even though we know its JS result will be the same) - // so that `astroFileToCompileMetadata` gets a fresh set of compile metadata to be used - // by the virtual modules later in the `load()` hook. - await compile(newCode, ctx.file); + // Invalidate its `astroFileToCompileMetadata` so that its next transform will re-generate it + astroFileToCompileMetadata.delete(ctx.file); // Only return the Astro styles that have changed! return ctx.modules.filter((mod) => mod.id?.includes('astro&type=style')); } - - // Edge case handling usually caused by Tailwind creating circular dependencies - // - // TODO: we can also workaround this with better CSS dependency management for Astro files, - // so that changes within style tags are scoped to itself. But it'll take a bit of work. - // https://github.com/withastro/astro/issues/9370#issuecomment-1850160421 - for (const [astroFile, cssAstroDeps] of astroFileToCssAstroDeps) { - // If the `astroFile` has a CSS dependency on `ctx.file`, there's a good chance this causes a - // circular dependency, which Vite doesn't issue a full page reload. Workaround it by forcing a - // full page reload ourselves. (Vite bug) - // https://github.com/vitejs/vite/pull/15585 - if (cssAstroDeps.has(ctx.file)) { - // Mimic the HMR log as if this file is updated - logger.info('watch', getShortName(ctx.file, ctx.server.config.root)); - // Invalidate the modules of `astroFile` explicitly as Vite may incorrectly soft-invalidate - // the parent if the parent actually imported `ctx.file`, but `this.addWatchFile` was also called - // on `ctx.file`. Vite should do a hard-invalidation instead. (Vite bug) - const parentModules = ctx.server.moduleGraph.getModulesByFile(astroFile); - if (parentModules) { - for (const mod of parentModules) { - ctx.server.moduleGraph.invalidateModule(mod); - } - } - ctx.server.hot.send({ type: 'full-reload', path: '*' }); - } - } } // Disable eslint as we're not sure how to improve this regex yet @@ -112,7 +94,3 @@ function isArrayEqual(a: any[], b: any[]) { } return true; } - -function getShortName(file: string, root: string): string { - return file.startsWith(appendForwardSlash(root)) ? path.posix.relative(root, file) : file; -} diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 9cb36e493545..8fff21ff48ad 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -9,6 +9,7 @@ import { normalizeFilename } from '../vite-plugin-utils/index.js'; import { compileAstro, type CompileAstroResult } from './compile.js'; import { handleHotUpdate } from './hmr.js'; import { parseAstroRequest } from './query.js'; +import { loadId } from './utils.js'; export { getAstroMetadata } from './metadata.js'; export type { AstroPluginMetadata }; @@ -24,14 +25,10 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl const { config } = settings; let server: vite.ViteDevServer | undefined; let compile: (code: string, filename: string) => Promise; - // Tailwind styles could register Astro files as dependencies of other Astro files, - // causing circular imports which trips Vite's HMR. This set is passed to `handleHotUpdate` - // to force a page reload when these dependency files are updated - // NOTE: We need to initialize a map here and in `buildStart` because our unit tests don't - // call `buildStart` (test bug) - let astroFileToCssAstroDeps = new Map>(); // Each Astro file has its own compile metadata so that its scripts and styles virtual module // can retrieve their code from here. + // NOTE: We need to initialize a map here and in `buildStart` because our unit tests don't + // call `buildStart` (test bug) let astroFileToCompileMetadata = new Map(); // Variables for determining if an id starts with /src... @@ -65,7 +62,6 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl }); }, buildStart() { - astroFileToCssAstroDeps = new Map(); astroFileToCompileMetadata = new Map(); // Share the `astroFileToCompileMetadata` across the same Astro config as Astro performs @@ -86,10 +82,18 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl // Astro scripts and styles virtual module code comes from the main Astro compilation // through the metadata from `astroFileToCompileMetadata`. It should always exist as Astro - // modules are compiled first, then its virtual modules. If the virtual modules are somehow - // compiled first, throw an error and we should investigate it. + // modules are compiled first, then its virtual modules. const filename = normalizePath(normalizeFilename(parsedId.filename, config.root)); - const compileMetadata = astroFileToCompileMetadata.get(filename); + let compileMetadata = astroFileToCompileMetadata.get(filename); + // If `compileMetadata` doesn't exist in dev, that means the virtual module may have been invalidated. + // We try to re-compile the main Astro module (`filename`) first before retrieving the metadata again. + if (!compileMetadata && server) { + const code = await loadId(server.pluginContainer, filename); + if (code != null) await compile(code, filename); + compileMetadata = astroFileToCompileMetadata.get(filename); + } + // If the metadata still doesn't exist, that means the virtual modules are somehow compiled first, + // throw an error and we should investigate it. if (!compileMetadata) { throw new Error( `No cached compile metadata found for "${id}". The main Astro module "${filename}" should have ` + @@ -103,12 +107,15 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl throw new Error(`Requests for Astro CSS must include an index.`); } - const code = compileMetadata.css[query.index]; - if (!code) { + const result = compileMetadata.css[query.index]; + if (!result) { throw new Error(`No Astro CSS at index ${query.index}`); } - return { code }; + // Register dependencies from preprocessing this style + result.dependencies?.forEach((dep) => this.addWatchFile(dep)); + + return { code: result.code }; } case 'script': { if (typeof query.index === 'undefined') { @@ -174,34 +181,6 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl const filename = normalizePath(parsedId.filename); const transformResult = await compile(source, filename); - // Register dependencies of this module - const astroDeps = new Set(); - for (const dep of transformResult.cssDeps) { - if (dep.endsWith('.astro')) { - astroDeps.add(dep); - } - this.addWatchFile(dep); - } - astroFileToCssAstroDeps.set(id, astroDeps); - - // When a dependency from the styles are updated, the dep and Astro module will get invalidated. - // However, the Astro style virtual module is not invalidated because we didn't register that the virtual - // module has that dependency. We currently can't do that either because of a Vite bug. - // https://github.com/vitejs/vite/pull/15608 - // Here we manually invalidate the virtual modules ourselves when we're compiling the Astro module. - // When that bug is resolved, we can add the dependencies to the virtual module directly and remove this. - if (server) { - const mods = server.moduleGraph.getModulesByFile(filename); - if (mods) { - const seen = new Set(mods); - for (const mod of mods) { - if (mod.url.includes('?astro')) { - server.moduleGraph.invalidateModule(mod, seen); - } - } - } - } - const astroMetadata: AstroPluginMetadata['astro'] = { clientOnlyComponents: transformResult.clientOnlyComponents, hydratedComponents: transformResult.hydratedComponents, @@ -225,14 +204,7 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl }; }, async handleHotUpdate(ctx) { - if (!ctx.file.endsWith('.astro')) return; - - return handleHotUpdate(ctx, { - logger, - compile, - astroFileToCssAstroDeps, - astroFileToCompileMetadata, - }); + return handleHotUpdate(ctx, { logger, astroFileToCompileMetadata }); }, }; diff --git a/packages/astro/src/vite-plugin-astro/types.ts b/packages/astro/src/vite-plugin-astro/types.ts index 40979293dcbf..a954ac109ea2 100644 --- a/packages/astro/src/vite-plugin-astro/types.ts +++ b/packages/astro/src/vite-plugin-astro/types.ts @@ -1,5 +1,6 @@ import type { HoistedScript, TransformResult } from '@astrojs/compiler'; import type { PropagationHint } from '../@types/astro.js'; +import type { CompileCssResult } from '../core/compile/compile.js'; export interface PageOptions { prerender?: boolean; @@ -20,7 +21,7 @@ export interface CompileMetadata { /** Used for HMR to compare code changes */ originalCode: string; /** For Astro CSS virtual module */ - css: string[]; + css: CompileCssResult[]; /** For Astro hoisted scripts virtual module */ scripts: HoistedScript[]; } diff --git a/packages/astro/src/vite-plugin-astro/utils.ts b/packages/astro/src/vite-plugin-astro/utils.ts index 8bb5b617a3c2..f1a5923409dc 100644 --- a/packages/astro/src/vite-plugin-astro/utils.ts +++ b/packages/astro/src/vite-plugin-astro/utils.ts @@ -1 +1,21 @@ +import fs from 'node:fs/promises'; +import type { PluginContainer } from 'vite'; + export const frontmatterRE = /^---(.*?)^---/ms; + +export async function loadId(pluginContainer: PluginContainer, id: string) { + const result = await pluginContainer.load(id, { ssr: true }); + + if (result) { + if (typeof result === 'string') { + return result; + } else { + return result.code; + } + } + + // Fallback to reading from fs (Vite doesn't add this by default) + try { + return await fs.readFile(id, 'utf-8'); + } catch {} +} From 9af80c67cf8ede6765cfed7938a190cb87824ac3 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 20 Feb 2024 21:42:10 +0800 Subject: [PATCH 2/3] Try fix windows --- packages/astro/src/core/compile/compile.ts | 5 ++++- packages/astro/src/core/compile/style.ts | 7 +++++-- packages/astro/src/vite-plugin-astro/hmr.ts | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 832f0a3c6b08..67e660fe751f 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -22,7 +22,10 @@ export interface CompileProps { export interface CompileCssResult { code: string; - dependencies?: Set; + /** + * The dependencies of the transformed CSS (Normalized paths) + */ + dependencies?: string[]; } export interface CompileResult extends Omit { diff --git a/packages/astro/src/core/compile/style.ts b/packages/astro/src/core/compile/style.ts index b5a13f0bf869..63798389f401 100644 --- a/packages/astro/src/core/compile/style.ts +++ b/packages/astro/src/core/compile/style.ts @@ -1,6 +1,6 @@ import type { TransformOptions } from '@astrojs/compiler'; import fs from 'node:fs'; -import { preprocessCSS, type ResolvedConfig } from 'vite'; +import { normalizePath, preprocessCSS, type ResolvedConfig } from 'vite'; import { AstroErrorData, CSSError, positionAt } from '../errors/index.js'; import type { CompileCssResult } from './compile.js'; @@ -23,7 +23,10 @@ export function createStylePreprocessor({ const id = `${filename}?astro&type=style&index=${index}&lang${lang}`; try { const result = await preprocessCSS(content, id, viteConfig); - cssDeps[index] = result.deps; + + if (result.deps) { + cssDeps[index] = [...result.deps].map((dep) => normalizePath(dep)); + } let map: string | undefined; if (result.map) { diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts index 14f35df60105..b9d0a2d85d1f 100644 --- a/packages/astro/src/vite-plugin-astro/hmr.ts +++ b/packages/astro/src/vite-plugin-astro/hmr.ts @@ -17,7 +17,7 @@ export async function handleHotUpdate( // If `ctx.file` is part of a CSS dependency of any Astro file, invalidate its `astroFileToCompileMetadata` // so the next transform will re-generate it. for (const [astroFile, compileData] of astroFileToCompileMetadata) { - const isUpdatedFileCssDep = compileData.css.some((css) => css.dependencies?.has(ctx.file)); + const isUpdatedFileCssDep = compileData.css.some((css) => css.dependencies?.includes(ctx.file)); if (isUpdatedFileCssDep) { astroFileToCompileMetadata.delete(astroFile); } From c677b824556a7c9ac3f96e56cdd64223fe36992e Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 20 Feb 2024 22:20:50 +0800 Subject: [PATCH 3/3] Update comments --- packages/astro/src/vite-plugin-astro/hmr.ts | 23 ++++++++++--------- packages/astro/src/vite-plugin-astro/index.ts | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts index b9d0a2d85d1f..3d4e40c58aa4 100644 --- a/packages/astro/src/vite-plugin-astro/hmr.ts +++ b/packages/astro/src/vite-plugin-astro/hmr.ts @@ -12,10 +12,10 @@ export async function handleHotUpdate( ctx: HmrContext, { logger, astroFileToCompileMetadata }: HandleHotUpdateOptions ) { - const compileMetadata = astroFileToCompileMetadata.get(ctx.file); - - // If `ctx.file` is part of a CSS dependency of any Astro file, invalidate its `astroFileToCompileMetadata` - // so the next transform will re-generate it. + // HANDLING 1: Invalidate compile metadata if CSS dependency updated + // + // If any `ctx.file` is part of a CSS dependency of any Astro file, invalidate its `astroFileToCompileMetadata` + // so the next transform of the Astro file or Astro script/style virtual module will re-generate it for (const [astroFile, compileData] of astroFileToCompileMetadata) { const isUpdatedFileCssDep = compileData.css.some((css) => css.dependencies?.includes(ctx.file)); if (isUpdatedFileCssDep) { @@ -23,18 +23,19 @@ export async function handleHotUpdate( } } - // Bail if not Astro file, we only handle them below - if (compileMetadata == null || !ctx.file.endsWith('.astro')) return; - - const oldCode = compileMetadata.originalCode; - const newCode = await ctx.read(); - + // HANDLING 2: Only invalidate Astro style virtual module if only style tags changed + // // If only the style code has changed, e.g. editing the `color`, then we can directly invalidate // the Astro CSS virtual modules only. The main Astro module's JS result will be the same and doesn't // need to be invalidated. + const oldCode = astroFileToCompileMetadata.get(ctx.file)?.originalCode; + if (oldCode == null) return; + const newCode = await ctx.read(); + if (isStyleOnlyChanged(oldCode, newCode)) { logger.debug('watch', 'style-only change'); - // Invalidate its `astroFileToCompileMetadata` so that its next transform will re-generate it + // Invalidate its `astroFileToCompileMetadata` so that the next transform of Astro style virtual module + // will re-generate it astroFileToCompileMetadata.delete(ctx.file); // Only return the Astro styles that have changed! return ctx.modules.filter((mod) => mod.id?.includes('astro&type=style')); diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 8fff21ff48ad..621995b6c07e 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -89,6 +89,7 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl // We try to re-compile the main Astro module (`filename`) first before retrieving the metadata again. if (!compileMetadata && server) { const code = await loadId(server.pluginContainer, filename); + // `compile` should re-set `filename` in `astroFileToCompileMetadata` if (code != null) await compile(code, filename); compileMetadata = astroFileToCompileMetadata.get(filename); }