mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Support Rolldown integration
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': patch
|
||||
---
|
||||
|
||||
Support Rolldown integration
|
||||
@@ -29,6 +29,7 @@
|
||||
"./plugins/index-file": "./dist/plugins/index-file.js",
|
||||
"./plugins/json-schema": "./dist/plugins/json-schema.js",
|
||||
"./plugins/last-modified": "./dist/plugins/last-modified.js",
|
||||
"./rolldown": "./dist/rolldown/index.js",
|
||||
"./runtime/browser": "./dist/runtime/browser.js",
|
||||
"./runtime/dynamic": "./dist/runtime/dynamic.js",
|
||||
"./runtime/server": "./dist/runtime/server.js",
|
||||
@@ -96,9 +97,13 @@
|
||||
"mdast-util-directive": "*",
|
||||
"next": "^15.3.0 || ^16.0.0",
|
||||
"react": "^19.2.0",
|
||||
"rolldown": "*",
|
||||
"vite": "7.x.x || 8.x.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rolldown": {
|
||||
"optional": true
|
||||
},
|
||||
"mdast-util-directive": {
|
||||
"optional": true
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { CompilerOptions } from '@/loaders/mdx/build-mdx';
|
||||
import type { LoadFnOutput, LoadHook } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import fs from 'node:fs/promises';
|
||||
import type { TransformPluginContext } from 'rolldown';
|
||||
import type { HookFilter, TransformPluginContext } from 'rolldown';
|
||||
import type { Environment, TransformResult } from 'vite';
|
||||
import { parse } from 'node:querystring';
|
||||
import { ValidationError } from '@/utils/validation';
|
||||
@@ -90,10 +90,10 @@ export function toNode(loader: Loader): LoadHook {
|
||||
}
|
||||
|
||||
export interface ViteLoader {
|
||||
filter: (id: string) => boolean;
|
||||
filter: HookFilter;
|
||||
|
||||
transform: (
|
||||
this: TransformPluginContext & { environment: Environment },
|
||||
this: TransformPluginContext & { environment?: Environment },
|
||||
value: string,
|
||||
id: string,
|
||||
) => Promise<TransformResult | null>;
|
||||
@@ -101,33 +101,40 @@ export interface ViteLoader {
|
||||
|
||||
export function toVite(loader: Loader): ViteLoader {
|
||||
return {
|
||||
filter(id) {
|
||||
return !loader.test || loader.test.test(id);
|
||||
filter: {
|
||||
id: loader.test,
|
||||
},
|
||||
async transform(value, id) {
|
||||
const environment = this.environment;
|
||||
const [file, query = ''] = id.split('?', 2);
|
||||
|
||||
const result = await loader.load({
|
||||
filePath: file,
|
||||
query: parse(query),
|
||||
getSource() {
|
||||
return value;
|
||||
},
|
||||
development: environment.mode === 'dev',
|
||||
compiler: {
|
||||
addDependency: (file) => {
|
||||
this.addWatchFile(file);
|
||||
try {
|
||||
const result = await loader.load({
|
||||
filePath: file,
|
||||
query: parse(query),
|
||||
getSource() {
|
||||
return value;
|
||||
},
|
||||
},
|
||||
});
|
||||
development: this.environment ? this.environment.mode === 'dev' : false,
|
||||
compiler: {
|
||||
addDependency: (file) => {
|
||||
this.addWatchFile(file);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result === null) return null;
|
||||
return {
|
||||
code: result.code,
|
||||
map: result.map as TransformResult['map'],
|
||||
moduleType: result.moduleType,
|
||||
};
|
||||
if (result === null) return null;
|
||||
return {
|
||||
code: result.code,
|
||||
map: result.map as TransformResult['map'],
|
||||
moduleType: result.moduleType,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
throw new Error(await e.toStringFormatted(), { cause: e });
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { frontmatter } from 'fumadocs-core/content/md/frontmatter';
|
||||
import type { Loader } from '@/loaders/adapter';
|
||||
import type { Loader, LoaderOutput } from '@/loaders/adapter';
|
||||
import { z } from 'zod';
|
||||
import type { DocCollectionItem } from '@/config/build';
|
||||
import fs from 'node:fs/promises';
|
||||
@@ -96,8 +96,9 @@ export function createMdxLoader({ getCore }: ConfigLoader): Loader {
|
||||
environment: 'bundler',
|
||||
});
|
||||
|
||||
const out = {
|
||||
const out: LoaderOutput = {
|
||||
code: String(compiled.value),
|
||||
moduleType: 'js',
|
||||
map: compiled.map,
|
||||
};
|
||||
|
||||
|
||||
@@ -271,7 +271,10 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
|
||||
const attributes = parseElementAttributes(node);
|
||||
const { file: relativePath, section } = parseSpecifier(specifier);
|
||||
const targetPath = path.resolve('cwd' in attributes ? file.cwd : file.dirname!, relativePath);
|
||||
const targetPath = path.resolve(
|
||||
'cwd' in attributes || !file.dirname ? file.cwd : file.dirname,
|
||||
relativePath,
|
||||
);
|
||||
|
||||
queue.push(
|
||||
embedContent(targetPath, section, attributes, file).then((replace) => {
|
||||
|
||||
@@ -181,7 +181,7 @@ async function init(dev: boolean, core: Core): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function postInstall(options: CreateMDXOptions) {
|
||||
export async function postInstall(options: CreateMDXOptions = {}) {
|
||||
const core = createNextCore(applyDefaults(options));
|
||||
await core.init({
|
||||
config: loadConfig(core, true),
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface NodeLoaderOptions extends Partial<CoreOptions> {
|
||||
}
|
||||
|
||||
export function register(options: NodeLoaderOptions = {}) {
|
||||
module.register('./_loader.mjs', import.meta.url, {
|
||||
module.register('./_loader.js', import.meta.url, {
|
||||
data: options,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { buildConfig } from '@/config/build';
|
||||
import { _Defaults, createCore } from '@/core';
|
||||
import { mdxLoaderGlob, metaLoaderGlob } from '@/loaders';
|
||||
import { toVite } from '@/loaders/adapter';
|
||||
import { createIntegratedConfigLoader } from '@/loaders/config';
|
||||
import { createMdxLoader } from '@/loaders/mdx';
|
||||
import { createMetaLoader } from '@/loaders/meta';
|
||||
import fs from 'node:fs/promises';
|
||||
import type { RolldownPlugin } from 'rolldown';
|
||||
|
||||
export interface PluginOptions {
|
||||
configPath?: string;
|
||||
outDir?: string;
|
||||
}
|
||||
|
||||
export default async function mdx(
|
||||
config: Record<string, unknown> | Promise<Record<string, unknown>>,
|
||||
options?: PluginOptions,
|
||||
): Promise<RolldownPlugin[]> {
|
||||
const core = createCore({
|
||||
configPath: options?.configPath ?? _Defaults.configPath,
|
||||
outDir: options?.outDir ?? _Defaults.outDir,
|
||||
environment: 'rolldown',
|
||||
});
|
||||
await core.init({
|
||||
config: buildConfig(await config),
|
||||
});
|
||||
|
||||
const configLoader = createIntegratedConfigLoader(core);
|
||||
const mdxLoader = toVite(createMdxLoader(configLoader));
|
||||
const metaLoader = toVite(
|
||||
createMetaLoader(configLoader, {
|
||||
// rolldown has built-in plugin for JSON files
|
||||
json: 'json',
|
||||
}),
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'fumadocs-mdx',
|
||||
load: {
|
||||
filter: { id: [metaLoaderGlob, mdxLoaderGlob] },
|
||||
// Rolldown couldn't read the correct file path when query params exist.
|
||||
async handler(id) {
|
||||
const idx = id.lastIndexOf('?');
|
||||
if (idx === -1) return null;
|
||||
|
||||
return fs.readFile(id.slice(0, idx), 'utf-8');
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fumadocs-mdx:mdx',
|
||||
transform: {
|
||||
filter: mdxLoader.filter,
|
||||
handler: mdxLoader.transform,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fumadocs-mdx:meta',
|
||||
transform: {
|
||||
filter: metaLoader.filter,
|
||||
handler: metaLoader.transform,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { runnerImport, type Plugin } from 'vite';
|
||||
import { type PluginOption, runnerImport } from 'vite';
|
||||
import { buildConfig } from '@/config/build';
|
||||
import { ValidationError } from '@/utils/validation';
|
||||
import { createMdxLoader } from '@/loaders/mdx';
|
||||
import { toVite } from '@/loaders/adapter';
|
||||
import type { FSWatcher } from 'chokidar';
|
||||
@@ -40,7 +39,7 @@ export interface PluginOptions {
|
||||
export default async function mdx(
|
||||
_config?: Record<string, unknown> | Promise<Record<string, unknown>>,
|
||||
pluginOptions: PluginOptions = {},
|
||||
): Promise<Plugin> {
|
||||
): Promise<PluginOption> {
|
||||
const options = applyDefaults(pluginOptions);
|
||||
const core = createViteCore(options);
|
||||
const config =
|
||||
@@ -58,47 +57,47 @@ export default async function mdx(
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
name: 'fumadocs-mdx',
|
||||
// needed, otherwise other plugins will be executed before our `transform`.
|
||||
enforce: 'pre',
|
||||
async config() {
|
||||
if (!options.updateViteConfig) return;
|
||||
return [
|
||||
{
|
||||
name: 'fumadocs-mdx',
|
||||
async config() {
|
||||
if (!options.updateViteConfig) return;
|
||||
|
||||
const { getConfig } = await import('@fumadocs/vite');
|
||||
return getConfig({ root: process.cwd() });
|
||||
const { getConfig } = await import('@fumadocs/vite');
|
||||
return getConfig({ root: process.cwd() });
|
||||
},
|
||||
async buildStart() {
|
||||
await core.emit({ write: true });
|
||||
},
|
||||
async configureServer(server) {
|
||||
await core.initServer({
|
||||
watcher: server.watcher as unknown as FSWatcher,
|
||||
});
|
||||
},
|
||||
},
|
||||
async buildStart() {
|
||||
await core.emit({ write: true });
|
||||
{
|
||||
name: 'fumadocs-mdx:mdx',
|
||||
enforce: 'pre',
|
||||
transform: {
|
||||
filter: mdxLoader.filter,
|
||||
async handler(code, id) {
|
||||
// Vite RSC will pass the compiled MDX file's client module with ID `virtual:vite-rsc/client-references/group/facade:xxx.mdx`.
|
||||
// The format of `value` becomes JavaScript, which will break the MDX compiler.
|
||||
// We have to ignore them.
|
||||
if (id.includes('virtual:vite-rsc')) return null;
|
||||
return await mdxLoader.transform.call(this, code, id);
|
||||
},
|
||||
},
|
||||
},
|
||||
async configureServer(server) {
|
||||
await core.initServer({
|
||||
watcher: server.watcher as unknown as FSWatcher,
|
||||
});
|
||||
{
|
||||
name: 'fumadocs-mdx:meta',
|
||||
enforce: 'pre',
|
||||
transform: {
|
||||
filter: metaLoader.filter,
|
||||
handler: metaLoader.transform,
|
||||
},
|
||||
},
|
||||
async transform(value, id) {
|
||||
// Vite RSC will pass the compiled MDX file's client module with ID `virtual:vite-rsc/client-references/group/facade:xxx.mdx`.
|
||||
// The format of `value` becomes JavaScript, which will break the MDX compiler.
|
||||
// We have to ignore them.
|
||||
if (id.includes('virtual:vite-rsc')) return null;
|
||||
|
||||
try {
|
||||
if (metaLoader.filter(id)) {
|
||||
return await metaLoader.transform.call(this, value, id);
|
||||
}
|
||||
|
||||
if (mdxLoader.filter(id)) {
|
||||
return await mdxLoader.transform.call(this, value, id);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
throw new Error(await e.toStringFormatted(), { cause: e });
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
export async function postInstall(pluginOptions: PluginOptions = {}) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineConfig } from 'tsdown';
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
'./src/{index,bin}.ts',
|
||||
'./src/{config,next,vite,bun}/index.ts',
|
||||
'./src/{config,next,vite,bun,rolldown}/index.ts',
|
||||
'./src/webpack/{mdx,meta}.ts',
|
||||
'./src/node/{index,_loader,loader}.ts',
|
||||
'./src/runtime/*.{ts,tsx}',
|
||||
|
||||
Reference in New Issue
Block a user