天天看點

Webpack 概念概念Webpack 系列教程

概念

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 。

Webpack 概念概念Webpack 系列教程

它能夠用于?

你可以在開發過程中将 HMR 作為 LiveReload 的替代。

 支援熱模式,在試圖重新加載整個頁面之前,熱模式會嘗試使用 HMR 來更新。檢視如何實作

在 React 項目中使用 HMR

 為例。

一些 loader 已經生成可熱更新的子產品。例如,

style-loader

 能夠置換出頁面的樣式表。對于這樣的子產品,你不需要做任何特殊處理。

webpack 的強大之處在于它的可定制化,取決于特定項目需求,這裡有許多配置 HMR 的方式。

Webpack 系列教程

歡迎閱讀其它内容:

繼續閱讀