摘要

在现代 AI 应用场景中,动态代码生成与执行已经成为一项核心能力。本文将深入探讨如何在前端实现一个完整的浏览器端 React 代码编译器,支持 TypeScript/JSX 实时转译、CommonJS 模块系统模拟、以及安全隔离的代码执行。


背景与需求

在 AI Agent 应用中,我们经常遇到这样的场景:

  • AI 生成 React 组件代码,需要立即预览效果
  • 用户编写自定义模板代码,实时查看渲染结果
  • 动态加载第三方依赖(React、Ant Design 等)

传统的做法是将代码发送到后端编译执行,但这样会带来:

  • ❌ 高延迟(网络请求 + 后端编译)
  • ❌ 服务端资源消耗
  • ❌ 安全隐患(恶意代码执行风险)

解决方案:在浏览器端完成所有编译和执行!


🏗️ 核心架构设计

整个编译器分为四个核心模块:

renderReactCode (入口)
  ↓
transpileCode (Babel 转译)
  ↓
buildDependency (依赖解析)
  ↓
transformCode (代码执行)

🛠️ 技术实现详解

1️⃣ Babel 转译:TSX → JavaScript

export async function transpileCode({ code, getResource }) {
  const [Babel] = await Promise.all([getResource('babel'), getResource('react')]);
  
  // 处理 Fragment 简写语法
  code = code.replace(/<>/g, '<React.Fragment>')
             .replace(/<\/>/g, '</React.Fragment>');

  const output = Babel.transform(code, {
    filename: `${uuid()}.tsx`,
    presets: [
      'env',  // ES6+ 语法转换
      ['typescript', {
        isTSX: true,
        allExtensions: true,
        jsxPragma: 'React',
        allowNamespaces: true,
        onlyRemoveTypeImports: true,
      }],
      ['react', {
        throwIfNamespace: false,
      }],
    ],
  }).code;
  
  return output;
}

关键技术点:

  • ✅ 使用 Babel Standalone 在浏览器中转译
  • ✅ 支持 TypeScript、JSX、ES6+ 语法
  • ✅ 自动处理 Fragment 简写(<>...</><React.Fragment>...</React.Fragment>
  • ✅ 生成唯一文件名用于调试

转译示例:

// 输入
const App = () => <div>Hello {name}</div>;

// 输出
const App = () => React.createElement('div', null, 'Hello ', name);

2️⃣ 依赖解析:模拟 CommonJS 模块系统

export async function buildDependency(output, getResource) {
  let names = [];
  const regexp = /require\(['"](.*)['"]\)/g;
  let match = regexp.exec(output);
  
  // 提取所有 require() 调用
  while (match) {
    names.push(match[1]);
    match = regexp.exec(output);
  }
  
  // 并行加载所有依赖模块
  const dependencies = (
    await Promise.all(
      names.map(async (name) => ({
        name,
        module: await getResource(name),
      }))
    )
  ).reduce((prev, { name, module }) => {
    prev[name] = module;
    return prev;
  }, {});
  
  // 返回 require 函数
  return (name) => dependencies[name];
}

工作原理:

  1. 使用正则表达式提取所有 require('xxx') 调用
  2. 并行加载所需模块(React、Ant Design 等)
  3. 构建模块映射表
  4. 返回模拟的 require 函数

3️⃣ 安全执行:new Function + 依赖注入

export async function transformCode({ code, getResource }) {
  const React = await getResource('react');
  const output = await transpileCode({ code, getResource });
  const require = await buildDependency(output, getResource);

  // 创建隔离函数
  const fn = new Function('module', 'exports', 'require', 'React', output);
  const module = { exports: {} };
  
  try {
    // 注入依赖并执行
    fn.call(module, module, module.exports, require, React);
  } catch (e) {
    console.error(e);
    return null;
  }
  
  // 返回默认导出的组件
  return module.exports.default;
}

为什么用 new Function 而不是 eval

  • ✅ 独立作用域,不会污染全局变量
  • ✅ 可以精确控制注入的依赖
  • ✅ 更好的性能(浏览器可优化)
  • ⚠️ 仍需注意 XSS 风险(生产环境建议沙箱隔离)

4️⃣ 错误隔离:SafeRender 组件

export class SafeRender extends Component<Props, State> {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Runtime Error:', error, errorInfo);
  }

  componentDidUpdate(prevProps) {
    // 代码更新时自动重置错误状态
    if (this.state.hasError && prevProps.children !== this.props.children) {
      this.setState({ hasError: false, error: null });
    }
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback(this.state.error);
    }
    return this.props.children;
  }
}

使用方式:

<SafeRender fallback={(error) => <ErrorDisplay error={error} />}>
  <ReactCodeRender code={userCode} attrs={{ data: formData }} />
</SafeRender>

🎨 完整工作流程

用户输入代码
  ↓
1. renderReactCode() 接收代码字符串
  ↓
2. transpileCode() → Babel 转译 TSX → JS
  ↓
3. buildDependency() → 解析 require() → 加载模块
  ↓
4. transformCode() → new Function 执行 → 返回组件
  ↓
5. React 渲染动态组件
  ↓
6. SafeRender 捕获可能的运行时错误

💡 技术亮点

特性说明
零后端依赖所有编译在浏览器完成
完整 TSX 支持TypeScript + JSX + ES6+ 语法
模块系统模拟完整的 CommonJS require/exports
错误隔离Error Boundary 防止崩溃传播
自动恢复代码更新时自动重置错误状态
依赖注入精确控制可用的全局变量

🚀 应用场景

  1. AI 代码生成预览:AI 返回 React 代码,立即渲染
  2. 动态模板系统:用户编写自定义组件,实时预览
  3. 在线代码编辑器:类似 CodeSandbox 的浏览器端 IDE
  4. 低代码平台:可视化生成代码并执行

⚠️ 安全建议

在生产环境中使用 new Function 需要注意:

  • 使用 Web Worker 隔离执行环境
  • 限制可访问的全局变量
  • 添加代码审核机制
  • 考虑使用更安全的沙箱方案(如 vm2 的浏览器替代方案)

📚 参考资源



PatWu16
82 声望6 粉丝

仰望星空,脚踏实地