总结webpack源码-简版
1.Compiler.js:负责整个编译过程的控制和管理,包含compiler构造函数,在构造函数中获取webpack.config.js的出口、入口、参数等
2.Bundler.js:引入webpack.config.js和compiler.js,创建compiler实例化对象并传入options。
3.拿到webpack配置参数后开始根据入口文件解析文件内容(fs.readFileSync读取文件内容)
(1). @babel/parser:将文件内容的的源代码解析成AST语法树,AST语法树的节点包含了文件依赖的文件名称。(type,start,end,...) (2). @babel/traverse:提取依赖的文件名称到一个数组中 (3). @babel/core:通过babel.transformFromAst方法将ast转换成浏览器可执行的代码;返回一个对象{文件名,依赖数组,可执行代码},作为依赖图的一个节点 (4). 遍历入口文件的依赖数组,数组中是文件名,递归执行上述方法直到找到所有的依赖,然后返回依赖对象。
4.Bundler.js使用compiler对象的run方法生成依赖对象,并通过bundle方法解析graph(依赖图对象),使用eval方法来调用code并输出分发代码
5.Bundler.js使用fs.writeFileSync将分发代码输出到文件中,生成bundle。
complier.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"); module.exports = class Complier { constructor(options) { const { entry, output } = options; this.entry = entry; this.output = output; } // 拿到参数、执行、分析入口文件 run() { const mainAsset = this.createAsset(this.entry); const queue = [mainAsset]; for (const asset of queue) { Object.values(asset.dependencies).forEach((filename) => { const child = this.createAsset(filename); queue.push(child); }); } console.log(queue); return queue; } // 开始编译,构建ast语法树 filename: ./src/index.js createAsset(filename) { const content = fs.readFileSync(filename, "utf-8"); const ast = parser.parse(content, { sourceType: "module" }); // 创建依赖 const dependencies = {}; traverse(ast, { ImportDeclaration: ({ node }) => { // 获取文件的路径名如 './src/index.js' dirname='./src' const dirname = path.dirname(filename); const absPath = path.join(dirname, node.source.value); dependencies[node.source.value] = absPath; }, }); // 将ast转换成代码 // https://www.babeljs.cn/docs/babel-core const { code } = babel.transformFromAst(ast, null, { presets: ["@babel/preset-env"], }); return { filename, dependencies, code }; } };
bundler.js
// 引入配置 const fs = require('fs'); const path = require('path') const options = require('../webpack.config') const complier = require('./complier') const { entry, output } = options function bundle(graph){ // 得到以依赖文件名的对象 let modules = {}; graph.forEach(item => { modules[item.filename] = { dependencies: item.dependencies, code: item.code } }) modules = JSON.stringify(modules) const result = `(function(graph){ function require(filepath) { function localRequire(relativePath) { // 将代码中的require中的路径转换成dependencies存储的带文件夹名的路径 return require(graph[filepath].dependencies[relativePath]) } var exports = {} function fn(require, exports, code) { eval(code) } fn(localRequire, exports, graph[filepath].code) return exports } require('${entry}') })(${modules})` return result } function createFile(code) { fs.writeFileSync(path.join(output.path, output.filename), code) } const graph = new complier(options).run() const result = bundle(graph) createFile(result)