From b5e4ab318f5789abf792e31f9c56620f93d781bf Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:49:23 +0100 Subject: [PATCH 01/13] feat: use the rust compiler --- packages/astro/package.json | 2 +- packages/astro/src/core/build/index.ts | 14 +++ packages/astro/src/core/compile/compile.ts | 12 ++- packages/astro/src/core/compile/index.ts | 2 +- .../astro/src/vite-plugin-astro/compile.ts | 96 ++----------------- packages/astro/src/vite-plugin-astro/index.ts | 1 - pnpm-lock.yaml | 9 +- 7 files changed, 34 insertions(+), 102 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 795f915aca4d..1f93f13ef3aa 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -116,7 +116,7 @@ "test:integration": "astro-scripts test \"test/*.test.js\"" }, "dependencies": { - "@astrojs/compiler": "^3.0.0-beta.0", + "@astrojs/compiler": "link:../../../compiler-rust/packages/compiler", "@astrojs/internal-helpers": "workspace:*", "@astrojs/markdown-remark": "workspace:*", "@astrojs/telemetry": "workspace:*", diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 069f63651903..112babbeae3a 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -18,6 +18,7 @@ import { createNodeLogger } from '../config/logging.js'; import { createSettings } from '../config/settings.js'; import { createVite } from '../create-vite.js'; import { createKey, getEnvironmentKey, hasEnvironmentKey } from '../encryption.js'; +import { compileTimeStats } from '../compile/compile.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import type { Logger } from '../logger/core.js'; import { levels, timerMessage } from '../logger/core.js'; @@ -296,5 +297,18 @@ class AstroBuilder { logger.info('build', messages.join(' ')); logger.info('build', `${colors.bold('Complete!')}`); + + // Log compile time stats table + if (compileTimeStats.files.size > 0) { + logger.info('build', ''); + logger.info('build', colors.bold('Compile times:')); + const sortedFiles = [...compileTimeStats.files.entries()].sort((a, b) => b[1] - a[1]); + for (const [file, time] of sortedFiles) { + const basename = file.split('/').pop() ?? file; + logger.info('build', ` ${basename} ${colors.dim(`${time.toFixed(2)}ms`)}`); + } + const compileTotal = sortedFiles.reduce((sum, [, time]) => sum + time, 0); + logger.info('build', colors.bold(` Total: ${compileTotal.toFixed(2)}ms`)); + } } } diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 66b8d4b96608..2267d81fd825 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url'; import type { TransformResult } from '@astrojs/compiler'; -import { transform } from '@astrojs/compiler'; +import { transform } from '@astrojs/compiler/sync'; import type { ResolvedConfig } from 'vite'; import type { AstroConfig } from '../../types/public/config.js'; import type { AstroError } from '../errors/errors.js'; @@ -10,6 +10,10 @@ import { normalizePath, resolvePath } from '../viteUtils.js'; import { createStylePreprocessor, type PartialCompileCssResult } from './style.js'; import type { CompileCssResult } from './types.js'; +export const compileTimeStats = { + files: new Map(), +}; + export interface CompileProps { astroConfig: AstroConfig; viteConfig: ResolvedConfig; @@ -40,7 +44,8 @@ export async function compile({ // 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, { + const t0 = performance.now(); + transformResult = transform(source, { compact: astroConfig.compressHTML, filename, normalizedFilename: normalizeFilename(filename, astroConfig.root), @@ -67,6 +72,9 @@ export async function compile({ return resolvePath(specifier, filename); }, }); + const t1 = performance.now(); + const elapsed = t1 - t0; + compileTimeStats.files.set(filename, elapsed); } 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 diff --git a/packages/astro/src/core/compile/index.ts b/packages/astro/src/core/compile/index.ts index afd5eec0bad8..a9f141a2afae 100644 --- a/packages/astro/src/core/compile/index.ts +++ b/packages/astro/src/core/compile/index.ts @@ -1,2 +1,2 @@ export type { CompileProps, CompileResult } from './compile.js'; -export { compile } from './compile.js'; +export { compile, compileTimeStats } from './compile.js'; diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts index 5000b42b70d7..a2f4a8c4dd7b 100644 --- a/packages/astro/src/vite-plugin-astro/compile.ts +++ b/packages/astro/src/vite-plugin-astro/compile.ts @@ -1,63 +1,21 @@ -import { type ESBuildTransformResult, transformWithEsbuild } from 'vite'; import { type CompileProps, type CompileResult, compile } from '../core/compile/index.js'; -import type { Logger } from '../core/logger/core.js'; -import type { AstroConfig } from '../types/public/config.js'; import { getFileInfo } from '../vite-plugin-utils/index.js'; import type { CompileMetadata } from './types.js'; -import { frontmatterRE } from './utils.js'; interface CompileAstroOption { compileProps: CompileProps; astroFileToCompileMetadata: Map; - logger: Logger; } export interface CompileAstroResult extends Omit { - map: ESBuildTransformResult['map']; -} - -interface EnhanceCompilerErrorOptions { - err: Error; - id: string; - source: string; - config: AstroConfig; - logger: Logger; + map: string | undefined; } export async function compileAstro({ compileProps, astroFileToCompileMetadata, - logger, }: CompileAstroOption): Promise { - let transformResult: CompileResult; - let esbuildResult: ESBuildTransformResult; - - try { - transformResult = await compile(compileProps); - // Compile all TypeScript to JavaScript. - // Also, catches invalid JS/TS in the compiled output before returning. - esbuildResult = await transformWithEsbuild(transformResult.code, compileProps.filename, { - ...compileProps.viteConfig.esbuild, - loader: 'ts', - sourcemap: 'external', - tsconfigRaw: { - compilerOptions: { - // Ensure client:only imports are treeshaken - verbatimModuleSyntax: false, - importsNotUsedAsValues: 'remove', - }, - }, - }); - } catch (err: any) { - await enhanceCompileError({ - err, - id: compileProps.filename, - source: compileProps.source, - config: compileProps.astroConfig, - logger: logger, - }); - throw err; - } + const transformResult = await compile(compileProps); const { fileId: file, fileUrl: url } = getFileInfo( compileProps.filename, @@ -87,51 +45,9 @@ export async function compileAstro({ return { ...transformResult, - code: esbuildResult.code + SUFFIX, - map: esbuildResult.map, + code: transformResult.code + SUFFIX, + // The Rust compiler returns an empty string when source maps are not yet implemented. + // Rollup/Vite will try to JSON.parse the map, so we must return undefined instead. + map: transformResult.map || undefined, }; } - -async function enhanceCompileError({ - err, - id, - source, -}: EnhanceCompilerErrorOptions): Promise { - const lineText = (err as any).loc?.lineText; - // 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 = frontmatterRE.exec(source); - if (scannedFrontmatter) { - // Top-level return is not supported, so replace `return` with throw - const frontmatter = scannedFrontmatter[1].replace(/\breturn\b/g, 'throw'); - - // If frontmatter does not actually include the offending line, skip - if (lineText && !frontmatter.includes(lineText)) throw err; - - try { - await transformWithEsbuild(frontmatter, id, { - loader: 'ts', - target: 'esnext', - 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?.message) { - frontmatterErr.message = frontmatterErr.message.replace( - 'end of file', - 'end of frontmatter', - ); - } - throw frontmatterErr; - } - } - - throw err; -} diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 0e302c10fdd1..9ca5addd5997 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -99,7 +99,6 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl source: code, }, astroFileToCompileMetadata, - logger, }); }; }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95746f2a8c2d..9d4d9539f23b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -494,8 +494,8 @@ importers: packages/astro: dependencies: '@astrojs/compiler': - specifier: ^3.0.0-beta.0 - version: 3.0.0-beta.0 + specifier: link:../../../compiler-rust/packages/compiler + version: link:../../../compiler-rust/packages/compiler '@astrojs/internal-helpers': specifier: workspace:* version: link:../internal-helpers @@ -7104,9 +7104,6 @@ packages: '@astrojs/compiler@2.13.0': resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==} - '@astrojs/compiler@3.0.0-beta.0': - resolution: {integrity: sha512-wrWArjuyeEhoUZUr+o3Ts3CFbxB+QFYvzsnnR0wr3L0b84JDxq107mZOfmNZ/XPE4VjFzjjApvHKCVGQjNJm8Q==} - '@astrojs/internal-helpers@0.7.1': resolution: {integrity: sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ==} @@ -16409,8 +16406,6 @@ snapshots: '@astrojs/compiler@2.13.0': {} - '@astrojs/compiler@3.0.0-beta.0': {} - '@astrojs/internal-helpers@0.7.1': {} '@astrojs/internal-helpers@0.7.5': {} From 9425ba71e86896d0c2d012c6ea5b33af0286af7b Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:36:37 +0100 Subject: [PATCH 02/13] fix: adjust for new compile preprocessStyle API --- packages/astro/package.json | 2 +- packages/astro/src/core/compile/compile.ts | 26 ++++++++++++------- packages/astro/src/core/compile/style.ts | 9 +++++-- packages/astro/src/vite-plugin-astro/index.ts | 5 ++-- pnpm-lock.yaml | 4 +-- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 1f93f13ef3aa..0e64a5674753 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -116,7 +116,7 @@ "test:integration": "astro-scripts test \"test/*.test.js\"" }, "dependencies": { - "@astrojs/compiler": "link:../../../compiler-rust/packages/compiler", + "@astrojs/compiler": "link:../../../compiler-rs/packages/compiler", "@astrojs/internal-helpers": "workspace:*", "@astrojs/markdown-remark": "workspace:*", "@astrojs/telemetry": "workspace:*", diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 2267d81fd825..7ef0136d0b1b 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url'; import type { TransformResult } from '@astrojs/compiler'; -import { transform } from '@astrojs/compiler/sync'; +import { preprocessStyles, transform } from '@astrojs/compiler'; import type { ResolvedConfig } from 'vite'; import type { AstroConfig } from '../../types/public/config.js'; import type { AstroError } from '../errors/errors.js'; @@ -45,6 +45,20 @@ export async function compile({ // use `sourcemap: "both"` so that sourcemap is included in the code // result passed to esbuild, but also available in the catch handler. const t0 = performance.now(); + + // Step 1: Preprocess styles (async — calls Vite's preprocessCSS) + const preprocessedStyles = await preprocessStyles( + source, + createStylePreprocessor({ + filename, + viteConfig, + astroConfig, + cssPartialCompileResults, + cssTransformErrors, + }), + ); + + // Step 2: Transform (always sync) transformResult = transform(source, { compact: astroConfig.compressHTML, filename, @@ -61,14 +75,8 @@ export async function compile({ astroConfig.devToolbar && astroConfig.devToolbar.enabled && toolbarEnabled, - preprocessStyle: createStylePreprocessor({ - filename, - viteConfig, - astroConfig, - cssPartialCompileResults, - cssTransformErrors, - }), - async resolvePath(specifier) { + preprocessedStyles, + resolvePath(specifier) { return resolvePath(specifier, filename); }, }); diff --git a/packages/astro/src/core/compile/style.ts b/packages/astro/src/core/compile/style.ts index d6e4dec983f6..051096fcaca4 100644 --- a/packages/astro/src/core/compile/style.ts +++ b/packages/astro/src/core/compile/style.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import type { TransformOptions } from '@astrojs/compiler'; +import type { PreprocessorError, PreprocessorResult } from '@astrojs/compiler'; import { preprocessCSS, type ResolvedConfig } from 'vite'; import type { AstroConfig } from '../../types/public/config.js'; import { AstroErrorData, CSSError, positionAt } from '../errors/index.js'; @@ -78,6 +78,11 @@ function rewriteCssUrls(css: string, base: string): string { }); } +type PreprocessStyleFn = ( + content: string, + attrs: Record, +) => Promise; + export function createStylePreprocessor({ filename, viteConfig, @@ -90,7 +95,7 @@ export function createStylePreprocessor({ astroConfig: AstroConfig; cssPartialCompileResults: Partial[]; cssTransformErrors: Error[]; -}): TransformOptions['preprocessStyle'] { +}): PreprocessStyleFn { let processedStylesCount = 0; return async (content, attrs) => { diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 9ca5addd5997..b77b0b80ffae 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -1,4 +1,4 @@ -import type { HydratedComponent } from '@astrojs/compiler/types'; +import type { Component } from '@astrojs/compiler/types'; import type { SourceDescription } from 'rollup'; import type * as vite from 'vite'; import { defaultClientConditions, defaultServerConditions, normalizePath } from 'vite'; @@ -38,8 +38,7 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl // Variables for determining if an id starts with /src... const srcRootWeb = config.srcDir.pathname.slice(config.root.pathname.length - 1); const isBrowserPath = (path: string) => path.startsWith(srcRootWeb) && srcRootWeb !== '/'; - const notAstroComponent = (component: HydratedComponent) => - !component.resolvedPath.endsWith('.astro'); + const notAstroComponent = (component: Component) => !component.resolvedPath.endsWith('.astro'); return [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d4d9539f23b..a974c6cc597a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -494,8 +494,8 @@ importers: packages/astro: dependencies: '@astrojs/compiler': - specifier: link:../../../compiler-rust/packages/compiler - version: link:../../../compiler-rust/packages/compiler + specifier: link:../../../compiler-rs/packages/compiler + version: link:../../../compiler-rs/packages/compiler '@astrojs/internal-helpers': specifier: workspace:* version: link:../internal-helpers From 8b1e2865dbfdc7ad0d1ebd854fbb658c873281b0 Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Sat, 14 Feb 2026 04:35:41 +0100 Subject: [PATCH 03/13] fix: update for diagnostic shape --- packages/astro/src/core/compile/compile.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 7ef0136d0b1b..bd2e6a754827 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -44,7 +44,6 @@ export async function compile({ // 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 t0 = performance.now(); // Step 1: Preprocess styles (async — calls Vite's preprocessCSS) const preprocessedStyles = await preprocessStyles( @@ -58,6 +57,7 @@ export async function compile({ }), ); + const t0 = performance.now(); // Step 2: Transform (always sync) transformResult = transform(source, { compact: astroConfig.compressHTML, @@ -96,7 +96,7 @@ export async function compile({ }); } - handleCompileResultErrors(transformResult, cssTransformErrors); + handleCompileResultErrors(filename, transformResult, cssTransformErrors); return { ...transformResult, @@ -107,19 +107,17 @@ export async function compile({ }; } -function handleCompileResultErrors(result: TransformResult, cssTransformErrors: AstroError[]) { - // TODO: Export the DiagnosticSeverity enum from @astrojs/compiler? - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - const compilerError = result.diagnostics.find((diag) => diag.severity === 1); +function handleCompileResultErrors(filename:string, result: TransformResult, cssTransformErrors: AstroError[]) { + const compilerError = result.diagnostics.find((diag) => diag.severity === 'error'); if (compilerError) { throw new CompilerError({ name: 'CompilerError', message: compilerError.text, location: { - line: compilerError.location.line, - column: compilerError.location.column, - file: compilerError.location.file, + line: compilerError.labels[0].line, + column: compilerError.labels[0].column, + file: filename }, hint: compilerError.hint, }); From 1edf10313642ad4ad567505745c1c7b2757d68fe Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:40:38 +0100 Subject: [PATCH 04/13] feat: experimental flag --- .changeset/rust-compiler-experimental.md | 7 + examples/basics/astro.config.mjs | 6 +- packages/astro/package.json | 3 +- packages/astro/src/core/build/index.ts | 15 +- packages/astro/src/core/compile/compile-rs.ts | 156 ++++++++++++++++++ packages/astro/src/core/compile/compile.ts | 50 ++---- packages/astro/src/core/compile/index.ts | 2 +- packages/astro/src/core/compile/style.ts | 20 ++- .../astro/src/core/config/schemas/base.ts | 2 + packages/astro/src/types/public/config.ts | 27 +++ .../astro/src/vite-plugin-astro/compile-rs.ts | 52 ++++++ .../astro/src/vite-plugin-astro/compile.ts | 97 ++++++++++- packages/astro/src/vite-plugin-astro/index.ts | 29 +++- pnpm-lock.yaml | 12 +- 14 files changed, 406 insertions(+), 72 deletions(-) create mode 100644 .changeset/rust-compiler-experimental.md create mode 100644 packages/astro/src/core/compile/compile-rs.ts create mode 100644 packages/astro/src/vite-plugin-astro/compile-rs.ts diff --git a/.changeset/rust-compiler-experimental.md b/.changeset/rust-compiler-experimental.md new file mode 100644 index 000000000000..41f09b42b5f6 --- /dev/null +++ b/.changeset/rust-compiler-experimental.md @@ -0,0 +1,7 @@ +--- +'astro': minor +--- + +Adds a new `experimental.rustCompiler` flag to opt into the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`). When enabled, `@astrojs/compiler-rs` must be installed as a dev dependency. + +This new compiler diff --git a/examples/basics/astro.config.mjs b/examples/basics/astro.config.mjs index e762ba5cf616..ce0d69090f80 100644 --- a/examples/basics/astro.config.mjs +++ b/examples/basics/astro.config.mjs @@ -2,4 +2,8 @@ import { defineConfig } from 'astro/config'; // https://astro.build/config -export default defineConfig({}); +export default defineConfig({ + experimental: { + rustCompiler: true + } +}); diff --git a/packages/astro/package.json b/packages/astro/package.json index 0e64a5674753..f1e33bceb96e 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -116,7 +116,7 @@ "test:integration": "astro-scripts test \"test/*.test.js\"" }, "dependencies": { - "@astrojs/compiler": "link:../../../compiler-rs/packages/compiler", + "@astrojs/compiler": "^3.0.0-beta.0", "@astrojs/internal-helpers": "workspace:*", "@astrojs/markdown-remark": "workspace:*", "@astrojs/telemetry": "workspace:*", @@ -180,6 +180,7 @@ }, "devDependencies": { "@astrojs/check": "workspace:*", + "@astrojs/compiler-rs": "link:../../../compiler-rs/packages/compiler", "@playwright/test": "1.58.1", "@types/aria-query": "^5.0.4", "@types/cssesc": "^3.0.2", diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 112babbeae3a..9c7418b78d2c 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -18,7 +18,7 @@ import { createNodeLogger } from '../config/logging.js'; import { createSettings } from '../config/settings.js'; import { createVite } from '../create-vite.js'; import { createKey, getEnvironmentKey, hasEnvironmentKey } from '../encryption.js'; -import { compileTimeStats } from '../compile/compile.js'; + import { AstroError, AstroErrorData } from '../errors/index.js'; import type { Logger } from '../logger/core.js'; import { levels, timerMessage } from '../logger/core.js'; @@ -297,18 +297,5 @@ class AstroBuilder { logger.info('build', messages.join(' ')); logger.info('build', `${colors.bold('Complete!')}`); - - // Log compile time stats table - if (compileTimeStats.files.size > 0) { - logger.info('build', ''); - logger.info('build', colors.bold('Compile times:')); - const sortedFiles = [...compileTimeStats.files.entries()].sort((a, b) => b[1] - a[1]); - for (const [file, time] of sortedFiles) { - const basename = file.split('/').pop() ?? file; - logger.info('build', ` ${basename} ${colors.dim(`${time.toFixed(2)}ms`)}`); - } - const compileTotal = sortedFiles.reduce((sum, [, time]) => sum + time, 0); - logger.info('build', colors.bold(` Total: ${compileTotal.toFixed(2)}ms`)); - } } } diff --git a/packages/astro/src/core/compile/compile-rs.ts b/packages/astro/src/core/compile/compile-rs.ts new file mode 100644 index 000000000000..e82a3138121f --- /dev/null +++ b/packages/astro/src/core/compile/compile-rs.ts @@ -0,0 +1,156 @@ +import { fileURLToPath } from 'node:url'; +import type { ResolvedConfig } from 'vite'; +import type { AstroConfig } from '../../types/public/config.js'; +import type { AstroError } from '../errors/errors.js'; +import { AggregateError, CompilerError } from '../errors/errors.js'; +import { AstroErrorData } from '../errors/index.js'; +import { normalizePath, resolvePath } from '../viteUtils.js'; +import { createStylePreprocessor, type PartialCompileCssResult } from './style.js'; +import type { CompileCssResult } from './types.js'; + +export interface CompileProps { + astroConfig: AstroConfig; + viteConfig: ResolvedConfig; + toolbarEnabled: boolean; + filename: string; + source: string; +} + +export interface CompileResult { + code: string; + map: string; + scope: string; + css: CompileCssResult[]; + scripts: any[]; + hydratedComponents: any[]; + clientOnlyComponents: any[]; + serverComponents: any[]; + containsHead: boolean; + propagation: boolean; + styleError: string[]; + diagnostics: any[]; +} + +export async function compile({ + astroConfig, + viteConfig, + toolbarEnabled, + filename, + source, +}: CompileProps): Promise { + const { preprocessStyles, transform } = await import('@astrojs/compiler-rs'); + + // Because `@astrojs/compiler-rs` 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 cssPartialCompileResults: PartialCompileCssResult[] = []; + const cssTransformErrors: AstroError[] = []; + let transformResult: any; + + try { + // Transform from `.astro` to valid `.js` + // use `sourcemap: "both"` so that sourcemap is included in the code + // result passed to esbuild, but also available in the catch handler. + + // Step 1: Preprocess styles (async — calls Vite's preprocessCSS) + const preprocessedStyles = await preprocessStyles( + source, + createStylePreprocessor({ + filename, + viteConfig, + astroConfig, + cssPartialCompileResults, + cssTransformErrors, + }), + ); + + // Step 2: Transform (always sync) + transformResult = transform(source, { + compact: astroConfig.compressHTML, + filename, + normalizedFilename: normalizeFilename(filename, astroConfig.root), + sourcemap: 'both', + internalURL: 'astro/compiler-runtime', + // TODO: remove in Astro v7 + astroGlobalArgs: JSON.stringify(astroConfig.site), + scopedStyleStrategy: astroConfig.scopedStyleStrategy, + resultScopedSlot: true, + transitionsAnimationURL: 'astro/components/viewtransitions.css', + annotateSourceFile: + viteConfig.command === 'serve' && + astroConfig.devToolbar && + astroConfig.devToolbar.enabled && + toolbarEnabled, + preprocessedStyles, + 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(filename, transformResult, cssTransformErrors); + + return { + ...transformResult, + css: transformResult.css.map((code: string, i: number) => ({ + ...cssPartialCompileResults[i], + code, + })), + }; +} + +function handleCompileResultErrors( + filename: string, + result: any, + cssTransformErrors: AstroError[], +) { + const compilerError = result.diagnostics.find((diag: any) => diag.severity === 'error'); + + if (compilerError) { + throw new CompilerError({ + name: 'CompilerError', + message: compilerError.text, + location: { + line: compilerError.labels[0].line, + column: compilerError.labels[0].column, + file: filename, + }, + hint: compilerError.hint, + }); + } + + switch (cssTransformErrors.length) { + case 0: + break; + case 1: { + throw cssTransformErrors[0]; + } + default: { + throw new AggregateError({ + ...cssTransformErrors[0], + errors: cssTransformErrors, + }); + } + } +} + +function normalizeFilename(filename: string, root: URL) { + const normalizedFilename = normalizePath(filename); + const normalizedRoot = normalizePath(fileURLToPath(root)); + if (normalizedFilename.startsWith(normalizedRoot)) { + return normalizedFilename.slice(normalizedRoot.length - 1); + } else { + return normalizedFilename; + } +} diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index bd2e6a754827..66b8d4b96608 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url'; import type { TransformResult } from '@astrojs/compiler'; -import { preprocessStyles, transform } from '@astrojs/compiler'; +import { transform } from '@astrojs/compiler'; import type { ResolvedConfig } from 'vite'; import type { AstroConfig } from '../../types/public/config.js'; import type { AstroError } from '../errors/errors.js'; @@ -10,10 +10,6 @@ import { normalizePath, resolvePath } from '../viteUtils.js'; import { createStylePreprocessor, type PartialCompileCssResult } from './style.js'; import type { CompileCssResult } from './types.js'; -export const compileTimeStats = { - files: new Map(), -}; - export interface CompileProps { astroConfig: AstroConfig; viteConfig: ResolvedConfig; @@ -44,22 +40,7 @@ export async function compile({ // 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. - - // Step 1: Preprocess styles (async — calls Vite's preprocessCSS) - const preprocessedStyles = await preprocessStyles( - source, - createStylePreprocessor({ - filename, - viteConfig, - astroConfig, - cssPartialCompileResults, - cssTransformErrors, - }), - ); - - const t0 = performance.now(); - // Step 2: Transform (always sync) - transformResult = transform(source, { + transformResult = await transform(source, { compact: astroConfig.compressHTML, filename, normalizedFilename: normalizeFilename(filename, astroConfig.root), @@ -75,14 +56,17 @@ export async function compile({ astroConfig.devToolbar && astroConfig.devToolbar.enabled && toolbarEnabled, - preprocessedStyles, - resolvePath(specifier) { + preprocessStyle: createStylePreprocessor({ + filename, + viteConfig, + astroConfig, + cssPartialCompileResults, + cssTransformErrors, + }), + async resolvePath(specifier) { return resolvePath(specifier, filename); }, }); - const t1 = performance.now(); - const elapsed = t1 - t0; - compileTimeStats.files.set(filename, elapsed); } 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 @@ -96,7 +80,7 @@ export async function compile({ }); } - handleCompileResultErrors(filename, transformResult, cssTransformErrors); + handleCompileResultErrors(transformResult, cssTransformErrors); return { ...transformResult, @@ -107,17 +91,19 @@ export async function compile({ }; } -function handleCompileResultErrors(filename:string, result: TransformResult, cssTransformErrors: AstroError[]) { - const compilerError = result.diagnostics.find((diag) => diag.severity === 'error'); +function handleCompileResultErrors(result: TransformResult, cssTransformErrors: AstroError[]) { + // TODO: Export the DiagnosticSeverity enum from @astrojs/compiler? + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + const compilerError = result.diagnostics.find((diag) => diag.severity === 1); if (compilerError) { throw new CompilerError({ name: 'CompilerError', message: compilerError.text, location: { - line: compilerError.labels[0].line, - column: compilerError.labels[0].column, - file: filename + line: compilerError.location.line, + column: compilerError.location.column, + file: compilerError.location.file, }, hint: compilerError.hint, }); diff --git a/packages/astro/src/core/compile/index.ts b/packages/astro/src/core/compile/index.ts index a9f141a2afae..afd5eec0bad8 100644 --- a/packages/astro/src/core/compile/index.ts +++ b/packages/astro/src/core/compile/index.ts @@ -1,2 +1,2 @@ export type { CompileProps, CompileResult } from './compile.js'; -export { compile, compileTimeStats } from './compile.js'; +export { compile } from './compile.js'; diff --git a/packages/astro/src/core/compile/style.ts b/packages/astro/src/core/compile/style.ts index 051096fcaca4..c0d63c04e1ea 100644 --- a/packages/astro/src/core/compile/style.ts +++ b/packages/astro/src/core/compile/style.ts @@ -1,5 +1,4 @@ import fs from 'node:fs'; -import type { PreprocessorError, PreprocessorResult } from '@astrojs/compiler'; import { preprocessCSS, type ResolvedConfig } from 'vite'; import type { AstroConfig } from '../../types/public/config.js'; import { AstroErrorData, CSSError, positionAt } from '../errors/index.js'; @@ -8,6 +7,20 @@ import type { CompileCssResult } from './types.js'; export type PartialCompileCssResult = Pick; +interface PreprocessorResult { + code: string; + map?: string; +} + +interface PreprocessorError { + error: string; +} + +export type PreprocessStyleFn = ( + content: string, + attrs: Record, +) => Promise; + /** * Rewrites absolute URLs in CSS to include the base path. * @@ -78,11 +91,6 @@ function rewriteCssUrls(css: string, base: string): string { }); } -type PreprocessStyleFn = ( - content: string, - attrs: Record, -) => Promise; - export function createStylePreprocessor({ filename, viteConfig, diff --git a/packages/astro/src/core/config/schemas/base.ts b/packages/astro/src/core/config/schemas/base.ts index fa9863a6539a..8f550224cc41 100644 --- a/packages/astro/src/core/config/schemas/base.ts +++ b/packages/astro/src/core/config/schemas/base.ts @@ -104,6 +104,7 @@ export const ASTRO_CONFIG_DEFAULTS = { contentIntellisense: false, chromeDevtoolsWorkspace: false, svgo: false, + rustCompiler: false, }, } satisfies AstroUserConfig & { server: { open: boolean } }; @@ -493,6 +494,7 @@ export const AstroConfigSchema = z.object({ .union([z.boolean(), z.custom((value) => value && typeof value === 'object')]) .optional() .default(ASTRO_CONFIG_DEFAULTS.experimental.svgo), + rustCompiler: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.rustCompiler), }) .prefault({}), legacy: z diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 730630bb6ec2..14febf9d8c01 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -2800,6 +2800,33 @@ export interface AstroUserConfig< * See the [experimental SVGO optimization docs](https://docs.astro.build/en/reference/experimental-flags/svg-optimization/) for more information. */ svgo?: boolean | SvgoConfig; + + /** + * @name experimental.rustCompiler + * @type {boolean} + * @default `false` + * @version 5.x + * @description + * + * Enables the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`) as a replacement + * for the default Go-based compiler. + * + * The Rust compiler is faster and produces JavaScript output directly, removing the need for + * an additional esbuild TypeScript transform step. It requires installing `@astrojs/compiler-rs` + * as a dev dependency. + * + * ```js + * // astro.config.mjs + * import { defineConfig } from 'astro/config'; + * + * export default defineConfig({ + * experimental: { + * rustCompiler: true, + * }, + * }); + * ``` + */ + rustCompiler?: boolean; }; } diff --git a/packages/astro/src/vite-plugin-astro/compile-rs.ts b/packages/astro/src/vite-plugin-astro/compile-rs.ts new file mode 100644 index 000000000000..9eff8a3916e5 --- /dev/null +++ b/packages/astro/src/vite-plugin-astro/compile-rs.ts @@ -0,0 +1,52 @@ +import type { SourceMapInput } from 'rollup'; +import { type CompileProps, type CompileResult, compile } from '../core/compile/compile-rs.js'; +import { getFileInfo } from '../vite-plugin-utils/index.js'; +import type { CompileMetadata } from './types.js'; + +interface CompileAstroOption { + compileProps: CompileProps; + astroFileToCompileMetadata: Map; +} + +export interface CompileAstroResult extends Omit { + map: SourceMapInput; +} + +export async function compileAstro({ + compileProps, + astroFileToCompileMetadata, +}: CompileAstroOption): Promise { + const transformResult = await compile(compileProps); + + const { fileId: file, fileUrl: url } = getFileInfo( + compileProps.filename, + compileProps.astroConfig, + ); + + 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 "${compileProps.filename}?astro&type=script&index=${i}&lang.ts";`; + i++; + } + } + + // Attach compile metadata to map for use by virtual modules + astroFileToCompileMetadata.set(compileProps.filename, { + originalCode: compileProps.source, + css: transformResult.css, + scripts: transformResult.scripts, + }); + + return { + ...transformResult, + code: transformResult.code + SUFFIX, + map: transformResult.map || null, + }; +} diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts index a2f4a8c4dd7b..0d8b000b29d6 100644 --- a/packages/astro/src/vite-plugin-astro/compile.ts +++ b/packages/astro/src/vite-plugin-astro/compile.ts @@ -1,21 +1,64 @@ +import { type ESBuildTransformResult, transformWithEsbuild } from 'vite'; import { type CompileProps, type CompileResult, compile } from '../core/compile/index.js'; +import type { Logger } from '../core/logger/core.js'; +import type { AstroConfig } from '../types/public/config.js'; import { getFileInfo } from '../vite-plugin-utils/index.js'; import type { CompileMetadata } from './types.js'; +import { frontmatterRE } from './utils.js'; +import type { SourceMapInput } from 'rollup'; interface CompileAstroOption { compileProps: CompileProps; astroFileToCompileMetadata: Map; + logger: Logger; } export interface CompileAstroResult extends Omit { - map: string | undefined; + map: SourceMapInput; +} + +interface EnhanceCompilerErrorOptions { + err: Error; + id: string; + source: string; + config: AstroConfig; + logger: Logger; } export async function compileAstro({ compileProps, astroFileToCompileMetadata, + logger, }: CompileAstroOption): Promise { - const transformResult = await compile(compileProps); + let transformResult: CompileResult; + let esbuildResult: ESBuildTransformResult; + + try { + transformResult = await compile(compileProps); + // Compile all TypeScript to JavaScript. + // Also, catches invalid JS/TS in the compiled output before returning. + esbuildResult = await transformWithEsbuild(transformResult.code, compileProps.filename, { + ...compileProps.viteConfig.esbuild, + loader: 'ts', + sourcemap: 'external', + tsconfigRaw: { + compilerOptions: { + // Ensure client:only imports are treeshaken + verbatimModuleSyntax: false, + importsNotUsedAsValues: 'remove', + }, + }, + }); + } catch (err: any) { + await enhanceCompileError({ + err, + id: compileProps.filename, + source: compileProps.source, + config: compileProps.astroConfig, + logger: logger, + }); + throw err; + } const { fileId: file, fileUrl: url } = getFileInfo( compileProps.filename, @@ -45,9 +88,51 @@ export async function compileAstro({ return { ...transformResult, - code: transformResult.code + SUFFIX, - // The Rust compiler returns an empty string when source maps are not yet implemented. - // Rollup/Vite will try to JSON.parse the map, so we must return undefined instead. - map: transformResult.map || undefined, + code: esbuildResult.code + SUFFIX, + map: esbuildResult.map, }; } + +async function enhanceCompileError({ + err, + id, + source, +}: EnhanceCompilerErrorOptions): Promise { + const lineText = (err as any).loc?.lineText; + // 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 = frontmatterRE.exec(source); + if (scannedFrontmatter) { + // Top-level return is not supported, so replace `return` with throw + const frontmatter = scannedFrontmatter[1].replace(/\breturn\b/g, 'throw'); + + // If frontmatter does not actually include the offending line, skip + if (lineText && !frontmatter.includes(lineText)) throw err; + + try { + await transformWithEsbuild(frontmatter, id, { + loader: 'ts', + target: 'esnext', + 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?.message) { + frontmatterErr.message = frontmatterErr.message.replace( + 'end of file', + 'end of frontmatter', + ); + } + throw frontmatterErr; + } + } + + throw err; +} diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index b77b0b80ffae..030dda2af0bb 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -1,4 +1,4 @@ -import type { Component } from '@astrojs/compiler/types'; +import type { HydratedComponent } from '@astrojs/compiler/types'; import type { SourceDescription } from 'rollup'; import type * as vite from 'vite'; import { defaultClientConditions, defaultServerConditions, normalizePath } from 'vite'; @@ -9,6 +9,7 @@ import type { AstroSettings } from '../types/astro.js'; import type { AstroConfig } from '../types/public/config.js'; import { normalizeFilename, specialQueriesRE } from '../vite-plugin-utils/index.js'; import { type CompileAstroResult, compileAstro } from './compile.js'; +import { compileAstro as compileAstroRs } from './compile-rs.js'; import { handleHotUpdate } from './hmr.js'; import { parseAstroRequest } from './query.js'; import type { PluginMetadata as AstroPluginMetadata, CompileMetadata } from './types.js'; @@ -38,7 +39,8 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl // Variables for determining if an id starts with /src... const srcRootWeb = config.srcDir.pathname.slice(config.root.pathname.length - 1); const isBrowserPath = (path: string) => path.startsWith(srcRootWeb) && srcRootWeb !== '/'; - const notAstroComponent = (component: Component) => !component.resolvedPath.endsWith('.astro'); + const notAstroComponent = (component: HydratedComponent) => + !component.resolvedPath.endsWith('.astro'); return [ { @@ -87,17 +89,26 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl }, async configResolved(viteConfig) { const toolbarEnabled = await settings.preferences.get('devToolbar.enabled'); + const useRustCompiler = config.experimental.rustCompiler; // Initialize `compile` function to simplify usage later compile = (code, filename) => { + const compileProps = { + astroConfig: config, + viteConfig, + toolbarEnabled, + filename, + source: code, + }; + if (useRustCompiler) { + return compileAstroRs({ + compileProps, + astroFileToCompileMetadata, + }); + } return compileAstro({ - compileProps: { - astroConfig: config, - viteConfig, - toolbarEnabled, - filename, - source: code, - }, + compileProps, astroFileToCompileMetadata, + logger, }); }; }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a974c6cc597a..5953efe566c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -494,8 +494,8 @@ importers: packages/astro: dependencies: '@astrojs/compiler': - specifier: link:../../../compiler-rs/packages/compiler - version: link:../../../compiler-rs/packages/compiler + specifier: ^3.0.0-beta.0 + version: 3.0.0-beta.0 '@astrojs/internal-helpers': specifier: workspace:* version: link:../internal-helpers @@ -671,6 +671,9 @@ importers: '@astrojs/check': specifier: workspace:* version: link:../language-tools/astro-check + '@astrojs/compiler-rs': + specifier: link:../../../compiler-rs/packages/compiler + version: link:../../../compiler-rs/packages/compiler '@playwright/test': specifier: 1.58.1 version: 1.58.1 @@ -7104,6 +7107,9 @@ packages: '@astrojs/compiler@2.13.0': resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==} + '@astrojs/compiler@3.0.0-beta.0': + resolution: {integrity: sha512-wrWArjuyeEhoUZUr+o3Ts3CFbxB+QFYvzsnnR0wr3L0b84JDxq107mZOfmNZ/XPE4VjFzjjApvHKCVGQjNJm8Q==} + '@astrojs/internal-helpers@0.7.1': resolution: {integrity: sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ==} @@ -16406,6 +16412,8 @@ snapshots: '@astrojs/compiler@2.13.0': {} + '@astrojs/compiler@3.0.0-beta.0': {} + '@astrojs/internal-helpers@0.7.1': {} '@astrojs/internal-helpers@0.7.5': {} From 4af051ec368d9100f49d75e0d5df00132b8dcd23 Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:43:28 +0100 Subject: [PATCH 05/13] fix: unrelated changes --- examples/basics/astro.config.mjs | 6 +----- packages/astro/src/core/build/index.ts | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/basics/astro.config.mjs b/examples/basics/astro.config.mjs index ce0d69090f80..e762ba5cf616 100644 --- a/examples/basics/astro.config.mjs +++ b/examples/basics/astro.config.mjs @@ -2,8 +2,4 @@ import { defineConfig } from 'astro/config'; // https://astro.build/config -export default defineConfig({ - experimental: { - rustCompiler: true - } -}); +export default defineConfig({}); diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 9c7418b78d2c..069f63651903 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -18,7 +18,6 @@ import { createNodeLogger } from '../config/logging.js'; import { createSettings } from '../config/settings.js'; import { createVite } from '../create-vite.js'; import { createKey, getEnvironmentKey, hasEnvironmentKey } from '../encryption.js'; - import { AstroError, AstroErrorData } from '../errors/index.js'; import type { Logger } from '../logger/core.js'; import { levels, timerMessage } from '../logger/core.js'; From f2c16651a3d34b43ac5fa8e679c1bd0bc4524096 Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:05:01 +0100 Subject: [PATCH 06/13] chore: lockfile --- packages/astro/package.json | 2 +- pnpm-lock.yaml | 113 +++++++++++++++++++++++++++++++++++- pnpm-workspace.yaml | 2 + 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index af6039fd9100..262fac6f12b5 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -178,7 +178,7 @@ }, "devDependencies": { "@astrojs/check": "workspace:*", - "@astrojs/compiler-rs": "link:../../../compiler-rs/packages/compiler", + "@astrojs/compiler-rs": "^0.1.0", "@playwright/test": "1.58.2", "@types/aria-query": "^5.0.4", "@types/cssesc": "^3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70ff61d82401..1bca68ba823a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -675,8 +675,8 @@ importers: specifier: workspace:* version: link:../language-tools/astro-check '@astrojs/compiler-rs': - specifier: link:../../../compiler-rs/packages/compiler - version: link:../../../compiler-rs/packages/compiler + specifier: ^0.1.0 + version: 0.1.0 '@playwright/test': specifier: 1.58.2 version: 1.58.2 @@ -7128,6 +7128,70 @@ packages: resolution: {integrity: sha512-bVzyKzEpIwqjihBU/aUzt1LQckJuHK0agd3/ITdXhPUYculrc6K1/K7H+XG4rwjXtg+ikT3PM05V1MVYWiIvQw==} engines: {node: '>=18.14.1'} + '@astrojs/compiler-binding-darwin-arm64@0.1.0': + resolution: {integrity: sha512-1uv15N1LZt3ss+y3VOIKCSicNaoOomb5D7VQuKxL4/QG1oier9ObPfIuxqTBxgtNUOPC87fYqiDkXRGxCi43Bg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@astrojs/compiler-binding-darwin-x64@0.1.0': + resolution: {integrity: sha512-I4VE7nwODs2UPf430EtQDwrAgJmZsdlxvIK/JqRvpu6C2nmjwGT4CibO8KACn+iBpUL4Bxk3+Ax+p3tVwKc9Ww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@astrojs/compiler-binding-linux-arm64-gnu@0.1.0': + resolution: {integrity: sha512-VNorBIxOrT55hxSS1gdPYbM1RJHYL4ahOk15qINiwWFQPBb03vb5fY3zxUJrIXLdumb7Xvwt7TnrrGb6klFZbA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@astrojs/compiler-binding-linux-arm64-musl@0.1.0': + resolution: {integrity: sha512-VJUzo4vu6wY6ZkbDPofbzH4SyoinZGZzBCkZ75dGVZO1Z49J0fFb0NwRGd4VhGyrB6vBSa7fQTqZD04S2BYl2Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@astrojs/compiler-binding-linux-x64-gnu@0.1.0': + resolution: {integrity: sha512-GxCimSZcsG+gOhMH2kZGCUuUxZ2TxPHny7phIgjmdyE1iAY/zOKWl+uHWF/QazFsug0Iba4WlvjDOrVUL5HTDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@astrojs/compiler-binding-linux-x64-musl@0.1.0': + resolution: {integrity: sha512-cR1B9rmMmmOoD4APlGQ3ujkOQQrDKpOY0GsLexwbrgzprkVW+mvEqRfCwoi9lJO7xTWMlIF5goesEDjvMvmnxg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@astrojs/compiler-binding-wasm32-wasi@0.1.0': + resolution: {integrity: sha512-gy9SVv+GTlBfeO6AOrhaLUYUkuKCvjFr94jiMI15DBS/xIXkxm5CMut4Y7yq7GEB5xMByMUOb6a0Yk9ELu8aDQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@astrojs/compiler-binding-win32-arm64-msvc@0.1.0': + resolution: {integrity: sha512-4lJSUe5qx+szLvly9FqtJ2BdAG5XEgaTk3anoq+JoaANA+pGk2mGGa50dTVcqzplVnqEQGY6+9dIklk/4hJ4IQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@astrojs/compiler-binding-win32-x64-msvc@0.1.0': + resolution: {integrity: sha512-HVpiVsr3Wcy1/1GMfzwSc0oFRpJw9Mz7PJpI53/xBt/0ySPWDmL0FZXRHSQgEvAfTcVmh2P2LPivK5AkEDKuzg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@astrojs/compiler-binding@0.1.0': + resolution: {integrity: sha512-NKI0m+2zA8n5s4Vg9rrzOIqJsKHsVu+ASVxaCk2PNPksdDBiPIZC0Hq2/kqJfzwCaGnU0kplcF/rM6kYlGpx/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@astrojs/compiler-rs@0.1.0': + resolution: {integrity: sha512-/ctF92BSNHLySrQgwa7z8U1SveNAsrZkumBybnoDmmtl1+ZV2p2ME7ODzk56xhidpwnP81V3VhPjWM8kWwq8fw==} + '@astrojs/compiler@2.13.1': resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} @@ -16369,6 +16433,51 @@ snapshots: log-update: 5.0.1 sisteransi: 1.0.5 + '@astrojs/compiler-binding-darwin-arm64@0.1.0': + optional: true + + '@astrojs/compiler-binding-darwin-x64@0.1.0': + optional: true + + '@astrojs/compiler-binding-linux-arm64-gnu@0.1.0': + optional: true + + '@astrojs/compiler-binding-linux-arm64-musl@0.1.0': + optional: true + + '@astrojs/compiler-binding-linux-x64-gnu@0.1.0': + optional: true + + '@astrojs/compiler-binding-linux-x64-musl@0.1.0': + optional: true + + '@astrojs/compiler-binding-wasm32-wasi@0.1.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@astrojs/compiler-binding-win32-arm64-msvc@0.1.0': + optional: true + + '@astrojs/compiler-binding-win32-x64-msvc@0.1.0': + optional: true + + '@astrojs/compiler-binding@0.1.0': + optionalDependencies: + '@astrojs/compiler-binding-darwin-arm64': 0.1.0 + '@astrojs/compiler-binding-darwin-x64': 0.1.0 + '@astrojs/compiler-binding-linux-arm64-gnu': 0.1.0 + '@astrojs/compiler-binding-linux-arm64-musl': 0.1.0 + '@astrojs/compiler-binding-linux-x64-gnu': 0.1.0 + '@astrojs/compiler-binding-linux-x64-musl': 0.1.0 + '@astrojs/compiler-binding-wasm32-wasi': 0.1.0 + '@astrojs/compiler-binding-win32-arm64-msvc': 0.1.0 + '@astrojs/compiler-binding-win32-x64-msvc': 0.1.0 + + '@astrojs/compiler-rs@0.1.0': + dependencies: + '@astrojs/compiler-binding': 0.1.0 + '@astrojs/compiler@2.13.1': {} '@astrojs/compiler@3.0.0-beta.0': {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f0fc2f493785..7145c214a76e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -32,6 +32,8 @@ minimumReleaseAge: 4320 minimumReleaseAgeExclude: # TODO: remove once more stable - '@flue/*' + - '@astrojs/compiler-rs' + - '@astrojs/compiler-binding*' peerDependencyRules: allowAny: - 'astro' From c683fe7e734360b71b5966baeb509dfadb6109a4 Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:29:04 +0100 Subject: [PATCH 07/13] fix: make tests pass on both compilers --- packages/astro/src/core/compile/style.ts | 4 +++- packages/astro/test/astro-partial-html.test.js | 4 ++-- packages/astro/test/content-collections-render.test.js | 5 +++-- packages/astro/test/css-order-import.test.js | 5 +++-- .../fixtures/astro-head/src/pages/head-own-component.astro | 1 + .../fixtures/core-image-ssg/src/pages/special-chars.astro | 1 + 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/astro/src/core/compile/style.ts b/packages/astro/src/core/compile/style.ts index c0d63c04e1ea..91754c763034 100644 --- a/packages/astro/src/core/compile/style.ts +++ b/packages/astro/src/core/compile/style.ts @@ -118,7 +118,9 @@ export function createStylePreprocessor({ const rewrittenCode = rewriteCssUrls(result.code, astroConfig.base); cssPartialCompileResults[index] = { - isGlobal: !!attrs['is:global'], + // Use `in` operator to handle both Go compiler (boolean `true`) and + // Rust compiler (empty string `""`) representations of boolean attributes. + isGlobal: 'is:global' in attrs, dependencies: result.deps ? [...result.deps].map((dep) => normalizePath(dep)) : [], }; diff --git a/packages/astro/test/astro-partial-html.test.js b/packages/astro/test/astro-partial-html.test.js index f6ea0de9525f..9d04ea0ba257 100644 --- a/packages/astro/test/astro-partial-html.test.js +++ b/packages/astro/test/astro-partial-html.test.js @@ -26,8 +26,8 @@ describe('Partial HTML', async () => { assert.match(html, /^ { diff --git a/packages/astro/test/content-collections-render.test.js b/packages/astro/test/content-collections-render.test.js index 4e6b680b0446..77c7a6ab7c8b 100644 --- a/packages/astro/test/content-collections-render.test.js +++ b/packages/astro/test/content-collections-render.test.js @@ -215,7 +215,8 @@ describe('Content Collections - render()', () => { let $ = cheerio.load(html); // Includes the red button styles used in the MDX blog post - assert.ok($('head > style').text().includes('background-color:red;')); + // CSS may be minified (background-color:red) or pretty-printed (background-color: red) + assert.match($('head > style').text(), /background-color:\s*red/); response = await fixture.fetch('/blog/about', { method: 'GET' }); assert.equal(response.status, 200); @@ -224,7 +225,7 @@ describe('Content Collections - render()', () => { $ = cheerio.load(html); // Does not include the red button styles not used in this page - assert.equal($('head > style').text().includes('background-color:red;'), false); + assert.doesNotMatch($('head > style').text(), /background-color:\s*red/); }); }); }); diff --git a/packages/astro/test/css-order-import.test.js b/packages/astro/test/css-order-import.test.js index f8a9cf5b2b02..6d94286f4568 100644 --- a/packages/astro/test/css-order-import.test.js +++ b/packages/astro/test/css-order-import.test.js @@ -74,8 +74,9 @@ describe('CSS ordering - import order', () => { let [style1, style2, style3] = getStyles(html); assert.ok(style1.includes('burlywood')); - assert.ok(style2.includes('aliceblue')); - assert.ok(style3.includes('whitesmoke')); + // CSS processors may resolve named colors to hex; match either form + assert.ok(style2.includes('aliceblue') || style2.includes('#f0f8ff')); + assert.ok(style3.includes('whitesmoke') || style3.includes('#f5f5f5')); }); }); diff --git a/packages/astro/test/fixtures/astro-head/src/pages/head-own-component.astro b/packages/astro/test/fixtures/astro-head/src/pages/head-own-component.astro index cbc99cb04169..ec951e3f5632 100644 --- a/packages/astro/test/fixtures/astro-head/src/pages/head-own-component.astro +++ b/packages/astro/test/fixtures/astro-head/src/pages/head-own-component.astro @@ -14,3 +14,4 @@ import Head from "../components/Head.astro"; } + diff --git a/packages/astro/test/fixtures/core-image-ssg/src/pages/special-chars.astro b/packages/astro/test/fixtures/core-image-ssg/src/pages/special-chars.astro index c774e87f9028..fa127dc5deb5 100644 --- a/packages/astro/test/fixtures/core-image-ssg/src/pages/special-chars.astro +++ b/packages/astro/test/fixtures/core-image-ssg/src/pages/special-chars.astro @@ -8,4 +8,5 @@ import { Image } from 'astro:assets'; C# + From 4b3b0624963976e4fb9cb02d1dc36624ca013aef Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:51:55 +0100 Subject: [PATCH 08/13] chore: docs --- .changeset/rust-compiler-experimental.md | 4 ++-- packages/astro/src/types/public/config.ts | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.changeset/rust-compiler-experimental.md b/.changeset/rust-compiler-experimental.md index 41f09b42b5f6..f536aea175c5 100644 --- a/.changeset/rust-compiler-experimental.md +++ b/.changeset/rust-compiler-experimental.md @@ -2,6 +2,6 @@ 'astro': minor --- -Adds a new `experimental.rustCompiler` flag to opt into the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`). When enabled, `@astrojs/compiler-rs` must be installed as a dev dependency. +Adds a new `experimental.rustCompiler` flag to opt into the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`). When enabled, `@astrojs/compiler-rs` must be installed into your project separately. -This new compiler +This new compiler is still in early development and may exhibit some differences compared to the existing Go-based compiler. The only expected difference at this time is that this compiler is generally more strict in regard to invalid syntax and may throw errors in cases where the Go-based compiler would have been more lenient. For example, unclosed tags will now result in errors. diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index df0d0a551feb..8779bf5691b6 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -2771,12 +2771,9 @@ export interface AstroUserConfig< * @version 5.x * @description * - * Enables the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`) as a replacement - * for the default Go-based compiler. - * - * The Rust compiler is faster and produces JavaScript output directly, removing the need for - * an additional esbuild TypeScript transform step. It requires installing `@astrojs/compiler-rs` - * as a dev dependency. + * Enables the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`) as a replacement to the current Go compiler. + * + * This option requires installing the `@astrojs/compiler-rs` package manually in your project. This compiler is a work in progress and may not yet support all features of the current Go compiler, but should offer improved performance and better error messages. This compiler is more strict than the previous Go compiler in regards to invalid syntax. For instance, unclosed HTML tags or missing closing brackets will throw an error instead of being ignored. * * ```js * // astro.config.mjs From 4df39263bfb559b21d61a09b1e86c27e0dda600a Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:59:21 +0100 Subject: [PATCH 09/13] chore: clean up --- packages/astro/src/core/compile/compile-rs.ts | 18 ++++++++---------- packages/astro/src/vite-plugin-astro/index.ts | 3 +-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/astro/src/core/compile/compile-rs.ts b/packages/astro/src/core/compile/compile-rs.ts index e82a3138121f..1394c0e756ec 100644 --- a/packages/astro/src/core/compile/compile-rs.ts +++ b/packages/astro/src/core/compile/compile-rs.ts @@ -38,21 +38,20 @@ export async function compile({ filename, source, }: CompileProps): Promise { - const { preprocessStyles, transform } = await import('@astrojs/compiler-rs'); + let preprocessStyles; + let transform; + try { + ({ preprocessStyles, transform } = await import('@astrojs/compiler-rs')); + } + catch (err: unknown) { + throw new Error(`Failed to load @astrojs/compiler-rs. Make sure it is installed and up to date. Original error: ${err}`); + } - // Because `@astrojs/compiler-rs` 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 cssPartialCompileResults: PartialCompileCssResult[] = []; const cssTransformErrors: AstroError[] = []; let transformResult: any; try { - // Transform from `.astro` to valid `.js` - // use `sourcemap: "both"` so that sourcemap is included in the code - // result passed to esbuild, but also available in the catch handler. - - // Step 1: Preprocess styles (async — calls Vite's preprocessCSS) const preprocessedStyles = await preprocessStyles( source, createStylePreprocessor({ @@ -64,7 +63,6 @@ export async function compile({ }), ); - // Step 2: Transform (always sync) transformResult = transform(source, { compact: astroConfig.compressHTML, filename, diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 030dda2af0bb..cf529e2e143b 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -89,7 +89,6 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl }, async configResolved(viteConfig) { const toolbarEnabled = await settings.preferences.get('devToolbar.enabled'); - const useRustCompiler = config.experimental.rustCompiler; // Initialize `compile` function to simplify usage later compile = (code, filename) => { const compileProps = { @@ -99,7 +98,7 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl filename, source: code, }; - if (useRustCompiler) { + if (config.experimental.rustCompiler) { return compileAstroRs({ compileProps, astroFileToCompileMetadata, From 081412a2be510d80361b7322d50e13f7b320b889 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:44:09 +0100 Subject: [PATCH 10/13] Apply suggestions from code review --- packages/astro/src/types/public/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 8779bf5691b6..618875999e6e 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -2768,7 +2768,7 @@ export interface AstroUserConfig< * @name experimental.rustCompiler * @type {boolean} * @default `false` - * @version 5.x + * @version 6.0.0 * @description * * Enables the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`) as a replacement to the current Go compiler. From beece3afb5f3681252e7abebac6503b31a259b5d Mon Sep 17 00:00:00 2001 From: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:30:05 +0100 Subject: [PATCH 11/13] fix: add tests and bump --- packages/astro/package.json | 2 +- .../test/units/compile/rust-compiler.test.js | 145 ++++++++++++++++++ pnpm-lock.yaml | 94 ++++++------ 3 files changed, 193 insertions(+), 48 deletions(-) create mode 100644 packages/astro/test/units/compile/rust-compiler.test.js diff --git a/packages/astro/package.json b/packages/astro/package.json index bd8997cc35c2..3d8945f7d2cc 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -178,7 +178,7 @@ }, "devDependencies": { "@astrojs/check": "workspace:*", - "@astrojs/compiler-rs": "^0.1.0", + "@astrojs/compiler-rs": "^0.1.1", "@playwright/test": "1.58.2", "@types/aria-query": "^5.0.4", "@types/cssesc": "^3.0.2", diff --git a/packages/astro/test/units/compile/rust-compiler.test.js b/packages/astro/test/units/compile/rust-compiler.test.js new file mode 100644 index 000000000000..4487f68f1740 --- /dev/null +++ b/packages/astro/test/units/compile/rust-compiler.test.js @@ -0,0 +1,145 @@ +import * as assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { pathToFileURL } from 'node:url'; +import { resolveConfig } from 'vite'; +import { compile } from '../../../dist/core/compile/compile-rs.js'; + +/** + * @param {string} source + * @param {object} [configOverrides] + */ +async function compileWithRust(source, configOverrides = {}) { + const viteConfig = await resolveConfig({ configFile: false }, 'serve'); + return compile({ + astroConfig: { + root: pathToFileURL('/'), + base: '/', + experimental: { rustCompiler: true }, + compressHTML: false, + scopedStyleStrategy: 'attribute', + devToolbar: { enabled: false }, + site: undefined, + ...configOverrides, + }, + viteConfig, + toolbarEnabled: false, + filename: '/src/components/index.astro', + source, + }); +} + +describe('experimental.rustCompiler - core compile', () => { + it('compiles a basic Astro component', async () => { + const result = await compileWithRust('

Hello World

'); + assert.ok(result.code); + }); + + it('compiles a component with frontmatter', async () => { + const result = await compileWithRust(`\ +--- +const greeting = 'Hello'; +--- +

{greeting}

`); + assert.ok(result.code); + }); + + it('returns a source map', async () => { + const result = await compileWithRust('

Hello

'); + assert.ok(result.map); + }); + + it('returns a scope string', async () => { + const result = await compileWithRust('

Hello

'); + assert.equal(typeof result.scope, 'string'); + }); + + it('returns populated css array for styled components', async () => { + const result = await compileWithRust(`\ + +

Hello

`); + assert.ok(Array.isArray(result.css)); + assert.equal(result.css.length, 1); + assert.ok(result.css[0].code); + }); + + it('returns empty css array for unstyled components', async () => { + const result = await compileWithRust('

Hello

'); + assert.ok(Array.isArray(result.css)); + assert.equal(result.css.length, 0); + }); + + it('returns populated scripts array for components with scripts', async () => { + const result = await compileWithRust(`\ + +

Hello

`); + assert.ok(Array.isArray(result.scripts)); + assert.equal(result.scripts.length, 1); + }); + + it('returns empty scripts array for components without scripts', async () => { + const result = await compileWithRust('

Hello

'); + assert.ok(Array.isArray(result.scripts)); + assert.equal(result.scripts.length, 0); + }); + + it('detects head content', async () => { + const result = await compileWithRust(`\ + + My Page + +

Hello

`); + assert.equal(result.containsHead, true); + }); + + it('reports containsHead as false when no head element present', async () => { + const result = await compileWithRust('

Hello

'); + assert.equal(result.containsHead, false); + }); + + it('marks global styles with isGlobal', async () => { + const result = await compileWithRust(`\ + +

Global

`); + assert.equal(result.css.length, 1); + assert.equal(result.css[0].isGlobal, true); + }); + + it('marks scoped styles with isGlobal false', async () => { + const result = await compileWithRust(`\ + +

Scoped

`); + assert.equal(result.css.length, 1); + assert.equal(result.css[0].isGlobal, false); + }); + + it('returns one css entry per style block', async () => { + const result = await compileWithRust(`\ + + +

Hello

+

World

`); + assert.equal(result.css.length, 2); + }); + + it('throws a CompilerError on unclosed tags', async () => { + await assert.rejects( + () => compileWithRust('

Unclosed tag'), + (err) => { + assert.ok(err.message || err.name); + assert.ok(err.message.includes( "Unexpected token")); + return true; + }, + ); + }); + + it('handles empty component without throwing', async () => { + const result = await compileWithRust(''); + assert.ok(result.code !== undefined); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26242a88c21f..473871e3e29a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -675,8 +675,8 @@ importers: specifier: workspace:* version: link:../language-tools/astro-check '@astrojs/compiler-rs': - specifier: ^0.1.0 - version: 0.1.0 + specifier: ^0.1.1 + version: 0.1.1 '@playwright/test': specifier: 1.58.2 version: 1.58.2 @@ -7095,69 +7095,69 @@ packages: resolution: {integrity: sha512-bVzyKzEpIwqjihBU/aUzt1LQckJuHK0agd3/ITdXhPUYculrc6K1/K7H+XG4rwjXtg+ikT3PM05V1MVYWiIvQw==} engines: {node: '>=18.14.1'} - '@astrojs/compiler-binding-darwin-arm64@0.1.0': - resolution: {integrity: sha512-1uv15N1LZt3ss+y3VOIKCSicNaoOomb5D7VQuKxL4/QG1oier9ObPfIuxqTBxgtNUOPC87fYqiDkXRGxCi43Bg==} + '@astrojs/compiler-binding-darwin-arm64@0.1.1': + resolution: {integrity: sha512-W5jNq18skbt9a1eTou73O7c4GQwYiE41FGyvLeCYR44Btmd9SPLzPgwdsGIRyVEPuy9xXSMvFl4ncVEXn09CCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@astrojs/compiler-binding-darwin-x64@0.1.0': - resolution: {integrity: sha512-I4VE7nwODs2UPf430EtQDwrAgJmZsdlxvIK/JqRvpu6C2nmjwGT4CibO8KACn+iBpUL4Bxk3+Ax+p3tVwKc9Ww==} + '@astrojs/compiler-binding-darwin-x64@0.1.1': + resolution: {integrity: sha512-U8NKnstwZWix4nPuLXcMU4Ni3r4Qe4CdwdBMVYXkFaaO4XJwOWDgEH7BXntrRAa3uleFKMIqpthoU9zZw4uwKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@astrojs/compiler-binding-linux-arm64-gnu@0.1.0': - resolution: {integrity: sha512-VNorBIxOrT55hxSS1gdPYbM1RJHYL4ahOk15qINiwWFQPBb03vb5fY3zxUJrIXLdumb7Xvwt7TnrrGb6klFZbA==} + '@astrojs/compiler-binding-linux-arm64-gnu@0.1.1': + resolution: {integrity: sha512-UgEsnEd1yIwAYbeLRgavqAYKUNrMEtjwaNUcG0utEXPZpvvjexLS608q1UO0S96DVe4WBZkKrgcdmftbSJJdzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@astrojs/compiler-binding-linux-arm64-musl@0.1.0': - resolution: {integrity: sha512-VJUzo4vu6wY6ZkbDPofbzH4SyoinZGZzBCkZ75dGVZO1Z49J0fFb0NwRGd4VhGyrB6vBSa7fQTqZD04S2BYl2Q==} + '@astrojs/compiler-binding-linux-arm64-musl@0.1.1': + resolution: {integrity: sha512-b9L5SSM9jzrz1GRTE22OioPKLAxgq91FxTVcq09of23AATXDWpxCaX2J1d+7jdSa1ANuImVsnni/za370/6r0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@astrojs/compiler-binding-linux-x64-gnu@0.1.0': - resolution: {integrity: sha512-GxCimSZcsG+gOhMH2kZGCUuUxZ2TxPHny7phIgjmdyE1iAY/zOKWl+uHWF/QazFsug0Iba4WlvjDOrVUL5HTDA==} + '@astrojs/compiler-binding-linux-x64-gnu@0.1.1': + resolution: {integrity: sha512-E6mGtZlsFsQ67qqGGaJMQFxCLbyCazwlwJZH3hsTcjwo1Z6c2FIb43sQ8O8txY7GOIYMrPUO5+LiGCY6E6GgDg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@astrojs/compiler-binding-linux-x64-musl@0.1.0': - resolution: {integrity: sha512-cR1B9rmMmmOoD4APlGQ3ujkOQQrDKpOY0GsLexwbrgzprkVW+mvEqRfCwoi9lJO7xTWMlIF5goesEDjvMvmnxg==} + '@astrojs/compiler-binding-linux-x64-musl@0.1.1': + resolution: {integrity: sha512-aNXZhmx7xSkOOdYNrvSl8jiVRostLymkt4oVp5I+N4YBXUAhkoDsbp1yLR/5i81tkkslvXmQX+RDHM1rdXTckQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@astrojs/compiler-binding-wasm32-wasi@0.1.0': - resolution: {integrity: sha512-gy9SVv+GTlBfeO6AOrhaLUYUkuKCvjFr94jiMI15DBS/xIXkxm5CMut4Y7yq7GEB5xMByMUOb6a0Yk9ELu8aDQ==} + '@astrojs/compiler-binding-wasm32-wasi@0.1.1': + resolution: {integrity: sha512-Q3jcnpGVidSL8D5RAHFhwghc4/+95ZXJ2PQU4P0O3ATUY9ZUoROsWetLRtQ7/kdB6lUjJEKEQdTgn+OStxi+nw==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@astrojs/compiler-binding-win32-arm64-msvc@0.1.0': - resolution: {integrity: sha512-4lJSUe5qx+szLvly9FqtJ2BdAG5XEgaTk3anoq+JoaANA+pGk2mGGa50dTVcqzplVnqEQGY6+9dIklk/4hJ4IQ==} + '@astrojs/compiler-binding-win32-arm64-msvc@0.1.1': + resolution: {integrity: sha512-QEo8oZXYA1U3/+gNyHCpOYaAzRVmJFxH/BCHJjvqlUn8wKuNXEfSu3E/5QWA8YaN5jcQAgGv8YZjOcU6XynSag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@astrojs/compiler-binding-win32-x64-msvc@0.1.0': - resolution: {integrity: sha512-HVpiVsr3Wcy1/1GMfzwSc0oFRpJw9Mz7PJpI53/xBt/0ySPWDmL0FZXRHSQgEvAfTcVmh2P2LPivK5AkEDKuzg==} + '@astrojs/compiler-binding-win32-x64-msvc@0.1.1': + resolution: {integrity: sha512-1+kuUy+zZE8/Cj7NffzaBAubbUx9WXxKrucVKTTyPJJlVk7N18G4/PtewiHuAg4my3wORkNns12WN1xYrZ7cuQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@astrojs/compiler-binding@0.1.0': - resolution: {integrity: sha512-NKI0m+2zA8n5s4Vg9rrzOIqJsKHsVu+ASVxaCk2PNPksdDBiPIZC0Hq2/kqJfzwCaGnU0kplcF/rM6kYlGpx/Q==} + '@astrojs/compiler-binding@0.1.1': + resolution: {integrity: sha512-V2kVIy1jPi97stjCjAMPbbdcRxSRwGptlYyspiEYCK1RztcXFcjaYeXfB084R2MhhTdZXSIeMfi3dWQ0rVKBjA==} engines: {node: ^20.19.0 || >=22.12.0} - '@astrojs/compiler-rs@0.1.0': - resolution: {integrity: sha512-/ctF92BSNHLySrQgwa7z8U1SveNAsrZkumBybnoDmmtl1+ZV2p2ME7ODzk56xhidpwnP81V3VhPjWM8kWwq8fw==} + '@astrojs/compiler-rs@0.1.1': + resolution: {integrity: sha512-zzi4N9/FXLHs68cMF1NwxSWRRgtNhxaHOVGG4q9FPAIZlrvkU2kyhpoYYTkE9Ghp+oXOsF998AmdTP4uDqNxYw==} '@astrojs/compiler@2.13.1': resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} @@ -16320,50 +16320,50 @@ snapshots: log-update: 5.0.1 sisteransi: 1.0.5 - '@astrojs/compiler-binding-darwin-arm64@0.1.0': + '@astrojs/compiler-binding-darwin-arm64@0.1.1': optional: true - '@astrojs/compiler-binding-darwin-x64@0.1.0': + '@astrojs/compiler-binding-darwin-x64@0.1.1': optional: true - '@astrojs/compiler-binding-linux-arm64-gnu@0.1.0': + '@astrojs/compiler-binding-linux-arm64-gnu@0.1.1': optional: true - '@astrojs/compiler-binding-linux-arm64-musl@0.1.0': + '@astrojs/compiler-binding-linux-arm64-musl@0.1.1': optional: true - '@astrojs/compiler-binding-linux-x64-gnu@0.1.0': + '@astrojs/compiler-binding-linux-x64-gnu@0.1.1': optional: true - '@astrojs/compiler-binding-linux-x64-musl@0.1.0': + '@astrojs/compiler-binding-linux-x64-musl@0.1.1': optional: true - '@astrojs/compiler-binding-wasm32-wasi@0.1.0': + '@astrojs/compiler-binding-wasm32-wasi@0.1.1': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@astrojs/compiler-binding-win32-arm64-msvc@0.1.0': + '@astrojs/compiler-binding-win32-arm64-msvc@0.1.1': optional: true - '@astrojs/compiler-binding-win32-x64-msvc@0.1.0': + '@astrojs/compiler-binding-win32-x64-msvc@0.1.1': optional: true - '@astrojs/compiler-binding@0.1.0': + '@astrojs/compiler-binding@0.1.1': optionalDependencies: - '@astrojs/compiler-binding-darwin-arm64': 0.1.0 - '@astrojs/compiler-binding-darwin-x64': 0.1.0 - '@astrojs/compiler-binding-linux-arm64-gnu': 0.1.0 - '@astrojs/compiler-binding-linux-arm64-musl': 0.1.0 - '@astrojs/compiler-binding-linux-x64-gnu': 0.1.0 - '@astrojs/compiler-binding-linux-x64-musl': 0.1.0 - '@astrojs/compiler-binding-wasm32-wasi': 0.1.0 - '@astrojs/compiler-binding-win32-arm64-msvc': 0.1.0 - '@astrojs/compiler-binding-win32-x64-msvc': 0.1.0 - - '@astrojs/compiler-rs@0.1.0': - dependencies: - '@astrojs/compiler-binding': 0.1.0 + '@astrojs/compiler-binding-darwin-arm64': 0.1.1 + '@astrojs/compiler-binding-darwin-x64': 0.1.1 + '@astrojs/compiler-binding-linux-arm64-gnu': 0.1.1 + '@astrojs/compiler-binding-linux-arm64-musl': 0.1.1 + '@astrojs/compiler-binding-linux-x64-gnu': 0.1.1 + '@astrojs/compiler-binding-linux-x64-musl': 0.1.1 + '@astrojs/compiler-binding-wasm32-wasi': 0.1.1 + '@astrojs/compiler-binding-win32-arm64-msvc': 0.1.1 + '@astrojs/compiler-binding-win32-x64-msvc': 0.1.1 + + '@astrojs/compiler-rs@0.1.1': + dependencies: + '@astrojs/compiler-binding': 0.1.1 '@astrojs/compiler@2.13.1': {} From a39997f1b82bcd04c3eb580a65e11ff5b76a41df Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 23 Feb 2026 21:27:25 +0100 Subject: [PATCH 12/13] Update .changeset/rust-compiler-experimental.md Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/rust-compiler-experimental.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.changeset/rust-compiler-experimental.md b/.changeset/rust-compiler-experimental.md index f536aea175c5..c1ecddf811a2 100644 --- a/.changeset/rust-compiler-experimental.md +++ b/.changeset/rust-compiler-experimental.md @@ -2,6 +2,22 @@ 'astro': minor --- -Adds a new `experimental.rustCompiler` flag to opt into the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`). When enabled, `@astrojs/compiler-rs` must be installed into your project separately. +Adds a new `experimental.rustCompiler` flag to opt into the experimental Rust-based Astro compiler -This new compiler is still in early development and may exhibit some differences compared to the existing Go-based compiler. The only expected difference at this time is that this compiler is generally more strict in regard to invalid syntax and may throw errors in cases where the Go-based compiler would have been more lenient. For example, unclosed tags will now result in errors. +This experimental compiler is faster, provides better error messages, and generally has better support for modern JavaScript, TypeScript and CSS features. + +After enabling in your Astro config, the `@astrojs/compiler-rs` package must also be installed into your project separately: + +```js +import { defineConfig } from "astro/config"; + +export default defineConfig({ + experimental: { + rustCompiler: true + } +}); +``` + +This new compiler is still in early development and may exhibit some differences compared to the existing Go-based compiler. Notably, this compiler is generally more strict in regard to invalid HTML syntax and may throw errors in cases where the Go-based compiler would have been more lenient. For example, unclosed tags (e.g. `

My paragraph`) will now result in errors. + +For more information about using this experimental feature in your project, especially regarding expected differences and limitations, please see the [experimental Rust compiler reference docs](https://v6.docs.astro.build/en/reference/experimental-flags/rust-compiler/). To give feedback on the compiler, or to keep up with its development, see the [RFC for a new compiler for Astro](https://github.com/withastro/roadmap/discussions/1306) for more information and discussion. From fc2b0bfd7ccad0a59b07d99e3045f4d0ca0bf950 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:56:04 +0100 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: Yan <61414485+yanthomasdev@users.noreply.github.com> --- .changeset/rust-compiler-experimental.md | 2 +- packages/astro/src/types/public/config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/rust-compiler-experimental.md b/.changeset/rust-compiler-experimental.md index c1ecddf811a2..8d343f84b4b6 100644 --- a/.changeset/rust-compiler-experimental.md +++ b/.changeset/rust-compiler-experimental.md @@ -4,7 +4,7 @@ Adds a new `experimental.rustCompiler` flag to opt into the experimental Rust-based Astro compiler -This experimental compiler is faster, provides better error messages, and generally has better support for modern JavaScript, TypeScript and CSS features. +This experimental compiler is faster, provides better error messages, and generally has better support for modern JavaScript, TypeScript, and CSS features. After enabling in your Astro config, the `@astrojs/compiler-rs` package must also be installed into your project separately: diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 618875999e6e..c6da8eaedf5e 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -2773,7 +2773,7 @@ export interface AstroUserConfig< * * Enables the experimental Rust-based Astro compiler (`@astrojs/compiler-rs`) as a replacement to the current Go compiler. * - * This option requires installing the `@astrojs/compiler-rs` package manually in your project. This compiler is a work in progress and may not yet support all features of the current Go compiler, but should offer improved performance and better error messages. This compiler is more strict than the previous Go compiler in regards to invalid syntax. For instance, unclosed HTML tags or missing closing brackets will throw an error instead of being ignored. + * This option requires installing the `@astrojs/compiler-rs` package manually in your project. This compiler is a work in progress and may not yet support all features of the current Go compiler, but it should offer improved performance and better error messages. This compiler is more strict than the previous Go compiler regarding invalid syntax. For instance, unclosed HTML tags or missing closing brackets will throw an error instead of being ignored. * * ```js * // astro.config.mjs