Webpack 概念

Roninwz發表於2017-09-21

概念

webpack 是一個現代的 JavaScript 應用程式的模組打包器(module bundler)。當 webpack 處理應用程式時,它會遞迴地構建一個依賴關係圖表(dependency graph),其中包含應用程式需要的每個模組,然後將所有這些模組打包成少量的 bundle - 通常只有一個,由瀏覽器載入。

學習 webpack,需要先了解幾個核心概念,下面會一一道來。

模組化(module)

模組化程式設計中,開發者將程式分解相對獨立的程式碼塊,並稱之為模組

每個模組具有比完整程式更小的接觸面,使得校驗、除錯、測試輕而易舉。 精心編寫的模組提供了可靠的抽象和封裝界限,使得應用程式中每個模組都具有條理清楚的設計和明確的目的。

Node.js 從最一開始就支援模組化程式設計。然而,在 web,模組化的支援正緩慢到來。在 web 存在多種支援 JavaScript 模組化的工具,這些工具各有優勢和限制。webpack 基於從這些系統獲得的經驗教訓,並將模組的概念應用於專案中的任何檔案。

什麼是 webpack 模組

對比 Node.js 模組,webpack 模組能夠以各種方式表達它們的依賴關係,幾個例子如下:

 webpack 1 需要特定的 loader 來轉換 ES 2015 import,然而 webpack 2 天然支援。

支援的模組型別

webpack 通過 loader 可以支援各種語言和前處理器編寫模組。loader 描述了 webpack 如何處理 非 JavaScript(non-JavaScript) 模組,並且在bundle中引入這些依賴。 webpack 社群已經為各種流行語言和語言處理器構建了 loader,包括:

總的來說,webpack 提供了可定製的、強大和豐富的 API,允許任何技術棧使用 webpack,保持了在你的開發、測試和生成流程中無侵入性(non-opinionated)

配置檔案 - webpack.config.js

webpack 是高度可配置的,如何模組化打包、載入都可以基於配置檔案定製。

webpack 的預設配置檔案是 webpack.config.js

因為 webpack 配置是標準的 Node.js CommonJS 模組,你可以使用如下特性

  • 通過 require(...) 匯入其他檔案
  • 通過 require(...) 使用 npm 的工具函式
  • 使用 JavaScript 控制流表示式,例如 ?: 操作符
  • 對常用值使用常量或變數
  • 編寫並執行函式來生成部分配置

依賴圖表(Dependency Graph)

任何時候,一個檔案依賴於另一個檔案,webpack 就把此視為檔案之間有依賴關係。這使得 webpack 可以接收非程式碼資源(non-code asset)(例如影象或 web 字型),並且可以把它們作為依賴提供給你的應用程式。

webpack 從命令列或配置檔案中定義的一個模組列表開始,處理你的應用程式。 從這些入口起點開始,webpack 遞迴地構建一個依賴圖表,這個依賴圖表包含著應用程式所需的每個模組,然後將所有這些模組打包為少量的 bundle- 通常只有一個 - 可由瀏覽器載入。

 對於 HTTP/1.1 客戶端,由 webpack 打包你的應用程式會尤其強大,因為在瀏覽器發起一個新請求時,它能夠減少應用程式必須等待的時間。對於 HTTP/2,你還可以使用程式碼拆分(Code Splitting)以及通過 webpack 打包來實現最佳優化

入口(entry)

webpack 將建立所有應用程式的依賴關係圖表(dependency graph)。圖表的起點被稱之為入口起點(entry point)入口起點告訴 webpack 從哪裡開始,並遵循著依賴關係圖表知道要打包什麼。可以將您應用程式的入口起點認為是根上下文(contextual root)或 app 第一個啟動檔案

在 webpack 中,我們使用 webpack 配置物件(webpack configuration object) 中的 entry 屬性來定義入口

例:

module.exports = {
  entry: './path/to/my/entry/file.js'
};

輸出(output)

將所有的資源(assets)歸攏在一起後,還需要告訴 webpack 在哪裡打包應用程式。webpack 的 output 屬性描述了如何處理歸攏在一起的程式碼(bundled code)。

例:

const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  }
};

載入(loader)

webpack 的目標是,讓 webpack 聚焦於專案中的所有資源(asset),而瀏覽器不需要關注考慮這些(這並不意味著資源(asset)都必須打包在一起)。webpack 把每個檔案(.css, .html, .scss, .jpg, etc.) 都作為模組處理。然而 webpack 只理解 JavaScript

webpack loader 會將這些檔案轉換為模組,而轉換後的檔案會被新增到依賴圖表中。

在更高層面,webpack 的配置有兩個目標。

  1. 識別出(identify)應該被對應的 loader 進行轉換(transform)的那些檔案

  2. 由於進行過檔案轉換,所以能夠將被轉換的檔案新增到依賴圖表(並且最終新增到 bundle 中)(use 屬性)

例:

const path = require('path');

const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      {test: /\.(js|jsx)$/, use: 'babel-loader'}
    ]
  }
};

module.exports = config;

外掛(plugins)

