前面说了,最终生成的代码,每个模块前面都有个__webpack_require__.r的调用
这个只是拿来给模块添加一个__esModule标记的,我们也给他加上吧,直接在前面export辅助方法后面加点代码就行了:
const ESMODULE_TAG_FUN = ` __webpack_require__.r(__webpack_exports__);\n `; function parseFile(file) { // 省略其他代码 // ...... let newCode = generate(ast).code; if (hasExport) { newCode = `${EXPORT_DEFAULT_FUN} ${newCode}`; } // 下面添加模块标记代码 newCode = `${ESMODULE_TAG_FUN} ${newCode}`; }再运行下看看,这个代码也加上了:
创建代码模板到现在,最难的一块,模块代码的解析和转换我们其实已经完成了。下面要做的工作就比较简单了,因为最终生成的代码里面,各种辅助方法都是固定的,动态的部分就是前面解析的模块和入口文件。所以我们可以创建一个这样的模板,将动态的部分标记出来就行,其他不变的部分写死。这个模板文件的处理,你可以将它读进来作为字符串处理,也可以用模板引擎,我这里采用ejs模板引擎:
// 模板文件,直接从webpack生成结果抄过来,改改就行 /******/ (() => { // webpackBootstrap /******/ "use strict"; // 需要替换的__TO_REPLACE_WEBPACK_MODULES__ /******/ var __webpack_modules__ = ({ <% __TO_REPLACE_WEBPACK_MODULES__.map(item => { %> '<%- item.file %>' : ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { <%- item.code %> }), <% }) %> }); // 省略中间的辅助方法 /************************************************************************/ /******/ // startup /******/ // Load entry module // 需要替换的__TO_REPLACE_WEBPACK_ENTRY /******/ __webpack_require__('<%- __TO_REPLACE_WEBPACK_ENTRY__ %>'); /******/ // This entry module used 'exports' so it can't be inlined /******/ })() ; //# sourceMappingURL=http://www.likecs.com/main.js.map 生成最终的代码生成最终代码的思路就是:
模板里面用__TO_REPLACE_WEBPACK_MODULES__来生成最终的__webpack_modules__
模板里面用__TO_REPLACE_WEBPACK_ENTRY__来替代动态的入口文件
webpack代码里面使用前面生成好的AST数组来替换模板的__TO_REPLACE_WEBPACK_MODULES__
webpack代码里面使用前面拿到的入口文件来替代模板的__TO_REPLACE_WEBPACK_ENTRY__
使用ejs来生成最终的代码
所以代码就是:
// 使用ejs将上面解析好的ast传递给模板 // 返回最终生成的代码 function generateCode(allAst, entry) { const temlateFile = fs.readFileSync( path.join(__dirname, "./template.js"), "utf-8" ); const codes = ejs.render(temlateFile, { __TO_REPLACE_WEBPACK_MODULES__: allAst, __TO_REPLACE_WEBPACK_ENTRY__: entry, }); return codes; } 大功告成最后将ejs生成好的代码写入配置的输出路径就行了:
const codes = generateCode(allAst, config.entry); fs.writeFileSync(path.join(config.output.path, config.output.filename), codes);然后就可以使用我们自己的webpack来编译代码,最后就可以像之前那样打开我们的html看看效果了:
总结本文使用简单质朴的方式讲述了webpack的基本原理,并自己手写实现了一个基本的支持import和export的default的webpack。
本文可运行代码已经上传GitHub,大家可以拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/mini-webpack
下面再就本文的要点进行下总结:
webpack最基本的功能其实是将JS的高级模块化语句,import和require之类的转换为浏览器能认识的普通函数调用语句。
要进行语言代码的转换,我们需要对代码进行解析。
常用的解析手段是AST,也就是将代码转换为抽象语法树。
AST是一个描述代码结构的树形数据结构,代码可以转换为AST,AST也可以转换为代码。
babel可以将代码转换为AST,但是webpack官方并没有使用babel,而是基于acorn自己实现了一个。
本文从webpack构建的结果入手,也使用AST自己生成了一个类似的代码。
webpack最终生成的代码其实分为动态和固定的两部分,我们将固定的部分写入一个模板,动态的部分在模板里面使用ejs占位。
生成代码动态部分需要借助babel来生成AST,并对其进行修改,最后再使用babel将其生成新的代码。