模块
模块是什么呢?为什么需要模块功能。模块简单来说就是功能的封装和复用。比如一个加减乘除的计算器功能,可以被实现并封装成一个模块,在别人开发项目的时候,直接引入这个模块进行功能的复用就好了。
因此简单来说,模块的功能就是,抽离功能代码,封装功能,将功能再次复用。
前世今生
在 javascript 早期的时候,是仅能运行在浏览器的,在浏览器中需要做的事就是一些简单的交互。随着 javascript 脚本变得越来越复杂,尤其是在09年 node 出来后, javascript 语言在后端也开始发光发热了 。node 环境下的 javascript 可以做的事特别多,因此就迫切需要将 javascript 进行模块化管理。
由于 javascript 语言本身没有模块功能的,即没有官方的模块规范,因此就开始出现了各种人为规定的野生模块规范,这些模块规范规定了如何为 javascript 赋予模块功能。最先出来的是 CommonJS 规范,node 的模块化部分就是 CommonJS 规范的一种具体实现。
在 javascript 在后端的模块化实现后,前端开发者们也希望 javascript 在前端部分拥有模块功能。如果没有限制,浏览器部分也可以参照 CommonJS 规范为 javascript 赋予模块功能。但是浏览器和服务端最大的不同是,浏览器加载文件是异步的,一般一个模块是一个单独的文件用于按需加载。浏览器如果加载一个模块的话,需要向服务端发送请求,然后等待服务器响应返回,这个过程受到网络的影响。而且在浏览器加载 js 模块的时候,浏览器页面会处于一个卡死的状态。这对于用户体验非常不好。但在服务端加载文件是同步的,一般加载速度就是从本地读取文件的速度,是很快的。CommonJS 规范是基于同步加载的规范,规定在当前模块加载完后才往后执行。
显然,CommonJS 规范不适用于浏览器端。后来就出现了 AMD 规范。AMD 规范是基于异步加载的规范。至此,AMD 规范被用于定义 javascript 在浏览器的模块规范,CommonJS 被用于定义 javascript 在服务端的模块规范。
模块规范
模块规范是用于规定如何进行模块的定义和加载的标准。在模块规范出现前,大家想尽各种办法实现模块功能,你有你的规定,我有我的规定。但在模块规范出现后,将模块功能如何实现做了统一标准规定。
以下模块规范中,最常用的是 IIFE,CommonJS,AMD,UMD。
IIFE
IIFE 全称是 Immediately-Invoked Function Expression,即立即执行函数
严格来说,这不是一个标准的模块规范。是将 模块 的概念以函数的方式进行定义和加载。
iife 在现在已经不会手动去编写这样的格式了,而是运用各种打包工具进行 bundle 打包,将多个文件打包成一个 bundle ,进行加载,为了保证加载效率和调度效果,不同打包工具打包成的 iife 格式都会有一个对应的 runtime 部分的代码用于代码的调度。比如 webpack 有注入 webpack runtime 调度代码,vite 有注入 vite runtime 调度代码。
CommonJS 规范
CommonJS 规范是同步的规范,会先将依赖的模块加载完成后再执行。用于服务端。
CommonJS 规范规定,require
方法用于导入模块,module.exports
和 exports
用于导出模块。
AMD 规范
AMD 规范全称是 Asynchronous Module Definition,即异步模块标准。
AMD 规范是异步的规范,会等待依赖的模块加载完成后被执行。用于浏览器端。
AMD 规范规定,require([module1, module2], function(m1, m2) { })
方法用于加载模块,define(function() {})
用于定义模块。
UMD 规范
UMD 的全称是 Universal Module Definition,即通用模块标准。
嗯,UMD 规范严格来说不是个规范,其实算个多个规范的融合写法。用于兼容浏览器端和服务端。就是根据判断用不同的规范实现,以便于其在前后端都可以执行。UMD 规范集合了 IIFE,AMD,CommonJS 的实现。
ES 模块规范(JS 官方模块规范)
es 模块规范是随着 es6 出现的 javascript 语言官方的模块规范。这才是正统的 javascript 语言的模块化功能。但是因为 es6 语法太新了,并不是全部的浏览器都支持直接运行 es6 语法的,因此就不是所有的浏览器都支持 es 模块规范的。
es 模块的使用方法,详看 JavaScript 模块化
System 规范
System 规范是和 AMD 规范很相似的一种规范。System.register([],function() {}) 方法用于注册模块,System.import() 方法用于引入模块。
CMD 规范
CMD 全称是 Common Module Definition。
CMD 规范是异步的规范,用于浏览器端。但是由于 CMD 回调函数内也是有 require / modules.exports / exports ,因此可以通过改造后,在服务端运行。
CMD 模块定义规范
模块加载器
模块规范只是一个标准,用代码实现了模块规范的工具叫做模块加载器。模块加载器才是可以直接使用的工具。引入了模块加载器,根据模块规范就可以使用模块化功能了。因此,模块加载器其实起到的作用是调度整个模块的运行,包括解析模块语法,调度模块按需加载和管理模块间的相互依赖。
node 的模块
node 的模块规范最初是基于 CommonJS 规范实现的,不过后来 es 模块规范出现后,node 又基于 es 模块规范实现了一套。因此,在 node 环境中,可以用 CommonJS 语法编写模块,也可以使用 es 模块规范编写模块,具体需要什么配置请移步 node 官方文档。
require.js
require.js 库是基于 AMD 规范实现的,在浏览器模块加载中用的最广泛的。
curl.js
curl.js 库是基于 AMD 规范实现的,不过目前该库已经不再维护了。
sea.js
sea.js 库是基于 CMD 规范实现的。目前 Sea.js 的模块,如果没有用到浏览器环境下的特有属性,可以很方便跑在 NodeJS 端。
system.js
模块打包器
模块打包器可以代替模块加载器,与模块加载器不同的是,模块打包器时在构建时运行的。
browserify
webpack
rollup
parcel
esbuild
vite
Turbopack
Snowpack
参考
Javascript模块化编程(一):模块的写法 - 阮一峰的网络日志
Javascript模块化编程(二):AMD规范 - 阮一峰的网络日志
Javascript模块化编程(三):require.js的用法 - 阮一峰的网络日志
一文搞懂JS模块、模块格式、模块加载器和模块打包器(上)-阿里云开发者社区
一文搞懂JS模块、模块格式、模块加载器和模块打包器(中)-阿里云开发者社区
一文搞懂JS模块、模块格式、模块加载器和模块打包器(下)-阿里云开发者社区
vite多久后能干掉webpack? - 知乎