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

webpack模块化原理

时间:2023-03-26 21:11:41 JavaScript

commonjs可以在webpack中同时编写commonjs模块和es模块,无需考虑浏览器兼容性问题。下面来分析一下原理。首先搞清楚commonjs模块化是怎么处理的,简单配置webpack,写两个模块编译看看:webpack.config.jsmodule.exports={mode:"development",devtool:"none"}index.jsconsta=require('./a')console.log(a)a.jsconsta='a';module.exports=a;编译结果查看编译结果,可以发现webpack对每个模块的处理方式和node类似,每一个模块都放在一个函数环境中,传递一些必要的参数进去。Webpack将这些模块组成一个对象(属性名是模块路径(模块id),属性值是模块内容),传入一个立即执行的函数。在立即执行函数中定义了一个函数__webpack_require__,类似于node.js中的require函数。导入模块的作用。打包结果中删除了一些注释和暂时不需要的代码。可以明显看出,实现commonjs模块化的关键是__webpack_require__函数,通过传入模块id来导出。require函数的实现__webpack_require__function:function__webpack_require__(moduleId){//检查模块是否在缓存中if(installedModules[moduleId]){returninstalledModules[moduleId].exports;}//创建一个新模块(并将其放入缓存)varmodule=installedModules[moduleId]={i:moduleId,l:false,exports:{}};//执行模块函数modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);//将模块标记为已加载module.l=true;//返回模块的exportsreturnmodule.exports;}如果熟悉node的话,很容易理解这个函数:首先检查模块是否已经加载,因此需要一个全局变量installedModules来记录exports所有加载的模块。对于没有加载的模块,先构造一个模块对象。关键是要有一个exports属性来执行模块代码并返回模块导出值。最后一步是加载启动模块,也就是IIFE的最后一句:return__webpack_require__("./src/index.js");ESModules模块化处理需要借助__webpack_require__来实现。首先看一些刚刚删除的代码:__webpack_require__。r该函数用于标识es模块的export//define__esModuleonexports__webpack_require__.r=function(exports){if(typeofSymbol!=='undefined'&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:'Module'});}Object.defineProperty(exports,'__esModule',{value:true});};__webpack_require__.d用于处理es模块的命名导出//definegetterfunctionforharmonyexports__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter});}};__webpack_require__.o是改名hasOwnPreperty__webpack_require__.o=function(object,property){returnObject.prototype.hasOwnProperty.call(object,property);};我们改一下模块代码看看纯es模块导入导出的编译结果:index.jsimporta,{test}from'./a'importbfrom'./b'console.log(a);test();console.log(b)a.jsconsta='a';functiontest(){}exportdefaulta;export{test}b.jsconstb='b';exportdefaultb;参考前端进阶面试题详细解答编译结果{"./src/a.js":(function(module,__webpack_exports__,__webpack_require__){"usestrict";__webpack_require__.r(__webpack_exports__);/*和谐导出(绑定)*/__webpack_require__.d(__webpack_exports__,"test",function(){returntest;});consta='a';functiontest(){}/*harmonydefaultexport*/__webpack_exports__["default"]=(a);}),"./src/b.js":(function(module,__webpack_exports__,__webpack_require__){"usestrict";__webpack_require__.r(__webpack_exports__);constb='b';/*harmonydefaultexport*/__webpack_exports__["default"]=(b);}),"./src/index.js":(function(module,__webpack_exports__,__webpack_require__){"usestrict";__webpack_require__.r(__webpack_exports__);/*harmonyimport*/var_a__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__("./src/a.js");/*和谐导入*/var_b__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__("./src/b.js");console.log(_a__WEBPACK_IMPORTED_MODULE_0__["default"])对象(_a__WEBPACK_IMPORTED_MODULE_0__["test"])();console.log(_b__WEBPACK_IMPORTED_MODULE_1__["default"])})}根据编译结果可以明显看出和commonjs编译的结果差不多。核心是使用__webpack_require__函数。不同的是es是模块化的。exports对象首先会被__webpack_require__.r标记为es模块。对于默认导出,它是导出的默认属性。对于命名的导出,使用__webpack_require__.d来包装它。目的是让模块中的这些具名的exports,只能读取,不能修改(这是es模块的特性)。但是为什么默认不通过__webpack_require__.d来处理,这是不合理的。本来是用webpack4打包的,后来用webpack5试了一下,默认也是在webpack5打包的结果里处理的。这可能是webpack4的一个小bug,webpack5的编译结果略有不同,但是整个逻辑是一样的:两种模块化交互webpack支持两种模块化代码共存,虽然不推荐这样做所以。首先我们看一下相互导入时的导入结果:先看看webpack是怎么实现的,先修改module:index.jsconst{a,test}=require('./a')a.jsimportbfrom'./b'import*asbbbfrom'./b'console.log(bbb)console.log(b)console.log(b.b)consta='a';functiontest(){}exportdefaulta;export{test};b.jsmodule.exports={b:()=>{},moduleName:'b'}编译结果{"./src/a.js":(function(module,__webpack_exports__,__webpack_require__){"usestrict";__webpack_require__.r(__webpack_exports__);/*harmonyexport(binding)*/__webpack_require__.d(__webpack_exports__,"test",function(){returntest;});/*harmony导入*/var_b__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__("./src/b.js");/*和声导入*/var_b__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(_b__WEBPACK_IMPORTED_MODULE_0__);console.log(_b__WEBPACK_BP_IMPORTED_WACKED)console.log_MODULE_0___default.a)console.log(_b__WEBPACK_IMPORTED_MODULE_0___default.a.b)consta='a';functiontest(){}/*harmonydefaultexport*/__webpack_exports__["default"]=(a);}),"./src/b.js":(function(module,exports){module.exports={b:()=>{},moduleName:'b'}}),"./src/index.js":(function(module,exports,__webpack_require__){const{a,test}=__webpack_require__("./src/a.js")})}可以发现,当通过es导入一个commonjs模块时module,导入的模块会被import进行一层包装,通过__webpack_require__.n,主要目的应该是兼容import*asobjfrom'....'的语法该函数的具体实现:__webpack_require__.n=function(module){vargetter=module&&module.__esModule?函数getDefault(){返回模块['默认'];}:函数getModuleExports(){返回模块;};__webpack_require__.d(getter,'a',getter);returngetter;}总结webpack模块化的核心是__webpack_require__函数,commonjs模块化和es模块都是通过这个函数导入的。并且利用立即执行函数的特点,实现了作用域的关闭,避免了全局变量的污染,非常巧妙。