一、 没有模块化存在的问题
在es6之前,js语言层面没有模块化规范,这会带来一些问题,比如通过script标签引入的多个js文件,由于变量都是定义在全局,会有变量冲突的问题。再比如互相依赖的js文件,在引入时需额外注意顺序,如果有所变动,需要在html中同时变动。
<!-- index.html -->
<script>
// a.js
var index = 1;
setTimeout(() => console.log(index), 1000);// 2
</script>
<script>
// b.js
var index = 2;
</script>
我们需要将这些js文件 模块化:
- 模块有独立的 变量作用域;
- 模块之间可以互相 导入和导出;
二、不同环境的解决方案
为了解决没有模块化的痛点,不同的js运行环境各自有一套解决方案。
CMJ:node环境中的模块化规范。AMD:在浏览器端运行的JS模块化规范。UMD:CMJ和AMD的集成,会依次判断exports和define是否存在来决定使用CMJ规范还是AMD规范。ESM:js语言层面的模块化规范。
node 之 CMJ
commonjs是node环境中的模块化规范,这是一个 同步加载模块 的规范,每当⼀个模块 require ⼀
个⼦模块时,都会停⽌当前模块的解析直到⼦模块读取解析并加载。
它使用require来导入一个模块,使用module.exports来导出一个模块。它不仅满足了上述的两点要求,还保证了 模块单例,即在不同的模块加载另一个模块两次,会得到相同的结果。
浏览器之AMD
AMD 是在浏览器端运行的JS模块化规范,它的全称 Asynchronous module definition,即异步的模块定义,不同于 commonjs 规范的同步加载,AMD 所有模块默认都是异步加载,因为如果在web 端使⽤同步加载,那么⻚⾯在解析脚本⽂件的过程中可能使⻚⾯暂停响应。
它使用require来导入模块,语法和commonjs的不同,使用define来定义一个模块。如果想要使⽤ AMD 规范,我们还需要添加⼀个符合 AMD 规范的加载器脚本在⻚⾯中,⽐较有名的就是 require.js。
// index.js
require(['moduleA', 'moduleB'], function(moduleA, moduleB) {
console.log(moduleB);
});
// moduleA.js
define(function(require) {
var m = require('moduleB');
return m;
});
需要在一个html中加载index.js来查看模块输出结果。
<html>
<!-- 此处必须加载 require.js 之类的 AMD 模块化库之后才可以继续加载模块-->
<script src="/require.js"></script>
<!-- 只需要加载⼊⼝模块即可 -->
<script src="/index.js"></script>
</html>
UMD
UMD是CMJ和AMD的集成,会依次判断exports和define是否存在来决定使用CMJ规范还是AMD规范。
js 之 ESM
ESM是js语言层面的模块化规范。与 CommonJS 和 AMD 最⼤的区别在于,ESM 是由 JS 解释器实现,⽽后两者是在运行环境中运⾏时实现。解释器是识别js语法的关键,运行环境会封装一个环境相关的API,如浏览器端的window和document,node端的global和process。
三、打包工具
既然有了ESM,为何还需要打包工具?
ESM有自己的关键字(import、export)和语法(空格等),每个JS的运行环境都有一个解析器,然而不是所有的解析器都能识别esm的新语法! 比如:
- 浏览器的版本比较低,就不能识别
ES6中的新增的ESM规范。WEB端受制于用户,不能让所有的用户都升级浏览器!因为社区出现了很多工具,用来转换es代码,如babel。但是babel对于esm的支持明显不好,因为它会将esm规范编译为cmj规范,同样不能在浏览器端运行。 - node在12版本之后才可以使用
ESM。 为了解决这个问题,需要 打包,打包工具的作用就是抹平模块化内部实现的细节,将其变成可以直接运行的web或者node中的内容。或者可以指定babel将模块化转化为umd的规范!