JS 模块化(CommonJS,AMD,CMD,ESM,UMD)

早期的 JS 编码并没有模块化这个概念,写出来的代码就像“挂面”一样,有时候单个页面的 JS 代码就很长,维护起来十分麻烦,而且还存在“变量污染”、“命名冲突”和“缺少文件依赖系统”等问题。随着Node.js的出现和前后端分离开发模式的愈益流行,JS模块化技术也越来越成熟,其主要思想是利用“闭包”和“异步加载JS”来解决以上的问题。其实JS模块化的发展历程也是比较曲折的,这里给出一个其描述比较详细的链接:https://www.cnblogs.com/lvdabao/p/js-modules-develop.html。

JS模块化的方式大致分为这么几种:CommonJSAMDCMDESM,UMD

JS 模块化(CommonJS,AMD,CMD,ESM,UMD)_第1张图片

CommonJS

CommonJS 是 Node.js 的模块化规范,在 Node.js 中每个 JS 文件就是一个模块。每个 JS 文件都有一个 module 对象,它又有一个 exprots 属性,顾名思义它就是JS文件默认导出的对象。

而且每个 JS 文件都有自己的私有作用域,其中的变量、常量、函数和类等对其他的 JS 文件隐藏,如果其他JS文件想要访问这个 JS 文件中的变量、常量、函数和类等,需要我们把变量、常量、函数和类等添加到 module.exports 对象上,然后通过 require 方法来引入该文件(实际上就是引入了该JS文件的 module.exports 对象)进行访问。

有一点需要注意的是:直接给 exports 赋值(exports = xxx)是起不到任何作用的,你可以通过 module.exports = xxx 来改变引用。

// ./test.js

// 这种方式起不到任何作用,它也不会被挂载到 global 上..
exports = {a:1};
console.log(module.exports) // {}

exports.a = 2;
console.log(module.exports) // { a: 2 }

module.exports = {a:3};
console.log(module.exports); // { a: 2 }

b = '我是所有模块共享的变量'
console.log(global.b) // 我是所有模块共享的变量

exports = {a:1}; // 它不会被挂载到 global 上
console.log(global.exports); // undefined
// ./main.js

var o = require('./test');
console.log(o); // { a: 3 }

AMD

AMD (Asynchronous Module Definition),即异步模块定义,是客户端实现 JS 模块化的一种方式,由 Require.js 实现。

它借鉴了 CommonJS 的思想,使用了 CommonJS requiremoduleexports 特性,其主要方法有 configdefinerequire 。

  • config 用于全局配置,它可以配置一个 paths 属性,异步加载指定链接的 JS ,如果该 JS 执行的结果不能通过全局对象访问,那么使用的时候就需要把它定义为 RequireJS 的一个模块。
  • define 的作用是定义一个模块,当然它还可以引入其他的模块依赖,这里不作详细介绍。
  • require 的作用是消费定义的模块,即引入模块并使用它。

在使用 Require.js 的时候,需要在标签属性上添加 data-main 属性,当 Require.js 加载完后会去加载 data-main 里面指定的 JS。




    Require.js


    

// ./main.js

/**
 * 配置模块加载位置,webpakc 需要配置别名。
 * Require.js 根据路径去加载对应的 JS,
 * 一般是引入第三方 JS 时使用
 */
requirejs.config({
    paths: {
        test: './test'
    }
});

/**
 * 这种方式 webpack 可以识别,需要当前目录的 test.js 文件存在
 * 
 * 第1种方式:根据 JS 文件路径去加载JS,
 * 等 test 对应的 JS 加载并执行完后就执行 callback
 */
require(['./test'], function(test) {
    console.log('test2', test);
});

/**
 * 如果不是以别名或 '/', './' 开头,
 * webpack 会使用 nodejs 的 require 方式
 * 从 node_modules 一层层的网上找,找不到
 * 则报错。所以 wepack 和 require.js 的
 * 处理方式不一致,webpack 会因为找不到对
 * 应的模块而报错。
 */

/**
 * 第2种方式:根据 id 去加载JS,
 * 等 test 对应的 JS 加载并执行完后就执行 callback
 */
require(['test'],function(test){
    console.log('test1', test);
});

/**
 * 等定义的 uitl1 模块执行完后,调用 callback
 */
require(['util1'],function(util){
    console.log('util1', util);
});

/**
 * webpack 编译后的代码只能导出最后一个,
 * 它默认一个文件只能有一个 define ,即
 * 一个文件一个模块。
 */

/**
 * 同一文件中定义一个 util1 模块,
 * 如果用 webpack 来编译,还是按照
 * 一个文件一个模块来编译,所以这里的
 * util1 是不起作用的,编译后被消除。
 */
define('util1', function() {
    return {desc: 'util1'};
});
// ./test.js

// 定义一个模块,实际上也可以依赖其他模块的,这里不做研究
// define({ a: 1, b: 2, c: 3 });

