# 打包资源处理 - 模块化
# 一、模块化的思想
模块化是将一个复杂的程序依据一定的规则/规范装成几个文件,并通过规则/规范组合在一起,内部的数据和实现是私有的,只是向外暴露一些接口活方法 与外部模块通信。
# 模块化解决的问题
- 全局变量冲突
- 依赖关系管理混乱
# 模块化的好处
- 解决命名冲突
- 代码分离,按需加载
- 提高代码复用性和可以维护性
# 二、cjs (commonjs)
1、commonjs 是 Node 中的模块规范,通过 require 及 exports 进行导入导出 (进一步延伸的话,module.exports 属于 commonjs2)
2、cjs 模块可以运行在node环境和webpack环境下,但是不能在浏览器直接使用。
TIP
webpack,rollup 也可以对 cjs 模块解析;
如果前端项目使用webpack解析,在前端项目中是可以写 cjs 代码。
ms (opens new window) 只支持 commonjs,但是我们一样可以在webpack项目中使用,但是如果通过cdn的方式直接在浏览器引入,就会有问题。
// sum.js
exports.sum - (x,y) => x + y;
// index.js
const { sum } = require('./sum.js');
2
3
4
5
3、由于cjs 为运行时动态加载,所以可以直接 require 一个变量。
require(`./${a}`)
4、cjs 模块输出的是一个值的拷贝
例子如下
# 三、esm (es module)
1、esm 是 tc39 对于js的模块规范
2、在Node及浏览器均会被支持的
3、使用export/import导入导出
// sum.js
export const sum - (x,y) => x + y;
// index.js
const { sum }from './sum.js';
2
3
4
5
- 4、esm为静态导入
因为静态导入,我们就能在编译期进行tree shaking,减少js体积。
tc39也为动态模块加载定义了API
import(module)
目前一些前端构建工具已经在从commonjs模块转向esm,比如: skypack、snowpack、vite等等。
# cjs && esm
- cjs 模块输出的是一个值的拷贝,esm 输出的是值的引用
基本数据类型
// 输出模块 counter.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// 引入模块 main.js
var mod = require('./counter');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复杂数据类型
// 输出模块 counter.js
var counter = {
value: 3
};
function incCounter() {
counter.value++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// 引入模块 main.js
var mod = require('./counter.js');
console.log(mod.counter.value); // 3
mod.incCounter();
console.log(mod.counter.value); // 4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
value 是会发生改变的。不过也可以说这是 "值的拷贝",只是对于引用类型而言,值指的其实是引用。
ESM
// counter.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './counter';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
2
3
4
5
6
7
8
9
10
11
在node.js中模块导出内容时,exports和module.exports之间的区别(exports就是 module.exports 的引用)。就是node.js 一个模块引入另一个模块的变量的时候就是获取的 module.exports上导出的内容。所以如果你是通过exports这种形式去导出的内容,那么在main.js里面也有是获取exports这个对象上某个属性的内容,在child.js里面改变了这个属性的内容,那么main.js也会有变化;
- cjs 模块是运行时加载,esm 是编译时加载
因为CommonJS加载的是一个对象,(即module.exports属性),该对象只有在脚本运行时才会生成,而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
# 四、umd(Universal Module Definition)
一种兼容 cjs 与 amd 的模块,既可以在 node/webpack 环境中被 require 引用,也可以在浏览器中直接用 CDN 被 script.src 引入。
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD
define(["jquery"], factory);
} else if (typeof exports === "object") {
// CommonJS
module.exports = factory(require("jquery"));
} else {
// 全局变量
root.returnExports = factory(root.jQuery);
}
})(this, function ($) {
// this is where I defined my module implementation
var Requester = { // ... };
return Requester;
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 可以在前端和后端通用
- 与CJS和AMD不同,UMD更像是配置多模块系统的模式
- UMD通常是Rollup、webpack的候补选择
示例: react-table (opens new window) , antd (opens new window)
这三种模块方案大致如此,部分 npm package 也会同时打包出 commonjs/esm/umd 三种模块化格式,供不同需求的业务使用,比如 antd (opens new window)。
模块化 →