webpack現(xiàn)代化前端應(yīng)用的基石
- 現(xiàn)階段的大型應(yīng)用就要求前端必須要有獨(dú)立項(xiàng)目,獨(dú)立的項(xiàng)目需要進(jìn)行工程化獲得足夠的效率
- 具有復(fù)雜數(shù)據(jù)狀態(tài)的應(yīng)用開發(fā)過程就必須要有合適的框架,采用數(shù)據(jù)驅(qū)動(dòng)的方式增加可維護(hù)性
- 復(fù)雜項(xiàng)目結(jié)構(gòu)必須進(jìn)行模塊化管理,提高公共部分的可復(fù)用性,增加團(tuán)隊(duì)的并行協(xié)作能力
- 重復(fù)規(guī)律性的工作必須采用自動(dòng)化工具,提高工作效率,避免人為錯(cuò)誤
webpack與模塊化開發(fā)
前端發(fā)展到前后端分離之后:前端項(xiàng)目作為一個(gè)獨(dú)立的個(gè)體,已經(jīng)具備了很高的復(fù)雜性,相對(duì)管理就變得很難,而在解決這一問題上,出現(xiàn)了前端模塊化。將不同功能的代碼劃分為不同的模塊單獨(dú)維護(hù)。
webpack的本質(zhì)是一個(gè)模塊打包工具(萬物皆可模塊:萬物皆可打包)
webpack解決了如何在前端項(xiàng)目中更加高效的管理和維護(hù)項(xiàng)目中的每一個(gè)資源
模塊化的演進(jìn)過程
1.Stage 1 - 文件劃分方式
最早期實(shí)現(xiàn)模塊化,是將每個(gè)功能及其狀態(tài)的數(shù)據(jù)各自單獨(dú)放到不同的js文件中,約定每個(gè)文件都是一個(gè)獨(dú)立的模塊。使用某個(gè)模塊就將某個(gè)模塊引入到的頁面中,一個(gè)script的標(biāo)簽對(duì)應(yīng)一個(gè)模塊,然后直接調(diào)用模塊中的成員
└─ stage—1
├── module—a.js
├── module—b.js
└── index.html
// module-a.js
function foo () {
console.log('moduleA#foo')
}
// module-b.js
var data = 'something'
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Stage 1</title>
</head>
<body>
<script src="module-a.js"></script>
<script src="module-b.js"></script>
<script>
// 直接使用全局成員
foo() // 可能存在命名沖突
console.log(data)
data = 'other' // 數(shù)據(jù)可能會(huì)被修改
</script>
</body>
</html>
缺點(diǎn):
- 模塊直接在全局工作,大量模塊成員污染全局作用域
- 沒有私有空間,所有模塊內(nèi)成員都可以在模塊外被訪問和修改
- 一旦模塊過多,容易產(chǎn)生命名沖突
- 無法管理模塊之間的依賴關(guān)系
- 維護(hù)過程中,難以區(qū)分模塊成員的的所屬關(guān)系
2.Stage 2 - 命名空間方式
后來,我們約定每個(gè)模塊只暴露一個(gè)全局對(duì)象,所有模塊成員都掛載到這個(gè)全局對(duì)象中,具體做法是在第一階段的基礎(chǔ)上,通過將每個(gè)模塊“包裹”為一個(gè)全局對(duì)象的形式實(shí)現(xiàn),這種方式就好像是為模塊內(nèi)的成員添加了“命名空間”,所以我們又稱之為命名空間方式。
// module-a.js
window.moduleA = {
method1: function () {
console.log('moduleA#method1')
}
}
// module-b.js
window.moduleB = {
data: 'something'
method1: function () {
console.log('moduleB#method1')
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Stage 2</title>
</head>
<body>
<script src="module-a.js"></script>
<script src="module-b.js"></script>
<script>
moduleA.method1()
moduleB.method1()
// 模塊成員依然可以被修改
moduleA.data = 'foo'
</script>
</body>
</html>
這種方式解決了全局作用域下的命名沖突,和模塊歸屬問題,但是并沒有解決模塊之間的依賴問題以及外部訪問和修改的問題
3.Stage 3 - IIFE(立即執(zhí)行函數(shù)表達(dá)式)
使用立即執(zhí)行函數(shù)表達(dá)式(IIFE,Immediately-Invoked Function Expression)為模塊提供私有空間。具體做法是將每個(gè)模塊成員都放在一個(gè)立即執(zhí)行函數(shù)所形成的私有作用域中,對(duì)于需要暴露給外部的成員,通過掛到全局對(duì)象上的方式實(shí)現(xiàn)。
// module-a.js
;(function () {
var name = 'module-a'
function method1 () {
console.log(name + '#method1')
}
window.moduleA = {
method1: method1
}
})()
// module-b.js
;(function () {
var name = 'module-b'
function method1 () {
console.log(name + '#method1')
}
window.moduleB = {
method1: method1
}
})()
這種方式帶來了私有成員的概念,私有成員只能在模塊成員內(nèi)通過閉包的形式訪問,這就解決了前面所提到的全局作用域污染和命名沖突的問題。
4. Stage 4 - IIFE 依賴參數(shù)
在 IIFE 的基礎(chǔ)之上,我們還可以利用 IIFE 參數(shù)作為依賴聲明使用,這使得每一個(gè)模塊之間的依賴關(guān)系變得更加明顯。
// module-a.js
;(function ($) { // 通過參數(shù)明顯表明這個(gè)模塊的依賴
var name = 'module-a'
function method1 () {
console.log(name + '#method1')
$('body').animate({ margin: '200px' })
}
window.moduleA = {
method1: method1
}
})(jQuery)
模塊加載的問題
以上四種方式確實(shí)解決了一些問題,但是仍然有一些問題并未解決,并且被忽略了
<!DOCTYPE html>
<html>
<head>
<title>Evolution</title>
</head>
<body>
<script src="https://unpkg.com/jquery"></script>
<script src="module-a.js"></script>
<script src="module-b.js"></script>
<script>
moduleA.method1()
moduleB.method1()
</script>
</body>
</html>
最明顯的問題就是:模塊的加載。
我們都是通過 script 標(biāo)簽的方式直接在頁面中引入的這些模塊,這意味著模塊的加載并不受代碼的控制,時(shí)間久了維護(hù)起來會(huì)十分麻煩。試想一下,如果你的代碼需要用到某個(gè)模塊,如果 HTML 中忘記引入這個(gè)模塊,又或是代碼中移除了某個(gè)模塊的使用,而 HTML 還忘記刪除該模塊的引用,都會(huì)引起很多問題和不必要的麻煩。
更為理想的方式應(yīng)該是在頁面中引入一個(gè) JS 入口文件,其余用到的模塊可以通過代碼控制,按需加載進(jìn)來。
模塊化規(guī)范的出現(xiàn)
- 一個(gè)統(tǒng)一的模塊化標(biāo)準(zhǔn)規(guī)范
- 一個(gè)可以自動(dòng)加載模塊的基礎(chǔ)庫
1.Node.js中的CommonJS規(guī)范:
一個(gè)文件就是一個(gè)模塊,每個(gè)模塊都有單獨(dú)的作用域,通過module.exports導(dǎo)出成員,再通過require函數(shù)載入模塊。注意:CommonJS僅支持在node環(huán)境中。
CommonJS約定是以同步的方式加載模塊,因?yàn)镹ode.js執(zhí)行機(jī)制是在啟動(dòng)時(shí)加載模塊,執(zhí)行過程中只是使用模塊,所以這種方式?jīng)]有問題。但是在瀏覽器端使用同步加載模式,會(huì)引起大量的同步模式請(qǐng)求,導(dǎo)致應(yīng)用效率地下。
2.瀏覽器端的AMD(異步模塊定義規(guī)范):
規(guī)范出現(xiàn)的同時(shí),推出了一個(gè)非常出名的庫,叫做Require.js,它實(shí)現(xiàn)了AMD模塊化規(guī)范,本身也是一個(gè)非常強(qiáng)大的模塊加載器
目前絕大多數(shù)第三方庫都支持 AMD 規(guī)范,但是它使用起來相對(duì)復(fù)雜,而且當(dāng)項(xiàng)目中模塊劃分過于細(xì)致時(shí),就會(huì)出現(xiàn)同一個(gè)頁面對(duì) js 文件的請(qǐng)求次數(shù)過多的情況,從而導(dǎo)致效率降低。AMD 規(guī)范為前端模塊化提供了一個(gè)標(biāo)準(zhǔn),但這只是一種妥協(xié)的實(shí)現(xiàn)方式
模塊化的標(biāo)準(zhǔn)規(guī)范
- 在Node.js環(huán)境中,我們遵循Common.js規(guī)范來組織模塊
- 在瀏覽器環(huán)境中,我們遵循ESModules規(guī)范

