為什么需要模塊化?
隨著網(wǎng)站內(nèi)容越來越復(fù)雜贤壁,瀏覽器和用戶的交互越來越細(xì)膩悼枢,網(wǎng)站再也不是簡單的內(nèi)容呈現(xiàn),更像是一個(gè)復(fù)雜的客戶端軟件脾拆,其中html/css/js代碼越來越多馒索,邏輯越來越復(fù)雜,越來越不便于管理名船,為了解決這個(gè)問題绰上,才出現(xiàn)了模塊化的概念,也就是說模塊化更多的是工程方面的產(chǎn)出渠驼,為了應(yīng)對更復(fù)雜的網(wǎng)站開發(fā)蜈块。
梳理下網(wǎng)站的發(fā)展過程,大家也就知道為什么模塊化是必然出現(xiàn)的了:
? 早期一個(gè)html文件迷扇,通過style引入內(nèi)聯(lián)css百揭,通過script引入內(nèi)聯(lián)js。
? 晚一點(diǎn)一個(gè)html蜓席,通過link引入外部css信峻,通過script引入外部js。
? 再復(fù)雜一點(diǎn)瓮床,多個(gè)html盹舞,各自引入自己的css和js。
? 再復(fù)雜一點(diǎn)隘庄,多個(gè)html中有復(fù)用的css和js踢步,怎么辦呢?拆分文件唄丑掺,將css和js拆分成小文件获印,然后通過link和script分別引入或者手動(dòng)合并之后引入。
? 再復(fù)雜一點(diǎn)街州,有很多js/css的小文件兼丰,手動(dòng)解決會有缺陷或者容易出錯(cuò)玻孟,怎么辦呢?引入自動(dòng)化工具唄鳍征,自動(dòng)合并黍翎,壓縮等。
? 再復(fù)雜一點(diǎn)艳丛,想要按需合并js/css小文件匣掸,開發(fā)模式自動(dòng)監(jiān)聽改動(dòng),甚至可以將圖片等其他靜態(tài)資源當(dāng)做模塊氮双。
? 再復(fù)雜一點(diǎn)碰酝,...
到現(xiàn)在為止,都有哪些優(yōu)秀的模塊化方案呢戴差?
非模塊
通過多個(gè)script標(biāo)簽引入多個(gè)js文件送爸,也即通過文件的方式來管理模塊。
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>
<script src="module3.js"></script>
這種原始的方案有很多顯而易見的弊端:
? 污染全局作用域暖释,多人協(xié)作簡直是災(zāi)難碱璃。
? 手動(dòng)維護(hù)script標(biāo)簽的順序,多頁應(yīng)用更慘饭入。
? 大型項(xiàng)目資源難以管理嵌器,容易留下隱患。
CommonJs谐丢,同步require
該方案的核心思想就是允許模塊通過require
方案同步加載依賴的其他模塊爽航,通過exports
或module.exports
來暴露出需要的接口。
require("../moduleA.js");
exports.doStuff = function() {
console.log('hello world');
};
這種原始的方案的優(yōu)點(diǎn):
? 復(fù)用性強(qiáng)乾忱。
? 有不少可以拿來即用的模塊讥珍,生態(tài)不錯(cuò)。
? 實(shí)現(xiàn)簡單窄瘟,使用簡單衷佃。
這種原始的方案的弊端:
? 同步加載不適合瀏覽器,瀏覽器的請求都是異步加載蹄葱。
? 不能并行加載多個(gè)模塊氏义。
AMD,異步require
該方案只有一個(gè)主要接口define(id?, dependencies?, factory)
图云,他要在聲明模塊的時(shí)候指定所有的依賴dependencies
惯悠,并傳入到factory
中,對于依賴的模塊異步加載并執(zhí)行竣况。
// 定義
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
});
// 加載
require(["module", "../file"], function(module, file) {
});
這種原始的方案的優(yōu)點(diǎn):
? 異步加載適合瀏覽器克婶。
? 可并行加載多個(gè)模塊。
這種原始的方案的弊端:
? 模塊定義方式不優(yōu)雅,不符合標(biāo)準(zhǔn)模塊化
ES6模塊
該方案最大的特點(diǎn)就是靜態(tài)化情萤,靜態(tài)化的優(yōu)勢在于可以在編譯的時(shí)候確定模塊的依賴關(guān)系以及輸入輸出的變量鸭蛙。上面提到的CommonJs和AMD都只能在運(yùn)行時(shí)確定這些東西。
import "jquery";
export function doStuff() {}
這種原始的方案的優(yōu)點(diǎn):
? 可靜態(tài)分析筋岛,提前編譯娶视。
? 面向未來的標(biāo)準(zhǔn)。
這種原始的方案的弊端:
? 瀏覽器原生兼容性差泉蝌,所以一般都編譯成ES5歇万。
? 目前可以拿來即用的模塊少揩晴,生態(tài)差勋陪。
webpack模塊化機(jī)制
webpack并不強(qiáng)制你使用某種模塊化方案,而是通過兼容所有模塊化方案讓你無痛接入項(xiàng)目硫兰,當(dāng)然這也是webpack牛逼的地方诅愚。
有了webpack,你可以隨意選擇你喜歡的模塊化方案劫映,至于怎么處理模塊之間的依賴關(guān)系及如何按需打包违孝,放輕松,webpack會幫你處理好的泳赋。
webpack的模塊化有什么特點(diǎn)雌桑?
? 可以兼容多模塊風(fēng)格,無痛遷移老項(xiàng)目祖今。
? 一切皆模塊校坑,js/css/圖片/字體都是模塊。
? 靜態(tài)解析千诬,按需打包耍目,動(dòng)態(tài)加載。
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");
本來徐绑,模塊化方案僅僅是針對JS邪驮。但實(shí)際項(xiàng)目中,我們希望coffeescript
也能被當(dāng)做模塊傲茄,進(jìn)一步毅访,css/less/sass
是不是也可以被當(dāng)做模塊,再進(jìn)一步盘榨,html/image/template
是不是也可以被當(dāng)做模塊俺抽。
想到這些就覺得腦洞大開,不可思議较曼!但是webpack居然做到了磷斧!webpack提供的loaders可以對文件做預(yù)處理,從而實(shí)現(xiàn)了一切皆模塊瓦哎。
那么叭喜,webpack到底對模塊代碼做了什么知态?
非模塊化代碼
一行alert('hello world');
代碼呵扛,經(jīng)過webpack打包后因痛,會生成如下50行代碼供屉;
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports) {
alert('hello world');
/***/ }
/******/ ]);
上面編譯出來的代碼主要包含兩個(gè)部分:Runtime昧廷,模塊蝶防。上半部分就是Runtime憔晒,作用是保證模塊順序加載和運(yùn)行藻肄。下半部分是我們的JS代碼,包裹了一個(gè)函數(shù)拒担,也就是模塊嘹屯。
運(yùn)行的時(shí)候模塊是作為Runtime的參數(shù)被傳進(jìn)去的。
(function(modules) {
// Runtime
})([
// 模塊數(shù)組
])
那么从撼,非模塊化代碼被編譯后會有什么缺陷呢州弟?
? 模塊不再暴露在全局作用域,模塊的全局變量也不再是全局變量低零。
? 模塊被引入的時(shí)候只是執(zhí)行代碼而無法將模塊賦值婆翔。因?yàn)榉悄K化規(guī)范的代碼沒有通過AMD的return
或者CommonJs
的exports
導(dǎo)出模塊本身。
AMD模塊
AMD的代碼
define([], function() {
alert('hello world!');
});
經(jīng)過webpack打包掏婶,會生成如下核心代碼:
function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, // AMD依賴列表
__WEBPACK_AMD_DEFINE_RESULT__; // AMD factory函數(shù)的返回值啃奴,即模塊內(nèi)容
__WEBPACK_AMD_DEFINE_ARRAY__ = [];
// 執(zhí)行factory函數(shù),獲取返回值作為模塊內(nèi)容
// 函數(shù)體使用.apply調(diào)用雄妥,函數(shù)體中this為exports
// 形參則分別對應(yīng)依賴列表中的各個(gè)依賴模塊
__WEBPACK_AMD_DEFINE_RESULT__ = function() {
alert('hello world!');
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__);
// 如果模塊內(nèi)容不為空最蕾,則通過module.exports返回
// 如果為空,則不處理茎芭,在Runtime中聲明為{}
if (__WEBPACK_AMD_DEFINE_RESULT__ !== undefined) {
module.exports = __WEBPACK_AMD_DEFINE_RESULT__;
}
}
CommonJs
CommonJs的代碼
var me = {
sayHello:function(){
alert('hello world!');
}
};
module.exports = me;
經(jīng)過webpack打包揖膜,會生成如下核心代碼:
function(module, exports) {
var me = {
sayHello: function() {
alert('hello world!');
}
};
module.exports = me;
}
模塊化是拆分,那傳輸怎么辦梅桩?
博文開頭提到壹粟,模塊化是工程化的需求,是為了更好的管理代碼宿百,最后上線的代碼并不應(yīng)該是這樣的趁仙,假設(shè)我們用兩個(gè)極端的方式去加載代碼:
? N個(gè)模塊N個(gè)請求。
? 所有模塊打包成一個(gè)文件垦页,一個(gè)請求雀费。
顯然,這兩種都不是最優(yōu)方案痊焊,第一種請求數(shù)量過多盏袄,第二種請求文件過大忿峻。
理論上,最優(yōu)方案是:按需打包辕羽,即將該頁面需要的所有模塊打包成一個(gè)文件逛尚,保證請求最少,且請求的代碼都是需要的刁愿。
在webpack之前的構(gòu)建工具里绰寞,都實(shí)現(xiàn)不了這個(gè)“最優(yōu)方案”,因?yàn)樗鼈儾恢滥K之前的依賴關(guān)系铣口,自然就不能按需打包了滤钱。
而webpack出現(xiàn)之后,它的代碼分片功能讓webpack擁有了按需打包的特性脑题,從而鶴立雞群件缸。當(dāng)然,webpack還有很多其他優(yōu)秀的特性旭蠕。