当前位置: 首页 > 科技观察

JS模块化JavaScript模块化方案总结

时间:2023-03-12 00:00:05 科技观察

本文由两部分组成。第一部分通过简洁的描述介绍什么是CommonJS、AMD、CMD、UMD、ESModule以及它们的常用用法。第二部分根据实际问题提出。在正常的webpack构建过程中,如何在打包配置中指定模块化参数。JavaScript模块化中的模块化这个话题在ES6之前是不存在的,因此也被批评为早期JavaScript开发中全局污染和依赖管理混乱的根源。这种历史渊源和发展概况本文不再赘述,有兴趣的可以自行搜索JavaScript发展史。开门见山,让我们来看看常见的模块化解决方案有哪些,它们都有哪些内容。CommonJSCommonJS的一个模块是一个脚本文件,它被执行以加载模块。CommonJS规范规定,在每个模块内部,模块变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载模块实际上是加载模块的module.exports属性。我们已经看到这样一个模块引用varmyModule=require('module');myModule.sayHello();这是因为我们在模块的属性上定义了模块的方法://module.jsmodule.exports.sayHello=function(){console.log('Hello');};//如果你这样写模块.exports=sayHello;//调用需要改成varsayHello=require('module');sayHello();require命令在第一次加载脚本时会执行整个脚本,然后内存中会生成一个对象(该模块可以多次加载,但第一次加载时会运行,结果会缓存),结果如下:{id:'...',exports:{...},loaded:true,...}Node.js的模块机制实现参考了CommonJS标准。但是Node.js还做了一件事,就是为每个模块提供一个exports变量指向module.exports,相当于在每个模块的开头写了这么一行代码:varexports=module.exports;CommonJS模块的特点:所有代码都在模块作用域内运行,不会污染全局作用域。独立性是模块的一个重要特征,最好不要在模块内部直接与程序的其他部分进行交互。模块可以多次加载,但第一次加载时只会运行一次,然后缓存运行结果,后面加载时直接读取缓存的结果。要使模块再次工作,必须清除缓存。加载模块的顺序,按照它们在代码中出现的顺序。AMDCommonJS规范很好,但是不适合浏览器环境,所以有两种方案,AMD和CMD。AMD的全称是AsynchronousModuleDefinition,即异步模块定义。它异步加载模块,模块的加载不影响后面语句的运行。所有依赖该模块的语句都定义在一个回调函数中,回调函数只有在加载完成后才会运行。除了不同于CommonJS的同步加载方式外,AMD在模块的定义和引用上也有所不同。定义(id?,依赖项?,工厂);AMD的模块引入是通过define方法定义的。defineAPI中:id:模块的名称,或者模块加载器请求的指定脚本的名称;dependencies:定义中模块的依赖项模块数组,默认为["require","exports","module"],举个例子更容易理解,当我们创建名为“alpha”的模块时,使用require,exports,和“beta”模块需要写成如下(例1);factory:初始化模块要执行的函数或对象。如果是一个函数,它应该只执行一次。如果是一个对象,这个对象应该是模块的输出值;//例子1define("alpha",["require","exports","beta"],function(require,exports,beta){exports.verb=function(){returnbeta.verb();//或returnrequire("beta").verb();}});如果模块定义中没有依赖,那么可以直接定义对象:define({add:function(x,y){returnx+y;}});在使用的时候,我们仍然使用require关键字,它包含两个参数,第一个数组是要加载的模块,第二个参数是回调函数:require([module],callback);例如:require(['math'],function(math){math.add(2,3);});CMDCMD全称CommonModuleDefinition,是Sea.jsOutput推动的一种模块化方案。在CMDdefine的输入参数中,虽然也支持id、deps、factory三个参数的形式,但是还是建议接受一个factory的输入参数,然后在输入的时候填写require、exports、module这三个参数参数执行。:define(function(require,exports,module){vara=require('./a');a.doSomething();varb=require('./b');b.doSomething();...})通过执行这个构造方法,可以得到模块提供的接口。与AMD相比,主要有两点不同(来自Yubo的回答):对于依赖模块,AMD是提前执行的,CMD是延迟执行的。不过从RequireJS2.0开始,也可以改为延迟执行(根据不同的写法,不同的处理方式)。CMD提倡越懒越好。CMD提倡靠就近,AMD提倡靠前面。如果不清楚,那我们直接看上面的代码用AMD怎么写:define(['./a','./b'],function(a,b){a.doSomething();b.doSomething();...})UMDUMD,全称UniversalModuleDefinition,即通用模块规范。现在CommonJs和AMD风格一样流行,需要一个能够统一浏览器端和非浏览器端模块化解决方案的规范。直接看jQuery官方模块如何使用UMD定义的代码:factory);}elseif(typeofmodule==='object'&&module.exports){//Node/CommonJSmodule.exports=function(root,jQuery){if(jQuery===undefined){//require('jQuery')将需要窗口的工厂返回到//buildajQuery实例,我们规范化我们如何使用需要此模式的模块//但提供的窗口是一个循环//ifit'sdefined(jqueryhowworks)if(typeofwindow!=='undefined'){jQuery=require('jquery');}else{jQuery')('j=reryroot);}}factory(jQuery);returnjQuery;};}else{//Browserglobalsfactory(jQuery);}}(function($){$.fn.jqueryPlugin=function(){returntrue;};}));UMD的实现很简单:首先判断是否支持AMD(define是否存在),如果存在则使用AMD方式加载模块;然后判断是否支持Node.js模块格式(exports是否存在),如果存在则使用Node.js模块格式;如果两者都不存在,则模块将暴露给全局(window或global);ESModules当然,上面提到的所有解决方案都是由社区提供的。从历史上看,JavaScript在语言标准级别的ES6之前没有模块系统,实现了它。它的设计思想是尽可能静态,这样模块之间的依赖关系,以及输入输出的变量都可以在编译时确定。CommonJS和AMD模块都只能在运行时确定这些东西。例如,CommonJS模块是对象,必须在输入时查找对象属性。ESModules不是对象,而是通过export命令明确指定输出的代码。ESModules的模块化能力包括导出和导入。export命令用于指定模块的对外接口,import命令用于导入其他模块提供的功能。我们可以这样定义一个模块://第一种方式exportvarfirstName='Michael';exportvarlastName='Jackson';exportvaryear=1958;//第二种方式varfirstName='Michael';varlastName='Jackson';variyear=1958;导出{名字,姓氏,年份};然后像这样介绍它们:import{firstName,lastName,year}from'module';import{firstNameasnewName}from'module';import*asmoduleAfrom'module';除了上面两个命令外,还有一个exportdefault命令指定模块的默认输出(一个模块只能有一个默认输出)。如果使用exportdefault语法,导入时可以任意命名。由于exportdefault命令的本质是给default变量赋以下值,所以也可以在exportdefault后直接写一个值。当然引用的方式有很多种:import{defaultasfoo}from'module';importfoofrom'module';需要注意的是Modules会自动采用严格模式,import命令有提升作用,会提升到整个模块的头部。先执行。延伸阅读循环加载JavaScript模块webpack打包输出配置说完了理论,我们来看看实际项目中遇到的问题。我们在开发一个JavaScript模块的时候,一定要经过打包的过程。在webpack配置中,通过指定output选项,我们可以告诉webpack如何输出bundle、asset等加载的内容。那么如何实现不同环境下的兼容构建呢?import:通过ESModules规范语法导入;变量:作为全局变量访问,例如通过脚本标签;this:通过这个对象访问;window:通过浏览器中的window对象访问;UMD:导入后通过AMD或CommonJSAccess中的require访问;输出中有一个名为libraryTarget的属性,用于指定如何公开模块的属性。你可以尝试像这样给变量或指定对象的属性赋值://加载后将模块赋值给指定变量(默认值){libraryTarget:'var',...}//赋值一个指定对象的属性,比如`this`或`window`{libraryTarget:"this",//libraryTarget:"window",...}//同理,如果指定了commonjs,那么模块可以赋值给exports,这也意味着可以在CommonJS环境中使用:{libraryTarget:"commonjs",...}如果你需要一个更完整的模块化bundle来确保与每个模块系统的兼容性,你可以试试这个://Contentisassigned到module.exports对象,使用在CommonJS环境下{libraryTarget:'commonjs2',...}//暴露为AMD模块,通过特定属性引入{libraryTarget:'amd',...}//全部modules兼容万能,可以在CommonJS,AMD环境下使用,或者将module导出到全局变量{libraryTarget:'umd',...}因此,如果你只看一个t输出内容,那么我的一个webpack生产环境配置可以这样写:module.exports={output:{//webpack如何输出相关选项path:path.resolve(__dirname,"dist"),filename:'index.js',library:'hijiangtao',umdNamedDefine:true,libraryTarget:'umd',},}