当前位置: 首页 > Web前端 > HTML

其实webpack编译“模块化”的源码并没有那么难

时间:2023-03-29 11:42:40 HTML

我们在webpack初体验一文中演示了浏览器不支持CommonJS,在特定场景下只支持EsModule,而webpack可以将这些模块化的转换代码被解析为浏览器可识别的语法。那么webpack是如何处理模块化的呢?让我们来看看。项目结构demo├─src│├─utils││├─common_math.js││└─esmodule_format.js│├─common_index.js│├─esmodule_index.js│└─index.js├─index.html├─package.json└─webpack.config.js使用CommonJS和ESModule分别导入和导出//common_math.js-使用CommonJS导出functionadd(a,b){returna+b;}functionsub(a,b){returna-b;}module.exports={add,sub,};//esmodule_format.js-使用ESModule导出函数timeFormat(){return"2022-02-02";}export{timeFormat};配置文件配置webpack.config.js中的“mode”默认为“production”。这时候代码被压缩和丑化了。为了方便阅读,我们将其设置为“开发”。在“development”模式下,“devtool”默认为“eval”,会增加很多我们暂时不需要的内容,所以将“devtool”设置为“source-map”(关于“source-map”,nextarticle文章会详细介绍)constpath=require("path");module.exports={entry:"./src/common_index.js",mode:"development",devtool:"source-map",output:{文件名:"./bundle.js",路径:path.resolve(__dirname,"./dist"),},};CommonJS首先让webpack解析CommonJS语法,所以入口指定common_index.js//common_index.js——useCommonJSimportconst{add,sub}=require("./utils/common_math.js");控制台日志(添加(20、30));控制台日志(子(20、30));执行npmrunbuild,为了阅读方便,将生成的bundle.js和自执行函数最外层的注释全部删除。对于CommonJS的处理,主要有两个对象和一个函数__webpack_modules__对象,用于保存文件路径和文件内容的映射关系__webpack_module_cache__对象,用于缓存加载的文件。key值为文件路径,值为export__webpack_require__函数的内容,用于加载文件并将导出的内容添加到module.exports和exports对象中。具体的webpack编译源码var__webpack_modules__={//定义一个对象,对象中key为文件路径,value为函数,函数包含文件内容"./src/utils/common_math.js":(module)=>{functionadd(a,b){returna+b;}functionsub(a,b){返回a-b;}module.exports={添加,子,};},};//定义用于缓存加载文件的对象var__webpack_module_cache__={};function__webpack_require__(moduleId){//从缓存对象中获取当前moduleId的值varcachedModule=__webpack_module_cache__[moduleId];//如果存在,直接返回exports对象if(cachedModule!==undefined){returncachedModule.exports;}//如果不存在,则将key添加到缓存对象中作为moduleId,值为{export:{}}datavarmodule=(__webpack_module_cache__[moduleId]={exports:{},});//通过moduleId,执行__webpack_modules__中保存的方法,传入参数module对象,//方法执行后,module.exports和exports对象会被修改__webpack_modules__[moduleId](module,module.exports,__webpack_require__);//当前返回的是对象{add,sub}returnmodule.exports;}var__webpack_exports__={};//此处未使用//执行common_math.jsconst{add,sub}=__webpack_require__("./src/utils/common_math.js");console.log(add(20,30));console.log(sub(20,30));通过上述方式,webpack将浏览器无法识别的CommonJS代码编译成浏览器可以正常运行的语法。ESModule将webpack.config.js文件中的入口改为esmodule_index.js//esmodule_index.js——使用ESModuleimportimport{timeFormat}from"./utils/esmodule_format.js";console.log(timeFormat());然后执行npmrunbuild。为了可读性,注释中的bundle.js,以及自执行函数的最外层严格模式规定全部去掉。ESModule的实现和CommonJS一样,也是有这两个对象和一个函数__webpack_modules__对象,用来保存文件路径和文件内容的映射关系__webpack_module_cache__对象,用来缓存加载的文件和keyvalues为文件路径,value为导出的内容。__webpack_require__函数用于加载文件并将导出的内容添加到module.exports和exports对象中。但是ESModule比较复杂,有3个函数__webpack_require__.d会把导入的对象属性和值遍历到exports对象__webpack_require__.o判断某个对象是否存在某个属性__webpack_require__.r添加__esModule属性给文件中的exports对象,使用ESModule实现模块化。具体的webpack编译源码var__webpack_modules__={"./src/utils/esmodule_format.js":(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{//添加__esModule属性到exports__webpack_require__.r(__webpack_exports__);timeFormat被添加到导出对象__webpack_require__.d(__webpack_exports__,{timeFormat:()=>timeFormat,});函数timeFormat(){返回“2022-02-02”;}},};var__webpack_module_cache__c={}f;__webpack_require__(moduleId){缓存变量模块=__webpack_module_cache__[moduleId];if(cachedModule!==undefined){返回cachedModule.exports;}varmodule=(__webpack_module_cache__[moduleId]={exports:{},});__webpack_modules__[moduleId](module,module.exports,__webpack_require__);returnmodule.exports;}__webpack_require__.d=(exports,definition)=>{//遍历esmodule_format.js中导出的对象for(varkeyindefinition){//如果当前导出对象不存在If(__webpack_require__.o(definition,key)&&!__webpack_require__.o(exports,key)){//定义exports对象并将该函数用作get属性Object.defineProperty(exports,key,{enumerable:true,get:definition[key],});}}};//判断obj对象中是否存在prop属性__webpack_require__.o=(obj,prop)=>Object.prototype.hasOwnProperty.call(obj,prop);//添加__esModule属性到导出对象__webpack_require__.r=(exports)=>{if(typeofSymbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module",});}Object.defineProperty(exports,"__esModule",{value:true});};var__webpack_exports__={};//__webpack_exports__上下两行暂时不用__webpack_require__.r(__webpack_exports__);//使用函数将导出的内容加载到exports和module.exports对象中var_utils_esmodule_format_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__("./src/utils/esmodule_format.");//下面的代码与直接调用函数一致。console.log((0,_utils_esmodule_format_js__WEBPACK_IMPORTED_MODULE_0__.timeFormat)());webpack以不同的方式处理ES模块和CommonJS。在CommonJS中,属性直接添加到exports对象中,ESModule是通过defineProperty在属性的访问选择器get中定义的。ESModule和CommonJS一起使用。将webpack.config.js文件中的条目更改为index.js//index.js//ESModule导出内容CommonJSimportconst{timeFormat}=require("./utils/esmodule_format.js");//CommonJS导出内容ESModuleimportimport{add,sub}from("./utils/common_math.js");console.log(timeFormat());console.log(add(20,30));console.log(sub(20,30));再次执行npmrunbuild,为了阅读方便,删除了bundle.js中的所有注释,最外层的自执行函数,以及严格的模式规定。模块化互引用方式复用了ESModule和CommonJS都有的两个对象和一个函数__webpack_modules__对象,用来保存文件路径和文件内容的映射关系__webpack_module_cache__对象,用来缓存加载的文件,key值为文件路径,值为导出的内容。__webpack_require__函数用于加载文件并将导出的内容添加到module.exports和exports对象中。同时保留了ESModule特有的这三个功能。__webpack_require__.d将传入的对象属性和值遍历到exports对象__webpack_require__.o来判断对象中是否存在某个属性__webpack_require__.r在使用ESModule的文件中的exports对象中添加一个__esModule属性实现模块化__webpack_require__.n函数将遍历赋值定义为一个函数,并为变量添加一个属性,值为函数var__webpack_modules__={"./src/utils/common_math.js":(module)=>{函数添加(a,b){返回a+b;}functionsub(a,b){返回a-b;}module.exports={添加,子,};},"./src/utils/esmodule_format.js":(ule__unused_webpack_,__webpack_exports__,__webpack_require__)=>{"usestrict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,{时间格式:()=>时间格式,});函数timeFormat(){返回“2022-02-02”;}},};var__webpack_module_cache__={};function__webpack_require__(moduleId){varcachedModule=__webpack_module_cache__[moduleId];if(cachedModule!==undefined){returncachedModule.exports;}varmodule=(__webpack_module_cache__[moduleId]={exports:{},});__webpack_modules__[moduleId](module,module.exports,__webpack_require__);returnmodule.exports;}__webpack_require__.n=(module)=>{vargetter=module&&module.__esModule?()=>模块["默认"]:()=>模块;__webpack_require__.d(getter,{a:getter});returngetter;};__webpack_require__.d=(exports,definition)=>{for(varkeyindefinition){if(__webpack_require__.o(definition,key)&&!__webpack_require__.o(exports,key)){Object.defineProperty(出口,关键,{可枚举:真,得到:定义[键],});}}};__webpack_require__.o=(obj,prop)=>Object.prototype.hasOwnProperty.call(obj,prop);__webpack_require__.r=(exports)=>{if(typeofSymbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});}Object.defineProperty(exports,"__esModule",{value:true});};var__webpack_exports__={};__webpack_require__.r(__webpack_exports__);var_utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__("./src/utils/common_math.js");//暂时没有用到var_utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__default=__webpack_require__.n(_utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__);const{timeFormat}=__webpack_require__("./src/utils/esmodule_format.log();(0,_utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__.add)(20,30));console.log((0,_utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__.sub)(20,30));支持ESModule和CommonJS混合使用,webpack的处理方式是将两者分开组合。以上就是webpack编译模块化文件源码的内容,更多关于webpack的内容可以参考我的其他博文,持续更新中~