- 什麼是模組化 ?
- 為什麼要做
Javascript
模組化? - JavaScript 模組化發展歷程
什麼是模組化 ?
模組化是一種處理複雜系統分解成為更好的可管理模組的方式,它可以把系統程式碼劃分為一系列職責單一,高度解耦且可替換的模組,系統中某一部分的變化將如何影響其它部分就會變得顯而易見,系統的可維護性更加簡單易得。
一個模組就是實現特定功能的檔案, 邏輯上相關的程式碼組織到同一個包內,包內是一個相對獨立的王國,不用擔心命名衝突什麼的,那麼外部使用的話直接引入對應的package
即可.
就好像作家會把他的書分章節和段落;程式設計師會把他的程式碼分成模組。
就好像書籍的一章,模組僅僅是一坨程式碼而已。
好的程式碼模組分割的內容一定是很合理的,便於你增加減少或者修改功能,同時又不會影響整個系統。
為什麼要做Javascript
模組化?
早期前端只是為了實現簡單的頁面互動邏輯,隨著Ajax
技術的廣泛應用,前端庫的層出不窮,前端程式碼日益膨脹,JavaScript
卻沒有為組織程式碼提供任何明顯幫助,甚至沒有類的概念,更不用說模組(module
)了,這時候JavaScript
極其簡單的程式碼組織規範不足以駕馭如此龐大規模的程式碼.
模組化可以使你的程式碼低耦合,功能模組直接不相互影響。
-
可維護性:根據定義,每個模組都是獨立的。良好設計的模組會盡量與外部的程式碼撇清關係,以便於獨立對其進行改進和維護。維護一個獨立的模組比起一團凌亂的程式碼來說要輕鬆很多。
-
名稱空間:在JavaScript中,最高階別的函式外定義的變數都是全域性變數(這意味著所有人都可以訪問到它們)。也正因如此,當一些無關的程式碼碰巧使用到同名變數的時候,我們就會遇到“名稱空間汙染”的問題。
-
可複用性:現實來講,在日常工作中我們經常會複製自己之前寫過的程式碼到新專案中, 有了模組, 想複用的時候直接引用進來就行。
JavaScript 模組化發展歷程
前端的先驅在刀耕火種的階段開始,做了很多努力,在現有的執行環境中,實現"模組"的效果。
函式封裝
模組就是實現特定功能的一組方法。在JavaScript中,函式是建立作用域的唯一方式, 所以把函式作為模組化的第一步是很自然的事情.
function foo(){
//...
}
function bar(){
//...
}
複製程式碼
上面的,組成一個模組。使用的時候,直接呼叫就行了。
這種做法的缺點很明顯:全域性變數被汙染,很容易命名衝突, 而且模組成員之間看不出直接關係。
物件寫法
為了解決上面的缺點,可以把模組寫成一個物件,所有的模組成員都放到這個物件裡面。
var MYAPP = {
count: 0,
foo: function(){},
bar: function(){}
}
MYAPP.foo();
複製程式碼
上面的程式碼中,函式foo
和bar
, 都封裝在MYAPP物件裡。使用的時候,就是呼叫這個物件的屬性。 但是,這樣的寫法會暴露所有模組成員,內部狀態可以被外部改寫.
立即執行函式(IIFE)寫法
使用立即執行函式(Immediately-Invoked Function Expression,IIFE),可以達到不暴露私有成員的目的。
var Module = (function(){
var _private = "safe now";
var foo = function(){
console.log(_private)
}
return {
foo: foo
}
})()
Module.foo();
Module._private; // undefined
複製程式碼
這種方法的好處在於,你可以在函式內部使用區域性變數,而不會意外覆蓋同名全域性變數,但仍然能夠訪問到全域性變數, 在模組外部無法修改我們沒有暴露出來的變數、函式.
引入依賴
將全域性變數當成一個引數傳入到匿名函式然後使用
var Module = (function($){
var _$body = $("body"); // we can use jQuery now!
var foo = function(){
console.log(_$body); // 特權方法
}
// Revelation Pattern
return {
foo: foo
}
})(jQuery)
Module.foo();
複製程式碼
jQuery
的封裝風格曾經被很多框架模仿,通過匿名函式包裝程式碼,所依賴的外部變數傳給這個函式,在函式內部可以使用這些依賴,然後在函式的最後把模組自身暴漏給window
。
如果需要新增擴充套件,則可以作為jQuery
的外掛,把它掛載到$上。
這種風格雖然靈活了些,但並未解決根本問題:所需依賴還是得外部提前提供、還是增加了全域性變數。
模組化面臨什麼問題
從以上的嘗試中,可以歸納出js模組化需要解決那些問題:
- 如何安全的包裝一個模組的程式碼?(不汙染模組外的任何程式碼)
- 如何唯一標識一個模組?
- 如何優雅的把模組的API暴漏出去?(不能增加全域性變數)
- 如何方便的使用所依賴的模組?
圍繞著這些問題,js模組化開始了一段艱苦而曲折的征途。
JavaScript 模組規範
上述的所有解決方案都有一個共同點:使用單個全域性變數來把所有的程式碼包含在一個函式內,由此來建立私有的名稱空間和閉包作用域。
你必須清楚地瞭解引入依賴檔案的正確順序。就拿Backbone.js
來舉個例子,想要使用Backbone
就必須在你的頁面裡引入Backbone
的原始檔。
然而Backbone
又依賴 Underscore.js
,所以Backbone
的引入必須在其之後。
而在工作中,這些依賴管理經常會成為讓人頭疼的問題。
另外一點,這些方法也有可能引起名稱空間衝突。舉個例子,要是你碰巧寫了倆重名的模組怎麼辦?或者你同時需要一個模組的兩個版本時該怎麼辦?
還有就是協同開發的時候, 大家編寫模組的方式各不相同,你有你的寫法,我有我的寫法, 那就亂了套.
接下來介紹幾種廣受歡迎的解決方案
- CommonJS
- AMD
- CMD
- ES6模組
CommonJS
2009年,美國程式設計師Ryan Dahl
創造了node.js
專案,將javascript
語言用於伺服器端程式設計。
這標誌Javascript
模組化程式設計正式誕生。因為老實說,在瀏覽器環境下,沒有模組也不是特別大的問題,畢竟網頁程式的複雜性有限;但是在伺服器端,一定要有模組,與作業系統和其他應用程式互動,否則根本沒法程式設計。
node.js
的模組系統,就是參照CommonJS
規範實現的。
CommonJS
定義的模組分為:
-
定義模組: 根據
CommonJS
規範,一個單獨的檔案就是一個模組。每一個模組都是一個單獨的作用域,也就是說,在該模組內部定義的變數,無法被其他模組讀取,除非定義為global
物件的屬性。 -
模組輸出: 模組只有一個出口,
module.exports
物件,我們需要把模組希望輸出的內容放入該物件。module
物件就代表模組本身。 -
載入模組: 載入模組使用
require
方法,該方法讀取一個檔案並執行,返回檔案內部的module.exports
物件。
// 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
複製程式碼
這種實現模式有兩點好處:
- 避免全域性名稱空間汙染
- 明確程式碼之間的依賴關係
但是, 由於一個重大的侷限,使得CommonJS
規範不適用於瀏覽器環境。
看上面的main.js
程式碼, 第二行的math.add(1, 2)
,在第一行require('math')之後執行,因此必須等math.js
載入完成。也就是說,如果載入的依賴很多, 時間很長,整個應用就會停在那裡等。
我們分析一下瀏覽器端的js和伺服器端js都主要做了哪些事,有什麼不同:
伺服器端JS | 瀏覽器端JS |
---|---|
相同的程式碼需要多次執行 | 程式碼需要從一個伺服器端分發到多個客戶端執行 |
CPU和記憶體資源是瓶頸 | 頻寬是瓶頸 |
載入時從磁碟中載入 | 載入時需要通過網路載入 |
這對伺服器端不是一個問題,因為所有的模組都存放在本地硬碟,可以同步載入完成,等待時間就是硬碟的讀取時間。但是,對於瀏覽器,這卻是一個大問題,因為模組都放在伺服器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。
因此,瀏覽器端的模組,不能採用"同步載入"(synchronous
),只能採用"非同步載入"(asynchronous
)。這就是AMD
規範誕生的背景。
AMD
AMD 即
Asynchronous Module Definition
,中文名是非同步模組定義的意思。它採用非同步方式載入模組,模組的載入不影響它後面語句的執行。所有依賴這個模組的語句,都定義在一個回撥函式中,等到載入完成之後,這個回撥函式才會執行。
// main.js
  require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // some code here
  });
