前面我们通过 Babel 将代码片段解析成我们需要的 Schema 结构,这一过程使得我们能够以一种标准化、结构化的方式理解和处理代码逻辑,方便进行后续的分析和优化。那么现在,我们试着将这个过程反转一下,我们试着将 Schema 转换成我们需要的代码片段。这一步骤的目标是将抽象的 Schema 数据重新还原为可执行的代码,从而实现从设计到实现的完整闭环,进一步提升开发效率和代码生成的灵活性。事不宜迟,我们开始行动。
首先,我们先准备一段解析的代码片段
js
import React, { useState,useEffect } from 'react';
import { Button, Modal, Space } from 'antd';
const App = () => {
const [open, setOpen] = useState(false);
useEffect(()=>{
console.log(open,'open')
},[open])
return (
<div>
<Space>
<Button type="primary" onClick={()=>setOpen(true)}>
Open Modal
</Button>
</Space>
<Modal
open={open}
title="Title"
onOk={()=>{
setOpen(false)
}}
onCancel={()=>setOpen(false)}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</div>
);
};
export default App;
处理Import语句
首先,我们先处理Import语句(这里默认esm),根据之前对代码的解析,我们获取到了importMap,这是上述代码得到的importMap
js
{
"useState": "react",
"useEffect": "react",
"Button": "antd",
"Modal": "antd",
"Space": "antd"
}
我们拿到importMap之后就可以将其转换成对应的代码,这是我这边的处理
js
const getImportCode = () => {
let res = ``
let importArrMap: Record<string, Array<String>> = {}
//处理import代码
for (const key in importMap) {
if (Object.prototype.hasOwnProperty.call(importMap, key)) {
const element = importMap[key];
if (key && key !== "undefined") {
if (importArrMap[element]) {
importArrMap[element].push(key)
} else {
importArrMap[element] = [key]
}
}
}
}
for (const key in importArrMap) {
if (Object.prototype.hasOwnProperty.call(importArrMap, key)) {
const element = importArrMap[key];
res += `\nimport {${element?.join(',')}} from '${key}';`
}
}
return res
}
这样,我们就得到了import的代码

处理useState语句
和上述一样的处理,我们在之前的解析中得到了stateMap和stateNameMap2个对象
js
stateMap={
"setOpen": "open"
}
stateNameMap={
"open": {
"initValue": false
}
}
接下来,我们对这2个对象进行处理
js
const getStateCode = () => {
let res = ``
for (const key in stateMap) {
if (Object.prototype.hasOwnProperty.call(stateMap, key)) {
const element = stateMap[key];
let stateItem = `const [${element}, ${key}] = useState(${stateNameMap[element].initValue});`
res += `\n${stateItem}`
}
}
return res
}
这样,我们可以获取到useState代码片段

处理useEffect语句
我们在之前的解析中得到了一个effectFnMap的对象用来记录对应的useEffect,结构如下
js
{
"open": [
{
"originlValue": "() => {\n console.log(open, 'open');\n}",
value:fn
}
]
}
同理,我们处理effectFnMap这个对象
js
const getEffectCode = () => {
let res = ``
// 创建一个map,收集一个方法对应依赖的值
let functionToDepMap={}
for (const key in effectFnMap) {
if (Object.prototype.hasOwnProperty.call(effectFnMap, key)) {
const element = effectFnMap[key];
element?.forEach(item => {
if(!functionToDepMap[item.originlValue]){
functionToDepMap[item.originlValue]=[key]
}else{
functionToDepMap[item.originlValue].push(key)
}
});
}
}
//遍历functionToDepMap对象,生成useEffect代码
for (const key in functionToDepMap) {
if (Object.prototype.hasOwnProperty.call(functionToDepMap, key)) {
const element = functionToDepMap[key];
res+=`useEffect(${key},[${element.join(',')}])\n\n`
}
}
return res
}
这样,我们就获取到useEffect代码片段

