编写一个bundler的步骤
1.安装node环境;
2.创建项目;
3.创建src源代码目录;
4.创建bundler.js分析文件;
5.首先定义一个函数,用来分析模块代码;
1) fs读取文件;
2)使用babel/parser代码解析工具解析文件,解析为抽象语法树;
3)使用babel/traverse工具遍历抽象语法树,把依赖模块路径替换为绝对路径;
4)使用babel/core中的transformFromAst解析转译语法树中的code代码,转换成浏览器可以运行的代码;
5)返回需要内容;
6.再定义一个函数–通过队列的形式----把所有模块的依赖信息存放到依赖图谱中;
7.定义一个函数—用来把获取到的依赖模块代码生成浏览器可执行的代码;(通过闭包的形式实现,避免各模块内变量的污染)
具体操作
1.安装node环境
进入node.js官网进行安装
2.创建项目
创建bundler文件夹
3.创建项目文件
目录如下:
|--bundler
|--src
|--index.js
|--message.js
|--word.js
|--bundler.js
项目入口文件: src/index.js
import message from './message.js';
console.log(`say ${message}`);
依赖模块: src/message.js
import { word } from './word.js';
export default word;
依赖模块: src/word.js
export const word = 'hello';
5. bundler.js分析文件:
const fs = require('fs'); // 引入node中的fs模块
const path = require('path'); // 引入path
const parser = require('@babel/parser'); // 引入@babel/parser模块
const traverse = require('@babel/traverse').default; // 引入traverse
const babel = require('@babel/core'); // 引入babel转译模块
// 定义一个函数
const moduleAnalyser = (filename) => {
// filename为要分析的入口文件
// 5-1. fs读取文件
const content = fs.readFileSync(filename, 'utf-8'); // 读取文件内容,并设置为utf-8编码格式
// 5-2. 使用babel/parser代码解析工具解析文件,解析为抽象语法树;
const ast = parser.parse(content, {
sourceType: 'module'
});
//console.log(ast); // 打印语法树结构
//console.log(ast.program.body); // 打印抽象语法树进行中的body内容
const dependencies = {}; // 收集模块名字
// 5-3. 使用babel/traverse工具遍历抽象语法树,把依赖模块路径替换为绝对路径;
traverse(ast, {
ImportDeclaration({ node }) { // 遇到ImportDeclaration类型,就执行这个函数
// node为包含的子内容
const dirname = path.dirname(filename);
const newFile = path.join(dirname, node.source.value); // 把获取到的模块路径更改为绝对路径
dependencies[node.source.value] = newFile;
}
});
// console.log(dependencies);
// 5-4. 使用babel/core中的transformFromAst解析转译语法树中的code代码;
const { code } = babel.transformFromAst(ast, null, {
// 第一个参数为抽象语法树
// 第二个参数为code,可为null
// 第三个参数为在转换的过程中需要使用的一些配置
presets: ['@babel/preset-env']
});
// console.log(code); // 解析后的入口文件代码
// 5-5. 返回需要内容;
return {
filename, // 入口文件
dependencies, // 收集的模块
code // 转译好的代码
}
}
moduleAnalyser('./src/index.js');
- 此处需要安装依赖工具:@babel/parser 、@babel/traverse 、@babel/core
运行bundler.js文件: node bundler.js
上方代码----语法树打印结果如下:
上方代码----抽象语法树进程中的body内容打印结果:
上方代码—整理的依赖对象打印结果如下:
上方代码----解析后的代码打印如下:
6. 接下来:再定义一个函数–通过队列的形式----把所有模块的依赖信息存放到依赖图谱中。
const fs = require('fs'); // 引入node中的fs模块
const path = require('path'); // 引入path
const parser = require('@babel/parser'); // 引入@babel/parser模块
const traverse = require('@babel/traverse').default; // 引入traverse
const babel = require('@babel/core'); // 引入babel转译模块
// 定义一个函数
const moduleAnalyser = (filename) => {
// filename为要分析的入口文件
// 5-1. fs读取文件
const content = fs.readFileSync(filename, 'utf-8'); // 读取文件内容,并设置为utf-8编码格式
// 5-2. 使用babel/parser代码解析工具解析文件,解析为抽象语法树;
const ast = parser.parse(content, {
sourceType: 'module'
});
const dependencies = {}; // 收集模块名字
// 5-3. 使用babel/traverse工具遍历抽象语法树,把依赖模块路径替换为绝对路径;
traverse(ast, {
ImportDeclaration({ node }) { // 遇到ImportDeclaration类型,就执行这个函数
// node为包含的子内容
const dirname = path.dirname(filename);
const newFile = path.join(dirname, node.source.value); // 把获取到的模块路径更改为绝对路径
dependencies[node.source.value] = newFile;
}
});
// 5-4. 使用babel/core中的transformFromAst解析转译语法树中的code代码;
const { code } = babel.transformFromAst(ast, null, {
presets: ['@babel/preset-env']
});
// 5-5. 返回需要内容;
return {
filename, // 入口文件
dependencies, // 收集的模块
code // 转译好的代码
}
}
// 6. 把所有模块的依赖信息存放到依赖图谱中
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.push(moduleAnalyser(dependencies[j]));
}
}
}
// 把依赖图谱转为需要的对象格式
const graph = {};
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
});
return graph; // 返回依赖图谱
}
const graph = makeDependenciesGraph('./src/index.js');
console.log(graph); // 打印我们整理的依赖图谱
运行bundler.js: node bundler.js
(打印结果如下)
上图就是我们整理得到的依赖图谱:
- 里边包含依赖关系和每个模块的转译后的代码。
- 依赖图谱,其实就是最终上图中生成的一个js对象,描述了各模块之间的关系。
const fs = require('fs'); // 引入node中的fs模块
const path = require('path'); // 引入path
const parser = require('@babel/parser'); // 引入@babel/parser模块
const traverse = require('@babel/traverse').default; // 引入traverse
const babel = require('@babel/core'); // 引入babel转译模块
// 定义一个函数
const moduleAnalyser = (filename) => {
// filename为要分析的入口文件
// 5-1. fs读取文件
const content = fs.readFileSync(filename, 'utf-8'); // 读取文件内容,并设置为utf-8编码格式
// 5-2. 使用babel/parser代码解析工具解析文件,解析为抽象语法树;
const ast = parser.parse(content, {
sourceType: 'module'
});
const dependencies = {}; // 收集模块名字
// 5-3. 使用babel/traverse工具遍历抽象语法树,把依赖模块路径替换为绝对路径;
traverse(ast, {
ImportDeclaration({ node }) { // 遇到ImportDeclaration类型,就执行这个函数
// node为包含的子内容
const dirname = path.dirname(filename);
const newFile = path.join(dirname, node.source.value); // 把获取到的模块路径更改为绝对路径
dependencies[node.source.value] = newFile;
}
});
// 5-4. 使用babel/core中的transformFromAst解析转译语法树中的code代码;
const { code } = babel.transformFromAst(ast, null, {
presets: ['@babel/preset-env']
});
// 5-5. 返回需要内容;
return {
filename, // 入口文件
dependencies, // 收集的模块
code // 转译好的代码
}
}
// 6. 把所有模块的依赖信息存放到依赖图谱中
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.push(moduleAnalyser(dependencies[j]));
}
}
}
// 把依赖图谱转为需要的对象格式
const graph = {};
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
});
return graph; // 返回依赖图谱
}
// 7. 生成浏览器可执行代码
const generateCode = (entry) => {
const graph = JSON.stringify(makeDependenciesGraph(entry));
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);
- 根据获取到的依赖关系和模块代码,通过闭包的形式整理成浏览器可执行代码。
- 使用闭包的原因是:避免各个模块的变量以及全局变量的污染。
运行bundler.js: node bundler.js
(生成代码如下)
复制这段代码,到浏览器控制台执行,发现成功打印我们需要的信息:
到此一个简单的打包工具bundler就编写完成了。