webpack 打包 bundle 原理分析
js
const compier = webpack(options);
compier.run();
通过 webpack 生命周期概率,可以知道打包大致过程
- 读取到配置的入口
- 入口模板的路径
- 模块分析
- 模块依赖(依赖的路径)
- 可以用递归的方式处理依赖模块
- 内容处理(处理后的内容代码)
- 依赖图谱对象
- 模块依赖(依赖的路径)
- webpackBootstrap 入口函数
- 读取到配置的出口
- 生产文件
- 文件存放的位置
- 生产文件
根据以上的分析,编写一个自定义 webpack 打包文件
编写自定义 webpack
打包项目
新增 webpack.config.js
, 跟 webpack
默认配置一样
js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'), // 输出的文件目录,必须是绝对路径
filename: 'main.js', // 输出的文件名称
},
mode: 'development',
};
在此之前,先安装 @babel 库
js
npm i -D @babel/parser @babel/traverse @babel/core @babel/preset-env
@babel/parser
来支持解析内容转ast
树方便解析@babel/traverse
来可以过滤ast
树@babel/core
和@babel/preset-env
将ast
转为js
Ast
抽象语法书:以 node 节点为单位的 tree 树结构
新建 /lib/webpack.js
, 打包的核心文件
js
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const { transformFromAst } = require('@babel/core');
module.exports = class webpack {
constructor(options) {
// console.log(options)
const { entry, output } = options;
this.entry = entry;
this.output = output;
this.modules = [];
}
run() {
const info = this.parse(this.entry);
// 输出依赖图谱
this.modules.push(info);
for (let i = 0; i < this.modules.length; i++) {
const item = this.modules[i];
const { yilai } = item;
for (let j in yilai) {
this.modules.push(this.parse(yilai[j]));
}
}
// 数据结构转换 arr->obj
const obj = {};
this.modules.forEach((item) => {
obj[item.entryFile] = item;
});
this.file(obj);
}
parse(entryFile) {
// 读取入口文件
const content = fs.readFileSync(entryFile, 'utf-8');
// 解析内容为 ast 树
const ast = parser.parse(content, { sourceType: 'module' });
// 过滤 ast 树
const dirname = path.dirname(entryFile);
//分析依赖 以及依赖路径
// 内容处理
const yilai = {};
traverse(ast, {
ImportDeclaration({ node }) {
const newPathName = './' + path.join(dirname, node.source.value);
yilai[node.source.value] = newPathName;
},
});
// ast 转 js
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env'],
});
return {
entryFile,
yilai,
code,
};
}
file(code) {
// 生产文件,放入到 dist 的目录
const filePath = path.join(this.output.path, this.output.filename);
// 生产 bundle 文件内容
const newCode = JSON.stringify(code);
const bundle = `(function (modules) {
function require(module) {
function pathRequire(relaPath) {
// relaPath: ./a.js -> ./src/a.js
return require(modules[module].yilai[relaPath])
}
const exports = {};
(function (require,exports, code) {
eval(code)
})(pathRequire,exports, modules[module].code)
return exports
}
require('${this.entry}')
})(${newCode})`;
// webpackBootstrap
fs.writeFileSync(filePath, bundle, 'utf-8');
}
};
以下测试代码,新建 src/b.js
js
export const strb = 'b.js';
新建 src/a.js
js
import { strb } from './b.js';
export const stra = 'a.js' + strb;
新建入口文件 src/index.js
js
import { stra } from './a.js';
console.log(stra + 'index.js');
最后新建打包文件 bundle.js
js
const webapck = require('./lib/webpack');
const options = require('./webpack.config');
new webapck(options).run();
执行 node bundle.js
, 即可生产 ./dist/main.js
js
(function (modules) {
function require(module) {
function pathRequire(relaPath) {
// relaPath: ./a.js -> ./src/a.js
return require(modules[module].yilai[relaPath]);
}
const exports = {};
(function (require, exports, code) {
eval(code);
})(pathRequire, exports, modules[module].code);
return exports;
}
require('./src/index.js');
})({
'./src/index.js': {
entryFile: './src/index.js',
yilai: { './a.js': './src\\a.js' },
code: '"use strict";\n\nvar _a = require("./a.js");\n\nconsole.log(_a.stra + \'index.js\');',
},
'./src\\a.js': {
entryFile: './src\\a.js',
yilai: { './b.js': './src\\b.js' },
code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.stra = void 0;\n\nvar _b = require("./b.js");\n\nvar stra = \'a.js\' + _b.strb;\nexports.stra = stra;',
},
'./src\\b.js': {
entryFile: './src\\b.js',
yilai: {},
code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.strb = void 0;\nvar strb = \'b.js\';\nexports.strb = strb;',
},
});
运行以上代码结果
js
a.jsb.jsindex.js;