複製程式碼
- 用全域性函式
define
來定義模組,用法為:define(id?, dependencies?, factory)
; id
為模組標識,遵從CommonJS Module Identifiers
規範dependencies
為依賴的模組陣列,在factory
中需傳入形參與之一一對應- 如果
dependencies
的值中有"require"、"exports"
或"module"
,則與commonjs
中的實現保持一致 - 如果
dependencies
省略不寫,則預設為["require", "exports", "module"]
,factory
中也會預設傳入require,exports,module
. - 如果
factory
為函式,模組對外暴漏API
的方法有三種:return
任意型別的資料、exports.xxx=xxx、module.exports=xxx
. - 如果
factory
為物件,則該物件即為模組的返回值
大名鼎鼎的require.js
就是AMD規範的實現.
require.js
要求,每個模組是一個單獨的js
檔案。這樣的話,如果載入多個模組,就會發出多次HTTP
請求,會影響網頁的載入速度。因此,require.js
提供了一個優化工具(Optimizer
)r.js
,當模組部署完畢以後,可以用這個工具將多個模組合併在一個檔案中,實現前端檔案的壓縮與合併, 減少HTTP請求數。
我們來看一個require.js
的例子
//a.js
define(function(){
console.log('a.js執行');
return {
hello: function(){
console.log('hello, a.js');
}
}
});
複製程式碼
//b.js
define(function(){
console.log('b.js執行');
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執行');
a.hello();
$('#btn').click(function(){
b.hello();
});
})
複製程式碼
上面的main.js被執行的時候,會有如下的輸出: a.js執行 b.js執行 main.js執行 hello, a.js
在點選按鈕後,會輸出: hello, b.js
但是如果細細來看,b.js
被預先載入並且預先執行了,(第二行輸出),b.hello
這個方法是在點選了按鈕之後才會執行,如果使用者壓根就沒點,那麼b.js
中的程式碼應不應該執行呢?
這其實也是AMD/RequireJs
被吐槽的一點,由於瀏覽器的環境特點,被依賴的模組肯定要預先下載的。問題在於,是否需要預先執行?如果一個模組依賴了十個其他模組,那麼在本模組的程式碼執行之前,要先把其他十個模組的程式碼都執行一遍,不管這些模組是不是馬上會被用到。這個效能消耗是不容忽視的。
另一點被吐槽的是,在定義模組的時候,要把所有依賴模組都羅列一遍,而且還要在factory
中作為形參傳進去,要寫兩遍很大一串模組名稱,像這樣:
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
時提出的一種規範.
在 CMD
規範中,一個模組就是一個檔案。程式碼的書寫格式如下:
define(function(require, exports, module) {
// 模組程式碼
// 使用require獲取依賴模組的介面
// 使用exports或者module或者return來暴露該模組的對外介面
})
複製程式碼
- 也是用全域性的
define
函式定義模組, 無需羅列依賴陣列,在factory
函式中需傳入形參require,exports,module
. require
用來載入一個js
檔案模組,require
用來獲取指定模組的介面物件module.exports
。
//a.js
define(function(require, exports, module){
console.log('a.js執行');
return {
hello: function(){
console.log('hello, a.js');
}
}
});
複製程式碼
//b.js
define(function(require, exports, module){
console.log('b.js執行');
return {
hello: function(){
console.log('hello, b.js');
}
}
});
複製程式碼
//main.js
define(function(require, exports, module){
console.log('main.js執行');
var a = require('a');
a.hello();
$('#b').click(function(){
var b = require('b');
b.hello();
});
});
複製程式碼
上面的main.js執行會輸出如下: main.js執行 a.js執行 hello, a.js
a.js和b.js都會預先下載,但是b.js中的程式碼卻沒有執行,因為還沒有點選按鈕。當點選按鈕的時候,會輸出如下: b.js執行 hello, b.js
Sea.js
載入依賴的方式
- 載入期:即在執行一個模組之前,將其直接或間接依賴的模組從伺服器端同步到瀏覽器端;
- 執行期:在確認該模組直接或間接依賴的模組都載入完畢之後,執行該模組。
AMD vs CMD
- AMD推崇依賴前置,在定義模組的時候就要宣告其依賴的模組,
- CMD推崇就近依賴,只有在用到某個模組的時候再去require,
- AMD和CMD最大的區別是對依賴模組的執行時機處理不同
同樣都是非同步載入模組,AMD在載入模組完成後就會執行改模組,所有模組都載入執行完後會進入require的回撥函式,執行主邏輯.
CMD載入完某個依賴模組後並不執行,只是下載而已,在所有依賴模組載入完成後進入主邏輯,遇到require
語句的時候才執行對應的模組,這樣模組的執行順序和書寫順序是完全一致的。
這也是很多人說AMD使用者體驗好,因為沒有延遲,依賴模組提前執行了,CMD效能好,因為只有使用者需要的時候才執行的原因。
ES6模組
上述的這幾種方法都不是JS原生支援的, 在
ECMAScript 6 (ES6)
中,引入了模組功能, ES6 的模組功能汲取了CommonJS 和 AMD 的優點,擁有簡潔的語法並支援非同步載入,並且還有其他諸多更好的支援。
簡單來說,ES6 模組的設計思想就是:一個 JS 檔案就代表一個 JS 模組。在模組中你可以使用 import 和 export 關鍵字來匯入或匯出模組中的東西。
ES6 模組主要具備以下幾個基本特點:
- 自動開啟嚴格模式,即使你沒有寫 use strict
- 每個模組都有自己的上下文,每一個模組內宣告的變數都是區域性變數,不會汙染全域性作用域
- 模組中可以匯入和匯出各種型別的變數,如函式,物件,字串,數字,布林值,類等
- 每一個模組只載入一次,每一個 JS 只執行一次, 如果下次再去載入同目錄下同檔案,直接從記憶體中讀取。
補充: Typescript 識別模組的模式
一般來講,組織宣告檔案的方式取決於庫是如何被使用的。 在JavaScript中一個庫有很多使用方式,這就需要你書寫宣告檔案去匹配它們.
通過庫的使用方法及其原始碼來識別庫的型別。
- 全域性庫
全域性庫是指能在全域性名稱空間下訪問的,許多庫都是簡單的暴露出一個或多個全域性變數。 比如jQuery.
當你檢視全域性庫的原始碼時,你通常會看到:
- 頂級的var語句或function宣告
- 一個或多個賦值語句到window.someName
- 模組化庫
一些庫只能工作在模組載入器的環境下。 比如,像 express
只能在Node.js
裡工作所以必須使用CommonJS
的require
函式載入。
模組庫至少會包含下列具有代表性的條目之一:
- 無條件的呼叫
require
或define
- 像
import * as a from 'b'; or export c
;這樣的宣告 - 賦值給
exports
或module.exports
-
UMD
(Universal Module Definition)
庫UMD創造了一種同時使用兩種規範的方法,並且也支援全域性變數定義。所以UMD的模組可以同時在客戶端和服務端使用。
本質上,UMD 是一套用來識別當前環境支援的模組風格的 if/else 語句。下面是一個解釋其功能的例子:
複製程式碼
(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) {}) ```
前端自動化構建工具
- Grunt
- Gulp
- Webpack
- Browserify
- ......
簡單的說,Grunt / Gulp 和 browserify / webpack
不是一回事。
-
Gulp / Grunt
Gulp / Grunt 是一種工具,能夠優化前端工作流程。比如自動重新整理頁面、combo、壓縮css、js、編譯less等等。簡單來說,就是使用Gulp/Grunt,然後配置你需要的外掛,就可以把以前需要手工做的事情讓它幫你做了。 -
說到
browserify / webpack
,那還要說到seajs / requirejs
。這四個都是JS模組化的方案。其中seajs / require
是一種型別,browserify / webpack
是另一種型別。seajs / require
: 是一種線上"編譯" 模組的方案,相當於在頁面上載入一個CMD/AMD
直譯器。這樣瀏覽器就認識了define、exports、module
這些東西。也就實現了模組化。 -
browserify / webpack
: 是一個預編譯模組的方案,相比於上面 ,這個方案更加智慧, 首先,它是預編譯的,不需要在瀏覽器中載入直譯器。另外,你在本地直接寫JS,不管是 AMD / CMD / ES6
風格的模組化,它都能認識,並且編譯成瀏覽器認識的JS。這樣就知道,Gulp
是一個工具,而webpack
等等是模組化方案。Gulp
也可以配置seajs、requirejs
甚至webpack
的外掛。
Grunt
每次執行grunt
時,他就利用node
提供的require()
系統查詢本地安裝的 Grunt
。
如果找到一份本地安裝的 Grunt
,grunt-CLI
就將其載入,並傳遞Gruntfile
中的配置資訊,然後執行你所指定的任務。
- 安裝grunt-cli
npm install -g grunt-cli
複製程式碼
- 配置
gruntfile.js
檔案
module.exports = function(grunt) {
  // 專案配置.
  grunt.initConfig({
    // 定義Grunt任務
 });
 // 載入能夠提供"uglify"任務的外掛。
  grunt.loadNpmTasks('grunt外掛');
  // Default task(s).
  grunt.registerTask('default', ['任務名']);
}
複製程式碼
gulp
gulp
是基於Nodejs的自動化任務執行器,它能自動化地完成javascript/sass/less/html/image/css
等檔案的的測試、檢查、合併、壓縮、格式化、瀏覽器自動重新整理、部署檔案生成,並監聽檔案在改動後重復指定的這些步驟。
使用Gulp
的優勢就是利用流的方式進行檔案的處理,使用管道(pipe
)思想,前一級的輸出,直接變成後一級的輸入,通過管道將多個任務和操作連線起來,因此只有一次I/O
的過程,流程更清晰,更純粹。Gulp
去除了中間檔案,只將最後的輸出寫入磁碟,整個過程因此變得更快。
使用Gulp
,可以避免瀏覽器快取機制,效能優化(檔案合併,減少http請求;檔案壓縮)以及效率提升(自動新增CSS3字首;程式碼分析檢查)
browserify
Browserify
是一個模組打包器,它遍歷程式碼的依賴樹,將依賴樹中的所有模組打包成一個檔案。有了 Browserify
,我們就可以在瀏覽器應用程式中使用 CommonJS
模組。
browserify模組化的用法和node是一樣的,所以npm上那些原本僅僅用於node環境的包,在瀏覽器環境裡也一樣能用.
webpack官網有對二者的使用方法進行對比,可以看一下:webpack for browserify users
browserify main.js -o bundle.js
複製程式碼
Compare Webpack vs Browserify vs RequireJS
webpack
官網對webpack
的定義是MODULE BUNDLER
(模組打包器),他的目的就是把有依賴關係的各種檔案打包成一系列的靜態資源。 請看下圖
Webpack
的工作方式是:把你的專案當做一個整體,通過一個給定的主檔案(如:main.js
),Webpack
將從這個檔案開始找到你的專案的所有依賴檔案,使用loaders
處理它們,最後打包為一個(或多個)瀏覽器可識別的JavaScript
檔案。
webpack核心概念
1. 入口(entry):
webpack
將建立所有應用程式的依賴關係圖表(dependency graph
)。
entry配置項告訴Webpack應用的根模組或起始點在哪裡,
入口起點告訴 webpack
從哪裡開始,並遵循著依賴關係圖表知道要打包什麼。可以將應用程式的入口起點認為是根上下文或 app
第一個啟動檔案。它的值可以是字串、陣列或物件.
//webpack.config.js
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};
複製程式碼
2. 出口(output)
將所有的資源(assets
)合併在一起後,我們還需要告訴 webpack
在哪裡打包我們的應用程式。output
選項控制 webpack
如何向硬碟寫入編譯檔案。注意,即使可以存在多個入口起點,但只指定一個輸出配置。
output: {
path: helpers.root('dist/nonghe'),
publicPath: '/',
filename: 'js/[name].[chunkhash].bundle.js',
chunkFilename: 'js/[name].[chunkhash].bundle.js'
}
複製程式碼
3. 載入器(loader)
在webpack的世界裡, 一切皆模組, 通過
loader
的轉換,任何形式的資源都可以視作模組,比如CommonJs 模組、 AMD 模組、 ES6 模組、CSS、圖片、 JSON、Coffeescript、 LESS
等。而且 webpack 只理解 JavaScript。
對比 Node.js 模組,webpack 模組能夠以各種方式表達它們的依賴關係:
- ES2015 import 語句
- CommonJS require() 語句
- AMD define 和 require 語句
- css/sass/less 檔案中的 @import 語句。
- 樣式(
url(...
))或 HTML 檔案(<img src=...>
)中的圖片連結
webpack compiler
在碰到上面那些語句的時候, 通過與其相對應的loader
將這些檔案進行轉換,而轉換後的檔案會被新增到依賴圖表中。
module: {
loaders: [{
test: /\.scss$/,
loaders: 'style!css!sass'
}, {
test: /\.(png|jpg|svg)$/,
loader: 'url?limit=20480' //20k
}]
}}
複製程式碼
4. 外掛(plugin)
plugin
外掛,用於擴充套件webpack
的功能,在webpack
構建生命週期的節點上加入擴充套件hook
為webpack
加入功能。
Loaders
和Plugins
常常被弄混,但是他們其實是完全不同的東西,可以這麼來說,loaders
是在打包構建過程中用來處理原始檔的(js,ts, Scss,Less..),一次處理一個,通常作用於包生成之前或生成的過程中。
外掛並不直接操作單個檔案,它直接對整個構建過程其作用。
幾款常用的外掛
-
HtmlWebpackPlugin : 這個外掛的作用是依據一個簡單的
html
模板,生成一個自動引用打包後的JS檔案的新index.html
。 -
Hot Module Replacement: 它允許你在修改元件程式碼後,自動重新整理實時預覽修改後的效果。
-
CommonsChunkPlugin: 對於有多個入口檔案的, 可以抽取公共的模組,最終合成的檔案能夠在最開始的時候載入一次,便存起來到快取中供後續使用。
-
DefinePlugin: 允許你建立一個在編譯時可以配置的全域性常量。這可能會對開發模式和釋出模式的構建允許不同的行為非常有用。
-
ExtractTextWebpackPlugin: 它會將打包在
js
程式碼中的樣式檔案抽離出來, 放到一個單獨的css
包檔案 (styles.css)當中, 這樣js
程式碼就可以和css
並行載入. -
UglifyjsWebpackPlugin: 這個外掛使用 UglifyJS 去壓縮你的JavaScript程式碼。
webpack構建流程
從啟動webpack構建到輸出結果經歷了一系列過程,它們是:
- 解析
webpack
配置引數,合併從shell
傳入和webpack.config.js
檔案裡配置的引數,生產最後的配置結果。 - 註冊所有配置的外掛,讓外掛監聽webpack構建生命週期的事件節點,以做出對應的反應。
- 從配置的
entry
入口檔案開始解析檔案構建依賴圖譜,找出每個檔案所依賴的檔案,遞迴下去。 - 在解析檔案遞迴的過程中根據檔案型別和
loader
配置找出合適的loader
用來對檔案進行轉換。 - 遞迴完後得到每個檔案的最終結果,根據
entry
配置生成程式碼塊chunk
。 - 輸出所有
chunk
到檔案系統。
程式碼拆分(Code Splitting)
程式碼拆分是 webpack
中最引人注目的特性之一。你可以把程式碼分離到不同的 bundle
中,然後就可以去按需載入這些檔案.
- 分離資源,實現快取資源
- 分離第三方庫(vendor)
CommonsChunkPlugin
- 分離 CSS
- 傳統的模組打包工具(
module bundlers
)最終將所有的模組編譯生成一個龐大的bundle.js
檔案。因此Webpack使用許多特性來分割程式碼然後生成多個“bundle”檔案,而且非同步載入部分程式碼以實現按需載入
-
使用
require.ensure()
按需分離程式碼require.ensure(dependencies: String[], callback: function(require), chunkName: String) 複製程式碼
模組熱替換(Hot Module Replacement)
模組熱替換功能會在應用程式執行過程中替換、新增或刪除模組,而無需重新載入頁面。這使得你可以在獨立模組變更後,無需重新整理整個頁面,就可以更新這些模組.
webpack-dev-server
支援熱模式,在試圖重新載入整個頁面之前,熱模式會嘗試使用 HMR 來更新。
webpack-dev-server 主要是啟動了一個使用 express 的 Http伺服器 。它的作用 主要是用來伺服資原始檔 。此外這個 Http伺服器 和 client 使用了 websocket 通訊協議,原始檔案作出改動後, webpack-dev-server 會實時的編譯,但是最後的編譯的檔案並沒有輸出到目標資料夾, 實時編譯後的檔案都儲存到了記憶體當中。
"server": "webpack-dev-server --inline --progress --hot",
複製程式碼
webpack-dev-server 支援2種自動重新整理的方式:
-
Iframe mode
-
Iframe mode 是在網頁中嵌入了一個 iframe ,將我們自己的應用注入到這個 iframe 當中去,因此每次你修改的檔案後,都是這個 iframe 進行了 reload 。
-
inline mode
-
而 Inline-mode ,是 webpack-dev-server 會在你的 webpack.config.js 的入口配置檔案中再新增一個入口,
複製程式碼
module.exports = { entry: { app: [ 'webpack-dev-server/client?http://localhost:8080/', './src/js/index.js' ] }, output: { path: './dist/js', filename: 'bundle.js' } }
這樣就完成了將 `inlinedJS `打包進 `bundle.js` 裡的功能,同時 `inlinedJS `裡面也包含了 `socket.io` 的 `client` 程式碼,可以和 `webpack-dev-server` 進行 `websocket` 通訊。
其他配置選項
- --hot 開啟 `Hot Module Replacement`功能
- --quiet 控制檯中不輸出打包的資訊
- --compress 開啟gzip壓縮
- --progress 顯示打包的進度
複製程式碼