From 402c876e7db120dd2dcb6f0907d0a38d12d0210b Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 21:01:57 +0800 Subject: [PATCH 01/12] Dedupe Astro package when resolving --- .changeset/lazy-walls-sneeze.md | 5 +++++ packages/astro/src/core/compile/compile.ts | 7 ++----- packages/astro/src/core/create-vite.ts | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 .changeset/lazy-walls-sneeze.md diff --git a/.changeset/lazy-walls-sneeze.md b/.changeset/lazy-walls-sneeze.md new file mode 100644 index 000000000000..117c163cc3ea --- /dev/null +++ b/.changeset/lazy-walls-sneeze.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Dedupe Astro package when resolving diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index a305ef5eca64..62de6e6fd6a9 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -5,8 +5,7 @@ import type { AstroConfig } from '../../@types/astro'; import { transform } from '@astrojs/compiler'; import { AggregateError, AstroError, CompilerError } from '../errors/errors.js'; import { AstroErrorData } from '../errors/index.js'; -import { prependForwardSlash } from '../path.js'; -import { resolvePath, viteID } from '../util.js'; +import { resolvePath } from '../util.js'; import { createStylePreprocessor } from './style.js'; type CompilationCache = Map; @@ -42,9 +41,7 @@ async function compile({ site: astroConfig.site?.toString(), sourcefile: filename, sourcemap: 'both', - internalURL: `/@fs${prependForwardSlash( - viteID(new URL('../../runtime/server/index.js', import.meta.url)) - )}`, + internalURL: 'astro/server/index.js', // TODO: baseline flag experimentalStaticExtraction: true, preprocessStyle: createStylePreprocessor({ diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 0b075e21a0bb..4339563c46ae 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -151,6 +151,8 @@ export async function createVite( }, ], conditions: ['astro'], + // Astro imports in third-party packages should use the same version as root + dedupe: ['astro'], }, ssr: { noExternal: [ From d74f39b2ae9edcefdf200fae84c1e2a8a80bd432 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 21:56:54 +0800 Subject: [PATCH 02/12] Improve Astro cache loading --- packages/astro/src/core/compile/compile.ts | 9 +++-- packages/astro/src/core/compile/index.ts | 7 +++- packages/astro/src/vite-plugin-astro/index.ts | 40 ++++++------------- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 62de6e6fd6a9..db4e6c3db43b 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -119,11 +119,12 @@ export function isCached(config: AstroConfig, filename: string) { return configCache.has(config) && configCache.get(config)!.has(filename); } -export function getCachedSource(config: AstroConfig, filename: string): string | null { +export function getCachedCompileResult( + config: AstroConfig, + filename: string +): CompileResult | null { if (!isCached(config, filename)) return null; - let src = configCache.get(config)!.get(filename); - if (!src) return null; - return src.source; + return configCache.get(config)!.get(filename)!; } export function invalidateCompilation(config: AstroConfig, filename: string) { diff --git a/packages/astro/src/core/compile/index.ts b/packages/astro/src/core/compile/index.ts index 1ee6af06a069..f2b023910a85 100644 --- a/packages/astro/src/core/compile/index.ts +++ b/packages/astro/src/core/compile/index.ts @@ -1,3 +1,8 @@ export type { CompileProps } from './compile'; -export { cachedCompilation, getCachedSource, invalidateCompilation, isCached } from './compile.js'; +export { + cachedCompilation, + getCachedCompileResult, + invalidateCompilation, + isCached, +} from './compile.js'; export type { TransformStyle } from './types'; diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 59aea73a0bc9..d37a520d2020 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -7,7 +7,7 @@ import type { PluginMetadata as AstroPluginMetadata } from './types'; import esbuild from 'esbuild'; import slash from 'slash'; import { fileURLToPath } from 'url'; -import { cachedCompilation, CompileProps, getCachedSource } from '../core/compile/index.js'; +import { cachedCompilation, CompileProps, getCachedCompileResult } from '../core/compile/index.js'; import { isRelativePath, prependForwardSlash, @@ -103,35 +103,22 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P if (!query.astro) { return null; } - let filename = parsedId.filename; - // For CSS / hoisted scripts we need to load the source ourselves. - // It should be in the compilation cache at this point. - let raw = await this.resolve(filename, undefined); - if (!raw) { - return null; - } - - let source = getCachedSource(config, raw.id); - if (!source) { + // For CSS / hoisted scripts, the main Astro module should already be cached + const filename = normalizeFilename(parsedId.filename, config); + const compileResult = getCachedCompileResult(config, filename); + if (!compileResult) { return null; } - - const compileProps: CompileProps = { - astroConfig: config, - viteConfig: resolvedConfig, - filename, - source, - }; - switch (query.type) { case 'style': { if (typeof query.index === 'undefined') { throw new Error(`Requests for Astro CSS must include an index.`); } - const transformResult = await cachedCompilation(compileProps); - const csses = transformResult.css; - const code = csses[query.index]; + const code = compileResult.css[query.index]; + if (!code) { + throw new Error(`No Astro CSS at index ${query.index}`); + } return { code, @@ -153,10 +140,7 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P }; } - const transformResult = await cachedCompilation(compileProps); - const scripts = transformResult.scripts; - const hoistedScript = scripts[query.index]; - + const hoistedScript = compileResult.scripts[query.index]; if (!hoistedScript) { throw new Error(`No hoisted script at index ${query.index}`); } @@ -171,7 +155,7 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P } } - let result: SourceDescription & { meta: any } = { + const result: SourceDescription = { code: '', meta: { vite: { @@ -182,7 +166,7 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P switch (hoistedScript.type) { case 'inline': { - let { code, map } = hoistedScript; + const { code, map } = hoistedScript; result.code = appendSourceMap(code, map); break; } From 58cd5550d113345c6a09ad29d8a1f44a50bd1d9b Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 22:03:20 +0800 Subject: [PATCH 03/12] Move cache to separate file --- packages/astro/src/core/compile/cache.ts | 42 +++++++++++++++++ packages/astro/src/core/compile/compile.ts | 55 ++++------------------ packages/astro/src/core/compile/index.ts | 4 +- 3 files changed, 52 insertions(+), 49 deletions(-) create mode 100644 packages/astro/src/core/compile/cache.ts diff --git a/packages/astro/src/core/compile/cache.ts b/packages/astro/src/core/compile/cache.ts new file mode 100644 index 000000000000..1a4bd778538b --- /dev/null +++ b/packages/astro/src/core/compile/cache.ts @@ -0,0 +1,42 @@ +import type { AstroConfig } from '../../@types/astro'; +import { compile, CompileProps, CompileResult } from './compile'; + +type CompilationCache = Map; + +const configCache = new WeakMap(); + +export function isCached(config: AstroConfig, filename: string) { + return configCache.has(config) && configCache.get(config)!.has(filename); +} + +export function getCachedCompileResult( + config: AstroConfig, + filename: string +): CompileResult | null { + if (!isCached(config, filename)) return null; + return configCache.get(config)!.get(filename)!; +} + +export function invalidateCompilation(config: AstroConfig, filename: string) { + if (configCache.has(config)) { + const cache = configCache.get(config)!; + cache.delete(filename); + } +} + +export async function cachedCompilation(props: CompileProps): Promise { + const { astroConfig, filename } = props; + let cache: CompilationCache; + if (!configCache.has(astroConfig)) { + cache = new Map(); + configCache.set(astroConfig, cache); + } else { + cache = configCache.get(astroConfig)!; + } + if (cache.has(filename)) { + return cache.get(filename)!; + } + const compileResult = await compile(props); + cache.set(filename, compileResult); + return compileResult; +} diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index db4e6c3db43b..2174b1b04da8 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -8,14 +8,6 @@ import { AstroErrorData } from '../errors/index.js'; import { resolvePath } from '../util.js'; import { createStylePreprocessor } from './style.js'; -type CompilationCache = Map; -type CompileResult = TransformResult & { - cssDeps: Set; - source: string; -}; - -const configCache = new WeakMap(); - export interface CompileProps { astroConfig: AstroConfig; viteConfig: ResolvedConfig; @@ -23,14 +15,19 @@ export interface CompileProps { source: string; } -async function compile({ +export interface CompileResult extends TransformResult { + cssDeps: Set; + source: string; +} + +export async function compile({ astroConfig, viteConfig, filename, source, }: CompileProps): Promise { - let cssDeps = new Set(); - let cssTransformErrors: AstroError[] = []; + const cssDeps = new Set(); + const cssTransformErrors: AstroError[] = []; // Transform from `.astro` to valid `.ts` // use `sourcemap: "both"` so that sourcemap is included in the code @@ -114,39 +111,3 @@ async function compile({ return compileResult; } - -export function isCached(config: AstroConfig, filename: string) { - return configCache.has(config) && configCache.get(config)!.has(filename); -} - -export function getCachedCompileResult( - config: AstroConfig, - filename: string -): CompileResult | null { - if (!isCached(config, filename)) return null; - return configCache.get(config)!.get(filename)!; -} - -export function invalidateCompilation(config: AstroConfig, filename: string) { - if (configCache.has(config)) { - const cache = configCache.get(config)!; - cache.delete(filename); - } -} - -export async function cachedCompilation(props: CompileProps): Promise { - const { astroConfig, filename } = props; - let cache: CompilationCache; - if (!configCache.has(astroConfig)) { - cache = new Map(); - configCache.set(astroConfig, cache); - } else { - cache = configCache.get(astroConfig)!; - } - if (cache.has(filename)) { - return cache.get(filename)!; - } - const compileResult = await compile(props); - cache.set(filename, compileResult); - return compileResult; -} diff --git a/packages/astro/src/core/compile/index.ts b/packages/astro/src/core/compile/index.ts index f2b023910a85..5b7dac0d9f02 100644 --- a/packages/astro/src/core/compile/index.ts +++ b/packages/astro/src/core/compile/index.ts @@ -1,8 +1,8 @@ -export type { CompileProps } from './compile'; export { cachedCompilation, getCachedCompileResult, invalidateCompilation, isCached, -} from './compile.js'; +} from './cache.js'; +export type { CompileProps } from './compile'; export type { TransformStyle } from './types'; From 1f6337490857f63b0cc9598143ae88271c8c6a50 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 22:08:31 +0800 Subject: [PATCH 04/12] Handle errors as a separate function --- packages/astro/src/core/compile/compile.ts | 140 +++++++++++---------- 1 file changed, 71 insertions(+), 69 deletions(-) diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 2174b1b04da8..bd02035d3aa3 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -28,77 +28,45 @@ export async function compile({ }: CompileProps): Promise { const cssDeps = new Set(); const cssTransformErrors: AstroError[] = []; + let transformResult: TransformResult; - // Transform from `.astro` to valid `.ts` - // use `sourcemap: "both"` so that sourcemap is included in the code - // result passed to esbuild, but also available in the catch handler. - const transformResult = await transform(source, { - pathname: filename, - projectRoot: astroConfig.root.toString(), - site: astroConfig.site?.toString(), - sourcefile: filename, - sourcemap: 'both', - internalURL: 'astro/server/index.js', - // TODO: baseline flag - experimentalStaticExtraction: true, - preprocessStyle: createStylePreprocessor({ - filename, - viteConfig, - cssDeps, - cssTransformErrors, - }), - async resolvePath(specifier) { - return resolvePath(specifier, filename); - }, - }) - .catch((err: Error) => { - // The compiler should be able to handle errors by itself, however - // for the rare cases where it can't let's directly throw here with as much info as possible - throw new CompilerError({ - ...AstroErrorData.UnknownCompilerError, - message: err.message ?? 'Unknown compiler error', - stack: err.stack, - location: { - file: filename, - }, - }); - }) - .then((result) => { - const compilerError = result.diagnostics.find((diag) => diag.severity === 1); - - if (compilerError) { - throw new CompilerError({ - code: compilerError.code, - message: compilerError.text, - location: { - line: compilerError.location.line, - column: compilerError.location.column, - file: compilerError.location.file, - }, - hint: compilerError.hint, - }); - } - - switch (cssTransformErrors.length) { - case 0: - return result; - case 1: { - let error = cssTransformErrors[0]; - if (!error.errorCode) { - error.errorCode = AstroErrorData.UnknownCSSError.code; - } - - throw cssTransformErrors[0]; - } - default: { - throw new AggregateError({ - ...cssTransformErrors[0], - code: cssTransformErrors[0].errorCode, - errors: cssTransformErrors, - }); - } - } + try { + // Transform from `.astro` to valid `.ts` + // use `sourcemap: "both"` so that sourcemap is included in the code + // result passed to esbuild, but also available in the catch handler. + transformResult = await transform(source, { + pathname: filename, + projectRoot: astroConfig.root.toString(), + site: astroConfig.site?.toString(), + sourcefile: filename, + sourcemap: 'both', + internalURL: 'astro/server/index.js', + // TODO: baseline flag + experimentalStaticExtraction: true, + preprocessStyle: createStylePreprocessor({ + filename, + viteConfig, + cssDeps, + cssTransformErrors, + }), + async resolvePath(specifier) { + return resolvePath(specifier, filename); + }, + }); + } catch (err: any) { + // The compiler should be able to handle errors by itself, however + // for the rare cases where it can't let's directly throw here with as much info as possible + throw new CompilerError({ + ...AstroErrorData.UnknownCompilerError, + message: err.message ?? 'Unknown compiler error', + stack: err.stack, + location: { + file: filename, + }, }); + } + + handleCompileResultErrors(transformResult, cssTransformErrors); const compileResult: CompileResult = Object.create(transformResult, { cssDeps: { @@ -111,3 +79,37 @@ export async function compile({ return compileResult; } + +function handleCompileResultErrors(result: TransformResult, cssTransformErrors: AstroError[]) { + const compilerError = result.diagnostics.find((diag) => diag.severity === 1); + + if (compilerError) { + throw new CompilerError({ + code: compilerError.code, + message: compilerError.text, + location: { + line: compilerError.location.line, + column: compilerError.location.column, + file: compilerError.location.file, + }, + hint: compilerError.hint, + }); + } + + switch (cssTransformErrors.length) { + case 1: { + const error = cssTransformErrors[0]; + if (!error.errorCode) { + error.errorCode = AstroErrorData.UnknownCSSError.code; + } + throw cssTransformErrors[0]; + } + default: { + throw new AggregateError({ + ...cssTransformErrors[0], + code: cssTransformErrors[0].errorCode, + errors: cssTransformErrors, + }); + } + } +} From 8b2ba4550a01275a9a7084befe1263883272230b Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 22:36:54 +0800 Subject: [PATCH 05/12] Extract main cached compilation flow --- .../astro/src/vite-plugin-astro/compile.ts | 44 +++++++++++++++++++ packages/astro/src/vite-plugin-astro/index.ts | 37 +++------------- 2 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 packages/astro/src/vite-plugin-astro/compile.ts diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts new file mode 100644 index 000000000000..4973d044646a --- /dev/null +++ b/packages/astro/src/vite-plugin-astro/compile.ts @@ -0,0 +1,44 @@ +import { ESBuildTransformResult, transformWithEsbuild } from 'vite'; +import { cachedCompilation, CompileProps } from '../core/compile'; +import { CompileResult } from '../core/compile/compile'; +import { getFileInfo } from '../vite-plugin-utils'; + +interface FullCompileResult extends Omit { + map: ESBuildTransformResult['map']; +} + +export async function cachedFullCompilation( + compileProps: CompileProps, + rawId: string +): Promise { + const transformResult = await cachedCompilation(compileProps); + const { fileId: file, fileUrl: url } = getFileInfo(rawId, compileProps.astroConfig); + + // Compile all TypeScript to JavaScript. + // Also, catches invalid JS/TS in the compiled output before returning. + const { code, map } = await transformWithEsbuild(transformResult.code, rawId, { + loader: 'ts', + sourcemap: 'external', + }); + + let SUFFIX = ''; + SUFFIX += `\nconst $$file = ${JSON.stringify(file)};\nconst $$url = ${JSON.stringify( + url + )};export { $$file as file, $$url as url };\n`; + + // Add HMR handling in dev mode. + if (!compileProps.viteConfig.isProduction) { + let i = 0; + while (i < transformResult.scripts.length) { + SUFFIX += `import "${rawId}?astro&type=script&index=${i}&lang.ts";`; + i++; + } + } + + // Prefer live reload to HMR in `.astro` files + if (!compileProps.viteConfig.isProduction) { + SUFFIX += `\nif (import.meta.hot) { import.meta.hot.decline() }`; + } + + return { ...transformResult, code, map }; +} diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index d37a520d2020..5f357d8ae7db 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -18,6 +18,8 @@ import { viteID } from '../core/util.js'; import { getFileInfo, normalizeFilename } from '../vite-plugin-utils/index.js'; import { handleHotUpdate } from './hmr.js'; import { parseAstroRequest, ParsedRequestResult } from './query.js'; +import { CompileResult } from '../core/compile/compile'; +import { cachedFullCompilation } from './compile'; const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms; interface AstroPluginOptions { @@ -203,41 +205,12 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P }; try { - const transformResult = await cachedCompilation(compileProps); - const { fileId: file, fileUrl: url } = getFileInfo(id, config); + const transformResult = await cachedFullCompilation(compileProps, id); for (const dep of transformResult.cssDeps) { this.addWatchFile(dep); } - // Compile all TypeScript to JavaScript. - // Also, catches invalid JS/TS in the compiled output before returning. - const { code, map } = await esbuild.transform(transformResult.code, { - loader: 'ts', - sourcemap: 'external', - sourcefile: id, - // Pass relevant Vite options, if needed: - define: config.vite?.define, - }); - - let SUFFIX = ''; - SUFFIX += `\nconst $$file = ${JSON.stringify(file)};\nconst $$url = ${JSON.stringify( - url - )};export { $$file as file, $$url as url };\n`; - // Add HMR handling in dev mode. - if (!resolvedConfig.isProduction) { - let i = 0; - while (i < transformResult.scripts.length) { - SUFFIX += `import "${id}?astro&type=script&index=${i}&lang.ts";`; - i++; - } - } - - // Prefer live reload to HMR in `.astro` files - if (!resolvedConfig.isProduction) { - SUFFIX += `\nif (import.meta.hot) { import.meta.hot.decline() }`; - } - const astroMetadata: AstroPluginMetadata['astro'] = { clientOnlyComponents: transformResult.clientOnlyComponents, hydratedComponents: transformResult.hydratedComponents, @@ -245,8 +218,8 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P }; return { - code: `${code}${SUFFIX}`, - map, + code: transformResult.code, + map: transformResult.map, meta: { astro: astroMetadata, vite: { From 6eae3b427763b34f45795f2a1ad2c407257fee87 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 22:54:32 +0800 Subject: [PATCH 06/12] Refactor error handling --- .../astro/src/vite-plugin-astro/compile.ts | 128 ++++++++++++++++-- packages/astro/src/vite-plugin-astro/index.ts | 108 ++++----------- 2 files changed, 140 insertions(+), 96 deletions(-) diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts index 4973d044646a..a2b433dc3881 100644 --- a/packages/astro/src/vite-plugin-astro/compile.ts +++ b/packages/astro/src/vite-plugin-astro/compile.ts @@ -1,25 +1,59 @@ +import { fileURLToPath } from 'url'; import { ESBuildTransformResult, transformWithEsbuild } from 'vite'; +import { AstroConfig } from '../@types/astro'; import { cachedCompilation, CompileProps } from '../core/compile'; import { CompileResult } from '../core/compile/compile'; +import { LogOptions } from '../core/logger/core'; import { getFileInfo } from '../vite-plugin-utils'; +interface CachedFullCompilation { + compileProps: CompileProps; + rawId: string; + logging: LogOptions; +} + interface FullCompileResult extends Omit { map: ESBuildTransformResult['map']; } -export async function cachedFullCompilation( - compileProps: CompileProps, - rawId: string -): Promise { - const transformResult = await cachedCompilation(compileProps); - const { fileId: file, fileUrl: url } = getFileInfo(rawId, compileProps.astroConfig); +interface EnhanceCompilerErrorOptions { + err: Error; + id: string; + source: string; + config: AstroConfig; + logging: LogOptions; +} + +const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms; - // Compile all TypeScript to JavaScript. - // Also, catches invalid JS/TS in the compiled output before returning. - const { code, map } = await transformWithEsbuild(transformResult.code, rawId, { - loader: 'ts', - sourcemap: 'external', - }); +export async function cachedFullCompilation({ + compileProps, + rawId, + logging, +}: CachedFullCompilation): Promise { + let transformResult: CompileResult; + let esbuildResult: ESBuildTransformResult; + + try { + transformResult = await cachedCompilation(compileProps); + // Compile all TypeScript to JavaScript. + // Also, catches invalid JS/TS in the compiled output before returning. + esbuildResult = await transformWithEsbuild(transformResult.code, rawId, { + loader: 'ts', + sourcemap: 'external', + }); + } catch (err: any) { + await enhanceCompileError({ + err, + id: rawId, + source: compileProps.source, + config: compileProps.astroConfig, + logging: logging, + }); + throw err; + } + + const { fileId: file, fileUrl: url } = getFileInfo(rawId, compileProps.astroConfig); let SUFFIX = ''; SUFFIX += `\nconst $$file = ${JSON.stringify(file)};\nconst $$url = ${JSON.stringify( @@ -40,5 +74,73 @@ export async function cachedFullCompilation( SUFFIX += `\nif (import.meta.hot) { import.meta.hot.decline() }`; } - return { ...transformResult, code, map }; + return { + ...transformResult, + code: esbuildResult.code, + map: esbuildResult.map, + }; +} + +async function enhanceCompileError({ + err, + id, + source, + config, + logging, +}: EnhanceCompilerErrorOptions): Promise { + // Verify frontmatter: a common reason that this plugin fails is that + // the user provided invalid JS/TS in the component frontmatter. + // If the frontmatter is invalid, the `err` object may be a compiler + // panic or some other vague/confusing compiled error message. + // + // Before throwing, it is better to verify the frontmatter here, and + // let esbuild throw a more specific exception if the code is invalid. + // If frontmatter is valid or cannot be parsed, then continue. + const scannedFrontmatter = FRONTMATTER_PARSE_REGEXP.exec(source); + if (scannedFrontmatter) { + try { + await transformWithEsbuild(scannedFrontmatter[1], id, { + loader: 'ts', + sourcemap: false, + }); + } catch (frontmatterErr: any) { + // Improve the error by replacing the phrase "unexpected end of file" + // with "unexpected end of frontmatter" in the esbuild error message. + if (frontmatterErr && frontmatterErr.message) { + frontmatterErr.message = frontmatterErr.message.replace( + 'end of file', + 'end of frontmatter' + ); + } + throw frontmatterErr; + } + } + + // improve compiler errors + if (err.stack && err.stack.includes('wasm-function')) { + const search = new URLSearchParams({ + labels: 'compiler', + title: '🐛 BUG: `@astrojs/compiler` panic', + template: '---01-bug-report.yml', + 'bug-description': `\`@astrojs/compiler\` encountered an unrecoverable error when compiling the following file. + +**${id.replace(fileURLToPath(config.root), '')}** +\`\`\`astro +${source} +\`\`\``, + }); + (err as any).url = `https://github.com/withastro/astro/issues/new?${search.toString()}`; + err.message = `Error: Uh oh, the Astro compiler encountered an unrecoverable error! + + Please open + a GitHub issue using the link below: + ${(err as any).url}`; + + if (logging.level !== 'debug') { + // TODO: remove stack replacement when compiler throws better errors + err.stack = ` at ${id}`; + } + } + + throw err; } diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 5f357d8ae7db..05bd16c51cb1 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -4,7 +4,6 @@ import type { AstroSettings } from '../@types/astro'; import type { LogOptions } from '../core/logger/core.js'; import type { PluginMetadata as AstroPluginMetadata } from './types'; -import esbuild from 'esbuild'; import slash from 'slash'; import { fileURLToPath } from 'url'; import { cachedCompilation, CompileProps, getCachedCompileResult } from '../core/compile/index.js'; @@ -15,13 +14,11 @@ import { startsWithForwardSlash, } from '../core/path.js'; import { viteID } from '../core/util.js'; -import { getFileInfo, normalizeFilename } from '../vite-plugin-utils/index.js'; +import { normalizeFilename } from '../vite-plugin-utils/index.js'; import { handleHotUpdate } from './hmr.js'; import { parseAstroRequest, ParsedRequestResult } from './query.js'; -import { CompileResult } from '../core/compile/compile'; import { cachedFullCompilation } from './compile'; -const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms; interface AstroPluginOptions { settings: AstroSettings; logging: LogOptions; @@ -204,89 +201,34 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P source, }; - try { - const transformResult = await cachedFullCompilation(compileProps, id); + const transformResult = await cachedFullCompilation({ + compileProps, + rawId: id, + logging, + }); - for (const dep of transformResult.cssDeps) { - this.addWatchFile(dep); - } + for (const dep of transformResult.cssDeps) { + this.addWatchFile(dep); + } - const astroMetadata: AstroPluginMetadata['astro'] = { - clientOnlyComponents: transformResult.clientOnlyComponents, - hydratedComponents: transformResult.hydratedComponents, - scripts: transformResult.scripts, - }; + const astroMetadata: AstroPluginMetadata['astro'] = { + clientOnlyComponents: transformResult.clientOnlyComponents, + hydratedComponents: transformResult.hydratedComponents, + scripts: transformResult.scripts, + }; - return { - code: transformResult.code, - map: transformResult.map, - meta: { - astro: astroMetadata, - vite: { - // Setting this vite metadata to `ts` causes Vite to resolve .js - // extensions to .ts files. - lang: 'ts', - }, + return { + code: transformResult.code, + map: transformResult.map, + meta: { + astro: astroMetadata, + vite: { + // Setting this vite metadata to `ts` causes Vite to resolve .js + // extensions to .ts files. + lang: 'ts', }, - }; - } catch (err: any) { - // Verify frontmatter: a common reason that this plugin fails is that - // the user provided invalid JS/TS in the component frontmatter. - // If the frontmatter is invalid, the `err` object may be a compiler - // panic or some other vague/confusing compiled error message. - // - // Before throwing, it is better to verify the frontmatter here, and - // let esbuild throw a more specific exception if the code is invalid. - // If frontmatter is valid or cannot be parsed, then continue. - const scannedFrontmatter = FRONTMATTER_PARSE_REGEXP.exec(source); - if (scannedFrontmatter) { - try { - await esbuild.transform(scannedFrontmatter[1], { - loader: 'ts', - sourcemap: false, - sourcefile: id, - }); - } catch (frontmatterErr: any) { - // Improve the error by replacing the phrase "unexpected end of file" - // with "unexpected end of frontmatter" in the esbuild error message. - if (frontmatterErr && frontmatterErr.message) { - frontmatterErr.message = frontmatterErr.message.replace( - 'end of file', - 'end of frontmatter' - ); - } - throw frontmatterErr; - } - } - - // improve compiler errors - if (err.stack && err.stack.includes('wasm-function')) { - const search = new URLSearchParams({ - labels: 'compiler', - title: '🐛 BUG: `@astrojs/compiler` panic', - template: '---01-bug-report.yml', - 'bug-description': `\`@astrojs/compiler\` encountered an unrecoverable error when compiling the following file. - -**${id.replace(fileURLToPath(config.root), '')}** -\`\`\`astro -${source} -\`\`\``, - }); - err.url = `https://github.com/withastro/astro/issues/new?${search.toString()}`; - err.message = `Error: Uh oh, the Astro compiler encountered an unrecoverable error! - - Please open - a GitHub issue using the link below: - ${err.url}`; - - if (logging.level !== 'debug') { - // TODO: remove stack replacement when compiler throws better errors - err.stack = ` at ${id}`; - } - } - - throw err; - } + }, + }; }, async handleHotUpdate(context) { if (context.server.config.isProduction) return; From 0bc8d0e237e456f066e3fa35b62ebb2e938520b5 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 23:51:27 +0800 Subject: [PATCH 07/12] Fix missing paths --- packages/astro/src/core/compile/cache.ts | 2 +- packages/astro/src/core/compile/compile.ts | 17 +++++++---------- packages/astro/src/core/compile/index.ts | 2 +- packages/astro/src/vite-plugin-astro/compile.ts | 11 ++++++----- packages/astro/src/vite-plugin-astro/index.ts | 2 +- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/astro/src/core/compile/cache.ts b/packages/astro/src/core/compile/cache.ts index 1a4bd778538b..01de66af6f08 100644 --- a/packages/astro/src/core/compile/cache.ts +++ b/packages/astro/src/core/compile/cache.ts @@ -1,5 +1,5 @@ import type { AstroConfig } from '../../@types/astro'; -import { compile, CompileProps, CompileResult } from './compile'; +import { compile, CompileProps, CompileResult } from './compile.js'; type CompilationCache = Map; diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index bd02035d3aa3..9936fc5e36e9 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -68,16 +68,11 @@ export async function compile({ handleCompileResultErrors(transformResult, cssTransformErrors); - const compileResult: CompileResult = Object.create(transformResult, { - cssDeps: { - value: cssDeps, - }, - source: { - value: source, - }, - }); - - return compileResult; + return { + ...transformResult, + cssDeps, + source, + }; } function handleCompileResultErrors(result: TransformResult, cssTransformErrors: AstroError[]) { @@ -97,6 +92,8 @@ function handleCompileResultErrors(result: TransformResult, cssTransformErrors: } switch (cssTransformErrors.length) { + case 0: + break; case 1: { const error = cssTransformErrors[0]; if (!error.errorCode) { diff --git a/packages/astro/src/core/compile/index.ts b/packages/astro/src/core/compile/index.ts index 5b7dac0d9f02..6851c7c5ecbc 100644 --- a/packages/astro/src/core/compile/index.ts +++ b/packages/astro/src/core/compile/index.ts @@ -4,5 +4,5 @@ export { invalidateCompilation, isCached, } from './cache.js'; -export type { CompileProps } from './compile'; +export type { CompileProps, CompileResult } from './compile'; export type { TransformStyle } from './types'; diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts index a2b433dc3881..f9e581e841fd 100644 --- a/packages/astro/src/vite-plugin-astro/compile.ts +++ b/packages/astro/src/vite-plugin-astro/compile.ts @@ -1,10 +1,9 @@ import { fileURLToPath } from 'url'; import { ESBuildTransformResult, transformWithEsbuild } from 'vite'; import { AstroConfig } from '../@types/astro'; -import { cachedCompilation, CompileProps } from '../core/compile'; -import { CompileResult } from '../core/compile/compile'; -import { LogOptions } from '../core/logger/core'; -import { getFileInfo } from '../vite-plugin-utils'; +import { cachedCompilation, CompileProps, CompileResult } from '../core/compile/index.js'; +import { LogOptions } from '../core/logger/core.js'; +import { getFileInfo } from '../vite-plugin-utils/index.js'; interface CachedFullCompilation { compileProps: CompileProps; @@ -40,6 +39,7 @@ export async function cachedFullCompilation({ // Also, catches invalid JS/TS in the compiled output before returning. esbuildResult = await transformWithEsbuild(transformResult.code, rawId, { loader: 'ts', + target: 'esnext', sourcemap: 'external', }); } catch (err: any) { @@ -76,7 +76,7 @@ export async function cachedFullCompilation({ return { ...transformResult, - code: esbuildResult.code, + code: esbuildResult.code + SUFFIX, map: esbuildResult.map, }; } @@ -101,6 +101,7 @@ async function enhanceCompileError({ try { await transformWithEsbuild(scannedFrontmatter[1], id, { loader: 'ts', + target: 'esnext', sourcemap: false, }); } catch (frontmatterErr: any) { diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 05bd16c51cb1..f5c5e34f533e 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -17,7 +17,7 @@ import { viteID } from '../core/util.js'; import { normalizeFilename } from '../vite-plugin-utils/index.js'; import { handleHotUpdate } from './hmr.js'; import { parseAstroRequest, ParsedRequestResult } from './query.js'; -import { cachedFullCompilation } from './compile'; +import { cachedFullCompilation } from './compile.js'; interface AstroPluginOptions { settings: AstroSettings; From 94c62a9c3d5637b8e26214b2a206290487f0dead Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 1 Dec 2022 23:51:39 +0800 Subject: [PATCH 08/12] Add changeset --- .changeset/seven-seahorses-talk.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/seven-seahorses-talk.md diff --git a/.changeset/seven-seahorses-talk.md b/.changeset/seven-seahorses-talk.md new file mode 100644 index 000000000000..fc130f8b07f3 --- /dev/null +++ b/.changeset/seven-seahorses-talk.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Refactor Astro compile flow From a09f0d9de8a1ed6f11f2e23e15a28eb4f45b143c Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 2 Dec 2022 17:18:32 +0800 Subject: [PATCH 09/12] Try fix windows --- packages/astro/src/vite-plugin-utils/index.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/astro/src/vite-plugin-utils/index.ts b/packages/astro/src/vite-plugin-utils/index.ts index bea28877e92b..7a3fb164f7c3 100644 --- a/packages/astro/src/vite-plugin-utils/index.ts +++ b/packages/astro/src/vite-plugin-utils/index.ts @@ -1,7 +1,13 @@ import ancestor from 'common-ancestor-path'; +import path from 'path'; +import { fileURLToPath } from 'url'; import { Data } from 'vfile'; import type { AstroConfig, MarkdownAstroData } from '../@types/astro'; -import { appendExtension, appendForwardSlash } from '../core/path.js'; +import { + appendExtension, + appendForwardSlash, + removeLeadingForwardSlashWindows, +} from '../core/path.js'; export function getFileInfo(id: string, config: AstroConfig) { const sitePathname = appendForwardSlash( @@ -60,9 +66,9 @@ export function safelyGetAstroData(vfileData: Data): MarkdownAstroData { */ export function normalizeFilename(filename: string, config: AstroConfig) { if (filename.startsWith('/@fs')) { - filename = filename.slice('/@fs'.length); + filename = removeLeadingForwardSlashWindows(filename.slice('/@fs'.length)); } else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) { - filename = new URL('.' + filename, config.root).pathname; + filename = path.join(fileURLToPath(config.root), filename); } return filename; } From d1e176f479dfbec903163472830c8cb70e077209 Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 2 Dec 2022 20:18:05 +0800 Subject: [PATCH 10/12] Revert raw resolve for now --- packages/astro/src/vite-plugin-astro/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index f5c5e34f533e..f3e0c48c3298 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -102,12 +102,18 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P if (!query.astro) { return null; } + + const raw = await this.resolve(parsedId.filename, undefined); + if (!raw) { + return null; + } + // For CSS / hoisted scripts, the main Astro module should already be cached - const filename = normalizeFilename(parsedId.filename, config); - const compileResult = getCachedCompileResult(config, filename); + const compileResult = getCachedCompileResult(config, raw.id); if (!compileResult) { return null; } + switch (query.type) { case 'style': { if (typeof query.index === 'undefined') { From b9d496a50f93d4ef002543ea8703ced6e9e7c7c2 Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 2 Dec 2022 20:20:50 +0800 Subject: [PATCH 11/12] Fix typo --- packages/astro/src/vite-plugin-astro/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index f3e0c48c3298..6b9d5c6de579 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -103,7 +103,8 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P return null; } - const raw = await this.resolve(parsedId.filename, undefined); + const filename = parsedId.filename; + const raw = await this.resolve(filename, undefined); if (!raw) { return null; } From 6282c33a11ccd8f5610963b546e5ceb016b4b14e Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 2 Dec 2022 22:51:54 +0800 Subject: [PATCH 12/12] I hate windows --- packages/astro/src/vite-plugin-astro/index.ts | 11 ++--------- packages/astro/src/vite-plugin-utils/index.ts | 8 ++++---- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 6b9d5c6de579..f5c5e34f533e 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -102,19 +102,12 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P if (!query.astro) { return null; } - - const filename = parsedId.filename; - const raw = await this.resolve(filename, undefined); - if (!raw) { - return null; - } - // For CSS / hoisted scripts, the main Astro module should already be cached - const compileResult = getCachedCompileResult(config, raw.id); + const filename = normalizeFilename(parsedId.filename, config); + const compileResult = getCachedCompileResult(config, filename); if (!compileResult) { return null; } - switch (query.type) { case 'style': { if (typeof query.index === 'undefined') { diff --git a/packages/astro/src/vite-plugin-utils/index.ts b/packages/astro/src/vite-plugin-utils/index.ts index 7a3fb164f7c3..82de4c097893 100644 --- a/packages/astro/src/vite-plugin-utils/index.ts +++ b/packages/astro/src/vite-plugin-utils/index.ts @@ -62,13 +62,13 @@ export function safelyGetAstroData(vfileData: Data): MarkdownAstroData { * - /@fs/home/user/project/src/pages/index.astro * - /src/pages/index.astro * - * as absolute file paths. + * as absolute file paths with forward slashes. */ export function normalizeFilename(filename: string, config: AstroConfig) { if (filename.startsWith('/@fs')) { - filename = removeLeadingForwardSlashWindows(filename.slice('/@fs'.length)); + filename = filename.slice('/@fs'.length); } else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) { - filename = path.join(fileURLToPath(config.root), filename); + filename = new URL('.' + filename, config.root).pathname; } - return filename; + return removeLeadingForwardSlashWindows(filename); }