实现
废话补多说,直接上代码
1const fs = require("fs");2const path = require("path");3const parser = require("@babel/parser");4const traverse = require("@babel/traverse").default;5const babel = require("@babel/core");6const options = require("./mini-webpack.config");78class MiniWebpack {9 constructor(options) {10 this.options = options;11 }1213 parse = (filename) => {14 // 读取文件15 const fileBuffer = fs.readFileSync(filename, "utf-8");16 // 转换成抽象语法树17 const ast = parser.parse(fileBuffer, { sourceType: "module" });1819 const dependencies = {};20 // 遍历抽象语法树21 traverse(ast, {22 // 处理ImportDeclaration节点23 ImportDeclaration({ node }) {24 const dirname = path.dirname(filename);25 const newDirname =26 "./" + path.join(dirname, node.source.value).replace("\\", "/");27 dependencies[node.source.value] = newDirname;28 },29 });30 // 将抽象语法树转换成代码31 const { code } = babel.transformFromAst(ast, null, {32 presets: ["@babel/preset-env"],33 });3435 return {36 filename,37 dependencies,38 code,39 };40 };4142 analyse = (entry) => {43 // 解析入口文件44 const entryModule = this.parse(entry);45 const graphArray = [entryModule];46 // 循环解析模块,保存信息47 for (let i = 0; i < graphArray.length; ++i) {48 const { dependencies } = graphArray[i];49 Object.keys(dependencies).forEach((filename) => {50 graphArray.push(this.parse(dependencies[filename]));51 });52 }5354 const graph = {};55 // 生成依赖图谱对象56 graphArray.forEach(({ filename, dependencies, code }) => {57 graph[filename] = {58 dependencies,59 code,60 };61 });6263 return graph;64 };6566 generate = (graph, entry) => {67 return `68 (function(graph){69 function require(filename){70 function localRequire(relativePath){71 return require(graph[filename].dependencies[relativePath]);72 }73 const exports = {};74 (function(require, exports, code){75 eval(code);76 })(localRequire, exports, graph[filename].code)7778 return exports;79 }8081 require('${entry}');82 })(${graph})83 `;84 };8586 fileOutput = (output, code) => {87 const { path: dirPath, filename } = output;88 const outputPath = path.join(dirPath, filename);8990 // 如果没有文件夹的话,生成文件夹91 if (!fs.existsSync(dirPath)) {92 fs.mkdirSync(dirPath);93 }94 // 写入文件中95 fs.writeFileSync(outputPath, code, "utf-8");96 };9798 run = () => {99 const { entry, output } = this.options;100 const graph = this.analyse(entry);101 // stringify一下依赖图谱对象,防止在模板字符串中调用toString()返回[object Object]102 const graphStr = JSON.stringify(graph);103 const code = this.generate(graphStr, entry);104 this.fileOutput(output, code);105 };106}107108const miniWebpack = new MiniWebpack(options);109miniWebpack.run();
可以在 codesadebox 里测试
Terminal 里执行 node main.js