javascript模組化發展歷程

Escape Plan發表於2019-02-22
  • 什麼是模組化 ?
  • 為什麼要做Javascript模組化?
  • JavaScript 模組化發展歷程

什麼是模組化 ?

模組化是一種處理複雜系統分解成為更好的可管理模組的方式,它可以把系統程式碼劃分為一系列職責單一,高度解耦且可替換的模組,系統中某一部分的變化將如何影響其它部分就會變得顯而易見,系統的可維護性更加簡單易得。

一個模組就是實現特定功能的檔案, 邏輯上相關的程式碼組織到同一個包內,包內是一個相對獨立的王國,不用擔心命名衝突什麼的,那麼外部使用的話直接引入對應的package即可.

就好像作家會把他的書分章節和段落;程式設計師會把他的程式碼分成模組。

就好像書籍的一章,模組僅僅是一坨程式碼而已。

好的程式碼模組分割的內容一定是很合理的,便於你增加減少或者修改功能,同時又不會影響整個系統。

為什麼要做Javascript模組化?

早期前端只是為了實現簡單的頁面互動邏輯,隨著Ajax技術的廣泛應用,前端庫的層出不窮,前端程式碼日益膨脹,JavaScript卻沒有為組織程式碼提供任何明顯幫助,甚至沒有類的概念,更不用說模組(module)了,這時候JavaScript極其簡單的程式碼組織規範不足以駕馭如此龐大規模的程式碼.

模組化可以使你的程式碼低耦合,功能模組直接不相互影響。

  1. 可維護性:根據定義,每個模組都是獨立的。良好設計的模組會盡量與外部的程式碼撇清關係,以便於獨立對其進行改進和維護。維護一個獨立的模組比起一團凌亂的程式碼來說要輕鬆很多。

  2. 名稱空間:在JavaScript中,最高階別的函式外定義的變數都是全域性變數(這意味著所有人都可以訪問到它們)。也正因如此,當一些無關的程式碼碰巧使用到同名變數的時候,我們就會遇到“名稱空間汙染”的問題。

  3. 可複用性:現實來講,在日常工作中我們經常會複製自己之前寫過的程式碼到新專案中, 有了模組, 想複用的時候直接引用進來就行。

JavaScript 模組化發展歷程

前端的先驅在刀耕火種的階段開始,做了很多努力,在現有的執行環境中,實現"模組"的效果。

函式封裝

模組就是實現特定功能的一組方法。在JavaScript中,函式是建立作用域的唯一方式, 所以把函式作為模組化的第一步是很自然的事情.

function foo(){
    //...
}

function bar(){
    //...
}
複製程式碼

上面的,組成一個模組。使用的時候,直接呼叫就行了。

這種做法的缺點很明顯:全域性變數被汙染,很容易命名衝突, 而且模組成員之間看不出直接關係。

物件寫法

為了解決上面的缺點,可以把模組寫成一個物件,所有的模組成員都放到這個物件裡面。

var MYAPP = {
    count: 0,
    foo: function(){},
    bar: function(){}
}

MYAPP.foo();
複製程式碼

上面的程式碼中,函式foobar, 都封裝在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模組化需要解決那些問題:

  1. 如何安全的包裝一個模組的程式碼?(不汙染模組外的任何程式碼)
  2. 如何唯一標識一個模組?
  3. 如何優雅的把模組的API暴漏出去?(不能增加全域性變數)
  4. 如何方便的使用所依賴的模組?

圍繞著這些問題,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定義的模組分為:

  1. 定義模組: 根據CommonJS規範,一個單獨的檔案就是一個模組。每一個模組都是一個單獨的作用域,也就是說,在該模組內部定義的變數,無法被其他模組讀取,除非定義為global物件的屬性。

  2. 模組輸出: 模組只有一個出口,module.exports物件,我們需要把模組希望輸出的內容放入該物件。module物件就代表模組本身。

  3. 載入模組: 載入模組使用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
  });
複製程式碼
  1. 用全域性函式define來定義模組,用法為:define(id?, dependencies?, factory);
  2. id為模組標識,遵從CommonJS Module Identifiers規範
  3. dependencies為依賴的模組陣列,在factory中需傳入形參與之一一對應
  4. 如果dependencies的值中有"require"、"exports""module",則與commonjs中的實現保持一致
  5. 如果dependencies省略不寫,則預設為["require", "exports", "module"]factory中也會預設傳入require,exports,module.
  6. 如果factory為函式,模組對外暴漏API的方法有三種:return任意型別的資料、exports.xxx=xxx、module.exports=xxx.
  7. 如果factory為物件,則該物件即為模組的返回值