1.模塊打包工具的出現(xiàn)
模塊化思想的進(jìn)一步引入和發(fā)展,前端應(yīng)用有產(chǎn)生了一些新的問題,比如:
- 使用ES Modules模塊標(biāo)準(zhǔn),存在兼容性性問題。盡管主流瀏覽器的最新版本都支持這一特性,但是我們無法保證用戶所使用的瀏覽器情況。
- 模塊化分出的模塊文件過多,前端應(yīng)用又運(yùn)行在瀏覽器環(huán)境中,每一個(gè)文件都需要單獨(dú)從服務(wù)器請(qǐng)求回來。零散的模塊文件必然會(huì)導(dǎo)致瀏覽器頻繁發(fā)送網(wǎng)絡(luò)請(qǐng)求,影響應(yīng)用的實(shí)際體驗(yàn)
- 隨著前端應(yīng)用的增大,不僅僅是js需要模塊化,html和css也面臨相同的問題
所以,我們理想的打包工具需要實(shí)現(xiàn)一些功能:
- 第一,它需要具備編譯代碼的能力,也就是將我們開發(fā)階段編寫的那些包含新特性的代碼轉(zhuǎn)換為能夠兼容大多數(shù)環(huán)境的代碼,解決我們所面臨的環(huán)境兼容問題。
- 第二,能夠?qū)⑸⒙涞哪K再打包到一起,這樣就解決了瀏覽器頻繁請(qǐng)求模塊文件的問題。這里需要注意,只是在開發(fā)階段才需要模塊化的文件劃分,因?yàn)樗軌驇臀覀兏玫亟M織代碼,到了實(shí)際運(yùn)行階段,這種劃分就沒有必要了。
- 第三,它需要支持不同種類的前端模塊類型,也就是說可以將開發(fā)過程中涉及的樣式、圖片、字體等所有資源文件都作為模塊使用,這樣我們就擁有了一個(gè)統(tǒng)一的模塊化方案,所有資源文件的加載都可以通過代碼控制,與業(yè)務(wù)代碼統(tǒng)一維護(hù),更為合理。