一、為什么需要模塊化?
代碼量驟增 => 分治管理的剛性需求
二、模塊化方案需解決什么問題?
模塊化要實現兩個東西:模塊加載與模塊封裝。面臨的具體問題包括:
1、如何定義模塊以確保模塊的作用域獨立,避免命名沖突?
2、如何管理模塊間的依賴關系,避免重復加載與循環引用?
3、模塊化的代碼如何部署,以降低HTTP請求數?
4、如何實現按需加載?
5、如何在解決上述問題之后,保證性能且不影響debug?
三、原始的解決方案有何局限?
命名空間 + 立即執行函數 + script標簽
局限性:
1、全局空間污染
2、需手動管理依賴,不具備可擴展性
3、無法實現按需加載
四、新的解決方案
1、CommonJS
CommonJS 起源於一個服務端項目 SeverJS,該項目意在通過模塊化的開發模式, 解決 JS 作用域的問題。后來發展成了一個致力於構建 JS 生態圈的組織。
CommonJS 提供了一套模塊加載的規范,其核心語法是通過 module.exports 暴露接口,通過 require() 加載資源。
CommonJS 規范采用同步加載,適用於服務端,但並不適用於瀏覽器環境(網絡延遲、異步特性),因此在瀏覽器端出現了各類模塊加載器,以解決模塊加載的問題。
各類模塊加載器提出了各自的模塊封裝的規范。其中 Sea.js 提出/實現的封裝規范,就是 CMD 規范;RequireJS 提出/實現的封裝規范,就是 AMD 規范。
2、Sea.js
模塊封裝:
define (function (require, exports, module) {
var a = require('./a') // 模塊加載
a.doSomething();
// ……
var b = require('./b') // 依賴可以就近書寫
b.doSomething();
// 通過 exports 對外提供接口
exports.doSomething = ...
// 或者通過 module.exports 提供整個接口
module.exports = ...
})
模塊加載:
var $ = require('jquery');
3、RequireJS
模塊封裝:
define (['./a', './b'], function(a, b) {
a.doSomething();
// 此處略去 100 行
b.doSomething();
return function () { } //返回模塊的值,可以是函數,也可以是對象
})
模塊加載:
require (['./a', './b'], function (a, b) {
// do sth
})
區別
RequireJS:依賴前置,提前加載
Sea.js:依賴就近,延遲加載
4、UMD
一種兼容 CommonJS 和 AMD 的語法糖。事實上 RequireJS 和 Sea.js 就是相互支持的。
圖片來源:https://www.leanpanda.com/blog/2015/06/28/amd-requirejs-commonjs-browserify/
5、打包工具:Browserify&Webpack
在模塊化的開發方式下,模塊加載器(Module loader)解決了模塊的加載與依賴的自動管理,但是並沒有解決 HTTP 請求數的問題。如何將分散的文件合並成一個或幾個文件以減少 HTTP 請求,這就是打包工具(Module bundler)的作用。
打包工具的核心能力是對 js 代碼進行合並,擴展能力是對 js 代碼進行優化、編譯和壓縮。webpack 的特色是擴展得比較狠,通過各類插件,可以打包任意類型的文件。
打包工具的真正價值在哪里呢?僅僅是合並文件從而減少 HTTP 請求數嗎?當然不止,打包工具的真正價值是在工程開發中,完成從開發狀態到發布狀態的自動化構建。
減少 HTTP 請求和按需加載其實是相互矛盾的,都打包成一個文件了,自然不需要考慮模塊加載的問題。但是把所有文件都打包成一個文件,又顯得有些冗余。所以 webpack 也支持 code spliting,把文件打成多個包。
6、終結者:ES6 Module
原生模塊標准得到瀏覽器全面支持之時,就是所有模塊封裝方案滅亡之日。但打包工具(自動化構建工具)仍會繼續存在。
五、模塊打包的技術實現
圖片來源:https://www.leanpanda.com/blog/2015/06/28/amd-requirejs-commonjs-browserify/
以上是 browserify 的實現,webpack 的實現也差不多,這里詳細說明一下。
首先,webpack 會將每個 js 文件編譯成一個函數:
webpack 會將文件路徑映射為一個數字 id,入口文件默認 id 為 0,在入口文件中遇到的第一個加載的模塊,id 為 1……,總之按照加載的順序依次賦予一個 id。
webpack 函數用於整個模塊的加載,所有模塊函數會按照 id 的順序組成一個數組,傳給 webpack 進行加載。
最后運行的結果是: