这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
本文基于rollup打包,配置其输出格式,浅谈一下模块化规范的差异
rollup.rollup
rollup提供 rollup.rollup API
- 接收一个输入选项对象
inputOptions
作为参数 - 返回一个
Promise
- 解析出一个
bundle
对象,调用当中bundle.write
方法,输出配置outputOptions
作为传参,将打包结果写入磁盘中
该 API 配合 Gulp,让自定义打包更为方便
示例代码
package.json添加命令
"build:demo": "gulp --gulpfile build-demo.mjs"
demo.ts作为入口文件
export const add: Function = (...arr: number[]) => {
return arr.reduce((acc, item) => acc + item, 0)
}
build-demo.mjs
import path from 'path'
import gulp from 'gulp'
import { rollup } from 'rollup'
import typescript from 'rollup-plugin-typescript2'
const { resolve } = path
const formats = ['es', 'amd', 'cjs', 'iife', 'umd', 'system'] //输出格式可选值
formats.map((format) => {
gulp.task(`${format}`, () => {
return rollup({
input: './demo.ts',
plugins: [
typescript({
tsconfigOverride: {
compilerOptions: {
declaration: false, // 不生成类型声明文件
},
},
}),
],
}).then((bundle) => {
return bundle.write({
file: resolve(`../dist/${format}.js`),
format,
name: 'demo',
sourcemap: false,
})
})
})
})
export default gulp.series(formats)
模块化规范
根据上述代码,执行 npm run build:demo,对应输出各种格式的打包文件
不同的输出格式,为了适配不同的运行环境
iife
var demo = (function (exports) {
'use strict';
const add = (...arr) => {
return arr.reduce((acc, item) => acc + item, 0);
};
exports.add = add;
Object.defineProperty(exports, '__esModule', { value: true });
return exports;
})({});
模块化实现方式前期演变过程:
-
文件划分方式:约定一个 JS 文件是一个模块
-
命名空间方式:每个模块暴露一个全局对象,模块成员都挂载到这个全局对象
-
IIFE
方式:使用 立即执行函数表达式 为模块提供私有空间(见上输出:(function () {})()
)-
避免了外界访问当中的变量,且避免污染全局作用域
-
在定义时立即执行,适合使用
<script>
标签直接引用
-
cjs
该格式的打包结果:
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const add = (...arr) => {
return arr.reduce((acc, item) => acc + item, 0);
};
exports.add = add;
对应的是 CommonJS
规范,其特点:
-
一个文件就是一个模块,每个模块都有单独的作用域
-
通过 exports 导出,如上
exports.add = add
-
使用模块时,需要通过
require
函数引入
-
-
同步方式加载模块
-
适合 服务端:Node.js 同步加载,模块文件存储在本地磁盘,读取快
-
不合适在浏览器端直接使用此规范:若引起大量的同步加载请求,受限于网络,效率低
-
amd
浏览器端适合异步加载模块,而 amd 格式对应的 AMD
规范:异步模块定义规范,故适合浏览器端
define(['exports'], (function (exports) { 'use strict';
const add = (...arr) => {
return arr.reduce((acc, item) => acc + item, 0);
};
exports.add = add;
Object.defineProperty(exports, '__esModule', { value: true });
}));
-
基于 Require.js 实现
-
用
define()
来定义模块 -
require()
函数用于自动加载模块- 当加载一个模块时,内部自动创建 script 标签去请求并执行相应模块的代码
umd
对应的规范是 UMD
,即通用模块定义规范
此格式打包结果,可见具有较多条件(三元)运算符
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.demo = {}));
})(this, (function (exports) { 'use strict';
const add = (...arr) => {
return arr.reduce((acc, item) => acc + item, 0);
};
exports.add = add;
Object.defineProperty(exports, '__esModule', { value: true });
}));
其判断:CommonJS ---> AMD ---> 其他
在定义模块时,检查当前使用环境和模块的定义方式
-
集合 CommonJS、CMD规范于一身,可理解成一种规范集合
-
适用于浏览器端、服务器端
es
es格式打包结果较简洁,基本上与示例代码一致
const add = (...arr) => {
return arr.reduce((acc, item) => acc + item, 0);
};
export { add };
对应的规范就是目前主流浏览器环境遵循的 ES Modules
规范
ESM 特点:
-
在 ECMAScript 2015(
ES6
)后定义的模块规范 -
exports
使得模块可让外界引用,而外界引用通过import
有import就有对应要有export,如:
export { add }; import { add } from 'es.js'
-
需要把
type="module"
放到<script>
标签中,来声明此 js 文件是一个模块
system
SystemJS 加载器格式,支持 AMD、CommonJS、ES Modules 等各种格式的JS模块加载
System.register('demo', [], (function (exports) {
'use strict';
return {
execute: (function () {
const add = exports('add', (...arr) => {
return arr.reduce((acc, item) => acc + item, 0);
});
})
};
}));
链接传送门
# 前端工程化:CommonJS、AMD、CMD、UMD和ES Modules的区别
Last but not least
如有不妥,请多指教~