[TOC]
## [CommonJS](http://javascript.ruanyifeng.com/nodejs/module.html)
CommonJS 是同步加載模塊,但其實也有瀏覽器端的實現,其原理是現將所有模塊都定義好并通過 id 索引,這樣就可以方便的在瀏覽器環境中解析了,可以參考 require1k 和 tiny-browser-require 的源碼來理解其解析(resolve)的過程。
類似的還有 CommonJS Modules/2.0 規范,是 [BravoJS](https://github.com/pinf/loader-js/blob/master/lib/pinf-loader-js/bravojs/bravo.js) 在推廣過程中對模塊定義的規范化產出。
CommonJS就是為JS的表現來制定規范,因為js沒有模塊的功能所以CommonJS應運而生,它希望js可以在任何地方運行,不只是瀏覽器中。
CommonJS能有一定的影響力,我覺得絕對離不開Node的人氣。
Node,CommonJS,瀏覽器甚至是W3C之間有什么關系呢,圖:
~~~
|---------------瀏覽器-----------------------| |--------------------------CommonJS----------------------------------|
| BOM | | DOM | | ECMAScript | | FS | | TCP | | Stream | | Buffer | |........|
|-------W3C-----------| |---------------------------------------Node--------------------------------------------------|
~~~
CommonJS 定義的模塊分為:
* 模塊引用(`require`)
* 模塊定義(`exports`)
* 模塊標識(`module`)
`require()`用來引入外部模塊;`exports`對象用于導出當前模塊的方法或變量,唯一的導出口;`module`對象就代表模塊本身。
比如說我們就可以這樣用了:
```js
// sum.js
module.exports = {sum: function(){...做加操作..}; } //或者 exports.sum = function(){...做加操作..};
// calculate.js
var math = require('sum');
exports.add = function(n){
return math.sum(val,n);
};
```
雖說Node遵循CommonJS的規范,但是相比也是做了一些取舍,填了一些新東西的。
不過,說了CommonJS也說了 Node,那么我覺得也得先了解下 NPM 了。NPM 作為Node的包管理器,不是為了幫助Node解決依賴包的安裝問題嘛,那它肯定也要遵循 CommonJS 規范啦,它遵循包規范(還是理論)的。
[CommonJS WIKI](http://en.wikipedia.org/wiki/CommonJS) 講了它的歷史,還介紹了 modules 和 packages 等。
## AMD
由于一個重大的局限,使得CommonJS規范不適用于瀏覽器環境。還是上一節的代碼,如果在瀏覽器中運行,會有一個很大的問題,你能看出來嗎?
```js
var math = require('math');
math.add(2, 3);
```
第二行`math.add(2, 3)`,在第一行`require('math')`之后運行,因此必須等math.js加載完成。也就是說,如果加載時間很長,整個應用就會停在那里等。
這對服務器端不是一個問題,因為所有的模塊都存放在本地硬盤,可以同步加載完成,等待時間就是硬盤的讀取時間。但是,對于瀏覽器,這卻是一個大問題,因為模塊都放在服務器端,等待時間取決于網速的快慢,可能要等很長時間,瀏覽器處于"假死"狀態。
因此,瀏覽器端的模塊,不能采用"同步加載"(synchronous),只能采用"異步加載"(asynchronous)。這就是AMD規范誕生的背景。
CommonJS 是主要為了 JS 在后端的表現制定的,他是不適合前端的,為什么這么說呢?
這需要分析一下瀏覽器端的js和服務器端js都主要做了哪些事,有什么不同了:
| 服務器端JS | 瀏覽器端JS |
| --- | --- |
| 相同的代碼需要多次執行 | 代碼需要從一個服務器端分發到多個客戶端執行 |
| CPU和內存資源是瓶頸 | 帶寬是瓶頸 |
| 加載時從磁盤中加載 | 加載時需要通過網絡加載 |
于是乎,AMD (異步模塊定義) 出現了,它就主要為前端JS的表現制定規范。
AMD 就只有一個接口:
```
define(id?,dependencies?,factory);
```
它要在聲明模塊的時候制定所有的依賴 (dep),并且還要當做形參傳到 factory 中,像這樣:
```
define(['dep1','dep2'],function(dep1,dep2){...});
```
要是沒什么依賴,就定義簡單的模塊,下面這樣就可以啦:
```
define(function(){
var exports = {};
exports.method = function(){...};
return exports;
});
```
咦,這里有define,把東西包裝起來啦,那Node實現中怎么沒看到有define關鍵字呢,它也要把東西包裝起來呀,其實吧,只是Node隱式包裝了而已.....
[RequireJS](http://requirejs.org/)和[curl.js](https://github.com/cujojs/curl)就是實現了AMD規范
更讓人混亂的是,RequireJS在實現AMD的同時,還提供了一個CommonJS包裹,這樣CommonJS模塊可以幾乎直接被RequireJS引入。
```
define(function(require, exports, module) {
var someModule = require('someModule'); // in the vein of node
exports.doSomethingElse = function() { return someModule.doSomething() + "bar"; };
});
```
這有AMD的WIKI中文版,講了很多蠻詳細的東西,用到的時候可以查看:[AMD的WIKI中文版](https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88)
## [CMD](https://seajs.github.io/seajs/docs/#docs)
CMD 規范在這里:https://github.com/seajs/seajs/issues/242 ,并與 CommonJS 和 Node.js 的 Modules 規范保持了很大的兼容性。
(1)CMD 全稱為 Common Module Definition,它是國內**玉伯**大神在開發 SeaJS 的時候提出來的。
(2)CMD 與 AMD 挺相近,二者區別如下:
* 對于依賴的模塊 CMD 是延遲執行,而 AMD 是提前執行(不過 RequireJS 從 2.0 開始,也改成可以延遲執行。 )
* CMD 推崇 as lazy as possible(依賴就近),AMD 推崇依賴前置。
* AMD 的 api 默認是一個當多個用,CMD 嚴格的區分推崇職責單一,其每個 API 都簡單純粹。例如:AMD 里 require 分全局的和局部的。CMD 里面沒有全局的 require,提供 seajs.use() 來實現模塊系統的加載啟動。
```js
define(function(require,exports,module){
//TODO...
/**
*
* 一:使用 exports 暴露模塊接口
define(function(require, exports) {
// 對外提供name屬性
exports.name = 'hangge';
// 對外提供hello方法
exports.hello = function() {
console.log('Hello hangge.com');
};
});
二:使用 modul.exports 暴露模塊對象
define(function(require, exports, module) {
// 對外提供接口
module.exports = {
name: 'hangge',
hello: function() {
console.log('Hello hangge.com');
}
};
});
*
*/
});
```
前面說 AMD,說RequireJS 實現了 AMD,CMD 看起來與 AMD 好像呀,那 RequireJS 與 SeaJS 像不像呢?
雖然 CMD 與 AMD 蠻像的,但區別還是挺明顯的,官方非官方都有闡述和理解,我覺得吧,說的都挺好:
[官方闡述SeaJS與RequireJS異同](https://github.com/seajs/seajs/issues/277)
[SeaJS與RequireJS的最大異同(這個說的也挺好)](http://www.douban.com/note/283566440/)
移動端加載:
[mt.js](https://github.com/mtjs/mt)是手機騰訊網前端團隊開發維護的一個專注于移動端的、帶有增量更新特色的js模塊管理框架
>摘自:http://www.cnblogs.com/skylar/p/4065455.html
## UMD(Universal Module Definition)
官網:https://github.com/umdjs/umd
缺點:
1. 代碼量。兼容需要額外的代碼,而且是每個文件都要寫這么一大段代碼。
2. 代碼合并。我沒試過用webpack去合并代碼,但明顯requireJS的方法是合不了UMD的代碼的。
在什么時候不應該使用UMD呢,就是獨立項目里,一般獨立項目不會向外界提供API,所以一種模塊定義方法就好。
如果是要做UI或SDK要用在多種環境下,可以選擇UMD,當然是選擇,但不一定只能UMD。
其實還可以通過腳本打包的方式按需求打包成不同的模塊定義方式提供給其他人調用,這樣可以減少代碼,應該也可以順利地合并了。
### 統一模塊加載代碼
我們除了提供 AMD 模塊接口,CMD 模塊接口,還得提供原生的 JS 接口。
由于 CMD 和 AMD 都可以使用 `return` 來定義對外接口,故可以合并成一句代碼。
一個直接可以用的代碼如下:
```js
;(function(){
function MyModule() {
// ...
}
var moduleName = MyModule;
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = moduleName;
} else if (typeof define === 'function' && (define.amd || define.cmd)) {
define(function() { return moduleName; });
} else {
this.moduleName = moduleName;
}
}).call(function() {
return this || (typeof window !== 'undefined' ? window : global);
});
```
從 knockoutjs 源碼中讀到了一個很好的能兼容 AMD , commonjs 規范的模塊定義:
```js
//閉包執行一個立即定義的匿名函數
!function(factory) {
// factory是一個函數,下面的koExports就是他的參數
// Support three module loading scenarios
if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
// [1] CommonJS/Node.js
// [1] 支持在module.exports.abc,或者直接exports.abc
var target = module['exports'] || exports; // module.exports is for Node.js
factory(target);
} else if (typeof define === 'function' && define['amd']) {
// [2] AMD anonymous module
// [2] AMD 規范
//define(['exports'],function(exports){
// exports.abc = function(){}
//});
define(['exports'], factory);
} else {
// [3] No module loader (plain <script> tag) - put directly in global namespace
factory(window['ko'] = {});
}
}(function(koExports){
// ko 的全局定義 koExports 是 undefined 對應著上面的[3] 這種情況
var ko = typeof koExports !== 'undefined' ? koExports : {};
// 定義一個ko的方法
ko.abc = function(s){
alert(s);
}
});
// [3]中情況的調用
ko.abc("msg");
```
兼容 CommonJS 和 CMD(SeaJS) 規范的。例子:
```js
;(function(factory) {
// CommonJS/NodeJS
if(typeof require === 'function' && typeof exports === "object" && typeof module === "object") {
factory(require, exports, module);
}
// CMD/SeaJS
else if(typeof define === "function") {
define(factory);
}
// No module loader
else {
factory(function(){}, window['idcard']={}, {});
}
}(function(require, exports, module) {
// something...
exports.hello = function() {
return 'hello212';
}
}));
```
## commonjs
實現:
* 服務器端的 Node.js
* Browserify,瀏覽器端的 CommonJS 實現,可以使用 NPM 的模塊,但是編譯打包后的文件體積可能很大
服務器端的 Node.js 遵循 CommonJS 規范,在ES6標準發布后 module 成為標準。標準的使用 `export` 指令導出接口,以import引入模塊,但是在我們一貫的 node 模塊中,我們仍然采用 CommonJs 規范。
nodejs v6+ 開始支持 90% 以上的 ES6,however,import還暫不支持。
# ES6 Import
[ES Modules in Node Today!](https://blogs.windows.com/msedgedev/2017/08/10/es-modules-node-today/)
2015 年 6 月, ES2015(即 ECMAScript 6、ES6) 正式發布。ES2015 是該語言的一個顯著更新,也是自 2009 年 ES5 標準確定后的第一個重大更新。
雖然 ES2015 提出了許多令人激動的新特性,但由于目前 JavaScript 的運行環境眾多,對 ECMAScript 標準的支持程度也不一樣。
## ES2015 的模塊規范
一個模塊就是一個獨立的文件。該文件內部的所有變量,外部無法獲取。
`export` 命令用于規定模塊的對外接口。
`import` 命令用于輸入其他模塊提供的功能。
ES6 模塊的設計思想是盡量的靜態化,**使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量**。
## 使用 export 命令規定對外接口
(1)下面我們在 Node.js 中創建一個模塊,文件名為:hangge.js
```js
//圓面積計算
export function area(radius) {
return Math.PI * radius * radius;
}
//圓周長計算
export function circumference(radius) {
return 2 * Math.PI * radius;
}
```
(2)創建一個 main.js 文件,引入這個模塊并調用。這里 `import` 命令使用大括號的形式加載模塊對外的接口。
```js
import {area,circumference} from './hangge';
console.log('圓面積:' + area(10));
console.log('圓周長:' + circumference(11));
```
當然也可以使用星號(*)指定一個對象,實現模塊的整體加載。
```js
import * as circle from './hangge';
console.log('圓面積:' + circle.area(10));
console.log('圓周長:' + circle.circumference(11));
```
# 參考
[javascript模塊化之CommonJS、AMD、CMD、UMD、ES6](http://blog.csdn.net/Real_Bird/article/details/54869066)
http://www.cnblogs.com/zzsdream/p/5158968.html
- 講解 Markdown
- 示例
- SVN
- Git筆記
- github 相關
- DESIGNER'S GUIDE TO DPI
- JS 模塊化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模塊化打包
- 學習Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基礎知識
- package.json 詳解
- corepack
- npm
- yarn
- pnpm
- yalc
- 庫處理
- Babel
- 相關庫
- 轉譯基礎
- 插件
- AST
- Rollup
- 基礎
- 插件
- Webpack
- 詳解配置
- 實現 loader
- webpack 進階
- plugin 用法
- 輔助工具
- 解答疑惑
- 開發工具集合
- 花樣百出的打包工具
- 紛雜的構建系統
- monorepo
- 前端工作流
- 爬蟲
- 測試篇
- 綜合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程開發
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服務
- 編碼規范檢查
- 前端優化
- 優化策略
- 高性能HTML5
- 瀏覽器端性能
- 前后端分離篇
- 分離部署
- API 文檔框架
- 項目開發環境
- 基于 JWT 的 Token 認證
- 扯皮時間
- 持續集成及后續服務
- 靜態服務器搭建
- mock與調試
- browserslist
- Project Starter
- Docker
- 文檔網站生成
- ddd