每个模块执行除了执行我们的逻辑外,还会将export的内容添加到module.exports上,这就是前面说的__webpack_require__.d辅助方法的作用。添加到module.exports上其实就是添加到了__webpack_module_cache__缓存上,后面再引用这个模块就直接从缓存拿了。
这个流程我太熟悉了,因为他简直跟Node.js的CommonJS实现思路一模一样,具体的可以看我之前写的这篇文章:深入Node.js的模块加载机制,手写require函数。
第三块代码其实就是我们前面看到过的几个辅助函数的定义,具体干啥的,其实他的注释已经写了:
__webpack_require__.d:核心其实是Object.defineProperty,主要是用来将我们模块导出的内容添加到全局的__webpack_module_cache__缓存上。
__webpack_require__.o:其实就是Object.prototype.hasOwnProperty的一个简写而已。
__webpack_require__.r:这个方法就是给每个模块添加一个属性__esModule,来表明他是一个ES6的模块。
第四块就一行代码,调用__webpack_require__加载入口模块,启动执行。
这样我们将代码分成了4块,每块的作用都搞清楚,其实webpack干的事情就清晰了:
将import这种浏览器不认识的关键字替换成了__webpack_require__函数调用。
__webpack_require__在实现时采用了类似CommonJS的模块思想。
一个文件就是一个模块,对应模块缓存上的一个对象。
当模块代码执行时,会将export的内容添加到这个模块对象上。
当再次引用一个以前引用过的模块时,会直接从缓存上读取模块。
自己实现一个webpack现在webpack到底干了什么事情我们已经清楚了,接下来我们就可以自己动手实现一个了。根据前面最终生成的代码结果,我们要实现的代码其实主要分两块:
遍历所有模块,将每个模块代码读取出来,替换掉import和export关键字,放到__webpack_modules__对象上。
整个代码里面除了__webpack_modules__和最后启动的入口是变化的,其他代码,像__webpack_require__,__webpack_require__.r这些方法其实都是固定的,整个代码结构也是固定的,所以完全可以先定义好一个模板。
使用AST解析代码由于我们需要将import这种代码转换成浏览器能识别的普通JS代码,所以我们首先要能够将代码解析出来。在解析代码的时候,可以将它读出来当成字符串替换,也可以使用更专业的AST来解析。AST全称叫Abstract Syntax Trees,也就是抽象语法树,是一个将代码用树来表示的数据结构,一个代码可以转换成AST,AST又可以转换成代码,而我们熟知的babel其实就可以做这个工作。要生成AST很复杂,涉及到编译原理,但是如果仅仅拿来用就比较简单了,本文就先不涉及复杂的编译原理,而是直接将babel生成好的AST拿来使用。
注意: webpack源码解析AST并不是使用的babel,而是使用的acorn,webpack继承acorn的Parser,自己实现了一个,本文写作时采用了babel,这也是一个大家更熟悉的工具。
比如我先将入口文件读出来,然后用babel转换成AST可以直接这样写:
const fs = require("fs"); const parser = require("@babel/parser"); const config = require("../webpack.config"); // 引入配置文件 // 读取入口文件 const fileContent = fs.readFileSync(config.entry, "utf-8"); // 使用babel parser解析AST const ast = parser.parse(fileContent, { sourceType: "module" }); console.log(ast); // 把ast打印出来看看上面代码可以将生成好的ast打印在控制台:
这虽然是一个完整的AST,但是看起来并不清晰,关键数据其实是body字段,这里的body也只是展示了类型名字。所以照着这个写代码其实不好写,这里推荐一个在线工具https://astexplorer.net/,可以很清楚的看到每个节点的内容:
从这个解析出来的AST我们可以看到,body主要有4块代码:
ImportDeclaration:就是第一行的import定义
VariableDeclaration:第三行的一个变量申明
FunctionDeclaration:第五行的一个函数定义
ExpressionStatement:第十三行的一个普通语句
你如果把每个节点展开,会发现他们下面又嵌套了很多其他节点,比如第三行的VariableDeclaration展开后,其实还有个函数调用helloWorld():