跳到主要内容

webpack打包机制【原理】

参考:https://juejin.cn/post/6987180860852142093#comment

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

打包过程可以拆分为四步:

  1. 利用babel完成代码转换,并生成单个文件的依赖
  2. 从入口开始递归分析,并生成依赖图谱
  3. 将各个引用模块打包为一个立即执行函数
  4. 将最终的bundle.js写入bundle.js中

简单的说

  • 1、找到入口文件,分析内容,获取AST抽象语法树
  • 2、递归分析找出模块依赖, 生成依赖关系图,拿到依赖路径,转换代码
  • 3、输出源代码,把bundle.js输出到 dist目录中

手写一个bundle.js​

1、借助@babel/parser :https://babeljs.io/docs/en/babel-parser#docsNav

  • 这是 babel7的工具,来帮助我们分析内部的语法,包括es6,返回一个AST抽象语法树
  • AST:Abstract Syntax Tree :抽象语法树

2、借助@babel/traverse : https://www.babeljs.cn/docs/babel-traverse

  • 根据AST的对象中body:Ast.program.body里面分析结果,遍历出所有的引入模块,但是比较麻烦
  • 这里还是推荐babel推荐的一个模块@babel/traverse来帮我们处理
  • @babel/traverse 可以用来遍历@babel/parser生成的AST

3、借助@babel/core 、@babel/preset-env

  • @babel/core: 这个库负责解析,方便各个插件分析语法进行相应的处理
  • @babel/preset-env: 包含比较新的ES语法 ,es6转es5的转换规则,转换当前浏览器可支持的语法特性
text
npm install @babel/parser --save //返回一个AST抽象语法树
npm install --save @babel/traverse //用于遍历AST

1、拿到入口文件名字,读取入口文件,分析代码

2、若入口文件有依赖,拿到对应的依赖模块

3、循环递归分析出所有依赖关系

4、对依赖模块进行语法分析,转换对应的代码

js
//找到入口文件,分析内容,有依赖的话,拿到依赖路径,转换代码(浏览器里可以运行的)
const path = require("path");
const fs = require("fs");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");

const entry = entryFile => {
const content = fs.readFileSync(entryFile, "utf-8");
const ast = parser.parse(content, {
sourceType: "module"
});

const dependecies = {};
traverse(ast, {
//以函数的方式

ImportDeclaration({ node }) {
const dirname = path.dirname(entryFile);
const newPath = "./" + path.join(dirname, node.source.value);
dependecies[node.source.value] = newPath;
}
});

const { code } = transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
});

return {
entryFile,
dependecies,
code
};
};

//分析出所有依赖关系

const Dependecies = entryFile => {
const info = entry(entryFile);

const modules = [];
modules.push(info);

for (let i = 0; i < modules.length; i++) {
const item = modules[i];
const { dependecies } = item;
if (dependecies) {
for (let j in dependecies) {
modules.push(entry(dependecies[j]));
}
}
}

const obj = {};
modules.forEach(item => {
obj[item.entryFile] = {
dependecies: item.dependecies,
code: item.code
};
});
return obj;
};

//生成代码

const genCode = entryFile => {
const obj = Dependecies("./src/index.js");
const graph = JSON.stringify(obj);
const bundle = `(function(graph){
function require(module){
function localRequire(relativePath){
return require(graph[module].dependecies[relativePath])
}
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[module].code);
return exports;
}
require('${entryFile}')
})(${graph})`;

fs.writeFileSync(path.resolve(__dirname, "./dist/main.js"), bundle, "utf-8");
};

genCode("./src/index.js");