Bundler编写

113 阅读2分钟
// 高亮代码
npm i cli-highlight -g
// 安装依赖包
npm install @babel/parser --save
npm install @babel/core --save
npm install @babel/traverse --save
npm install @babel/preset-env --save

进入正文

  1. 模块分析: 通过fs读取文件内容,通过@babel/parser将文件内容转化为AST,使用@babel/traverse遍历AST,对每个路径信息做映射,将依赖关系拼装在依赖对象里,使用@babel/core将AST转化为可运行的code,最后该函数return出去文件名称、依赖和代码。
const fs = require('fs'); // 文件处理模块
const parser = require('@babel/parser'); // 用于AST转化
const tarverse = require('@babel/traverse').default; // 引入babel/traverse的默认导出 对AST进行过滤解析
// 分析入口文件
const moduleAnalyser = (entryFile) => { // entryFile 入口文件路径
    // fs模块根据路径读取到了module的内容
    const content = fs.readFileSync(entryFile, 'utf-8');
    console.log(content);
    
    // 分析内容
    const AST = parser.parse(content, {
        // ESModule形式导入的模块
        sourceType: 'module'
    });
    // 使用@babel/traverse遍历了AST ,对每个ImportDeclaration节点做映射,把依赖关系拼装在 dependencies对象里
    const dependencies = {};
    tarverse(AST, {
        ImportDeclaration({node}) {
            console.log(node, 'node');
            const dirname = path.dirname(entryFile);
            // 新路径
            const newPathName = "./" + path.join(dirname, node.source.value)
            dependencies[node.source.value] = newPathName;
        }
    })
    
    // 使用@babel/core结合@babel/preset-env预设,将AST转换成了浏览器可以执行的代码
    const { code } = babel.transformFromAst(AST, null, { 
        presets: ["@babel/preset-env"] 
    }) 
    return {
        entryFile,
        dependencies,
        code
    }
}
  1. 生成依赖图谱: 拿到上步的dependencies依赖映射,将入口文件的依赖路径再做一次模块分析,再把依赖模板的依赖路径在做一次模块分析.....
const makeDependenciesGraph = (entryFile) => {
    const entryModule = moduleAnalyser(entryFile);
    const graphArray = [entryModule];
    for(let i = 0; i < graphArray.length; i++) {
        const item = graphArray[i];
        const { dependencies } = item;
        if (dependencies) {
            for(let j in dependencies) {
                graphArray.push(moduleAnalyser(dependencies[j]));
            }
        }
    }
    console.log(graphArray, 'graphArray');
    const graph = {}; 
    graphArray.forEach(item => { 
        graph[item.entryFile] = { 
            dependencies: item.dependencies, 
            code: item.code 
        } 
   }) 
   return graph;
};

  1. 生成代码:
const generateCode = (entry) => {
    const graph = JSON.stringify(makeDependenciesGraph(entry));
    // 浏览器可执行的代码里有require方法,有exports对象,bundler.js打包后的代码需要提供一个require方法和exports对象。
    // localRequire 传入依赖相对于module的相对路径,根据graph对象,返回依赖相对于bundler.js的相对路径
    const bundle = `(function(graph) {
        function require(module) {

            function localRequire(relativePath) {
                return require(graph[module].dependencies[relativePath]);
            };

            var exports = {};
            (function(require, exports, code) {
                eval(code);
            })(localRequire, exports, graph[module].code);
            return exports;
        };
        require('${entry}');
    })(${graph})`
}