类似于webpack打包工具的底层原理
步骤:1.拿到入口文件的代码并读出来转化为js对象(抽象语法术parser)
2.拿到所有模块的依赖 ‘./message.js’,放进数组中 引入第三方模块和babel相关
3.对代码进行转换使浏览器能够执行从es6/es module语法转化
目的:入口文件的完整分析
node bundler.js //输出文件内容
实现
创建函数
传入入口文件
引入fs模块
使用fs.readFileSync方法读取文件,格式为utf-8
安装babel/parser模块
使用parser分析出抽象语法术
安装traverse模块
使用traverse进行模块分析获取内容 获取依赖路径
存储相对路径和绝对路径至对象中
安装babel/core模块进行代码转换,使代码能够在浏览器上运行
对所有模块进行分析
makeDependenciesGraph
获取模块的路径对象和浏览器运行的代码entryModule
放入数组中graphArray = [ entryModule]
循环graphArray .length
获取当前对象
获取对象当前的路径对象
如果路径存在
遍历路径对象
将绝对路径传递给dependencies方法并添加进graphArray 数组 可以继续进行遍历 获取依赖当前模块的模块
创建graph对象
将graphArray数组遍历
将对象中的绝对路径 路径对象和代码作为键值存储在graph对象中
返回graph对象
generateCode
将makeDependenciesGraph函数传入的对象转化为字符串graph = JSON.stringify(makeDependenciesGraph(entry))
输出代码 构造export 和 require依赖
写一个大的闭包避免污染环境${graph} graph为依赖图谱
创建require函数传入'${entry}' 拿到入口
拿到依赖图谱执行对应的代码
再一个闭包拿到 入口对应的对象.code对应的代码eval(code) ,拿到其中的代码又调用了require,但拿到的是相对路径,无法找到相应的对象
module对应的是./message.js
对相对路径做出转换,执行require函数时,变成执行localRequire函数获取relativePath中的值./src/message.js!
exports创建对象并返回
.src
index.js
import message from './message.js';
console.log(message)
message.js
import word from './word.js'
const message = `say ${word}`;
export default message;
word.js
const word = 'hello';
export word;
bundler.js
cnpm install @babel/traverse --save //帮助遍历找出对应项,分析模块
cnpm install @babel/parser -D
cnpm install @babel/core -save
cnpm install @babel/preset-env -save
const fs = require('fs');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const path = require('path');
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; //相对路径为key,绝对路径为value对俩者进行保存
}
});
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
return {
filename,
dependencies,
code
}
}
https://www.babeljs.cn/docs/babel-parser ->网址
//parser.parse 方法分析出抽象语法术来解析出当前的代码
//sourceType es6 module方式编写原代码 做代码分析的时候使用sourceType
//content 代码的内容传递
//moduleAnalyser 帮助分析模块
//:fs 模块是文件操作的封装,它提供了文件的读取,写入,更名,删除,遍历目录,链接POSIX文件系统操
//ast.program.body program对应的内容 节点显示代码的声明和结构
//require('@babel/traverse').default 它默认导出的内容是esmodule导出,如果想用export default导出的内容,必须加一个default这样的属性
//ast 抽象语法术内容
//ImportDeclaration 节点
//node 内容里面的子内容
//dependencies 数组,只要碰到这种依赖就放到里面去
//抽象语法术将js代码转化为js对象
//transformFromAst 抽象语法术转化为对象,这个对象会有code这样的字段,code就是可以在浏览器运行的代码
Dependencies Graph依赖图谱
目标:对工程里所有模块进行分析
难点:使用队列的方式实现递归的效果
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 { dependencies } = item; 拿到对象文件进一步分析,拿到dependencies就可以对其中的依赖模块进行分析
对代码进行转换使浏览器能够执行
闭包的方式
构造require函数,export对象
const generateCode = (entry) => {
const graph = JSON.stringify(makeDependenciesGraph(entry));
return ` //返回的是一段字符串,代码
(function(graph) { //代码放在大的闭包中执行避免污染环境
function require(module) {
function localRequire(relativePath) { //将相对路径进行转化
return require(graph[module].dependencies[relativePath]) //relativePath === ./message.js 在graph对象中dependencies中寻找键对应的值//返回真实的路径
} //再调用外部的require,递归着执行下面的函数
var exports = {}; //
(function(require, exports, code) { //每一个模块的代码放在一个闭包中执行,避免污染全局
eval(code) //eval(code) 的时候require方法又会重新被调用,但传递的是相对路径,然后去graph[module]中寻找./message中的相应对象,但graph中的路径为./src/message.js所以找不到对应的模块,所以需要对相对路径做出转换
//所以构造localRequire方法 function(require) localRequire eval(code) 的时候执行的函数为localRequire而不是require
})(localRequire, exports, graph[module].code) //graph[module].code 对应模块中的代码
return exports; //export就是一个空对象,代码执行的时候接受一些内容并导出
};
require('${entry}') //调用require方法,拼接字符串所以需要添加引号
})(${graph});
`;
}
const code = generateCode('./src/index.js')
console.log(code)
//JSON.stringify 将对象转化为字符串
//relativePath ./message.js