模块系统的演变

2017-03-29  本文已影响0人  RossWen

首先,模块系统主要解决模块的定义,依赖和导出,先看看已经存在的模块系统。

1:<script>标签

<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>
<script src="module3.js"></script>

这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window
对象中,不同模块的接口调用都是一个作用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口,典型的例子如 YUI 库。

这种原始的加载方式暴露了一些显而易见的弊端:

2:CommonJS

服务器端的 Node.js 遵循 CommonJS规范,该规范的核心思想是允许模块通过 require方法来同步加载所要依赖的其他模块,然后通过 exports或 module.exports来导出需要暴露的接口。

require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;

优点:

缺点:

实现:

3:AMD

Asynchronous Module Definition 规范其实只有一个主要口 define(id?, dependencies?, factory),它要在声明模块的时候指定所有的依赖 dependencies,并且还要当做形参传到 factory中,对于依赖的模块提前执行,依赖前置。

define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });

优点:

缺点:

实现:

4:CMD

Common Module Definition 规范和 AMD 很相似,尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})

优点:

缺点:

实现:

5:UMD

Universal Module Definition 规范类似于兼容 CommonJS 和 AMD 的语法糖,是模块定义的跨平台解决方案。

6:ES6 模块

EcmaScript6 标准增加了 JavaScript 语言层面的模块体系定义。ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
优点:

缺点:

实现:

7:前端模块加载

前端模块要在客户端中执行,所以他们需要增量加载到浏览器中。

模块的加载和传输,我们首先能想到两种极端的方式,一种是每个模块文件都单独请求,另一种是把所有模块打包成一个文件然后只请求一次。显而易见,每个模块都发起单独的请求造成了请求次数过多,导致应用启动速度慢;一次请求加载所有模块导致流量浪费、初始化过程慢。这两种方式都不是好的解决方案,它们过于简单粗暴。

分块传输,按需进行懒加载,在实际用到某些模块的时候再增量更新,才是较为合理的模块加载方案。

要实现模块的按需加载,就需要一个对整个代码库中的模块进行静态分析、编译打包的过程。

8:所有资源都是模块

在上面的分析过程中,我们提到的模块仅仅是指JavaScript模块文件。然而,在前端开发过程中还涉及到样式、图片、字体、HTML 模板等等众多的资源。这些资源还会以各种方言的形式存在,比如 coffeescript、 less、 sass、众多的模板库、多语言系统(i18n)等等。

如果他们都可以视作模块,并且都可以通过require的方式来加载,将带来优雅的开发体验,比如:

require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");

那么如何做到让 require 能加载各种资源呢?

9:静态分析

在编译的时候,要对整个代码进行静态分析,分析出各个模块的类型和它们依赖关系,然后将不同类型的模块提交给适配的加载器来处理。比如一个用 LESS 写的样式模块,可以先用 LESS 加载器将它转成一个CSS 模块,在通过 CSS 模块把他插入到页面的 <style> 标签中执行。Webpack 就是在这样的需求中应运而生。

同时,为了能利用已经存在的各种框架、库和已经写好的文件,我们还需要一个模块加载的兼容策略,来避免重写所有的模块。

上一篇 下一篇

猜你喜欢

热点阅读