JS模块化方案的发展
把一个复杂庞大的 JS 进行模块化分割,封闭内部的实现,对外暴露接口。
Intro
模块化发展: 1、NameSpace 模式,每个模块都导出一个对象 (不安全) 2、IIFE 模式 + window 暴露,基于函数作用域闭包私有模块,需要手动维护依赖关系(自己用IIFE声明依赖,自己控制script标签的引用顺序) 3、自动构建依赖图,模块化模式(在浏览器下要运行)
- CommonJS 的 broswerify
- AMD 的 RequireJS
模块化有利于: 1、功能解耦、分离,支持复用,支持按需加载,提高可维护性 2、避免命名空间污染
模块化面临的问题: 1、模块依赖关系 2、如果生产版本也是细粒度模块化的,那么请求过多,页面加载慢
CommonJS
intro
- 每个文件都可以是一个模块
- 模块是在运行时才同步加载的,在 Node 环境没问题
- 在浏览器环境,必须要提前编译打包处理。
🌟 模块导入导出
模块的导入导出
//导出
module.exports = xxx
exports.xxx = ...
//导入
const xx = require('xxx')结合上一节我们对打包产物的分析,可以知道,模块导出的本质是一个对象 module.exports,而且这个 module 是被缓存起来了的。
所以我们可以说,module.exports 是一个对象,require 的结果是这个对象的引用
let fs = require('fs') 就相当于是定义一个 fs 变量,其值为module.exports对象的引用
所以,会发生下面的情况,改变属性会影响原模块,重新赋值变量则只是操作这个变量,对原模块无影响。
// tool.js
module.exports = { val: 1 };
// a.js
const tool = require('./tool');
tool.val = 100; // 改变对象属性
tool = { val: 200 }; // 改变局部变量指向,不影响导出
// b.js
const tool = require('./tool');
console.log(tool.val); // 100相关的生态
| 年代 | 工具 | 主要作用 | 背景 |
|---|---|---|---|
| 2011 | Browserify | 让浏览器能使用 Node.js 的 require() | 解决浏览器没有模块化的问题 |
| 2014 | Webpack | 不仅打包 JS,还能打包 CSS、图片等所有资源 | 现代前端工程化爆发期(SPA、React、Vue) |
Browserify 的原理:
- 分析入口文件
- 静态分析,找出 require 语句,递归分析依赖,构建依赖图
- 把每个文件包装为一个函数,存在一个对象里
- 然后就是类似上一节我们分析的 webpack 这一套,modules,require,export等等。
Webpack 借鉴了 Browserify 的思想(依赖图 + 模块封装 + 自定义 require),
但功能更强、扩展性更好。
都说 require 是在运行时同步加载依赖,那么 browserify,webpack 是怎么做的呢?
答: Browserify(以及后来的 Webpack、Rollup 等)之所以能“在打包时”分析出依赖关系,靠的正是 静态分析 + 抽象语法树(AST)。只要你的 require() 参数是字符串常量,它就能在编译时解析出依赖。
AMD
intro
- Asynchronous Module Definition 异步模块定义规范
- 专门用于浏览器端,模块的加载是异步的。
- 显示声明的依赖注入
模块定义、暴露和引入
特点:显示声明的依赖注入
1、定义没有依赖的模块并暴露
define(function(){
// ...
return exposeModule
})2、定义有依赖的模块并暴露
define(['module1', 'module2'], function(m1,m2){
// ... 依赖 m1, m2 的逻辑
return exposeModule
})3、引入模块并使用
requirejs(['moduleA', 'moduleB'], function(mA,mB){
// ... 依赖 m1, m2 的函数逻辑
})生态 RequireJS
统一采用上面的语法,去写模块和引入模块。
比起 IIFE 方案,模块依赖关系由 RequireJS 去维护,用户只用做配置。
结合 requirejs.config 去配置模块名字和文件的映射。 
引入require.js ,设置 data-main 为项目入口文件 
但是并非所有第三方库都支持 AMD,有的需要额外配置,比如下面的 angular.js 
CMD
Intro
- Common Module Definition
- 相对小众,是早期阿里搞的
- 专门用于浏览器端,模块的加载是异步的。
- 模块使用时,才加载执行
- Look Like
AMD + CommonJS
模块定义、暴露和引入
CMD 在语法规范上有点像 CommonJS 和 AMD 的结合
1、定义没有依赖的模块并暴露
define(function(require, exports, module){
// ...
exports.xxx = xxx
// 或
module.exports = xxxxx
})2、定义有依赖的模块并暴露
define(function(require, exports, module){
// 同步引入
var moduleA = require('./moduleA')
// 异步引入
require.async('./moduleB',function(mb){
// ....
})
// ...
exports.xxx = xxx
// 或
module.exports = xxxxx
})3、引入模块并使用
define(function(require){
// ... 依赖 m1, m2 的函数逻辑
var mA = require('./moduleA')
})生态 Sea.JS
.......
ESM
Intro
- 现代浏览器已经支持 ESM,
<script type="module"></script> - 兼容不支持ESM 的老浏览,需要进行打包构建处理
- 还有在生产模式下,要把细粒度的模块整合成 bundle,也需要打包构建处理
模块导入导出
// 分别导出
export function foo(){
//...
}
// 统一导出
export {
a,
b
}
// 默认导出
export default xxximport {a} from './foo.js'
import mod from './bar.js'生态和方案
Babel + Webpack
1、Babel 负责代码编译,降级 ES6+ --> ES5
- babel-cli:babel 的 commond line interface
- babel-preset-es2015: 专门用于降级到 es5 的
.babelrc 文件:babel run control
{
"presets" :["es2015"] // 告诉 babel 要转 es6
}babel 会把 import / export 降级为 CommonJS语法
2、Webpack 实现浏览器内部的 require
__webpack_modules____webpack_module_cache____webpack_require__- 当然,还有庞大的 loader、plugin 体系