webpack进阶(代码转换/依赖分析)

550 阅读2分钟

JavaScript 是一门编译型语言。

AST/Babel/依赖

bable的原理

  1. parse 把代码code变成AST(Abstract Syntax Tree:什么是抽象语法树
  2. traverser遍历AST进行修改
  3. generate:把AST变成代码code2 即 原代码 --(1)-> ast ---(2)->ast--(3)->code(2)

示例(将let 变成var)

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)
console.log(result.code)
//输出
//var a = 'let';
//var b = 2;

将ES6转换成ES5

import { parse } from '@babel/parser'
import * as babel from '@babel/core'
import * as fs from 'fs'

const code = `let a = 'let'; let b = 2; const c = 3`
const ast = parse(code, { sourceType: 'module' })
const result = babel.transformFromAstAsync(ast, code, {
    presets: ['@babel/preset-env']
})
result.then(res => {
    fs.writeFileSync('./test.es5.js', res.code)
})

依赖分析

// 请确保你的 Node 版本大于等于 14
// 请先运行 yarn 或 npm i 来安装依赖
// 然后使用 node -r ts-node/register 文件路径 来运行,
// 如果需要调试,可以加一个选项 --inspect-brk,再打开 Chrome 开发者工具,点击 Node 图标即可调试
import { parse } from "@babel/parser"
import traverse from "@babel/traverse"
import { readFileSync } from 'fs'
import { resolve, relative, dirname } from 'path';

// 设置根目录
const projectRoot = resolve(__dirname, 'project_1')

// 类型声明
type DepRelation = { [key: string]: { deps: string[], code: string } }
// 初始化一个空的 depRelation,用于收集依赖
const depRelation: DepRelation = {}

function getProjectPath(path: string) {
    return relative(projectRoot, path).replace(/\\/g, '/')
}
function collectCodeAndDeps(filepath: string) {
    const key = getProjectPath(filepath) // 文件的项目路径,如 index.js
    // 获取文件内容,将内容放至 depRelation
    const code = readFileSync(filepath).toString()
    // 初始化 depRelation[key]
    depRelation[key] = { deps: [], code: code }
    // 将代码转为 AST
    const ast = parse(code, { sourceType: 'module' })
    // 分析文件依赖,将内容放至 depRelation
    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)
            }
        }
    })
}


// 将入口文件的绝对路径传入函数,如 D:\demo\fixture_1\index.js
collectCodeAndDeps(resolve(projectRoot, 'index.js'))
console.log('depRelation')
console.log(depRelation)

用递归进行嵌套依赖分析

//其他代码同上,在collectCodeAndDeps中调用自己即可!缺点:如果太深会导致
function collectCodeAndDeps(filepath: string) {
    traverse(ast, {
        enter: path => {
            if (path.node.type === 'ImportDeclaration') {
                ...
                depRelation[key].deps.push(depProjectPath)
                collectCodeAndDeps(depAbsolutePath)
            }
        }
    })
}

避免循环依赖

if (Object.keys(depRelation).includes(key)) {
	console.warn(`duplicated dependency:${key}`)
    return
}