基于Schema代码片段的导出实践

前面我们通过 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 去转换成相应的代码,从而实现了一个闭环。这一过程不仅加深了我们对代码生成和解析的理解,还为我们在低代码开发领域提供了强大的工具和方法。未来,我们可以继续探索更多优化和扩展的可能性,以进一步提升开发效率和灵活性,为构建更高效、更智能的开发环境奠定坚实的基础。

相关推荐
—Qeyser5 小时前
用 Deepseek 写的uniapp血型遗传查询工具
前端·javascript·ai·chatgpt·uni-app·deepseek
codingandsleeping5 小时前
HTTP1.0、1.1、2.0 的区别
前端·网络协议·http
小满blue5 小时前
uniapp实现目录树效果,异步加载数据
前端·uni-app
喜樂的CC7 小时前
[react]Next.js之自适应布局和高清屏幕适配解决方案
javascript·react.js·postcss
天天扭码7 小时前
零基础 | 入门前端必备技巧——使用 DOM 操作插入 HTML 元素
前端·javascript·dom
咖啡虫8 小时前
css中的3d使用:深入理解 CSS Perspective 与 Transform-Style
前端·css·3d
拉不动的猪8 小时前
设计模式之------策略模式
前端·javascript·面试
旭久8 小时前
react+Tesseract.js实现前端拍照获取/选择文件等文字识别OCR
前端·javascript·react.js
独行soc8 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf
uhakadotcom8 小时前
Google Earth Engine 机器学习入门:基础知识与实用示例详解
前端·javascript·面试