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

Refactor Astro plugin and compile flow #5506

Merged
merged 14 commits into from Dec 5, 2022
5 changes: 5 additions & 0 deletions .changeset/lazy-walls-sneeze.md
@@ -0,0 +1,5 @@
---
'astro': patch
---

Dedupe Astro package when resolving
5 changes: 5 additions & 0 deletions .changeset/seven-seahorses-talk.md
@@ -0,0 +1,5 @@
---
'astro': patch
---

Refactor Astro compile flow
42 changes: 42 additions & 0 deletions packages/astro/src/core/compile/cache.ts
@@ -0,0 +1,42 @@
import type { AstroConfig } from '../../@types/astro';
import { compile, CompileProps, CompileResult } from './compile.js';

type CompilationCache = Map<string, CompileResult>;

const configCache = new WeakMap<AstroConfig, CompilationCache>();

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<CompileResult> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like that the caching part is split into a separate module now!

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;
}
202 changes: 80 additions & 122 deletions packages/astro/src/core/compile/compile.ts
Expand Up @@ -5,150 +5,108 @@ 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<string, CompileResult>;
type CompileResult = TransformResult & {
cssDeps: Set<string>;
source: string;
};

const configCache = new WeakMap<AstroConfig, CompilationCache>();

export interface CompileProps {
astroConfig: AstroConfig;
viteConfig: ResolvedConfig;
filename: string;
source: string;
}

async function compile({
export interface CompileResult extends TransformResult {
cssDeps: Set<string>;
source: string;
}

export async function compile({
astroConfig,
viteConfig,
filename,
source,
}: CompileProps): Promise<CompileResult> {
let cssDeps = new Set<string>();
let cssTransformErrors: AstroError[] = [];

// 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: `/@fs${prependForwardSlash(
viteID(new URL('../../runtime/server/index.js', import.meta.url))
)}`,
// 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,
});
}
const cssDeps = new Set<string>();
const cssTransformErrors: AstroError[] = [];
let transformResult: TransformResult;

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,
},
});
}

const compileResult: CompileResult = Object.create(transformResult, {
cssDeps: {
value: cssDeps,
},
source: {
value: source,
},
});

return compileResult;
}
handleCompileResultErrors(transformResult, cssTransformErrors);

export function isCached(config: AstroConfig, filename: string) {
return configCache.has(config) && configCache.get(config)!.has(filename);
return {
...transformResult,
cssDeps,
source,
};
}

export function getCachedSource(config: AstroConfig, filename: string): string | null {
if (!isCached(config, filename)) return null;
let src = configCache.get(config)!.get(filename);
if (!src) return null;
return src.source;
}
function handleCompileResultErrors(result: TransformResult, cssTransformErrors: AstroError[]) {
const compilerError = result.diagnostics.find((diag) => diag.severity === 1);

export function invalidateCompilation(config: AstroConfig, filename: string) {
if (configCache.has(config)) {
const cache = configCache.get(config)!;
cache.delete(filename);
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,
});
}
}

export async function cachedCompilation(props: CompileProps): Promise<CompileResult> {
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)!;
switch (cssTransformErrors.length) {
case 0:
break;
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,
});
}
}
const compileResult = await compile(props);
cache.set(filename, compileResult);
return compileResult;
}
9 changes: 7 additions & 2 deletions 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 './cache.js';
export type { CompileProps, CompileResult } from './compile';
export type { TransformStyle } from './types';
2 changes: 2 additions & 0 deletions packages/astro/src/core/create-vite.ts
Expand Up @@ -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: [
Expand Down