webpack原理浅析

208 阅读3分钟

webpack入门实践

原理分析

webpack在执npx webpack进⾏打包后,都⼲了什么事情?

image.png

⼤概的意思就是,我们实现了⼀个webpack_require来实现⾃⼰的模块化,把代码都缓存在installedModules⾥,代码⽂件以对象传递进来,key是路径,value是包裹的代码字符串,并且代码内部的require,都被替换成了webpack_require

简易实现(三步曲)

实现步骤

  • 基础配置,webpack会读取配置
    • 找到入口模块
  • 入口分析
    • 分析依赖模块(拿到模块的路径)
    • 分析内容(并对内容处理)
    • 编译内容
  • 依赖模块(递归的⽅式)
    • 分析依赖模块
    • 分析内容(并对内容处理)
    • 编译内容
  • ⽣成bundle.js(这个js可以直接在浏览器中执⾏)

1. 入口分析

读取配置

读取webpack配置,拿到入口文件路径

// 拿到webpack的配置文件
const options = require("./webpack.config.js");
const Webpack = require("./lib/webpack.js");

new Webpack(options).run();

分析入口模块

  1. 从配置文件中读取入口文件路径,读取入口文件
const fs = require("fs");

parse(entryFile) {
    // 分析入口模块的内容
    const content = fs.readFileSync(entryFile, "utf-8");
    console.log("content", content);
}
  1. 分析内部语法
  • ① 使⽤@babel/parser,这是babel7的工具,来帮助我们分析内部的语法,包括es6,返回⼀个ast抽象语法树
  • 安装 npm i @babel/parser--save
const parser = require("@babel/parser");

// 把内容通过parse 抽象成语法树,便于分析 提取
const ast = parser.parse(content, {
     sourceType: "module",
});
// console.log("ast", ast);
console.log("ast", ast.program.body);
  • ② 接下来我们就可以根据body⾥⾯的分析结果,遍历出所有的引⼊模块,但是⽐较麻烦,这⾥还是推荐babel推荐的⼀个模块@babel/traverse,来帮我们处理
  • 安装npm install @babel/traverse --save
const traverse = require("@babel/traverse").default;

traverse(ast, {
   ImportDeclaration({ node }) {
       // console.log(node); 
    }
});
  • ③ 处理目前入口文件的引入路径 ./expo.js -> ./src/expo.js
// 保存依赖路径跟相对路径的关系
const dependencies = {}
const newPathName =
          "./" + path.join(path.dirname(entryFile), node.source.value);
// console.log("newPathName", newPathName);
dependencies[node.source.value] = newPathName;
  • ④ 把代码处理成浏览器可运⾏的代码,需要借助@babel/core,和@babel/preset-env,把ast语法树转换成合适的代码
  • 安装 npm i @babel/core @babel/preset-env --save
const { transformFromAst } = require("@babel/core");

// 处理内容 转换ast
const { code } = transformFromAst(ast, null, {
    presets: ["@babel/preset-env"],
});
  • ⑤ 导出所有分析出的内容
return {
     entryFile,
     dependencies,
     code
}

2.依赖模块

  • 上⼀步我们已经完成了⼀个模块的分析,接下来我们要完成项⽬⾥所有模块的分析
  • 通过数组遍历的方式来递归依赖模块
// 处理其他依赖模块,做一个信息汇总
this.modules.push(info);
for (let i = 0; i < this.modules.length; i++) {
  const { dependencies } = this.modules[i];
  for (let j in dependencies) {
    this.modules.push(this.parse(dependencies[j]));
  }
}
console.log("this.modules", this.modules);

// 数组结构转换  依赖图谱 key:value
const graph = {};
this.modules.forEach((item) => {
  graph[item.entryFile] = {
    dependencies: item.dependencies,
    code: item.code,
  };
});

3.代码生成

  • 通过fs模块写入输出目录
  • 生成bundle.js -> ./dist/main.js
  • 浏览器可直接执行
generateCode(code) {
    //! 生成bundle.js => ./dist/main.js
    const filePath = path.join(this.output.path, this.output.filename);
    console.log(filePath);
    const newCode = JSON.stringify(code);
    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('${this.entry}') //./src/index
     })(${newCode})`;
    fs.writeFileSync(filePath, bundle, "utf-8");
}

end

  • 此时简易的webpack就已经完成了,通过执行生成的code,可直接在浏览器运行
  • 代码放置在github上,有兴趣的小伙伴可以去看看