摘要
在现代 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];
}工作原理:
- 使用正则表达式提取所有
require('xxx')调用 - 并行加载所需模块(React、Ant Design 等)
- 构建模块映射表
- 返回模拟的 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 防止崩溃传播 |
| 自动恢复 | 代码更新时自动重置错误状态 |
| 依赖注入 | 精确控制可用的全局变量 |
🚀 应用场景
- AI 代码生成预览:AI 返回 React 代码,立即渲染
- 动态模板系统:用户编写自定义组件,实时预览
- 在线代码编辑器:类似 CodeSandbox 的浏览器端 IDE
- 低代码平台:可视化生成代码并执行
⚠️ 安全建议
在生产环境中使用 new Function 需要注意:
- 使用 Web Worker 隔离执行环境
- 限制可访问的全局变量
- 添加代码审核机制
- 考虑使用更安全的沙箱方案(如
vm2的浏览器替代方案)
📚 参考资源
- Babel Standalone: https://babeljs.io/docs/babel-standalone
- React Error Boundaries: https://react.dev/reference/react/Component#catching-renderin...
- new Function MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。