由於 loader 僅在每個檔案的基礎上執行轉換,而 外掛(plugins) 最常用於(但不限於)在打包模組的“compilation”和“chunk”生命週期執行操作和自定義功能(檢視更多)。webpack 的外掛系統極其強大和可定製化

想要使用一個外掛,你只需要 require() 它,然後把它新增到 plugins 陣列中。多數外掛可以通過選項(option)自定義。你也可以在一個配置檔案中因為不同目的而多次使用同一個外掛,你需要使用 new 建立例項來呼叫它。

例:

const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
const path = require('path');

const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      {test: /\.(js|jsx)$/, use: 'babel-loader'}
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

module.exports = config;

熱替換(Hot Module Replacement)

模組熱替換功能會在應用程式執行過程中替換、新增或刪除模組,而無需重新載入頁面。這使得你可以在獨立模組變更後,無需重新整理整個頁面,就可以更新這些模組,極大地加速了開發時間。

這一切是如何執行的?

站在 App 的角度

  1. app 程式碼要求 HMR runtime 檢查更新。
  2. HMR runtime (非同步)下載更新,然後通知 app 程式碼更新可用。
  3. app 程式碼要求 HMR runtime 應用更新。
  4. HMR runtime (非同步)應用更新。

你可以設定 HMR,使此程式自動觸發更新,或者你可以選擇要求在使用者互動後進行更新。

站在編譯器(webpack) 的角度

除了普通資源,編譯器(compiler)需要發出 "update",以允許更新之前的版本到新的版本。"update" 由兩部分組成:

  1. 待更新 manifest (JSON)
  2. 一個或多個待更新 chunk (JavaScript)

manifest 包括新的編譯 hash 和所有的待更新 chunk 目錄。

每個待更新 chunk 包括用於與所有被更新模組相對應 chunk 的程式碼(或一個 flag 用於表明模組要被移除)。

編譯器確保模組 ID 和 chunk ID 在這些構建之間保持一致。通常將這些 ID 儲存在記憶體中(例如,當使用 webpack-dev-server 時),但是也可能將它們儲存在一個 JSON 檔案中。

站在模組的角度

HMR 是可選功能,只會影響包含 HMR 程式碼的模組。舉個例子,通過 style-loader 為 style 樣式追加補丁。 為了執行追加補丁,style-loader 實現了 HMR 介面;當它通過 HMR 接收到更新,它會使用新的樣式替換舊的樣式。

類似的,當在一個模組中實現了 HMR 介面,你可以描述出當模組被更新後發生了什麼。然而在多數情況下,不需要強制在每個模組中寫入 HMR 程式碼。如果一個模組沒有 HMR 處理函式,更新就會冒泡。這意味著一個簡單的處理函式能夠對整個模組樹(complete module tree)進行處理。如果在這個模組樹中,一個單獨的模組被更新,那麼整個模組樹都會被重新載入(只會重新載入,不會遷移)。

站在 HMR Runtime 的角度 (Technical)

對於模組系統的 runtime,附加的程式碼被髮送到 parents 和 children 跟蹤模組。

在管理方面,runtime 支援兩個方法 check 和 apply

check 傳送 HTTP 請求來更新 manifest。如果請求失敗,說明沒有可用更新。如果請求成功,待更新 chunk 會和當前載入過的 chunk 進行比較。對每個載入過的 chunk,會下載相對應的待更新 chunk。當所有待更新 chunk 完成下載,就會準備切換到 ready 狀態。

apply 方法將所有被更新模組標記為無效。對於每個無效模組,都需要在模組中有一個更新處理函式,或者在它的父級模組們中有更新處理函式。否則,無效標記冒泡,並將父級也標記為無效。每個冒泡繼續直到到達應用程式入口起點,或者到達帶有更新處理函式的模組(以最先到達為準)。如果它從入口起點開始冒泡,則此過程失敗。

之後,所有無效模組都被(通過 dispose 處理函式)處理和解除載入。然後更新當前 hash,並且呼叫所有 "accept" 處理函式。runtime 切換回閒置狀態,一切照常繼續。

產生的檔案 (Technical)

左側表示初始編譯器通過。右側表示更新了模組 4 和 9 。

它能夠用於?

你可以在開發過程中將 HMR 作為 LiveReload 的替代。webpack-dev-server 支援熱模式,在試圖重新載入整個頁面之前,熱模式會嘗試使用 HMR 來更新。檢視如何實現在 React 專案中使用 HMR 為例。

一些 loader 已經生成可熱更新的模組。例如,style-loader 能夠置換出頁面的樣式表。對於這樣的模組,你不需要做任何特殊處理。

webpack 的強大之處在於它的可定製化,取決於特定專案需求,這裡有許多配置 HMR 的方式。

Webpack 系列教程

歡迎閱讀其它內容:

作者:靜默虛空
歡迎任何形式的轉載,但請務必註明出處。

限於本人水平,如果文章和程式碼有表述不當之處,還請不吝賜教。


轉載來自:http://www.cnblogs.com/jingmoxukong/p/6994563.html

相關文章