模拟babal-loader简单实现webpack打包获取所有模块依赖的核心原理

1.核心打包原理:

1) 打包的主要流程如下:

  1. 需要读到入口文件里面的内容。
  2. 分析入口文件,递归的去读取模块所依赖的文件内容,生成AST语法树。
  3. 根据AST语法树,生成浏览器能够运行的代码

2. 常见的模块解析包

  1. @babel/parser:将获取到的模块内容 解析成AST(es6的)语法树
  2. @babel/traverse: 遍历AST语法树
  3. @babel/core @babel/preset-env:ES6的AST转化成ES5的AST

核心代码如下:

完整代码地址

// 获取主入口文件
const fs = require("fs");
const path = require("path");
//分析模块
const parser = require("@babel/parser");
//收集依赖
const traverse = require("@babel/traverse").default;
//转化代码语法的核心库
const babel = require("@babel/core");


/**
 * getModuleInfo  获取模块的内容
 * @param {文件路径} file 
 * @return  该模块的路径(file),该模块的依赖(deps),该模块转化成es5的代码
 */
const getModuleInfo = (file) => {
  const body = fs.readFileSync(file, "utf-8"); //同步获取文件的内容
  const ast = parser.parse(body, {
    sourceType: "module", //表示我们要解析的是ES模块
  });
  // traverse遍历AST语法树,保存Import依赖包的引用路径
  const deps = {};
  traverse(ast, {
    //  ImportDeclaration方法代表的是对type类型为ImportDeclaration的节点的处理。
    ImportDeclaration({ node }) {
      const dirname = path.dirname(file);
      //node.source.value 指的是import的值,如,index.js文件中引入的 './add' 和 './minus'
      const abspath = "./" + path.join(dirname, node.source.value);
      deps[node.source.value] = abspath;
    },
  });
  //transformFromAst传入的AST转化成我们在第三个参数里配置的模块类型
  const { code } = babel.transformFromAst(ast, null, {
    presets: ["@babel/preset-env"],     //es5模块类型
  });
  const moduleInfo = {file,deps,code}
  return moduleInfo
};
//递归循环遍历依赖
const parseModules = (file) =>{
    const entry =  getModuleInfo(file)
    const temp = [entry];
    const depsGraph = {};
    for (let i = 0;i<temp.length;i++){
        const deps = temp[i].deps
        if (deps){
            for (const key in deps){
                if (deps.hasOwnProperty(key)){
                    temp.push(getModuleInfo(deps[key]))
                }
            }
        }
    }
    temp.forEach(moduleInfo=>{
        depsGraph[moduleInfo.file] = {
            deps:moduleInfo.deps,
            code:moduleInfo.code
        }
    })
    return depsGraph;
}
const bundle = (file) =>{
    const depsGraph = JSON.stringify(parseModules(file));
    //因为浏览器不会识别执行require和exports,所以自己植入代码增加require和exports,让所有依赖代码都运行起来
    //absRequire 获取的是依赖的绝对路径
    return `(function (graph) {
        function require(file) {
            function absRequire(relPath) {
                return require(graph[file].deps[relPath])
            }
            var exports = {};
            (function (require,exports,code) {
                eval(code);
            })(absRequire,exports,graph[file].code)
            return exports;
        }
        require('${file}')
    })(${depsGraph})`

}
const content = bundle('./src/index.js')

//写入到我们的dist目录下
fs.mkdirSync('./dist');
fs.writeFileSync('./dist/bundle.js',content)

由上面的核心代码我们可以知道,webpack的打包过程其实就像一条加工的流水线一个一个的loader其实就像一个转化器,把看似并不通的东西,转化为webpack可以识别的语法,然后继续处理下去,知道最后打包成成品,而pingins的各种已经注册进webpack的插件(插件更多地是在打包的过程中提供起承转合的作用,优化等等),则一直在监听流水线的工作,需要的时候,插件就再做一层加工,比如压缩,分析打包速度等等,最终得到一个浏览器可以识别并运行的包

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值