nodejs vm+ babel ast 实现类似cube.js schema 的处理能力

很简单主要是学习下cube.js 关于schema 的特殊处理了解下原理
以下部分代码参考了cube.js compiler 部分

参考项目

  • package.json
 
{
  "name": "vm-scripts",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@babel/core": "^7.12.10",
    "@babel/generator": "^7.12.11",
    "@babel/parser": "^7.12.11",
    "@babel/preset-env": "^7.12.11",
    "@babel/traverse": "^7.12.12",
    "@babel/types": "^7.12.12"
  }
}
  • 仿cube.js schema 定义
app("dalong2",{
    "name":{
        age:333,
        version:"v1",
        name:"v1 rongfengliang",
        full_name: `${JSON.stringify(COMPILE_CONTEXT)}---demoapp`
    },
    "info":`${COMPILE_CONTEXT.name}`
})

简单说明,对于info 我们认为应该一个函数,同时app 也是我们一个全局变量,类似cube.js 的cube
同时我们也提供了一个全局变量COMPILE_CONTEXT,类似cube.js 编译时的schema 处理,当然其他的也是可以扩展的

  • 基于babel 的ast 进行代码转换
    主要是将info 属性变为info: COMPILE_CONTEXT=>${COMPILE_CONTEXT.name},这样就和cube.js sql 是一个函数类似了
    参考代码
 
const vm = require("vm")
const fs = require("fs")
const { parse } = require("@babel/parser")
const generate = require("@babel/generator")
const traverse = require("@babel/traverse")
const t = require("@babel/types")
// 进行数据的中间存储
const cubes = [];
const field  = /^.*(info|sql|conf)$/
// 包装的基于babel ast+ vm 的处理函数
function call_vm(context) {
        const code = fs.readFileSync("./cube.js", {
            encoding: "utf8"
        }).toString();
        const ast1 = parse(
            code,
            {
                sourceFilename: "cube.js",
                sourceType: 'module',
                plugins: ['objectRestSpread']
            },
        );
        traverse.default(ast1, {
            ObjectProperty(path) {
                console.log("path info:",JSON.stringify(path.node))
                 // 进行ast类型判断处理,同时进行as的替换处理,将info 的包含为一个箭头函数
                if (path.node.key.type === 'StringLiteral' && path.node.key.value.match(field)) {
                  path.get('value').replaceWith(
                    t.arrowFunctionExpression([t.identifier("COMPILE_CONTEXT")], path.node.value, false)
                  );
                }
              }
        })
        const output = generate.default(
            ast1,
            {},
            code
        );
        console.log(output)
        // 使用vm 沙箱提供数据隔离的环境
        vm.runInNewContext(output.code, {
           // 提供上下文的函数
            app: (name, conf) => {
                cubes.push({
                    name:name,
                    conf:conf
                })
            },
            COMPILE_CONTEXT: context
        })
}
context = {
    name: "rongfengliangdemoapp",
    appid: "app001",
    auth: {
        name: "dalong",
        age: 22
    }
}
call_vm(context)
cubes.forEach(cube => {
    console.log(cube)
    let result = cube.conf.info(cube.conf.name)
    console.log("=======",result,"=======")
});
  • 运行效果

参考资料

https://babeljs.io/docs/en/
https://astexplorer.net/
https://cube.dev/docs/schema/dynamic-schema-creation
https://nodejs.org/api/vm.html

原文地址:https://www.cnblogs.com/rongfengliang/p/14375123.html