1.解析模块
主要用到fs模块以及babel相关库,主要依赖如下:
const parser = require('@babel/parser');
const path = require('path');
const fs = require('fs');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');
const generate = require('@babel/generator').default;
先利用node提供的fs模块读取文件,再利用babel解析文件:生成AST -> 便利ast收集依赖 -> es6转es5,最后将模块信息返回
const getModuleInfo = (file) => {
const body = fs.readFileSync(file, 'utf-8');
// console.log('@@@@@@@@@@body====\n', body);
// 解析成ast
const ast = parser.parse(body, {
sourceType: 'module', //表示我们要解析的是ES模块
});
// console.log('@@@@ast===\n', ast);
// console.log('@@@@ast.program.body===\n', ast.program.body);
// 收集依赖
const deps = {};
traverse(ast, {
/* ImportDeclaration方法代表的是对type类型为ImportDeclaration的节点的处理 */
ImportDeclaration({ node }) {
const dirname = path.dirname(file);
console.log('@@@dirname==============', dirname); // ./src
const abspath = './' + path.join(dirname, node.source.value);
deps[node.source.value] = abspath;
},
/* 处理ast里的函数声明 */
// FunctionDeclaration: function (path) {
// /* 改变函数名 */
// path.node.id.name = 'x';
// console.log('@@@FunctionDeclaration===', path.node);
// },
});
console.log('@@@deps==============\n', deps);
const result = generate(ast);
console.log('@@@result==============\n', result);
/* 转换代码, es6转es5 */
const { code } = babel.transformFromAst(ast, null, {
presets: ['@babel/preset-env'],
});
// console.log('@@@@@@@@@@transformed code=====\n', code);
/*我们返回了一个对象 ,这个对象包括该模块的路径(file),该模块的依赖(deps),该模块转化成es5的代码 */
const moduleInfo = { file, deps, code };
return moduleInfo;
};
2.递归解析模块信息
根据入口文件递归获取依赖并解析,最后得到依赖图depsGraph(这里未考虑循环引用,是用键值对对象表示)
getModuleInfo('./src/index.js');
// getModuleInfo('./src/indexTest.js');
/* 递归获取依赖 */
const parseModules = (file) => {
const entry = getModuleInfo(file);
const temp = [entry];
for (let i = 0; i < temp.length; i++) {
const deps = temp[i].deps;
if (deps) {
/* 遍历所有依赖,递归获取到依赖模块数据 */
for (const key in deps) {
if (deps.hasOwnProperty(key)) {
temp.push(getModuleInfo(deps[key]));
}
}
}
}
// console.log('@@@temp====\n', temp);
/* 路径为key,{code,deps}为值的形式存储。因此,我们创建一个新的对象depsGraph。 */
const depsGraph = {}; //新增代码
temp.forEach((moduleInfo) => {
depsGraph[moduleInfo.file] = {
deps: moduleInfo.deps,
code: moduleInfo.code,
};
});
// console.log('@@@@@@@@depsGraph==========\n', depsGraph);
return depsGraph;
};
3.实现require和export
实现require 和 export关键字,以file为入口文件,根据依赖图递归执行代码
const bundle = (file) => {
const depsGraph = JSON.stringify(parseModules(file));
return `(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
var exports = {}
;(function (require,exports,code) {
eval(code)
})(absRequire,exports,graph[file].code)
return exports
}
require('${file}')
})(${depsGraph})`;
};
4.写入文件
将打包转换后的代码写入文件(一般是dist目录下)
//写入到我们的dist目录下
!!fs.existsSync('./dist') && fs.rmdirSync('./dist');
// fs.writeFileSync('./dist/bundle.js', content);