ES Module Commonjs AMD CMD UMD

175 阅读4分钟

背景:最近在搞组件库,用的是webpack,在output中发现了一个很有趣的api-libraryTarget,打包出来的模块有点不明所以然,所以特来总结一下前端模块化的概念

什么是模块化?

在js刚刚出现的时候,是为了实现一些简单的功能,但随着浏览器的不断发展,js越来越被重视起来,可以实现较为复杂的功能。这个时候开发者为了维护方便,会把不同功能的模块抽离出来写入单独的js文件,但是当项目更为复杂的时候,html可能会引入很多个js文件,而这个时候就会出现命名冲突、污染作用域、重复代码等一系列问题,这个时候模块化的概念及实现方法应运而生。

ES Module

  • es模块可用于服务器和浏览器
  • 编译时加载模块
  • 设计思想:尽量静态化
  • 可以通过接口取到模块内部实时的值,因为es module输出的是一个值的引用
export var a = '1';
export var b = '1';
// 等价于
 var a = '1';
 var b = '1';
 export {a, b}
function fn(){}
export {
    fn as fna,
    fn as fna1
}
// as关键字重命名,fn可以使用不同名字输出多次
// 以下两种写法都会报错,因为输出的都是一个只,而不是对外的接口
// 报错
export 1;
// 报错
var a = 1;
export m;

// 以下都不会报错,因为接口名与内部变量之间建立了一一对应的关系
export var a = 1;
var b = 1;
export {b}
export {b as c}
// 语法错误,export和import不能在块级作用域内
function fn () {
    export default 'a'
}
fn ()
// 默认导出引入不需要大括号
// export default 用于指定模块的默认输出,一个模块只能有一个默认输出,所以import可以不用大括号,因为对应的只用一个默认方法
export default function(){}
import defaultFn from './index.js'
// export 导出需要使用大括号接收
export function fn(){}
import {fn} from './index.js'
// 正确,因为默认导出的是default
export default 1
// import 也可以使用as关键字重命名变量名
import { fn as a } from './index.js';

// import 命令具有提升效果,会提升到整个模块的头部,首先执行
fn();
import { fn } from './index.js';
// import是静态执行,所以不能使用表达式和变量,这些只用在运行时才能得到语法结构
// 报错
import { 'a'+'b' } from './index.js';
// 报错
var a = 'b'
import { a } from './index.js';
// 报错
if (true) {
    import { a } from './index.js';
} else {
    import { b } from './index.js';
}

Commonjs

  • commonjs: exports 导出,require导入;commonjs2:module.exports导出,require导入;
  • commonjs使用场景是服务端编程,采用同步加载策略
  • commonjs的输出是一个对象
  • 运行时加载模块
  • commonjs模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存
  • commonjs不存在动态更新,因为commonjs只是对模块输出的值进行了拷贝
// a.js
function fn() {
    console.log(112312)
}
// b.js
var defaultFn = require('./a.js');
var newDefaultFn = new defaultFn()
newDefaultFn.fn() // 112312

AMD (Asynchronous Module Definition)异步模块定义

  • AMD使用场景一般都是浏览器场景
  • 运行时加载模块
  • AMD推崇依赖前置
  • requireJS框架遵循AMD规范
  • 定义模块:define(id?, dependencies?, factory)

    • 第一个参数属于可选参数,默认文件名,格式唯一标识的字符串
    • 第二个参数属于可选参数,默认 ["require", "exports", "module"],格式数组;
    • 第三个是需要实例化的函数或对象
    • require获取依赖项模块
// a.js
define(function(require, exports, module) {
    console.log(12312312);
})
// b.js
define(function(require, exports, module) {
    console.log(require('a'));
})

CMD(Common Module Definition)

  • 在CMD中,一个模块就是一个文件,推崇依赖就近
  • Sea.js框架遵循AMD规范
  • 定义模块:define(factory)

    • factory 是一个函数,

      • require, exports, module参数顺序不可乱
      • 暴露api方法可以使用exports、module.exports、return
      • 与requirejs不同的是,若是未暴露,则返回{},requirejs返回undefined
  • 加载模块:require
// a.js
define(function(require, exports, module) {
    console.log(1313123);
})

UMD(Universal Module Definition)

  • umd是可以适用于commonjs、commonjs2、amd,可以支持在node和浏览器中使用
  • libraryTarget: 'umd',的打包结果,从打包结果中可以看出UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块
// webpack打包文件
(function webpackUniversalModuleDefinition(root, factory) {
  if(typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if(typeof define === 'function' && define.amd)
    define("ELEMENT", [], factory);
  else if(typeof exports === 'object')
    exports["ELEMENT"] = factory();
  else
    root["ELEMENT"] = factory();
})()