摘要:可維護(hù)性根據(jù)定義,每個(gè)模塊都是獨(dú)立的。良好設(shè)計(jì)的模塊會(huì)盡量與外部的代碼撇清關(guān)系,以便于獨(dú)立對(duì)其進(jìn)行改進(jìn)和維護(hù)。這標(biāo)志模塊化編程正式誕生。的模塊系統(tǒng),就是參照規(guī)范實(shí)現(xiàn)的。對(duì)象就代表模塊本身。
javascript模塊化及webpack基本介紹 JavaScript 模塊化發(fā)展歷程
什么是模塊化 ?
為什么要做Javascript模塊化?
JavaScript 模塊化發(fā)展歷程
什么是模塊化 ?模塊化是一種處理復(fù)雜系統(tǒng)分解成為更好的可管理模塊的方式,它可以把系統(tǒng)代碼劃分為一系列職責(zé)單一,高度解耦且可替換的模塊,系統(tǒng)中某一部分的變化將如何影響其它部分就會(huì)變得顯而易見,系統(tǒng)的可維護(hù)性更加簡(jiǎn)單易得。
一個(gè)模塊就是實(shí)現(xiàn)特定功能的文件, 邏輯上相關(guān)的代碼組織到同一個(gè)包內(nèi),包內(nèi)是一個(gè)相對(duì)獨(dú)立的王國(guó),不用擔(dān)心命名沖突什么的,那么外部使用的話直接引入對(duì)應(yīng)的package即可.
就好像作家會(huì)把他的書分章節(jié)和段落;程序員會(huì)把他的代碼分成模塊。
就好像書籍的一章,模塊僅僅是一坨代碼而已。
好的代碼模塊分割的內(nèi)容一定是很合理的,便于你增加減少或者修改功能,同時(shí)又不會(huì)影響整個(gè)系統(tǒng)。
為什么要做Javascript模塊化?早期前端只是為了實(shí)現(xiàn)簡(jiǎn)單的頁面交互邏輯,隨著Ajax技術(shù)的廣泛應(yīng)用,前端庫的層出不窮,前端代碼日益膨脹,JavaScript卻沒有為組織代碼提供任何明顯幫助,甚至沒有類的概念,更不用說模塊(module)了,這時(shí)候JavaScript極其簡(jiǎn)單的代碼組織規(guī)范不足以駕馭如此龐大規(guī)模的代碼.
模塊化可以使你的代碼低耦合,功能模塊直接不相互影響。
可維護(hù)性:根據(jù)定義,每個(gè)模塊都是獨(dú)立的。良好設(shè)計(jì)的模塊會(huì)盡量與外部的代碼撇清關(guān)系,以便于獨(dú)立對(duì)其進(jìn)行改進(jìn)和維護(hù)。維護(hù)一個(gè)獨(dú)立的模塊比起一團(tuán)凌亂的代碼來說要輕松很多。
命名空間:在JavaScript中,最高級(jí)別的函數(shù)外定義的變量都是全局變量(這意味著所有人都可以訪問到它們)。也正因如此,當(dāng)一些無關(guān)的代碼碰巧使用到同名變量的時(shí)候,我們就會(huì)遇到“命名空間污染”的問題。
可復(fù)用性:現(xiàn)實(shí)來講,在日常工作中我們經(jīng)常會(huì)復(fù)制自己之前寫過的代碼到新項(xiàng)目中, 有了模塊, 想復(fù)用的時(shí)候直接引用進(jìn)來就行。
JavaScript 模塊化發(fā)展歷程前端的先驅(qū)在刀耕火種的階段開始,做了很多努力,在現(xiàn)有的運(yùn)行環(huán)境中,實(shí)現(xiàn)"模塊"的效果。函數(shù)封裝
模塊就是實(shí)現(xiàn)特定功能的一組方法。在JavaScript中,函數(shù)是創(chuàng)建作用域的唯一方式, 所以把函數(shù)作為模塊化的第一步是很自然的事情.
function foo(){ //... } function bar(){ //... }
上面的,組成一個(gè)模塊。使用的時(shí)候,直接調(diào)用就行了。
這種做法的缺點(diǎn)很明顯:全局變量被污染,很容易命名沖突, 而且模塊成員之間看不出直接關(guān)系。
對(duì)象寫法為了解決上面的缺點(diǎn),可以把模塊寫成一個(gè)對(duì)象,所有的模塊成員都放到這個(gè)對(duì)象里面。
var MYAPP = { count: 0, foo: function(){}, bar: function(){} } MYAPP.foo();
上面的代碼中,函數(shù)foo和bar, 都封裝在MYAPP對(duì)象里。使用的時(shí)候,就是調(diào)用這個(gè)對(duì)象的屬性。 但是,這樣的寫法會(huì)暴露所有模塊成員,內(nèi)部狀態(tài)可以被外部改寫.
立即執(zhí)行函數(shù)(IIFE)寫法使用立即執(zhí)行函數(shù)(Immediately-Invoked Function Expression,IIFE),可以達(dá)到不暴露私有成員的目的。
var Module = (function(){ var _private = "safe now"; var foo = function(){ console.log(_private) } return { foo: foo } })() Module.foo(); Module._private; // undefined
這種方法的好處在于,你可以在函數(shù)內(nèi)部使用局部變量,而不會(huì)意外覆蓋同名全局變量,但仍然能夠訪問到全局變量, 在模塊外部無法修改我們沒有暴露出來的變量、函數(shù).
引入依賴將全局變量當(dāng)成一個(gè)參數(shù)傳入到匿名函數(shù)然后使用
var Module = (function($){ var _$body = $("body"); // we can use jQuery now! var foo = function(){ console.log(_$body); // 特權(quán)方法 } // Revelation Pattern return { foo: foo } })(jQuery) Module.foo();
jQuery的封裝風(fēng)格曾經(jīng)被很多框架模仿,通過匿名函數(shù)包裝代碼,所依賴的外部變量傳給這個(gè)函數(shù),在函數(shù)內(nèi)部可以使用這些依賴,然后在函數(shù)的最后把模塊自身暴漏給window。
如果需要添加擴(kuò)展,則可以作為jQuery的插件,把它掛載到$上。
這種風(fēng)格雖然靈活了些,但并未解決根本問題:所需依賴還是得外部提前提供、還是增加了全局變量。
從以上的嘗試中,可以歸納出js模塊化需要解決那些問題:
如何安全的包裝一個(gè)模塊的代碼?(不污染模塊外的任何代碼)
如何唯一標(biāo)識(shí)一個(gè)模塊?
如何優(yōu)雅的把模塊的API暴漏出去?(不能增加全局變量)
如何方便的使用所依賴的模塊?
圍繞著這些問題,js模塊化開始了一段艱苦而曲折的征途。
JavaScript 模塊規(guī)范上述的所有解決方案都有一個(gè)共同點(diǎn):使用單個(gè)全局變量來把所有的代碼包含在一個(gè)函數(shù)內(nèi),由此來創(chuàng)建私有的命名空間和閉包作用域。
你必須清楚地了解引入依賴文件的正確順序。就拿Backbone.js來舉個(gè)例子,想要使用Backbone就必須在你的頁面里引入Backbone的源文件。
然而Backbone又依賴 Underscore.js,所以Backbone的引入必須在其之后。
而在工作中,這些依賴管理經(jīng)常會(huì)成為讓人頭疼的問題。
另外一點(diǎn),這些方法也有可能引起命名空間沖突。舉個(gè)例子,要是你碰巧寫了倆重名的模塊怎么辦?或者你同時(shí)需要一個(gè)模塊的兩個(gè)版本時(shí)該怎么辦?
還有就是協(xié)同開發(fā)的時(shí)候, 大家編寫模塊的方式各不相同,你有你的寫法,我有我的寫法, 那就亂了套.
接下來介紹幾種廣受歡迎的解決方案
CommonJS
AMD
CMD
ES6模塊
CommonJS2009年,美國(guó)程序員Ryan Dahl創(chuàng)造了node.js項(xiàng)目,將javascript語言用于服務(wù)器端編程。
這標(biāo)志Javascript模塊化編程正式誕生。因?yàn)槔蠈?shí)說,在瀏覽器環(huán)境下,沒有模塊也不是特別大的問題,畢竟網(wǎng)頁程序的復(fù)雜性有限;但是在服務(wù)器端,一定要有模塊,與操作系統(tǒng)和其他應(yīng)用程序互動(dòng),否則根本沒法編程。
node.js的模塊系統(tǒng),就是參照CommonJS規(guī)范實(shí)現(xiàn)的。
CommonJS定義的模塊分為:
定義模塊:
根據(jù)CommonJS規(guī)范,一個(gè)多帶帶的文件就是一個(gè)模塊。每一個(gè)模塊都是一個(gè)多帶帶的作用域,也就是說,在該模塊內(nèi)部定義的變量,無法被其他模塊讀取,除非定義為global對(duì)象的屬性。
模塊輸出:
模塊只有一個(gè)出口,module.exports對(duì)象,我們需要把模塊希望輸出的內(nèi)容放入該對(duì)象。module對(duì)象就代表模塊本身。
加載模塊:
加載模塊使用require方法,該方法讀取一個(gè)文件并執(zhí)行,返回文件內(nèi)部的module.exports對(duì)象。
// math.js exports.add = function(a, b){ return a + b; }
// main.js var math = require("math") // ./math in node console.log(math.add(1, 2)); // 3
這種實(shí)現(xiàn)模式有兩點(diǎn)好處:
避免全局命名空間污染
明確代碼之間的依賴關(guān)系
但是, 由于一個(gè)重大的局限,使得CommonJS規(guī)范不適用于瀏覽器環(huán)境。
看上面的main.js代碼, 第二行的math.add(1, 2),在第一行require("math")之后運(yùn)行,因此必須等math.js加載完成。也就是說,如果加載的依賴很多, 時(shí)間很長(zhǎng),整個(gè)應(yīng)用就會(huì)停在那里等。
我們分析一下瀏覽器端的js和服務(wù)器端js都主要做了哪些事,有什么不同:
服務(wù)器端JS | 瀏覽器端JS |
---|---|
相同的代碼需要多次執(zhí)行 | 代碼需要從一個(gè)服務(wù)器端分發(fā)到多個(gè)客戶端執(zhí)行 |
CPU和內(nèi)存資源是瓶頸 | 帶寬是瓶頸 |
加載時(shí)從磁盤中加載 | 加載時(shí)需要通過網(wǎng)絡(luò)加載 |
這對(duì)服務(wù)器端不是一個(gè)問題,因?yàn)樗械哪K都存放在本地硬盤,可以同步加載完成,等待時(shí)間就是硬盤的讀取時(shí)間。但是,對(duì)于瀏覽器,這卻是一個(gè)大問題,因?yàn)槟K都放在服務(wù)器端,等待時(shí)間取決于網(wǎng)速的快慢,可能要等很長(zhǎng)時(shí)間,瀏覽器處于"假死"狀態(tài)。
因此,瀏覽器端的模塊,不能采用"同步加載"(synchronous),只能采用"異步加載"(asynchronous)。這就是AMD規(guī)范誕生的背景。
AMDAMD 即Asynchronous Module Definition,中文名是異步模塊定義的意思。它采用異步方式加載模塊,模塊的加載不影響它后面語句的運(yùn)行。所有依賴這個(gè)模塊的語句,都定義在一個(gè)回調(diào)函數(shù)中,等到加載完成之后,這個(gè)回調(diào)函數(shù)才會(huì)運(yùn)行。
// main.js require(["moduleA", "moduleB", "moduleC"], function (moduleA, moduleB, moduleC){ // some code here });
用全局函數(shù)define來定義模塊,用法為:define(id?, dependencies?, factory);
id為模塊標(biāo)識(shí),遵從CommonJS Module Identifiers規(guī)范
dependencies為依賴的模塊數(shù)組,在factory中需傳入形參與之一一對(duì)應(yīng)
如果dependencies的值中有"require"、"exports"或"module",則與commonjs中的實(shí)現(xiàn)保持一致
如果dependencies省略不寫,則默認(rèn)為["require", "exports", "module"],factory中也會(huì)默認(rèn)傳入require,exports,module.
如果factory為函數(shù),模塊對(duì)外暴漏API的方法有三種:return任意類型的數(shù)據(jù)、exports.xxx=xxx、module.exports=xxx.
如果factory為對(duì)象,則該對(duì)象即為模塊的返回值
大名鼎鼎的require.js就是AMD規(guī)范的實(shí)現(xiàn).
require.js要求,每個(gè)模塊是一個(gè)多帶帶的js文件。這樣的話,如果加載多個(gè)模塊,就會(huì)發(fā)出多次HTTP請(qǐng)求,會(huì)影響網(wǎng)頁的加載速度。因此,require.js提供了一個(gè)優(yōu)化工具(Optimizer)r.js,當(dāng)模塊部署完畢以后,可以用這個(gè)工具將多個(gè)模塊合并在一個(gè)文件中,實(shí)現(xiàn)前端文件的壓縮與合并, 減少HTTP請(qǐng)求數(shù)。
我們來看一個(gè)require.js的例子
//a.js define(function(){ console.log("a.js執(zhí)行"); return { hello: function(){ console.log("hello, a.js"); } } });
//b.js define(function(){ console.log("b.js執(zhí)行"); return { hello: function(){ console.log("hello, b.js"); } } });
//main.js require.config({ paths: { "jquery": "../js/jquery.min" }, }); require(["jquery","a", "b"], function($, a, b){ console.log("main.js執(zhí)行"); a.hello(); $("#btn").click(function(){ b.hello(); }); })
上面的main.js被執(zhí)行的時(shí)候,會(huì)有如下的輸出:
a.js執(zhí)行
b.js執(zhí)行
main.js執(zhí)行
hello, a.js
在點(diǎn)擊按鈕后,會(huì)輸出:
hello, b.js
但是如果細(xì)細(xì)來看,b.js被預(yù)先加載并且預(yù)先執(zhí)行了,(第二行輸出),b.hello這個(gè)方法是在點(diǎn)擊了按鈕之后才會(huì)執(zhí)行,如果用戶壓根就沒點(diǎn),那么b.js中的代碼應(yīng)不應(yīng)該執(zhí)行呢?
這其實(shí)也是AMD/RequireJs被吐槽的一點(diǎn),由于瀏覽器的環(huán)境特點(diǎn),被依賴的模塊肯定要預(yù)先下載的。問題在于,是否需要預(yù)先執(zhí)行?如果一個(gè)模塊依賴了十個(gè)其他模塊,那么在本模塊的代碼執(zhí)行之前,要先把其他十個(gè)模塊的代碼都執(zhí)行一遍,不管這些模塊是不是馬上會(huì)被用到。這個(gè)性能消耗是不容忽視的。
另一點(diǎn)被吐槽的是,在定義模塊的時(shí)候,要把所有依賴模塊都羅列一遍,而且還要在factory中作為形參傳進(jìn)去,要寫兩遍很大一串模塊名稱,像這樣:
define(["a", "b", "c", "d", "e", "f", "g"], function(a, b, c, d, e, f, g){ ..... })CMD
CMD 即Common Module Definition, CMD是sea.js的作者在推廣sea.js時(shí)提出的一種規(guī)范.
在 CMD 規(guī)范中,一個(gè)模塊就是一個(gè)文件。代碼的書寫格式如下:
define(function(require, exports, module) { // 模塊代碼 // 使用require獲取依賴模塊的接口 // 使用exports或者module或者return來暴露該模塊的對(duì)外接口 })
也是用全局的define函數(shù)定義模塊, 無需羅列依賴數(shù)組,在factory函數(shù)中需傳入形參require,exports,module.
require 用來加載一個(gè) js 文件模塊,require 用來獲取指定模塊的接口對(duì)象 module.exports。
//a.js define(function(require, exports, module){ console.log("a.js執(zhí)行"); return { hello: function(){ console.log("hello, a.js"); } } });
//b.js define(function(require, exports, module){ console.log("b.js執(zhí)行"); return { hello: function(){ console.log("hello, b.js"); } } });
//main.js define(function(require, exports, module){ console.log("main.js執(zhí)行"); var a = require("a"); a.hello(); $("#b").click(function(){ var b = require("b"); b.hello(); }); });
上面的main.js執(zhí)行會(huì)輸出如下:
main.js執(zhí)行
a.js執(zhí)行
hello, a.js
a.js和b.js都會(huì)預(yù)先下載,但是b.js中的代碼卻沒有執(zhí)行,因?yàn)檫€沒有點(diǎn)擊按鈕。當(dāng)點(diǎn)擊按鈕的時(shí)候,會(huì)輸出如下:
b.js執(zhí)行
hello, b.js
Sea.js加載依賴的方式
加載期:即在執(zhí)行一個(gè)模塊之前,將其直接或間接依賴的模塊從服務(wù)器端同步到瀏覽器端;
執(zhí)行期:在確認(rèn)該模塊直接或間接依賴的模塊都加載完畢之后,執(zhí)行該模塊。
AMD vs CMD
AMD推崇依賴前置,在定義模塊的時(shí)候就要聲明其依賴的模塊,
CMD推崇就近依賴,只有在用到某個(gè)模塊的時(shí)候再去require,
AMD和CMD最大的區(qū)別是對(duì)依賴模塊的執(zhí)行時(shí)機(jī)處理不同
同樣都是異步加載模塊,AMD在加載模塊完成后就會(huì)執(zhí)行改模塊,所有模塊都加載執(zhí)行完后會(huì)進(jìn)入require的回調(diào)函數(shù),執(zhí)行主邏輯.
CMD加載完某個(gè)依賴模塊后并不執(zhí)行,只是下載而已,在所有依賴模塊加載完成后進(jìn)入主邏輯,遇到require語句的時(shí)候才執(zhí)行對(duì)應(yīng)的模塊,這樣模塊的執(zhí)行順序和書寫順序是完全一致的。
這也是很多人說AMD用戶體驗(yàn)好,因?yàn)闆]有延遲,依賴模塊提前執(zhí)行了,CMD性能好,因?yàn)橹挥杏脩粜枰臅r(shí)候才執(zhí)行的原因。
ES6模塊上述的這幾種方法都不是JS原生支持的, 在ECMAScript 6 (ES6)中,引入了模塊功能, ES6 的模塊功能汲取了CommonJS 和 AMD 的優(yōu)點(diǎn),擁有簡(jiǎn)潔的語法并支持異步加載,并且還有其他諸多更好的支持。
簡(jiǎn)單來說,ES6 模塊的設(shè)計(jì)思想就是:一個(gè) JS 文件就代表一個(gè) JS 模塊。在模塊中你可以使用 import 和 export 關(guān)鍵字來導(dǎo)入或?qū)С瞿K中的東西。
ES6 模塊主要具備以下幾個(gè)基本特點(diǎn):
自動(dòng)開啟嚴(yán)格模式,即使你沒有寫 use strict
每個(gè)模塊都有自己的上下文,每一個(gè)模塊內(nèi)聲明的變量都是局部變量,不會(huì)污染全局作用域
模塊中可以導(dǎo)入和導(dǎo)出各種類型的變量,如函數(shù),對(duì)象,字符串,數(shù)字,布爾值,類等
每一個(gè)模塊只加載一次,每一個(gè) JS 只執(zhí)行一次, 如果下次再去加載同目錄下同文件,直接從內(nèi)存中讀取。
補(bǔ)充: Typescript 識(shí)別模塊的模式一般來講,組織聲明文件的方式取決于庫是如何被使用的。 在JavaScript中一個(gè)庫有很多使用方式,這就需要你書寫聲明文件去匹配它們.
通過庫的使用方法及其源碼來識(shí)別庫的類型。
全局庫
全局庫是指能在全局命名空間下訪問的,許多庫都是簡(jiǎn)單的暴露出一個(gè)或多個(gè)全局變量。 比如jQuery.
當(dāng)你查看全局庫的源代碼時(shí),你通常會(huì)看到:
頂級(jí)的var語句或function聲明
一個(gè)或多個(gè)賦值語句到window.someName
模塊化庫
一些庫只能工作在模塊加載器的環(huán)境下。 比如,像 express只能在Node.js 里工作所以必須使用CommonJS的require函數(shù)加載。
模塊庫至少會(huì)包含下列具有代表性的條目之一:
無條件的調(diào)用require或define
像import * as a from "b"; or export c;這樣的聲明
賦值給exports或module.exports
UMD (Universal Module Definition)庫
UMD創(chuàng)造了一種同時(shí)使用兩種規(guī)范的方法,并且也支持全局變量定義。所以UMD的模塊可以同時(shí)在客戶端和服務(wù)端使用。 本質(zhì)上,UMD 是一套用來識(shí)別當(dāng)前環(huán)境支持的模塊風(fēng)格的 if/else 語句。下面是一個(gè)解釋其功能的例子:
(function (root, factory) { if (typeof define === "function" && define.amd) { define(["libName"], factory); } else if (typeof module === "object" && module.exports) { module.exports = factory(require("libName")); } else { root.returnExports = factory(root.libName); } }(this, function (b) {})前端自動(dòng)化構(gòu)建工具
Grunt
Gulp
Webpack
Browserify
......
簡(jiǎn)單的說,Grunt / Gulp 和 browserify / webpack 不是一回事。
Gulp / Grunt Gulp / Grunt 是一種工具,能夠優(yōu)化前端工作流程。比如自動(dòng)刷新頁面、combo、壓縮css、js、編譯less等等。簡(jiǎn)單來說,就是使用Gulp/Grunt,然后配置你需要的插件,就可以把以前需要手工做的事情讓它幫你做了。
說到 browserify / webpack ,那還要說到 seajs / requirejs 。這四個(gè)都是JS模塊化的方案。其中seajs / require 是一種類型,browserify / webpack 是另一種類型。seajs / require : 是一種在線"編譯" 模塊的方案,相當(dāng)于在頁面上加載一個(gè) CMD/AMD 解釋器。這樣瀏覽器就認(rèn)識(shí)了 define、exports、module 這些東西。也就實(shí)現(xiàn)了模塊化。
browserify / webpack : 是一個(gè)預(yù)編譯模塊的方案,相比于上面 ,這個(gè)方案更加智能, 首先,它是預(yù)編譯的,不需要在瀏覽器中加載解釋器。另外,你在本地直接寫JS,不管是 AMD / CMD / ES6 風(fēng)格的模塊化,它都能認(rèn)識(shí),并且編譯成瀏覽器認(rèn)識(shí)的JS。這樣就知道,Gulp是一個(gè)工具,而webpack等等是模塊化方案。Gulp也可以配置seajs、requirejs甚至webpack的插件。
Grunt每次運(yùn)行grunt 時(shí),他就利用node提供的require()系統(tǒng)查找本地安裝的 Grunt。
如果找到一份本地安裝的 Grunt,grunt-CLI就將其加載,并傳遞Gruntfile中的配置信息,然后執(zhí)行你所指定的任務(wù)。
安裝grunt-cli
npm install -g grunt-cli
配置gruntfile.js文件
module.exports = function(grunt) { // 項(xiàng)目配置. grunt.initConfig({ // 定義Grunt任務(wù) }); // 加載能夠提供"uglify"任務(wù)的插件。 grunt.loadNpmTasks("grunt插件"); // Default task(s). grunt.registerTask("default", ["任務(wù)名"]); }gulp
gulp是基于Nodejs的自動(dòng)化任務(wù)運(yùn)行器,它能自動(dòng)化地完成javascript/sass/less/html/image/css 等文件的的測(cè)試、檢查、合并、壓縮、格式化、瀏覽器自動(dòng)刷新、部署文件生成,并監(jiān)聽文件在改動(dòng)后重復(fù)指定的這些步驟。
使用Gulp的優(yōu)勢(shì)就是利用流的方式進(jìn)行文件的處理,使用管道(pipe)思想,前一級(jí)的輸出,直接變成后一級(jí)的輸入,通過管道將多個(gè)任務(wù)和操作連接起來,因此只有一次I/O的過程,流程更清晰,更純粹。Gulp去除了中間文件,只將最后的輸出寫入磁盤,整個(gè)過程因此變得更快。
使用Gulp,可以避免瀏覽器緩存機(jī)制,性能優(yōu)化(文件合并,減少http請(qǐng)求;文件壓縮)以及效率提升(自動(dòng)添加CSS3前綴;代碼分析檢查)
browserifyBrowserify 是一個(gè)模塊打包器,它遍歷代碼的依賴樹,將依賴樹中的所有模塊打包成一個(gè)文件。有了 Browserify,我們就可以在瀏覽器應(yīng)用程序中使用 CommonJS 模塊。
browserify模塊化的用法和node是一樣的,所以npm上那些原本僅僅用于node環(huán)境的包,在瀏覽器環(huán)境里也一樣能用.
webpack官網(wǎng)有對(duì)二者的使用方法進(jìn)行對(duì)比,可以看一下:[webpack for browserify users
](http://webpack.github.io/docs...
browserify main.js -o bundle.js
Compare Webpack vs Browserify vs RequireJS
webpack官網(wǎng)對(duì)webpack的定義是MODULE BUNDLER(模塊打包器),他的目的就是把有依賴關(guān)系的各種文件打包成一系列的靜態(tài)資源。 請(qǐng)看下圖
Webpack的工作方式是:把你的項(xiàng)目當(dāng)做一個(gè)整體,通過一個(gè)給定的主文件(如:main.js),Webpack將從這個(gè)文件開始找到你的項(xiàng)目的所有依賴文件,使用loaders處理它們,最后打包為一個(gè)(或多個(gè))瀏覽器可識(shí)別的JavaScript文件。
webpack核心概念 1. 入口(entry):webpack將創(chuàng)建所有應(yīng)用程序的依賴關(guān)系圖表(dependency graph)。
entry配置項(xiàng)告訴Webpack應(yīng)用的根模塊或起始點(diǎn)在哪里,
入口起點(diǎn)告訴 webpack 從哪里開始,并遵循著依賴關(guān)系圖表知道要打包什么??梢詫?yīng)用程序的入口起點(diǎn)認(rèn)為是根上下文或 app 第一個(gè)啟動(dòng)文件。它的值可以是字符串、數(shù)組或?qū)ο?
//webpack.config.js const config = { entry: { app: "./src/app.js", vendors: "./src/vendors.js" } };2. 出口(output)
將所有的資源(assets)合并在一起后,我們還需要告訴 webpack 在哪里打包我們的應(yīng)用程序。output 選項(xiàng)控制 webpack 如何向硬盤寫入編譯文件。注意,即使可以存在多個(gè)入口起點(diǎn),但只指定一個(gè)輸出配置。
output: { path: helpers.root("dist/nonghe"), publicPath: "/", filename: "js/[name].[chunkhash].bundle.js", chunkFilename: "js/[name].[chunkhash].bundle.js" }3. 加載器(loader)
在webpack的世界里, 一切皆模塊, 通過 loader 的轉(zhuǎn)換,任何形式的資源都可以視作模塊,比如 CommonJs 模塊、 AMD 模塊、 ES6 模塊、CSS、圖片、 JSON、Coffeescript、 LESS 等。而且 webpack 只理解 JavaScript。
對(duì)比 Node.js 模塊,webpack 模塊能夠以各種方式表達(dá)它們的依賴關(guān)系:
ES2015 import 語句
CommonJS require() 語句
AMD define 和 require 語句
css/sass/less 文件中的 @import 語句。
樣式(url(...))或 HTML 文件()中的圖片鏈接
webpack compiler在碰到上面那些語句的時(shí)候, 通過與其相對(duì)應(yīng)的loader將這些文件進(jìn)行轉(zhuǎn)換,而轉(zhuǎn)換后的文件會(huì)被添加到依賴圖表中。
module: { loaders: [{ test: /.scss$/, loaders: "style!css!sass" }, { test: /.(png|jpg|svg)$/, loader: "url?limit=20480" //20k }] }}4. 插件(plugin)
plugin 插件,用于擴(kuò)展webpack的功能,在webpack構(gòu)建生命周期的節(jié)點(diǎn)上加入擴(kuò)展hook為webpack加入功能。
Loaders和Plugins常常被弄混,但是他們其實(shí)是完全不同的東西,可以這么來說,loaders是在打包構(gòu)建過程中用來處理源文件的(js,ts, Scss,Less..),一次處理一個(gè),通常作用于包生成之前或生成的過程中。
插件并不直接操作單個(gè)文件,它直接對(duì)整個(gè)構(gòu)建過程其作用。
幾款常用的插件
HtmlWebpackPlugin : 這個(gè)插件的作用是依據(jù)一個(gè)簡(jiǎn)單的html模板,生成一個(gè)自動(dòng)引用打包后的JS文件的新index.html。
Hot Module Replacement: 它允許你在修改組件代碼后,自動(dòng)刷新實(shí)時(shí)預(yù)覽修改后的效果。
CommonsChunkPlugin: 對(duì)于有多個(gè)入口文件的, 可以抽取公共的模塊,最終合成的文件能夠在最開始的時(shí)候加載一次,便存起來到緩存中供后續(xù)使用。
DefinePlugin: 允許你創(chuàng)建一個(gè)在編譯時(shí)可以配置的全局常量。這可能會(huì)對(duì)開發(fā)模式和發(fā)布模式的構(gòu)建允許不同的行為非常有用。
ExtractTextWebpackPlugin: 它會(huì)將打包在js代碼中的樣式文件抽離出來, 放到一個(gè)多帶帶的 css 包文件 (styles.css)當(dāng)中, 這樣js代碼就可以和css并行加載.
UglifyjsWebpackPlugin: 這個(gè)插件使用 UglifyJS 去壓縮你的JavaScript代碼。
webpack構(gòu)建流程從啟動(dòng)webpack構(gòu)建到輸出結(jié)果經(jīng)歷了一系列過程,它們是:
解析webpack配置參數(shù),合并從shell傳入和webpack.config.js文件里配置的參數(shù),生產(chǎn)最后的配置結(jié)果。
注冊(cè)所有配置的插件,讓插件監(jiān)聽webpack構(gòu)建生命周期的事件節(jié)點(diǎn),以做出對(duì)應(yīng)的反應(yīng)。
從配置的entry入口文件開始解析文件構(gòu)建依賴圖譜,找出每個(gè)文件所依賴的文件,遞歸下去。
在解析文件遞歸的過程中根據(jù)文件類型和loader配置找出合適的loader用來對(duì)文件進(jìn)行轉(zhuǎn)換。
遞歸完后得到每個(gè)文件的最終結(jié)果,根據(jù)entry配置生成代碼塊chunk。
輸出所有chunk到文件系統(tǒng)。
代碼拆分(Code Splitting)代碼拆分是 webpack 中最引人注目的特性之一。你可以把代碼分離到不同的 bundle 中,然后就可以去按需加載這些文件.
分離資源,實(shí)現(xiàn)緩存資源
分離第三方庫(vendor) CommonsChunkPlugin
分離 CSS
傳統(tǒng)的模塊打包工具(module bundlers)最終將所有的模塊編譯生成一個(gè)龐大的bundle.js文件。因此Webpack使用許多特性來分割代碼然后生成多個(gè)“bundle”文件,而且異步加載部分代碼以實(shí)現(xiàn)按需加載
使用 require.ensure() 按需分離代碼
require.ensure(dependencies: String[], callback: function(require), chunkName: String)模塊熱替換(Hot Module Replacement)
模塊熱替換功能會(huì)在應(yīng)用程序運(yùn)行過程中替換、添加或刪除模塊,而無需重新加載頁面。這使得你可以在獨(dú)立模塊變更后,無需刷新整個(gè)頁面,就可以更新這些模塊.
webpack-dev-server 支持熱模式,在試圖重新加載整個(gè)頁面之前,熱模式會(huì)嘗試使用 HMR 來更新。
webpack-dev-server 主要是啟動(dòng)了一個(gè)使用 express 的 Http服務(wù)器 。它的作用 主要是用來伺服資源文件 。此外這個(gè) Http服務(wù)器 和 client 使用了 websocket 通訊協(xié)議,原始文件作出改動(dòng)后, webpack-dev-server 會(huì)實(shí)時(shí)的編譯,但是最后的編譯的文件并沒有輸出到目標(biāo)文件夾, 實(shí)時(shí)編譯后的文件都保存到了內(nèi)存當(dāng)中。
"server": "webpack-dev-server --inline --progress --hot",
webpack-dev-server 支持2種自動(dòng)刷新的方式:
Iframe mode
Iframe mode 是在網(wǎng)頁中嵌入了一個(gè) iframe ,將我們自己的應(yīng)用注入到這個(gè) iframe 當(dāng)中去,因此每次你修改的文件后,都是這個(gè) iframe 進(jìn)行了 reload 。
inline mode
而 Inline-mode ,是 webpack-dev-server 會(huì)在你的 webpack.config.js 的入口配置文件中再添加一個(gè)入口,
module.exports = { entry: { app: [ "webpack-dev-server/client?http://localhost:8080/", "./src/js/index.js" ] }, output: { path: "./dist/js", filename: "bundle.js" } }
這樣就完成了將 inlinedJS 打包進(jìn) bundle.js 里的功能,同時(shí) inlinedJS 里面也包含了 socket.io 的 client 代碼,可以和 webpack-dev-server 進(jìn)行 websocket 通訊。
其他配置選項(xiàng)
--hot 開啟 Hot Module Replacement功能
--quiet 控制臺(tái)中不輸出打包的信息
--compress 開啟gzip壓縮
--progress 顯示打包的進(jìn)度
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90329.html
摘要:簡(jiǎn)介來構(gòu)建用戶界面的庫,不是框架關(guān)注于層虛擬單向數(shù)據(jù)流這些概念如何使用下載文件也可以使用,需要用到的模塊介紹是編寫組件的一種語法規(guī)范,可以看為是的擴(kuò)展,它支持將和混寫在一起,最后使用編譯為常規(guī)的,方便瀏覽器解析編寫第一個(gè)例子使用編寫組件 react簡(jiǎn)介 來構(gòu)建用戶界面的庫,不是框架 關(guān)注于view層 虛擬DOM 單向數(shù)據(jù)流 JSX這些概念 如何使用react 下載文件 rea...
摘要:由于本篇我們只講的基本使用,故這里不再深入講解,有興趣的可以點(diǎn)擊這里學(xué)習(xí)。使用的方式有三種使用方式,如下配置推薦在文件中指定。下一篇會(huì)給大家介紹系列之及簡(jiǎn)單的使用 歡迎大家訪問我的github blog查看更多文章 webpack系列之loader及簡(jiǎn)單的使用 一. loader有什么用 webpack本身只能打包Javascript文件,對(duì)于其他資源例如 css,圖片,或者其他的語...
摘要:插件開發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。插....
摘要:我們也可以不用在命令行里面輸入,因?yàn)槲覀兛赡芤院髸?huì)查詢更多東西,因此我們可以,在的里面加入這里的和只是為了輸出進(jìn)度條和,沒有實(shí)際意義,然后在命令行輸入就可以默認(rèn)使用進(jìn)行打包了。更具體的就請(qǐng)查看一下官方文檔了,畢竟篇幅不能太長(zhǎng)。 Webpack 網(wǎng)上有很多webpack的介紹,也有不少的視頻,但是不少人看到應(yīng)該還是不是很了解webpack里面到底是講什么,而且報(bào)錯(cuò)了之后也是不知所措 那么...
摘要:我們也可以不用在命令行里面輸入,因?yàn)槲覀兛赡芤院髸?huì)查詢更多東西,因此我們可以,在的里面加入這里的和只是為了輸出進(jìn)度條和,沒有實(shí)際意義,然后在命令行輸入就可以默認(rèn)使用進(jìn)行打包了。更具體的就請(qǐng)查看一下官方文檔了,畢竟篇幅不能太長(zhǎng)。 Webpack 網(wǎng)上有很多webpack的介紹,也有不少的視頻,但是不少人看到應(yīng)該還是不是很了解webpack里面到底是講什么,而且報(bào)錯(cuò)了之后也是不知所措 那么...
閱讀 2585·2021-11-22 13:53
閱讀 4106·2021-09-28 09:47
閱讀 883·2021-09-22 15:33
閱讀 825·2020-12-03 17:17
閱讀 3325·2019-08-30 13:13
閱讀 2133·2019-08-29 16:09
閱讀 1186·2019-08-29 12:24
閱讀 2461·2019-08-28 18:14