原理分析
webpack在执⾏npx webpack进⾏打包后,都⼲了什么事情?
⼤概的意思就是,我们实现了⼀个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();
分析入口模块
- 从配置文件中读取入口文件路径,读取入口文件
const fs = require("fs");
parse(entryFile) {
// 分析入口模块的内容
const content = fs.readFileSync(entryFile, "utf-8");
console.log("content", content);
}
- 分析内部语法
- ① 使⽤@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上,有兴趣的小伙伴可以去看看