「本文正在参与技术专题征文Node.js进阶之路,点击查看详情」
为了更好的理解webpack 编译原理,
来写一个 类似webpack的bundler打包工具。
话不多说,直接开始 ⬇️⬇️⬇️
1. 建立文件结构
- bundler.js
- src/index.js
import message from './message.js';
console.log(message);
- src/message.js
import { word } from './word.js'; const message = `say ${word}`; export default message; - src/word.js
export const word = 'hello';
2. 对文件进行分析
类webpack:
1、使用nodeJS读取入口文件;
2、@babel/parser逐个获取入口文件中的依赖文件,解析代码生成AST语法树
该babel会解析出某个文件的AST,根据AST中对每行代码的type字段来判定该节点是否为依赖;
3、@babel/traverse遍历AST找到type为ImportDeclaretion的节点里的文件名,生成依赖关系树
es6转换为es5,安装@babel/core和@babel/preset-env
3. 分析单文件 moduleAnalyser
// bundler.js
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core'); //
const moduleAnalyser = (filename) => {
const content = fs.readFileSync(filename, 'utf-8');
// 语法树
const ast = parser.parse(content, {
sourceType: 'module'
});
// 声明依赖
const dependencies = {};
traverse(ast, {
// 语法树 引入
ImportDeclaration({ node }) {
const dirname = path.dirname(filename);
// 相对路径转为真实的路径
const newFile = './' + path.join(dirname, node.source.value);
dependencies[node.source.value] = newFile;
}
});
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
});
return {
filename,
dependencies,
code
}
}
const moduleInfo = moduleAnalyser('./src/index.js');
console.log(moduleInfo);
4 依赖图谱 分析( Dependencies Graph )
依赖图谱: 循环入口文件的分析结果,获取该入口文件的依赖文件,继续循环依赖文件的键值对。
// bundler.js
// ...
// const moduleAnalyser = (filename) => { …… }
// 依赖图谱分析
const makeDependenciesGraph = (entry) => {
const entryModule = moduleAnalyser(entry);
const graphArray = [ entryModule ];
for(let i = 0; i < graphArray.length; i++) {
const item = graphArray[i];
const { dependencies } = item;
// 如果有依赖,循环分析依赖
if(dependencies) {
for(let j in dependencies) {
// 把分析结果放入 graphArray
graphArray.push(
moduleAnalyser(dependencies[j])
);
}
}
}
const graph = {}; // 绝对路径对应:依赖和code
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
});
return graph;
}
const graghInfo = makeDependenciesGraph('./src/index.js');
console.log(graghInfo);
graph 打印内容 如下:
5. Bundler 源码编写( 生成代码
构建require、export
网页中执行的代码放在闭包中,避免污染全局环境
// bundler.js
// ……
// 单个文件时解析
// ... const moduleAnalyser = (filename) => { …… }
// 依赖图谱分析
// const makeDependenciesGraph = (entry) => { …… }
// 生成代码
const generateCode = (entry) => {
const graph = JSON.stringify(makeDependenciesGraph(entry));
// 构建require、export
return `
(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('${entry}')
})(${graph});
`;
}
const code = generateCode('./src/index.js');
console.log(code);