webpack模塊解析


前面的話

  在web存在多種支持JavaScript模塊化的工具(如requirejsr.js),這些工具各有優勢和限制。webpack基於從這些系統獲得的經驗教訓,並將模塊的概念應用於項目中的任何文件。本文將詳細介紹webpack的模塊解析

 

模塊

  在模塊化編程中,開發者將程序分解成離散功能塊(discrete chunks of functionality),並稱之為模塊

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

  Node.js從最一開始就支持模塊化編程。對比Node.js模塊,webpack模塊能夠以各種方式表達它們的依賴關系

ES2015 import 語句
CommonJS require() 語句
AMD define 和 require 語句
css/sass/less 文件中的 @import 語句。
樣式(url(...))或 HTML 文件(<img src=...>)中的圖片鏈接(image url)

  [注意]webpack 1需要特定的loader來轉換ES 2015 import,然而通過webpack 2可以開箱即用

【支持類型】

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

CoffeeScript
TypeScript
ESNext (Babel)
Sass
Less
Stylus

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

 

模塊解析

  resolver是一個庫(library),用於幫助找到模塊的絕對路徑。一個模塊可以作為另一個模塊的依賴模塊,然后被后者引用,如下:

import foo from 'path/to/module'
// 或者
require('path/to/module')

  所依賴的模塊可以是來自應用程序代碼或第三方的庫(library)。resolver幫助webpack找到bundle中需要引入的模塊代碼,這些代碼在包含在每個require/import語句中。當打包模塊時,webpack使用enhanced-resolve來解析文件路徑

【解析規則】

  使用enhanced-resolve,webpack能夠解析三種文件路徑:

  1、絕對路徑

import "/home/me/file";
import "C:\\Users\\me\\file";

  由於已經取得文件的絕對路徑,因此不需要進一步再做解析

  2、相對路徑

import "../src/file1";
import "./file2";

  在這種情況下,使用import或require的資源文件(resource file)所在的目錄被認為是上下文目錄(context directory)。在import/require中給定的相對路徑,會添加此上下文路徑(context path),以產生模塊的絕對路徑(absolute path)

  3、模塊路徑

import "module";
import "module/lib/file";

  模塊將在resolve.modules中指定的所有目錄內搜索。 可以替換初始模塊路徑,此替換路徑通過使用resolve.alias配置選項來創建一個別名

  一旦根據上述規則解析路徑后,解析器(resolver)將檢查路徑是否指向文件或目錄

  如果路徑指向一個文件:

    a、如果路徑具有文件擴展名,則被直接將文件打包

    b、否則,將使用 [resolve.extensions] 選項作為文件擴展名來解析,此選項告訴解析器在解析中能夠接受哪些擴展名(例如 .js, .jsx)

  如果路徑指向一個文件夾,則采取以下步驟找到具有正確擴展名的正確文件:

    a、如果文件夾中包含 package.json 文件,則按照順序查找 resolve.mainFields 配置選項中指定的字段。並且 package.json 中的第一個這樣的字段確定文件路徑

    b、如果package.json文件不存在或者package.json文件中的main字段沒有返回一個有效路徑,則按照順序查找 esolve.mainFiles配置選項中指定的文件名,看是否能在import/require目錄下匹配到一個存在的文件名

    c、文件擴展名通過 resolve.extensions 選項采用類似的方法進行解析

  webpack 根據構建目標(build target)為這些選項提供了合理的默認配置

【解析與緩存】

  Loader解析遵循與文件解析器指定的規則相同的規則。resolveLoader 配置選項可以用來為 Loader 提供獨立的解析規則。

  每個文件系統訪問都被緩存,以便更快觸發對同一文件的多個並行或穿行請求。在觀察模式下,只有修改過的文件會從緩存中摘出。如果關閉觀察模式,在每次編譯前清理緩存

 

依賴圖表

  任何時候,一個文件依賴於另一個文件,webpack就把此視為文件之間有依賴關系。這使得 webpack 可以接收非代碼資源(non-code asset)(例如圖像或 web 字體),並且可以把它們作為依賴提供給應用程序

  webpack從命令行或配置文件中定義的一個模塊列表開始,處理應用程序。 從這些入口起點開始,webpack 遞歸地構建一個依賴圖表,這個依賴圖表包含着應用程序所需的每個模塊,然后將所有這些模塊打包為少量的bundle(通常只有一個 )可由瀏覽器加載

 

構建目標

  因為服務器和瀏覽器代碼都可以用JavaScript編寫,所以webpack提供了多種構建目標(target),可以在webpack配置中設置

【用法】

  要設置target屬性,只需要在webpack配置中設置target的值

//webpack.config.js
module.exports = {
  target: 'node'
};

  在上面例子中,使用node webpack會編譯為用於「類Node.js」環境(使用Node.js的require,而不是使用任意內置模塊(如fs或path)來加載chunk)。

  每個target都有各種部署(deployment)/環境(environment)特定的附加項,以支持滿足其需求

【多個Target】

  盡管webpack不支持向target傳入多個字符串,可以通過打包兩份分離的配置來創建同構的庫

//webpack.config.js
var path = require('path');
var serverConfig = {
  target: 'node',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.node.js'
  }
  //
};
var clientConfig = {
  target: 'web', // <=== 默認是 'web',可省略
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.js'
  }
  //
};
module.exports = [ serverConfig, clientConfig ];

  上面的例子將在的dist文件夾下創建lib.js和lib.node.js文件

 

模塊熱替換

  模塊熱替換HMR(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的角度】

  對於模塊系統的 runtime,附加的代碼被發送到 parents 和 children 跟蹤模塊。

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

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

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

  之后,所有無效模塊都被(通過 dispose 處理函數)處理和解除加載。然后更新當前 hash,並且調用所有 "accept" 處理函數。runtime 切換回閑置狀態,一切照常繼續

  可以在開發過程中將 HMR 作為 LiveReload 的替代。webpack-dev-server 支持熱模式,在試圖重新加載整個頁面之前,熱模式會嘗試使用 HMR 來更新

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM