MDX: Support dynamic require of meta files

This commit is contained in:
Fuma Nama
2025-11-12 21:37:48 +08:00
parent 00058c8f6e
commit 58bf979602
4 changed files with 33 additions and 21 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': patch
---
[Bun Loader] Support dynamic require of meta files
+1 -1
View File
@@ -3,4 +3,4 @@ import { postInstall } from 'fumadocs-mdx/next';
const configPath = 'source.script.ts';
await postInstall(configPath);
Bun.plugin(createMdxPlugin({ configPath, disableMetaFile: true }));
Bun.plugin(createMdxPlugin({ configPath }));
+26 -18
View File
@@ -8,6 +8,7 @@ import { parse } from 'node:querystring';
import { ValidationError } from '@/utils/validation';
import path from 'node:path';
import type { LoaderContext } from 'webpack';
import { readFileSync } from 'node:fs';
export interface LoaderInput {
development: boolean;
@@ -23,6 +24,8 @@ export interface LoaderOutput {
map?: unknown;
}
type Awaitable<T> = T | Promise<T>;
export interface Loader {
/**
* Filter file paths, the input can be either a file URL or file path.
@@ -38,13 +41,14 @@ export interface Loader {
* - `LoaderOutput`: JavaScript code & source map.
* - `null`: skip the loader. Fallback to default behaviour if possible, otherwise the adapter will try workarounds.
*/
load: (input: LoaderInput) => Promise<LoaderOutput | null>;
load: (input: LoaderInput) => Awaitable<LoaderOutput | null>;
bun?: {
/**
* Bun doesn't allow `null` in loaders, this is used as the fallback.
* 1. Bun doesn't allow `null` in loaders.
* 2. Bun requires sync result to support dynamic require().
*/
fallback?: (input: LoaderInput) => Promise<Bun.OnLoadResult>;
loadSync?: (source: string, input: LoaderInput) => Bun.OnLoadResult;
};
}
@@ -162,8 +166,19 @@ export function toWebpack(loader: Loader): WebpackLoader {
}
export function toBun(loader: Loader) {
function toResult(output: LoaderOutput | null): Bun.OnLoadResult {
// it errors, treat this as an exception
if (!output) return;
return {
contents: output.code,
loader: 'js',
};
}
return (build: Bun.PluginBuilder) => {
build.onLoad({ filter: loader.test ?? /.+/ }, async (args) => {
// avoid using async here, because it will cause dynamic require() to fail
build.onLoad({ filter: loader.test ?? /.+/ }, (args) => {
const [filePath, query = ''] = args.path.split('?', 2);
const input: LoaderInput = {
async getSource() {
@@ -177,22 +192,15 @@ export function toBun(loader: Loader) {
},
};
const result = await loader.load(input);
// must return something, no fallback: https://github.com/oven-sh/bun/issues/5303
if (result === null) {
if (!loader.bun?.fallback) {
// it errors, treat this as an exception
return;
}
return loader.bun.fallback(input);
if (loader.bun?.loadSync) {
return loader.bun.loadSync(readFileSync(filePath).toString(), input);
}
return {
contents: result.code,
loader: 'js',
};
const result = loader.load(input);
if (result instanceof Promise) {
return result.then(toResult);
}
return toResult(result);
});
};
}
+1 -2
View File
@@ -77,8 +77,7 @@ export function createMetaLoader(
};
},
bun: {
async fallback({ getSource, filePath }) {
const source = await getSource();
loadSync(source, { filePath }) {
const isJson = filePath.endsWith('.json');
let data: unknown;
try {