Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/rust-compiler-experimental.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'astro': minor
---

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.

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. `<p>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.
1 change: 1 addition & 0 deletions .github/workflows/continuous_benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ env:

jobs:
codspeed:
if: ${{ github.repository_owner == 'withastro' }}
runs-on: ubuntu-latest
strategy:
matrix:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/examples-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ on:

jobs:
deploy:
if: ${{ github.repository_owner == 'withastro' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Send a POST request to Netlify to rebuild preview.astro.new
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ jobs:
- name: Build
run: pnpm build

- name: Log in to GHCR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Pull sandbox image
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker pull $IMAGE:latest
run: docker pull $IMAGE:latest

- name: Verify sandbox image
run: |
Expand Down
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@
},
"devDependencies": {
"@astrojs/check": "workspace:*",
"@astrojs/compiler-rs": "^0.1.1",
"@playwright/test": "1.58.2",
"@types/aria-query": "^5.0.4",
"@types/cssesc": "^3.0.2",
Expand Down
155 changes: 155 additions & 0 deletions packages/astro/src/core/compile/compile-rs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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<CompileResult> {
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}`,
);
}

const cssPartialCompileResults: PartialCompileCssResult[] = [];
const cssTransformErrors: AstroError[] = [];
let transformResult: any;

try {
const preprocessedStyles = await preprocessStyles(
source,
createStylePreprocessor({
filename,
viteConfig,
astroConfig,
cssPartialCompileResults,
cssTransformErrors,
}),
);

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;
}
}
21 changes: 18 additions & 3 deletions packages/astro/src/core/compile/style.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import fs from 'node:fs';
import type { TransformOptions } 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';
Expand All @@ -8,6 +7,20 @@ import type { CompileCssResult } from './types.js';

export type PartialCompileCssResult = Pick<CompileCssResult, 'isGlobal' | 'dependencies'>;

interface PreprocessorResult {
code: string;
map?: string;
}

interface PreprocessorError {
error: string;
}

export type PreprocessStyleFn = (
content: string,
attrs: Record<string, string>,
) => Promise<PreprocessorResult | PreprocessorError>;

/**
* Rewrites absolute URLs in CSS to include the base path.
*
Expand Down Expand Up @@ -90,7 +103,7 @@ export function createStylePreprocessor({
astroConfig: AstroConfig;
cssPartialCompileResults: Partial<CompileCssResult>[];
cssTransformErrors: Error[];
}): TransformOptions['preprocessStyle'] {
}): PreprocessStyleFn {
let processedStylesCount = 0;

return async (content, attrs) => {
Expand All @@ -105,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)) : [],
};

Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/config/schemas/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
contentIntellisense: false,
chromeDevtoolsWorkspace: false,
svgo: false,
rustCompiler: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };

Expand Down Expand Up @@ -493,6 +494,7 @@ export const AstroConfigSchema = z.object({
.union([z.boolean(), z.custom<SvgoConfig>((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
Expand Down
24 changes: 24 additions & 0 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2763,6 +2763,30 @@ 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 6.0.0
* @description
*
* 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 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
* import { defineConfig } from 'astro/config';
*
* export default defineConfig({
* experimental: {
* rustCompiler: true,
* },
* });
* ```
*/
rustCompiler?: boolean;
};
}

Expand Down
52 changes: 52 additions & 0 deletions packages/astro/src/vite-plugin-astro/compile-rs.ts
Original file line number Diff line number Diff line change
@@ -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<string, CompileMetadata>;
}

export interface CompileAstroResult extends Omit<CompileResult, 'map'> {
map: SourceMapInput;
}

export async function compileAstro({
compileProps,
astroFileToCompileMetadata,
}: CompileAstroOption): Promise<CompileAstroResult> {
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,
};
}
3 changes: 2 additions & 1 deletion packages/astro/src/vite-plugin-astro/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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;
Expand All @@ -13,7 +14,7 @@ interface CompileAstroOption {
}

export interface CompileAstroResult extends Omit<CompileResult, 'map'> {
map: ESBuildTransformResult['map'];
map: SourceMapInput;
}

interface EnhanceCompilerErrorOptions {
Expand Down
Loading
Loading