MDX: improve Next.js usage

This commit is contained in:
Fuma Nama
2025-01-10 21:47:17 +08:00
parent 6ad919a366
commit 7cc9f1f474
8 changed files with 82 additions and 55 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-mdx': patch
---
Support CommonJs usage temporarily
@@ -1,13 +1,12 @@
import createBundleAnalyzer from '@next/bundle-analyzer';
import { createMDX } from 'fumadocs-mdx/next';
import type { NextConfig } from 'next';
const withAnalyzer = createBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
/** @type {import('next').NextConfig} */
const config = {
const config: NextConfig = {
reactStrictMode: true,
eslint: {
// Replaced by root workspace command
+40 -35
View File
@@ -5,7 +5,6 @@ import {
frontmatterSchema,
metaSchema,
} from 'fumadocs-mdx/config';
import { rehypeCodeDefaultOptions } from 'fumadocs-core/mdx-plugins';
import { transformerTwoslash } from 'fumadocs-twoslash';
import remarkMath from 'remark-math';
import {
@@ -49,42 +48,48 @@ export const blog = defineCollections({
export default defineConfig({
lastModifiedTime: 'git',
mdxOptions: {
rehypeCodeOptions: {
inline: 'tailing-curly-colon',
themes: {
light: 'catppuccin-latte',
dark: 'catppuccin-mocha',
},
transformers: [
...(rehypeCodeDefaultOptions.transformers ?? []),
transformerTwoslash(),
{
name: 'transformers:remove-notation-escape',
code(hast) {
for (const line of hast.children) {
if (line.type !== 'element') continue;
mdxOptions: async () => {
const { rehypeCodeDefaultOptions } = await import(
'fumadocs-core/mdx-plugins'
);
const lastSpan = line.children.findLast(
(v) => v.type === 'element',
);
const head = lastSpan?.children[0];
if (head?.type !== 'text') return;
head.value = head.value.replace(/\[\\!code/g, '[!code');
}
},
return {
rehypeCodeOptions: {
inline: 'tailing-curly-colon',
themes: {
light: 'catppuccin-latte',
dark: 'catppuccin-mocha',
},
transformers: [
...(rehypeCodeDefaultOptions.transformers ?? []),
transformerTwoslash(),
{
name: 'transformers:remove-notation-escape',
code(hast) {
for (const line of hast.children) {
if (line.type !== 'element') continue;
const lastSpan = line.children.findLast(
(v) => v.type === 'element',
);
const head = lastSpan?.children[0];
if (head?.type !== 'text') return;
head.value = head.value.replace(/\[\\!code/g, '[!code');
}
},
},
],
},
remarkPlugins: [
remarkMermaid,
remarkMath,
[remarkInstall, { persist: { id: 'package-manager' } }],
[remarkDocGen, { generators: [fileGenerator()] }],
remarkTypeScriptToJavaScript,
],
},
remarkPlugins: [
remarkMermaid,
remarkMath,
[remarkInstall, { persist: { id: 'package-manager' } }],
[remarkDocGen, { generators: [fileGenerator()] }],
remarkTypeScriptToJavaScript,
],
rehypePlugins: (v) => [rehypeKatex, ...v],
rehypePlugins: (v) => [rehypeKatex, ...v],
};
},
});
+6 -3
View File
@@ -15,15 +15,18 @@
"./loader-mdx": "./loader-mdx.cjs",
"./config": {
"import": "./dist/config/index.js",
"types": "./dist/config/index.d.ts"
"types": "./dist/config/index.d.ts",
"require": "./dist/config/index.cjs"
},
"./next": {
"import": "./dist/next/index.js",
"types": "./dist/next/index.d.ts"
"types": "./dist/next/index.d.ts",
"require": "./dist/next/index.cjs"
},
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.js",
+23 -11
View File
@@ -2,8 +2,7 @@ import * as path from 'node:path';
import type { DocCollection, MetaCollection } from '@/config/define';
import { type GlobalConfig } from '@/config/types';
import type { ProcessorOptions } from '@mdx-js/mdx';
import { getDefaultMDXOptions } from '@/utils/mdx-options';
import { pathToFileURL } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';
export function findConfigFile(): string {
return path.resolve('source.config.ts');
@@ -11,7 +10,7 @@ export function findConfigFile(): string {
export interface LoadedConfig {
collections: Map<string, InternalDocCollection | InternalMetaCollection>;
defaultMdxOptions: ProcessorOptions;
getDefaultMDXOptions: () => Promise<ProcessorOptions>;
global?: GlobalConfig;
_runtime: {
@@ -26,8 +25,9 @@ export type InternalDocCollection = DocCollection;
export type InternalMetaCollection = MetaCollection;
export async function loadConfig(configPath: string): Promise<LoadedConfig> {
const outputPath = path.resolve('.source/source.config.mjs');
const { build } = await import('esbuild');
const url = pathToFileURL(path.resolve('.source/source.config.mjs'));
const transformed = await build({
entryPoints: [{ in: configPath, out: 'source.config' }],
bundle: true,
@@ -41,20 +41,18 @@ export async function loadConfig(configPath: string): Promise<LoadedConfig> {
'.js': '.mjs',
},
allowOverwrite: true,
splitting: true,
});
if (transformed.errors.length > 0) {
throw new Error('failed to compile configuration file');
}
const url = pathToFileURL(outputPath);
// every call to `loadConfig` should cause the previous cache to be ignored
const loaded = await import(`${url.href}?hash=${Date.now().toString()}`);
const [err, config] = buildConfig(
// every call to `loadConfig` will cause the previous cache to be ignored
(await import(`${url.href}?hash=${Date.now().toString()}`)) as Record<
string,
unknown
>,
loaded as Record<string, unknown>,
);
if (err !== null) throw new Error(err);
@@ -91,12 +89,26 @@ function buildConfig(
];
}
let cachedMdxOptions: ProcessorOptions | undefined;
return [
null,
{
global: globalConfig,
collections,
defaultMdxOptions: getDefaultMDXOptions(globalConfig?.mdxOptions ?? {}),
async getDefaultMDXOptions() {
if (cachedMdxOptions) return cachedMdxOptions;
const { getDefaultMDXOptions } = await import('@/utils/mdx-options');
const mdxOptions = globalConfig?.mdxOptions ?? {};
if (typeof mdxOptions === 'function') {
cachedMdxOptions = getDefaultMDXOptions(await mdxOptions());
} else {
cachedMdxOptions = getDefaultMDXOptions(mdxOptions);
}
return cachedMdxOptions;
},
_runtime: {
files: new Map(),
},
+3 -1
View File
@@ -8,7 +8,9 @@ export interface GlobalConfig {
/**
* Configure global MDX options
*/
mdxOptions?: DefaultMDXOptions;
mdxOptions?:
| DefaultMDXOptions
| (() => DefaultMDXOptions | Promise<DefaultMDXOptions>);
/**
* Fetch last modified time with specified version control
+2 -1
View File
@@ -64,7 +64,8 @@ export default async function loader(
collection = undefined;
}
const mdxOptions = collection?.mdxOptions ?? config.defaultMdxOptions;
const mdxOptions =
collection?.mdxOptions ?? (await config.getDefaultMDXOptions());
let frontmatter = matter.data;
if (collection?.schema) {
+1 -1
View File
@@ -2,7 +2,7 @@ import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['./src/{index,loader-mdx}.ts', './src/{config,next}/index.ts'],
format: 'esm',
format: ['esm', 'cjs'],
external: ['next', 'typescript'],
dts: true,
target: 'node18',