大名鼎鼎的require.js就是AMD規範的實現.

require.js要求,每個模組是一個單獨的js檔案。這樣的話,如果載入多個模組,就會發出多次HTTP請求,會影響網頁的載入速度。因此,require.js提供了一個優化工具(Optimizerr.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來暴露該模組的對外介面
})
複製程式碼
  1. 也是用全域性的define函式定義模組, 無需羅列依賴陣列,在factory函式中需傳入形參require,exports,module.
  2. 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中一個庫有很多使用方式,這就需要你書寫宣告檔案去匹配它們.

通過庫的使用方法及其原始碼來識別庫的型別。

  1. 全域性庫

全域性庫是指能在全域性名稱空間下訪問的,許多庫都是簡單的暴露出一個或多個全域性變數。 比如jQuery.

當你檢視全域性庫的原始碼時,你通常會看到:

  • 頂級的var語句或function宣告
  • 一個或多個賦值語句到window.someName
  1. 模組化庫

一些庫只能工作在模組載入器的環境下。 比如,像 express只能在Node.js 裡工作所以必須使用CommonJSrequire函式載入。

模組庫至少會包含下列具有代表性的條目之一:

  • 無條件的呼叫requiredefine
  • import * as a from 'b'; or export c;這樣的宣告
  • 賦值給exportsmodule.exports
  1. 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不是一回事。

  1. Gulp / Grunt Gulp / Grunt 是一種工具,能夠優化前端工作流程。比如自動重新整理頁面、combo、壓縮css、js、編譯less等等。簡單來說,就是使用Gulp/Grunt,然後配置你需要的外掛,就可以把以前需要手工做的事情讓它幫你做了。

  2. 說到 browserify / webpack ,那還要說到 seajs / requirejs 。這四個都是JS模組化的方案。其中seajs / require 是一種型別,browserify / webpack 是另一種型別。seajs / require : 是一種線上"編譯" 模組的方案,相當於在頁面上載入一個 CMD/AMD 直譯器。這樣瀏覽器就認識了 define、exports、module這些東西。也就實現了模組化。

  3. browserify / webpack : 是一個預編譯模組的方案,相比於上面 ,這個方案更加智慧, 首先,它是預編譯的,不需要在瀏覽器中載入直譯器。另外,你在本地直接寫JS,不管是 AMD / CMD / ES6風格的模組化,它都能認識,並且編譯成瀏覽器認識的JS。這樣就知道,Gulp是一個工具,而webpack等等是模組化方案。Gulp也可以配置seajs、requirejs甚至webpack的外掛。

Grunt

每次執行grunt 時,他就利用node提供的require()系統查詢本地安裝的 Grunt

如果找到一份本地安裝的 Gruntgrunt-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(模組打包器),他的目的就是把有依賴關係的各種檔案打包成一系列的靜態資源。 請看下圖

javascript模組化發展歷程

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構建生命週期的節點上加入擴充套件hookwebpack加入功能。

LoadersPlugins常常被弄混,但是他們其實是完全不同的東西,可以這麼來說,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構建到輸出結果經歷了一系列過程,它們是:

  1. 解析webpack配置引數,合併從shell傳入和webpack.config.js檔案裡配置的引數,生產最後的配置結果。
  2. 註冊所有配置的外掛,讓外掛監聽webpack構建生命週期的事件節點,以做出對應的反應。
  3. 從配置的entry入口檔案開始解析檔案構建依賴圖譜,找出每個檔案所依賴的檔案,遞迴下去。
  4. 在解析檔案遞迴的過程中根據檔案型別和loader配置找出合適的loader用來對檔案進行轉換。
  5. 遞迴完後得到每個檔案的最終結果,根據entry配置生成程式碼塊chunk
  6. 輸出所有chunk到檔案系統。

程式碼拆分(Code Splitting)

程式碼拆分是 webpack 中最引人注目的特性之一。你可以把程式碼分離到不同的 bundle 中,然後就可以去按需載入這些檔案.

  1. 分離資源,實現快取資源
  • 分離第三方庫(vendor) CommonsChunkPlugin
  • 分離 CSS
  1. 傳統的模組打包工具(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 顯示打包的進度




















複製程式碼

相關文章