You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在无磁盘访问下将带组件的MDX2编译为静态HTML?

无磁盘MDX2静态HTML编译方案(支持自定义组件)

核心逻辑

放弃hack式字符串拼接,基于unified生态的AST(抽象语法树)流程实现:

  1. 将自定义组件字符串预编译为可直接生成Hast节点的渲染函数
  2. 处理主MDX字符串时,在Esast(ECMAScript AST)阶段拦截组件引用,替换为对应组件的Hast输出
  3. 最终将整合后的Hast转换为静态HTML

具体实现步骤

1. 依赖准备

使用以下核心包:

  • @mdx-js/mdx:MDX到Esast的编译
  • unified:AST处理管道
  • estree-util-is-identifier-name:识别Esast中的组件标识符
  • hast-util-to-html:Hast转HTML
  • unist-util-visit:遍历AST节点
  • react-to-hast:React元素转Hast节点
  • jsdom:AWS Lambda环境模拟浏览器DOM

2. 预编译自定义组件

将组件字符串转换为可生成Hast的函数(全程内存操作,无磁盘读写):

import { compile } from '@mdx-js/mdx'
import { evaluate } from '@mdx-js/mdx/lib/evaluate.js'
import { JSDOM } from 'jsdom'
import { reactToHast } from 'react-to-hast'

// Lambda环境初始化DOM
const dom = new JSDOM('')
globalThis.document = dom.window.document

// 预编译组件为Hast生成函数
async function precompileComponent(componentStr) {
  const compiled = await compile(componentStr, { outputFormat: 'function-body' })
  const { default: Component } = await evaluate(compiled, {
    exports: {},
    runtime: await import('@mdx-js/react/lib/runtime.js')
  })
  
  return (props) => reactToHast(Component(props))
}

3. 拦截MDX组件引用并替换

在unified管道中添加自定义插件,遍历Esast节点替换组件调用:

import { visit } from 'unist-util-visit'
import { isIdentifierName } from 'estree-util-is-identifier-name'

// 自定义AST插件:替换组件调用为Hast节点
function mdxComponentReplacer(components) {
  return (tree) => {
    visit(tree, 'CallExpression', (node) => {
      if (node.callee.type === 'Identifier' && isIdentifierName(node.callee.name)) {
        const componentFn = components[node.callee.name]
        if (!componentFn) return
        
        // 解析组件props
        const props = node.arguments[0]?.properties?.reduce((acc, prop) => {
          acc[prop.key.name] = prop.value.value
          return acc
        }, {}) || {}
        
        // 替换当前节点为Hast结构
        Object.assign(node, componentFn(props))
      }
    })
  }
}

4. 完整编译流程

整合所有步骤,实现MDX字符串+组件字符串到HTML的转换:

import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkMdx from 'remark-mdx'
import mdx from '@mdx-js/mdx'
import rehypeStringify from 'rehype-stringify'

async function compileMdxWithComponents(mainMdxStr, componentMap) {
  // 预编译所有组件
  const compiledComponents = {}
  for (const [name, str] of Object.entries(componentMap)) {
    compiledComponents[name] = await precompileComponent(str)
  }

  // 构建unified处理管道
  const processor = unified()
    .use(remarkParse)
    .use(remarkMdx)
    .use(mdx, { outputFormat: 'estree' })
    .use(mdxComponentReplacer, compiledComponents)
    .use(rehypeStringify)

  // 处理主MDX并输出HTML
  const file = await processor.process(mainMdxStr)
  return String(file)
}

// 示例调用
const mainMdx = `
# Hello MDX
<ComponentA title="Test Component" />
<ComponentB content="Custom Content" />
`

const components = {
  ComponentA: `export default function ComponentA({ title }) {
    return <div class="component-a"><h2>{title}</h2></div>
  }`,
  ComponentB: `export default function ComponentB({ content }) {
    return <p class="component-b">{content}</p>
  }`
}

compileMdxWithComponents(mainMdx, components).then(html => console.log(html))

关键注意事项

  • Lambda环境需安装jsdom以模拟DOM,确保React组件渲染正常
  • 若组件依赖外部库,需在预编译阶段注入对应全局变量或模块
  • 复杂组件可调整reactToHast配置,保留更多样式与结构细节

内容的提问来源于stack exchange,提问作者Sir Robert

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 02:17:15