javascript模塊化發展歷程

  • 什麼是模塊化 ?
  • 爲何要作Javascript模塊化?
  • JavaScript 模塊化發展歷程

什麼是模塊化 ?

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

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

就好像做家會把他的書分章節和段落;程序員會把他的代碼分紅模塊。html

就好像書籍的一章,模塊僅僅是一坨代碼而已。前端

好的代碼模塊分割的內容必定是很合理的,便於你增長減小或者修改功能,同時又不會影響整個系統。java

爲何要作Javascript模塊化?

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

模塊化可使你的代碼低耦合,功能模塊直接不相互影響。jquery

  1. 可維護性:根據定義,每一個模塊都是獨立的。良好設計的模塊會盡可能與外部的代碼撇清關係,以便於獨立對其進行改進和維護。維護一個獨立的模塊比起一團凌亂的代碼來講要輕鬆不少。webpack

  2. 命名空間:在JavaScript中,最高級別的函數外定義的變量都是全局變量(這意味着全部人均可以訪問到它們)。也正因如此,當一些無關的代碼碰巧使用到同名變量的時候,咱們就會遇到「命名空間污染」的問題。git

  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(模塊打包器),他的目的就是把有依賴關係的各類文件打包成一系列的靜態資源。 請看下圖

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.ioclient 代碼,能夠和 webpack-dev-server 進行 websocket 通信。

其餘配置選項

  • --hot 開啓 Hot Module Replacement功能
  • --quiet 控制檯中不輸出打包的信息
  • --compress 開啓gzip壓縮
  • --progress 顯示打包的進度
相關文章
相關標籤/搜索