明天的太阳

导航

了解一下Babel、AST和依赖分析

Babel

Babel做了什么?

  1. parse: 把代码code变成AST
  2. traverse: 遍历AST进行修改
  3. generate: 根据AST重新生成代码

通过将let改成var来了解一下这个过程。

运行node -r ts-node/register --inspect-brk xxx.ts命令来通过浏览器控制台调试代码,以查看生成的ast的结构。
image

import { parse } from "@babel/parser"
import traverse from "@babel/traverse"
import generate from "@babel/generator"

const code = `let a = 'let'; let b = 2`;
const ast = parse(code, { sourceType: 'module' });
traverse(ast, {
    enter: item => {
        if(item.node.type === 'VariableDeclaration'){
           if(item.node.kind === 'let'){
              item.node.kind = 'var'
           }
        }
    }
})
const result = generate(ast, {}, code);

打印出如下结果,是不是有那么点味道了? 哈哈哈。。。

var a = 'let';
var b = 2;

使用Babel的transformFromAstSync自动将代码转换为ES5

image
通过配置presets: ['@babel/preset-env']可以将ES2015-2020的代码转换成ES5的代码。改写一下代码

const result = babel.transformFromAstSync(ast, code, {
  presets: ['@babel/preset-env']
})

运行得到结果

"use strict";

var a = 'let';
var b = 2;

依赖分析

创建三个js文件,index.js引入a和b,a和b互相引用。

/xxx/index.js

import a from './a.js'
import b from './b.js'
console.log(a.getB())
console.log(b.getA())

/xxx/a.js

import b from './b.js'
const a = {
  value: 'a',
  getB: () => b.value + ' from a.js'
}
export default a

xxx/b.js

import a from './a.js'
const b = {
  value: 'b',
  getA: () => a.value + ' from b.js'
}
export default b

编写文件分析依赖

import { parse } from "@babel/parser"
import traverse from "@babel/traverse"
import { readFileSync } from 'fs'
import { resolve, relative, dirname } from 'path';

// 得到xxx当前目录的绝对路径 如F:\xxx\yyy\xxx
const projectRoot = resolve(__dirname, 'xxx')
// 声明依赖的类型
type DepRelation = { [key: string]: { deps: string[], code: string } }

// 初始化一个空的 depRelation,用于收集依赖
const depRelation: DepRelation = {}

// 分析依赖
collectCodeAndDeps(resolve(projectRoot, 'index.js'))

function collectCodeAndDeps(filepath: string) {
    const key = getProjectPath(filepath); 

    if (Object.keys(depRelation).includes(key)) {
       console.warn(`duplicated dependency: ${key}`) // 注意,重复依赖不一定是循环依赖
       return
    }

    // 获取文件内容,将内容放至 depRelation
    const code = readFileSync(filepath).toString()
    // 初始化 depRelation[key]
    depRelation[key] = { deps: [], code: code }
    // 将代码转为 AST
    const ast = parse(code, { sourceType: 'module' })
    traverse(ast, {
        enter: path => {
            if (path.node.type === 'ImportDeclaration') {
                // path.node.source.value 往往是一个相对路径,如 ./a.js,需要先把它转为一个绝对路径
                const depAbsolutePath = resolve(dirname(filepath), path.node.source.value)
                // 然后转为项目路径
                const depProjectPath = getProjectPath(depAbsolutePath)
                // 把依赖写进 depRelation
                depRelation[key].deps.push(depProjectPath)
                collectCodeAndDeps(depAbsolutePath)
            }
        }
    })
}

// 获取文件相对于根目录projectRoot的相对路径 如index.js
function getProjectPath(path: string) {
  return relative(projectRoot, path).replace(/\\/g, '/')
}

运行以上代码我们的依赖分析就出来了

{
  'index.js': {
    deps: [ 'a.js', 'b.js' ],
    code: "import a from './a.js'\r\n" +
      "import b from './b.js'\r\n" +
      'console.log(a.value + b.value)\r\n'
  },
  ...
}

posted on 2022-12-15 11:45  东方来客  阅读(190)  评论(0编辑  收藏  举报