目录

前端的模块规范简述以及如何实现一个UMD-Loader方法

现在的前端开发中根本离不开组件化、模块化的开发方式,这些模块有各种各样的规范,比如:

1.CommonJS 规范

主要用在与 Nodejs 服务端,在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。模块本身是输出的是一个值的拷贝,意思就是说拿到模块后,对其中 exports 的变量进行修改的时候,是不会影响到原来的模块的

1.1.导出模块

导出模块的方式有两种,两种写法都可以,module.exports是对exports的一个引用

1
2
3
module.exports = {};

exports.xxx = "xxx";

1.2.引入模块

指的说的一点就是这个路径可以是一个 js 表达式计算的结果

1
const module = requore("路径");

2.AMD 规范

出现 AMD 规范的原因是因为 CommonJS 以服务端同步的方式加载本地的模块(文件),服务器端所有的模块都存放在本地硬盘中,可以同步加载完成,等待时间就是硬盘的读取时间;而如果在客户端使用这个规范的去请求服务器上的模块资源的时候,请求的时间就取决于当前的网络状态了,加上 CommonJS 是一个同步的加载,那页面就会阻塞、卡顿,所以也就有了 AMD(Asynchronous Module Definition)规范,主要用在客户端

2.1 定义(导出模块)

定义模块使用define(id?, dependencies?, factory)方法,id 为你自定义模块的名称,dependencies 为你当前模块所依赖的其他模块集合的路径数组,factory 则是实际的定义模块的方法。也就是说一个简单 AMD 模块甚至直接传个 factory 函数就行了,这个回调函数里面的入参是 dependencies 数组里面加载成功的依赖

1
2
3
define(function () {
  return;
});

2.2 引入模块

1
2
3
require(["module1", "module2"], function (m1, m2) {
  // 使用module1、module2
});

需要注意的地方浏览器本身是不支持这个规范的,要使用的话必须依赖于 require.js

3.CMD 规范

CMD 规范与 AMD 规范很相似,都用于浏览器编程,依赖就近执行,延迟执行

3.1 定义(导出模块)

define(id?, dependencies?, factory),关于定义它和 AMD 规范很像,包括入参,不同地方在于factory回调函数的入参不同require, exports, module,可以通过这些关键字再导出或者引入相关依赖,然后dependencies一般不会去使用,通常会在factory的回调函数中用require关键字去加载其他的依赖。

它和 AMD 规范最大的不同,就是定义模块时,AMD 会先去加载dependencies的内容,然后加载模块本身的内容,CMD 的话会加载就近的依赖,意思是遇到了 require 关键字的时候才会去加载模块。所以使用 CMD 规范的话,可以将 require 关键字写在模块的顶层,当然浏览器环境也要依赖与sea.js

1
2
3
4
5
define(function (require, exports, module) {
  // require对象可以在模块中引入其他模块
  exports.xxx = value;
  module.exports = value;
});

4.ES6 的模块规范

1.导出:ES6 模块有两种模块导出方式:命名式导出(名称导出)、默认导出(自定义导出)

命名式导出:

1
2
3
4
5
6
// 最基本的
export const a = "1";

// 导出使用别名
const a = "1";
export { a as A };

默认导出通常会在 export 后面加上一个 default:

1
2
const a = "1";
export default a;

2.import 导入模块

1
2
3
4
5
6
export const a = '1'
import { a } from '路径'


export default const a = '1'
import a from './module'

上述的Commone.jsCMDAMD规范的特点就是运行时加载、模块值的拷贝

ES6模块的特点则是静态编译时加载、按需加载、模块值的引用,每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域

requireJS 会先尽早地执行(依赖)模块, 相当于所有的 require 都被提前了, 而且模块执行的顺序也不一定 100%就是先 mod1 再 mod2,所以使用 requireJS 的时候模块与模块之间应当尽量没有业务或者逻辑的交叉

5.UMD 规范

所谓 UMD (Universal Module Definition),就是一种 javascript 通用模块定义规范,让你的模块能在 javascript 所有运行环境中发挥作用。顾名思义,就是兼容上述所有规范,实现方式是通过判断当前脚本运行环境是否支持相应的关键字来实现的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(function (root, factory) {
  if (typeof module === "object" && typeof module.exports === "object") {
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    define(factory());
  } else if (typeof define === "function" && define.cmd) {
    define(function (require, exports, module) {
      module.exports = factory();
    });
  } else {
    root.curModule = factory();
  }
})(this, function () {
  return {
    name: "我是一个umd模块",
  };
});

// 然后在使用的地方通过require或者根节点.curModule的形式引用

实际应用场景 1:

Webpack -> Output选项中,可以配置 library 库,我们可以设置打包后的文件暴露出去的模块名,并且可以指定生成支持某个规范的

1
2
3
4
output: {
  library: 'MyLibrary', // 暴露出的模块名
  libraryTarget: 'amd', // 指定的规范
},

打包代码生成,即生成一个兼容的 UMD 模块

1
2
3
4
5
6
7
(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === "object" && typeof module === "object")
    module.exports = factory();
  else if (typeof define === "function" && define.amd) define([], factory);
  else if (typeof exports === "object") exports["MyLibrary"] = factory();
  else root["MyLibrary"] = factory();
})(typeof self !== "undefined" ? self : this, function () {});

实际应用场景 2:

对于一个大型的应用来说,其复杂的应用以及功能用户只会用到一部分,所以,我们可以在适当的时候去请求相应的模块,这些模块的资源放在服务端,从而减轻项目的体量以及减少项目的开发编译时间。对于这些远程的模块,就可以使用 UMD 模块封装,兼容不同环境,然后本地去请求。

要做一个 umd-loader,通过传入的 url、name 去加载第三方的模块,然后创建 script 标签插入到模板文件的 head 当中,再就是视图里面自己处理逻辑,首先我在本地定义好这个 class,确定好相应的加载逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export class UMDLoader {
  static isExist(name) {
    return window.hasOwnProperty(name);
  }
  static getModule(name) {
    return window[name];
  }
  static getRemoteModule({ name, url }) {
    if (UMDLoader.isExist(name)) {
      return Promise.resolve(UMDLoader.getModule(name));
    }
    return new Promise((resolve, reject) => {
      const script = document.createElement("script");
      script.type = "module";
      script.url = url;
      script.onload = () => {
        return resolve(UMDLoader.getModule(name));
      };
      script.onerror = () => {
        return reject(new Error("加载失败"));
      };
      document.head.appendChild(script);
    });
  }
}

在视图代码中使用的时候

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React, { useEffect } from 'react
import { UMDLoader } from '路径'

const TestView = () => {
  useEffect(() => {
    UMDLoader.getRemoteModule({ url: 'js模块地址', name: '名字' })
      .then((module) => {
        console.log(module)
      }, (e) => {
        console.log(e)
      })
  }, [])
}