// 还可以这么写
define(function(require, exports, module) {
    // 可以这么写
    // return {a: 1, b: 2, c: 3};
    // 也可以这么些
    // exports.a = 1;
    // exports.b = 2;
    // exports.c = 3;
    module.exports = {a: 1, b: 2, c: 3};
});

CMD

CMD(Common Module Definition),即普通模块定义,它看上去像是 AMD CommonJS 的结合体,由 Sea.js 实现。在 CMD 的规范里,一个文件表示一个模块。

它同样使用了 CommonJS 里 requiremoduleexports 特性,常用方法有 configdefinerequire 这三个,这些方法和 AMD 中对应的方法类似,这里就不再赘述了。

另外它还有一个 use 方法,这个是用于加载入口模块的,和 data-mian 的作用类似,用来执行入口 JS 文件。和 AMD 不同的是,CMD 支持使用 require.async(id) 实现懒加载。




    Sea.js


    
    

// ./main.js

// webpack 需要配置别名
// seajs 的全局配置
seajs.config({
    // 配置加载 JS 的基础路径
    base: '.',
    // 别名
    alias: {
        'test': 'test'
    },
    charset: 'utf-8',
    timeout: 20000,
    debug: false
});

// 定义个模块,模块标识由文件名决定
define(function(require, exports, module) {
    /**
     * 引入 test 模块,如果使用 var a = require('test'),
     * sea.js 支持这种写法, 但是 webpack 不支持,它会因
     * 为找不到模块而报错,其原因和用 webpack 编译 AMD 模
     * 块化代码一样,需要换为路径才能解析成功。
     */
    var a = require('./test');
    // 也可以写成
    // exports.desc = 'entry module';
    // exports.data = a;
    // 也可以写成
    // module.exports = {desc: 'entry module', data: a};
    return {
        desc: 'entry module',
        data: a
    }
});
// ./test.js

// 定义一个模块
define((require, exports, module) => {
    // 也可以写成
    // exports.a = 1;
    // exports.b = 2;
    // exports.c = 3;
    // 也可以写成
    // return {a: 1, b: 2, c: 3}
    module.exports = {
        a:1,
        b:2,
        C:3
    }
});

ESM

ESM(ECMA Script Modules),即 ES6 的模块化规范。

  • 通过 export 导出变量、常量、函数或类等,export 支持默认和别名导出
  • 通过 import 引入变量、常量、函数或类等,import 支持解构、别名和星号导入

但是因为目前有些低版本的浏览器还是不支持 ESM ,所以一般需要将 ESM 转成其他的模块化方式,这个过程一般由 Webpack 等自动化构建工具去转换。

// ./main.js

// 星号别名导出,导出的是整个的模块( Module 的实例)
import * as obj from './test.js';
console.log(obj);

// 解构导入,这里从 Module 实例中解构 a
import {a, b} from './test.js'
console.log(a); // {a: 1}
console.log(b); // {b: 2}

// 导入默认,xxx 不是关键字就行了
// 前提是被导入模块必须有 default 默认导出
import xxx from './test.js';
console.log(xxx); // {desc: "我是默认导出"}
// ./test1.js

// 将 a 、b 和 default 变为 Module 实例的属性,并导出 Module 实例
export var a = { a: 1 }
export var b = { b: 2 };
export default { desc: '我是默认导出' };

// 以上导出相当于下面这一句, 不过这里 default 是关键字,会有错误,
// 所以这里的花括号并不是对象!它只是一种表示形式,类似于解构的反向操作。
// export { a, b , default }

UMD

UMD(Universal Module Definition),即通用模块定义,是 CommonJS 和 AMD 模块化标准的集合,它还兼容 “Root” 模式,支持从全局对象上获取模块。

// ./main.js

// UMD
(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        console.log('main', 'AMD');
        // AMD,定义一个以文件名为模块名的模块,并导入 vue 对应的依赖
        define(['./test'], factory);
    } else if (typeof exports === 'object') {
        console.log('main', 'CommonJS');
        // CommonJS
        module.exports = factory(require('./test'));
    } else {
        console.log('main', 'Root');
        // 浏览器全局变量(root 即 window)
        root.yyy= factory(root.test);
    }
}(this, function(a) {
    console.log(a);
}));
// ./test.js

// UMD
(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        console.log('test', 'AMD');
        // AMD,定义一个以文件名为模块名的模块,并导入 vue 对应的依赖
        define(factory);
    } else if (typeof exports === 'object') {
        console.log('test', 'CommonJS');
        // CommonJS
        module.exports = factory();
    } else {
        console.log('test', 'Root');
        // 浏览器全局变量(root 即 window)
        root.test= factory();
    }
}(this, function() {
    console.log('test');
    return {
        desc: 'test'
    }
}));

可以看出,UMD 没有自己的实现,而是 AMD CommonJS Root 模式的集合。

总结

webpack 支持编译以上所有模块化方式的代码,并且可以兼容它们的混合模式,因为它们最终都会被编译为 webpack 自己的模块化代码。

实践测试

https://github.com/zzp-dog/reception-learn/tree/master/webpack_review/module

你可能感兴趣的:(#,webpack,前端,javascript,webpack,es6,typescript)