关于系统模块化的一些历史

如果没有接触过大型复杂的WEB项目,你是不会对模块化感兴趣的,所以假设你有折腾过大型项目的经验,在项目过程中你会有如下困惑:

  • 将实现的功能细节隐藏起来,仅暴露接口
  • 将大型项目分成若干部分
  • 可复用的功能代码库

那么如何解决这些问题呢?

JavaScript 语言本身缺乏原生的模块化机制,那么只好另辟蹊径:

使用闭包(Closure)特性去模拟模块化

var MyModule = (function() {
var exports = {};
//抛出 foo 功能给外界
exports.foo = function() {
// ...
}
// 保持 bar 的私有性
var bar = { /* ... */ };
//抛出接口(interface)给外界
return exports;
})();

以上代码确实解决了模块化的问题,但是当模块暴增之后,如何解决之间的依赖呢?是个麻烦问题!
最开始学习WEB开发的时候有以下两种办法:

  • header里逐个加入script标签,引入需要的模块就OK了。
  • 另一种方法,将所有的模块有序放在一个JS文件里,整个应用依赖这一个JS文件就OK了,比如 Google closure compiler 这个工具可以方便合成,从而减少HTTP请求。

以上两种方法在jQuery时代很流行,很暴力很方便,但最大的坑点在于后期维护成本问题,以及资源浪费。
越大的WEB项目越是头疼,有时候干脆选择重新开发,而不愿意在原有基础上增删改查。
比如你只需要 jQuery 库中的某几个功能,然而你将整个 library 通过 < script > 方式引入,必然导致浪费带宽,如此一来像京东一类的大流量电商,企业成本瞬间增加许多。

后来大家越来越重视这些问题,为了解决以上问题,诸如CommonJS这类机制就登上了舞台。

CommonJS in the Server side

CommonJS 是一个有志于构建 JavaScript 生态圈的组织,在ES6还没成熟的时候,这很流行。
CommonJS 解决问题的方案,是提供模块的 export 和 require 机制,将模块隔离在各自独立的上下文中运行,类似闭包。
例如,以下代码片段是为了导出变量”foo”而创建:

// library.js 文件
exports.foo = function() {
console.log("Hello,Li")
}

您可以使用“require”功能,将以上库导入以下模块:

var lib = require("./library.js");
lib.foo();// => Hello,Li

以上就解决了依赖问题,最大程度地释放了开发者双手,提高了效率,CommonJS崇尚“小而精”(lean and mean)的审美哲学,模块专注做好自己的东西,尽量不添加多余的功能,但这仅仅是服务端的解决方案。

如今 CommonJS 是 JavaScript 中采用较广泛的模块系统,Node 模块的 NPM 也是支持该规范,起了积极的推动作用,我们使用 CommonJS 思想,可以轻松下载、安装和配置若干库,将后端的软件管理包(如maven、apt-get、yum、brew、gem、pip、bower)思想引入前端,实现模块的中心化管理,从此打开了大前端的门。

CommonJS in the Browser side

在 CommonJS/Modules 1.0 时代,require 是同步的,对于服务器而言,然而在浏览器端问题多多,当需要异步执行,这是有缺陷的,所以出现了各种解决方案,如服务器端组件、AMD(RequireJS)、CMD(SeaJS)、Modules/Wrappings、CommonJS/Modules 2.0 等。

后来其中两个规范被广泛应用,他们就是:AMD和CMD.

AMD

AMD是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行,它是 RequireJS 在推广过程中对模块定义的规范化的产出,也是 CommonJS 的一个分支。

采用异步方式加载模块,模块的加载不影响它后面语句的 define 定义模块

define(["./a","./b"],function(a,b){
a.method1();
//coding...
b.method2();
})

require加载模块,但是不同于CommonJS,它要求两个参数:

require([module],callback)
//第一个参数[module],是一个数组,里面的成员就是要加载的模块;
//第二个参数callback,则是加载成功之后的回调函数。

CMD

CMD是”Common Module Definition”的缩写,一种模块化规范,国内的玉伯大神提出的规范方案,SeaJS就遵循该规范。

CMD规范可参考:CMD 模块定义规范
AMD 和 CMD 的区别有哪些?https://www.zhihu.com/question/20351507

前端模块化工具

目前NPM上有20+万个NodeJS模块,它们都是通过CommonJS的方式打包的,除了特定的可以使用CommonJS模块加载器加载的模块,大部分NodeJS模块无法直接使用到浏览器环境中,要将 CommonJS 应用到浏览器端应用(Browser side application),就需要额外的工具来实现 Build process,例如 Browserify、Webpack等工具,Browserify 是一个供浏览器环境使用的模块打包工具,运行 Browserify 后,它将从某个固定的入口搜索所有的依赖代码,随后打包成单个JS文件,减少 HTTP 请求数,并压缩文件,减少代码的整体大小,从而加速页面的加载。

类似 Browserify 的工具的思路就是:分析代码中的模块依赖关系(require, import, whatever),然后按需将这些模块打包成一个文件,如bundle.js,让浏览器端应用加以依赖

Browserify 命令对你的代码进行打包了:

browserify index.js > bundle.js

然后只需在 HTML 页面中添加一个 < script > 标签即可

<script src="bundle.js"></script>

鉴于目前的状态,还是建议大家更倾向于学习Webpack,可以参考外文: plug-and-play template for getting started 、针对更复杂用例的 how-to guide for webpack,或者中文参考教程:Webpack 中文指南

参考

1.CommonJS规范 (By 阮一峰)
2.Require.js 规范文档
3.从 CommonJS 到 Sea.js
4.Browserify 使用指南

坚持原创技术分享,您的支持将鼓励我继续创作!