Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,21 @@ jobs:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"

# for checking if rust-parser is working
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@1fbea72663f6d4c03efaab13560c8a24cfd2a7cc # v1.9.0
with:
toolchain: stable

- name: Install dependencies
run: pnpm install

# for checking if rust-parser is working
- name: Build MDX Rust Parser
run: |
cd packages/integrations/mdx/src/rust-parser
pnpm run build

# Only build in ubuntu as windows can share the build cache.
# Also only build in core repo as forks don't have access to the Turbo cache.
- name: Build Packages
Expand Down Expand Up @@ -89,9 +101,19 @@ jobs:
node-version: 22
cache: "pnpm"

- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@1fbea72663f6d4c03efaab13560c8a24cfd2a7cc # v1.9.0
with:
toolchain: stable

- name: Install dependencies
run: pnpm install

- name: Build MDX Rust Parser
run: |
cd packages/integrations/mdx/src/rust-parser
pnpm run build

- name: Build Packages
# The cache doesn't contain prebuild files and causes knip to fail
run: pnpm run build --force
Expand Down Expand Up @@ -135,9 +157,19 @@ jobs:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"

- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@1fbea72663f6d4c03efaab13560c8a24cfd2a7cc # v1.9.0
with:
toolchain: stable

- name: Install dependencies
run: pnpm install

- name: Build MDX Rust Parser
run: |
cd packages/integrations/mdx/src/rust-parser
pnpm run build

- name: Build Packages
run: pnpm run build

Expand Down Expand Up @@ -172,9 +204,19 @@ jobs:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"

- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@1fbea72663f6d4c03efaab13560c8a24cfd2a7cc # v1.9.0
with:
toolchain: stable

- name: Install dependencies
run: pnpm install

- name: Build MDX Rust Parser
run: |
cd packages/integrations/mdx/src/rust-parser
pnpm run build

- name: Build Packages
run: pnpm run build

Expand Down Expand Up @@ -208,6 +250,11 @@ jobs:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"

- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@1fbea72663f6d4c03efaab13560c8a24cfd2a7cc # v1.9.0
with:
toolchain: stable

- name: Checkout docs
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
Expand All @@ -226,6 +273,11 @@ jobs:
- name: Reset lockfile changes
run: git reset --hard

- name: Build MDX Rust Parser
run: |
cd packages/integrations/mdx/src/rust-parser
pnpm run build

- name: Build Packages
run: pnpm run build

Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ examples/**/env.d.ts
# want to share with others (see
# https://github.com/withastro/astro/pull/11759#discussion_r1721444711)
*.code-workspace

# re-think this
# Rust build artifacts
packages/integrations/mdx/src/rust-parser/target/
packages/integrations/mdx/src/rust-parser/*.node
packages/*/target/
**/*.node
2 changes: 1 addition & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
"files": {
"includes": ["**", "!**/smoke/**", "!**/fixtures/**", "!**/_temp-fixtures/**", "!**/vendor/**"]
"includes": ["**", "!**/smoke/**", "!**/fixtures/**", "!**/_temp-fixtures/**", "!**/vendor/**", "!**/rust-parser/**"]
},
"vcs": {
"enabled": true,
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default [
'scripts/',
'.github/',
'.changeset/',
'**/rust-parser/**',
],
},

Expand Down
1 change: 1 addition & 0 deletions knip.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default {
},
'packages/integrations/mdx': {
entry: [testEntry],
ignore: ['src/rust-parser/**'],
// Required but not imported directly
ignoreDependencies: ['@types/*'],
},
Expand Down
33 changes: 33 additions & 0 deletions packages/astro/src/core/config/schemas/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,16 @@ export const ASTRO_CONFIG_DEFAULTS = {
liveContentCollections: false,
csp: false,
rawEnvValues: false,
mdxCompiler: 'js' as const,
},
} satisfies AstroUserConfig & { server: { open: boolean } };

const highlighterTypesSchema = z
.union([z.literal('shiki'), z.literal('prism')])
.default(syntaxHighlightDefaults.type);

const mdxCompilerSchema = z.enum(['js', 'rs']).default('js');

export const AstroConfigSchema = z.object({
root: z
.string()
Expand Down Expand Up @@ -375,6 +378,35 @@ export const AstroConfigSchema = z.object({
smartypants: z.boolean().default(ASTRO_CONFIG_DEFAULTS.markdown.smartypants),
})
.default({}),
mdx: z
.object({
gfm: z.boolean().optional().default(true),
smartypants: z.boolean().optional().default(true),
remarkPlugins: z
.union([
z.string(),
z.tuple([z.string(), z.any()]),
z.custom<RemarkPlugin>((data) => typeof data === 'function'),
z.tuple([z.custom<RemarkPlugin>((data) => typeof data === 'function'), z.any()]),
])
.array()
.optional()
.default([]),
rehypePlugins: z
.union([
z.string(),
z.tuple([z.string(), z.any()]),
z.custom<RehypePlugin>((data) => typeof data === 'function'),
z.tuple([z.custom<RehypePlugin>((data) => typeof data === 'function'), z.any()]),
])
.array()
.optional()
.default([]),
remarkRehype: z
.custom<RemarkRehype>((data) => data instanceof Object && !Array.isArray(data))
.optional(),
})
.optional(),
vite: z
.custom<ViteUserConfig>((data) => data instanceof Object && !Array.isArray(data))
.default(ASTRO_CONFIG_DEFAULTS.vite),
Expand Down Expand Up @@ -502,6 +534,7 @@ export const AstroConfigSchema = z.object({
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.csp),
rawEnvValues: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.rawEnvValues),
mdxCompiler: mdxCompilerSchema,
})
.strict(
`Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/experimental-flags/ for a list of all current experiments.`,
Expand Down
96 changes: 96 additions & 0 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,78 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
remarkRehype?: RemarkRehype;
};

/**
* @docs
* @kind heading
* @name mdx
* @type {object}
* @description
*
* Configuration options for MDX files when used with the @astrojs/mdx integration.
* These options override the default markdown options when processing .mdx files.
*
* ```js
* {
* mdx: {
* gfm: true,
* smartypants: true,
* remarkPlugins: [],
* rehypePlugins: [],
* }
* }
* ```
*/
mdx?: {
/**
* @docs
* @name mdx.gfm
* @type {boolean}
* @default `true`
* @description
* Enable GitHub Flavored Markdown (GFM) in MDX files.
*/
gfm?: boolean;

/**
* @docs
* @name mdx.smartypants
* @type {boolean}
* @default `true`
* @description
* Enable smart typography in MDX files.
*/
smartypants?: boolean;

/**
* @docs
* @name mdx.remarkPlugins
* @type {RemarkPlugin[]}
* @default `[]`
* @description
* Remark plugins to apply to MDX files.
*/
remarkPlugins?: RemarkPlugins;

/**
* @docs
* @name mdx.rehypePlugins
* @type {RehypePlugin[]}
* @default `[]`
* @description
* Rehype plugins to apply to MDX files.
*/
rehypePlugins?: RehypePlugins;

/**
* @docs
* @name mdx.remarkRehype
* @type {RemarkRehype}
* @description
* Options to pass to remark-rehype for MDX files.
*/
remarkRehype?: RemarkRehype;
};