编写Dom元素代码
现在,我们只剩下body这块的处理了,body的json为
js
{
"value": "div",
"type": "JSXElement",
"props": {},
"children": [
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "Space",
"type": "JSXElement",
"props": {},
"children": [
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "Button",
"type": "JSXElement",
"props": {
"type": {
"value": "primary",
"type": "StringLiteral"
},
"onClick": {
"originalValue": "() => setOpen(true)",
"type": "ExecutionFn"
}
},
"children": [
{
"value": "\n Open Modal\n ",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "Modal",
"type": "JSXElement",
"props": {
"open": {
"originalValue": "open",
"type": "ExecutionFn"
},
"title": {
"value": "Title",
"type": "StringLiteral"
},
"onOk": {
"originalValue": "() => {\n setOpen(false);\n}",
"type": "ExecutionFn"
},
"onCancel": {
"originalValue": "() => setOpen(false)",
"type": "ExecutionFn"
}
},
"children": [
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "p",
"type": "JSXElement",
"props": {},
"children": [
{
"value": "Some contents...",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "p",
"type": "JSXElement",
"props": {},
"children": [
{
"value": "Some contents...",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "p",
"type": "JSXElement",
"props": {},
"children": [
{
"value": "Some contents...",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "p",
"type": "JSXElement",
"props": {},
"children": [
{
"value": "Some contents...",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
},
{
"value": "p",
"type": "JSXElement",
"props": {},
"children": [
{
"value": "Some contents...",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
}
]
},
{
"value": "\n ",
"type": "JSXText",
"props": {}
}
]
}
我们对它进行简单处理下
js
const getTemplate = (item: any) => {
if (!item) return '';
if (item.type === 'JSXText') return item.value
let str = `<` + item.value;
for (const key in item.props || {}) {
if (Object.prototype.hasOwnProperty.call(item.props, key)) {
const element = item.props[key];
if (element.type === 'ExecutionFn') {
str += ` ${key}={${element.originalValue}}`;
} else {
str += ` ${key}={${JSON.stringify(element.value)}}`;
}
}
}
return str + `> ${getChildrenTemplate(item.children)}</${item.value}>`;
};
我们可以得到Dom元素的代码片段

合并所有代码
最后,我们只需将代码进行拼接即可
js
const generateCode = () => {
console.log(getTemplate(renderJson),'getTemplate')
let res = `import React from 'react';`
res += `${getImportCode()}
const App = () => {
${getStateCode()}
${getEffectCode()}
return ${getTemplate(renderJson)}
};
export default App;
`
}
这样,我们就实现了一个简单的代码片段的导出,让我们看下效果吧

完整代码如下
js
import generate from '@babel/generator';
import traverse from '@babel/traverse';
import { javascript } from '@codemirror/lang-javascript';
import CodeMirrorer from '@uiw/react-codemirror';
import { Button, Space, Modal } from 'antd';
import React, { useEffect, useState } from 'react';
const parser = require('@babel/parser');
const antd = await import('antd')
const types = require('@babel/parser');
interface itemType {
value: string | Function
type: string
props: Record<string, any>
children?: itemType[]
}
const Demo: React.FC = () => {
const [renderJson, setRenderJson] = useState<any>({ value: null })
const [state, setState] = useState<Record<string, any>>({})
const [stateKey, setStateKey] = useState<string>('')
const [effectFnMap, setEffectFnMap] = useState<Array<Record<string, Function>>>([])
const [importMap, setImportMap] = useState<Record<string, string>>({})
const [stateMap, setStateMap] = useState<Record<string, string>>({})
const [stateNameMap, setStateNameMap] = useState<Record<string, Record<string, any>>>({})
const [running,setRunning]=useState(false)
seEffect(()=>{
if(running){
codeToJson(code || '');
setTimeout(() => {
setRunning(false)
}, 100);
}
},[running])
const [code, setCode] = useState(`
import React, { useState,useEffect } from 'react';
import { Button, Modal, Space } from 'antd';
const App = () => {
const [open, setOpen] = useState(false);
useEffect(()=>{
console.log(open,'open')
},[open])
return (
<div>
<Space>
<Button type="primary" onClick={()=>setOpen(true)}>
Open Modal
</Button>
</Space>
<Modal
open={open}
title="Title"
onOk={()=>{
setOpen(false)
}}
onCancel={()=>setOpen(false)}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</div>
);
};
export default App;`)
const setActState = (name: string, value: any) => {
state[name] = value
setStateKey(name)
setState({ ...state })
}
useEffect(() => {
effectFnMap[stateKey]?.forEach(item => {
item?.value?.()
});
}, [state])
const changeCode = (item: any) => {
if(!item) return
let code = generate(item)
let ast = parser.parse(code.code, {
sourceType: 'module',
plugins: [
'js',
'flow',
],
});
traverse(ast, {
enter(path) {
//如果是变量且stateNameMap为true则为state中的变量,这里暂时不考虑多层作用域的情况
if (path?.node?.type === "Identifier" && stateNameMap[path.node.name]) {
//修改函数中state的值
path.node.name = 'state.' + path.node.name
path.skip()
} else if (
//如果是变量且stateNameMap有值则为state中的变量
path?.node?.type === "Identifier" && stateMap[path?.node?.name]
) {
//创建对应值名称的node节点
let node = {
"type": "DirectiveLiteral",
"extra": {
"rawValue": stateMap[path?.node?.name],
"raw": `\"${stateMap[path?.node?.name]}\"`,
"expressionValue": stateMap[path?.node?.name]
},
"value": stateMap[path?.node?.name]
}
//将对应值名称的node节点插入函数的第一个参数中
path?.container?.arguments?.unshift(node)
//修改方法名称
path.node.name = 'setActState'
path.skip()
}
}
});
return generate(ast).code
}
//处理对象属性
const getObjProps = (item: any, obj: Record<string, object | string>) => {
if (item.type === "ArrowFunctionExpression") {
return eval(changeCode(item))
} else {
item.properties?.forEach(itm => {
const keyName = itm.key.name
if (itm.value.type == 'StringLiteral' || itm.value.type === "NumericLiteral" || item.value.type == "BooleanLiteral") {
obj[keyName] = {
value: itm.value.value,
type: itm.value.type
}
} else if (itm.value.type === "ObjectExpression") {
obj[keyName] = {}
getObjProps(itm.value, obj[keyName])
}
});
return obj
}
}
//处理dom节点上的属性
const getItemProps = (attributes: any[], isAttributes: boolean) => {
let res: any = {};
attributes.forEach((item: any) => {
const name = isAttributes ? item.name.name : item.key.value;
if (item.value.type == 'StringLiteral' || item.value.type === "NumericLiteral" || item.value.type == "BooleanLiteral") {
res[name] = {
value: item.value.value,
type: item.value.type
}
} else if (item.value.type == 'JSXExpressionContainer' && item.value.expression.type == "ObjectExpression") {
res[name] = getObjProps(item.value.expression, {})
} else {
//处理代码中的表达式
if (item.value.expression.type == 'StringLiteral' || item.value.expression.type === "NumericLiteral" || item.value.expression.type == "BooleanLiteral") {
res[name] = {
value: item.value.expression.value,
type: item.value.expression.type
}
} else {
res[name] = {
value: () => {
return eval(changeCode(item.value.expression))
},
originalValue: generate(item.value.expression).code,
type: 'ExecutionFn'
}
}
}
});
return res;
};
//处理ast相关信息,这里没找到node的类型先用any处理
const getJsonItem = (item: any) => {
let res: any;
if (item.openingElement) {
res = {
value: item.openingElement.name.name,
type: item.type,
props: getItemProps(item.openingElement.attributes || [], true),
};
} else {
if (item.type == 'JSXText') {
res = {
value: item.value,
type: item.type,
props: {},
};
} else if (item.type == 'JSXExpressionContainer') {
let code = generate(item)
let ast = parser.parse(code.code, {
sourceType: 'module',
plugins: [
'js',
'flow',
],
});
traverse(ast, {
enter(path) {
if (path?.node?.type === "Identifier" && stateNameMap[path.node.name]) {
//修改元素中的变量
path.node.name = 'state.' + path.node.name
path.skip()
}
}
});
res = {
value: () => eval(generate(ast).code),
type: item.type,
props: {},
};
}
}
if (item.children?.length) {
let resChildren: any = [];
item.children.forEach((itm: any) => {
resChildren.push(getJsonItem(itm));
});
res.children = resChildren;
}
return res;
};
const codeToJson = (code?: string) => {
let ast = parser.parse(code, {
sourceType: 'module',
plugins: [
'jsx',
'flow',
],
});
traverse(ast, {
enter(path) {
if (path.node.type === "ImportDeclaration") {
const source = path.node.source
const specifiers = path.node.specifiers || []
specifiers.forEach(item => {
importMap[item?.imported?.name] = source.value
})
}
//处理useEffect触发事件
if (path.node.type === "Identifier" && path.node.name === "useEffect") {
//useEffect方法
const effectFunctionNode = path.container.arguments?.[0]
//将方法中的useState提到外部
const effectFunction: Function = eval(changeCode(effectFunctionNode))
effectFunction?.()
//useEffect依赖项
const depencyNode = path.container.arguments?.[1]
depencyNode?.elements?.forEach(item => {
if (!effectFnMap[item.name]) {
effectFnMap[item.name] = [{
value: effectFunction,
originlValue: generate(effectFunctionNode).code
}]
} else {
effectFnMap[item.name].push({
value: effectFunction,
originlValue: generate(effectFunctionNode).code
})
}
})
}
//处理useState
if (path.type == "VariableDeclarator") {
if (path.node?.init?.callee?.name == "useState") {
const callbackName = path?.node?.id?.elements?.[1]?.name
const stateName = path?.node?.id?.elements?.[0]?.name
//对应的方法和状态进行映射储存
stateMap[callbackName] = stateName
//state中存在的状态名
stateNameMap[stateName] = { initValue: path?.node?.init?.arguments?.[0]?.value }
//初始化state
state[stateName] = path?.node?.init?.arguments?.[0]?.value
setState({ ...state })
}
}
//处理JSX元素
if (
path.type === 'JSXElement' &&
path.parent.type == 'ReturnStatement'
) {
setRenderJson(getJsonItem(path.node))
console.log(getJsonItem(path.node), 'renderJson')
}
},
});
setImportMap({ ...importMap })
setEffectFnMap({ ...effectFnMap })
setStateNameMap({ ...stateNameMap })
setStateMap({ ...stateMap })
};
//递归获取子节点
const getChildren = (arr: itemType[]) => {
let res: any[] = []
arr.forEach(item => {
if (item.type === "JSXText") {
res.push(item.value)
}
else if (item.type == "JSXExpressionContainer") {
res.push(item.value())
}
else if (
item.type == "JSXElement"
) {
res.push(React.createElement(getItemValue(item.value, item), getRenderItemProps(item.props), ...getChildren(item?.children || [])))
}
});
return res
}
//渲染当前json到页面
const jsonToRender = (item: itemType) => {
return React.createElement(getItemValue(item.value, item), getRenderItemProps(item.props), ...getChildren(item.children || []))
}
const getItemValue = (value: string, item) => {
if (importMap[value]) {
return eval(importMap[value] + '.' + value)
} else {
return value
}
}
const getRenderItemProps = (props: any) => {
let propsObj: any = {}
for (const key in props) {
if (Object.prototype.hasOwnProperty.call(props, key)) {
const element = props[key];
if (element.type == "ExecutionFn") {
propsObj[key] = element.value()
} else {
propsObj[key] = element.value
}
}
}
return propsObj
}
const getTemplate = (item: any) => {
if (!item) return '';
if (item.type === 'JSXText') return item.value
let str = `<` + item.value;
for (const key in item.props || {}) {
if (Object.prototype.hasOwnProperty.call(item.props, key)) {
const element = item.props[key];
if (element.type === 'ExecutionFn') {
str += ` ${key}={${element.originalValue}}`;
} else {
str += ` ${key}={${JSON.stringify(element.value)}}`;
}
}
}
return str + `> ${getChildrenTemplate(item.children)}</${item.value}>`;
};
const getChildrenTemplate = (children: any[]) => {
let res = ``
children.forEach(item => {
res += getTemplate(item)
})
return res
}
const getImportCode = () => {
let res = ``
let importArrMap: Record<string, Array<String>> = {}
//处理import代码
for (const key in importMap) {
if (Object.prototype.hasOwnProperty.call(importMap, key)) {
const element = importMap[key];
if (key && key !== "undefined") {
if (importArrMap[element]) {
importArrMap[element].push(key)
} else {
importArrMap[element] = [key]
}
}
}
}
for (const key in importArrMap) {
if (Object.prototype.hasOwnProperty.call(importArrMap, key)) {
const element = importArrMap[key];
res += `\nimport {${element?.join(',')}} from '${key}';`
}
}
return res
}
const getStateCode = () => {
let res = ``
for (const key in stateMap) {
if (Object.prototype.hasOwnProperty.call(stateMap, key)) {
const element = stateMap[key];
let stateItem = `const [${element}, ${key}] = useState(${stateNameMap[element].initValue});`
res += `\n${stateItem}`
}
}
return res
}
const getEffectCode = () => {
let res = ``
// 创建一个map,收集一个方法对应依赖的值
let functionToDepMap={}
for (const key in effectFnMap) {
if (Object.prototype.hasOwnProperty.call(effectFnMap, key)) {
const element = effectFnMap[key];
element?.forEach(item => {
if(!functionToDepMap[item.originlValue]){
functionToDepMap[item.originlValue]=[key]
}else{
functionToDepMap[item.originlValue].push(key)
}
});
}
}
//遍历functionToDepMap对象,生成useEffect代码
for (const key in functionToDepMap) {
if (Object.prototype.hasOwnProperty.call(functionToDepMap, key)) {
const element = functionToDepMap[key];
res+=`useEffect(${key},[${element.join(',')}])\n\n`
}
}
return res
}
const generateCode = () => {
let res = `import React from 'react';`
res += `${getImportCode()}
const App = () => {
${getStateCode()}
${getEffectCode()}
return ${getTemplate(renderJson)}
};
export default App;
`
console.log(res,'code')
}
return (
<div style={{ display: 'flex' }}>
<CodeMirrorer
value={code}
height="100vh"
width="48vw"
theme="dark"
onChange={(e) => {
setCode(e);
}}
extensions={[javascript({ jsx: true })]}
></CodeMirrorer>
< Button
style={{ margin: '0 20px' }}
onClick={() => {
//初始化每个map的值
setRenderJson({})
setImportMap({})
setEffectFnMap([])
setStateMap({})
setStateNameMap({})
setRunning(true)
}}
>
运行
</Button>
{renderJson.value && <Button
onClick={() => { generateCode() }}
>生成代码</Button>}
<div >
{renderJson.value && jsonToRender(renderJson)}
</div>
</div>
);
};
export default Demo;
通过这几章的实践,我们实现了代码的解析,修改代码生成新的 AST 树,生成我们需要的 Schema 结构,也通过对应的 JSON 去转换成相应的代码,从而实现了一个闭环。这一过程不仅加深了我们对代码生成和解析的理解,还为我们在低代码开发领域提供了强大的工具和方法。未来,我们可以继续探索更多优化和扩展的可能性,以进一步提升开发效率和灵活性,为构建更高效、更智能的开发环境奠定坚实的基础。