/**
* @docs
* @kind heading
Expand Down Expand Up @@ -2458,6 +2530,30 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
* See the [experimental raw environment variables guide](https://docs.astro.build/en/reference/experimental-flags/raw-env-values/) for more information.
*/
rawEnvValues?: boolean;

/**
* @docs
* @name experimental.mdxCompiler
* @type {'js' | 'rs'}
* @default `'js'`
* @description
* Select the MDX compiler to use for processing MDX files.
*
* - `'js'` - Use the standard @mdx-js/mdx JavaScript compiler (default)
* - `'rs'` - Use Rust-powered AST bridge (Rust parser + JS plugins + Rust codegen)
*
* The 'rs' mode provides faster MDX compilation while maintaining full
* compatibility with JavaScript plugins through the AST bridge approach.
*
* ```js
* {
* experimental: {
* mdxCompiler: 'rs',
* }
* }
* ```
*/
mdxCompiler?: 'js' | 'rs';
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/integrations/mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
],
"scripts": {
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
"build:rust": "cd src/rust-parser && pnpm run build",
"build:ci": "astro-scripts build \"src/**/*.ts\"",
"dev": "astro-scripts dev \"src/**/*.ts\"",
"test": "astro-scripts test --timeout 70000 \"test/**/*.test.js\""
Expand Down
60 changes: 60 additions & 0 deletions packages/integrations/mdx/src/compiler-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { AstroIntegrationLogger } from 'astro';
import type { MdxOptions } from './index.js';

type CompilerMode = 'js' | 'rs';

/**
* Routes to the appropriate MDX compiler based on configuration
*/
export async function routeToCompiler(
options: MdxOptions,
mode: CompilerMode,
logger: AstroIntegrationLogger,
) {
logger.info(`MDX: Requested compiler mode: ${mode}`);

// For 'rs' mode, try to use Rust compiler
if (mode === 'rs') {
logger.info('MDX: Attempting to use Rust compiler...');
try {
// Check if mdx-rs-parser is available
const { isRustCompilerAvailable } = await import('./processors/rust.js');

if (await isRustCompilerAvailable()) {
logger.info('MDX: Rust compiler available, using high-performance compilation');
const { createRustProcessor } = await import('./processors/rust.js');
const processor = await createRustProcessor(options);

// Wrap processor to log metrics if enabled
if (process.env.MDX_PERF_LOG) {
return {
async process(vfile: any) {
const start = performance.now();
const result = await processor.process(vfile);
const time = performance.now() - start;
logger.info(
`MDX Performance: ${vfile.path || 'unknown'} - ${time.toFixed(2)}ms (rust)`,
);
return result;
},
};
}
return processor;
} else {
logger.info('MDX: Rust compiler not available (mdx-rs-parser not built)');
}
} catch (error: any) {
logger.warn(`MDX: Rust compiler initialization failed: ${error.message}`);
}

// Fall back to JS processor
logger.info('MDX: Falling back to JS processor');
const { createJSProcessor } = await import('./processors/js.js');
return createJSProcessor(options);
}

// Default to JS processor
logger.info('MDX: Using standard JS processor');
const { createJSProcessor } = await import('./processors/js.js');
return createJSProcessor(options);
}
Loading
Loading