天天看點

Webpack 深入淺出之公司級分享總結(内附完整ppt)

背景

前段時間,在公司做了個 Webpack 的分享。聽衆40多人,感覺還不錯。是以總結一下,先看一下ppt的目錄:

Webpack 深入淺出之公司級分享總結(内附完整ppt)

本篇文章,如果直接貼ppt圖,了解起來可能比較費勁,加上我之前已經把部分内容輸出了完整的文章了,這裡就大概講一下内容,友善大家結合ppt來了解~

PS:公衆号背景回複 webpack 即可擷取本次分享的完整ppt

基本打包機制

本質上,webpack 是一個現代 JavaScript 應用程式的靜态子產品打包器(module bundler)。當 webpack 處理應用程式時,它會遞歸地建構一個依賴關系圖(dependency graph),其中包含應用程式需要的每個子產品,然後将所有這些子產品打包成一個或多個 bundle。

打包過程可以拆分為四步:

  1. 利用babel完成代碼轉換,并生成單個檔案的依賴
  2. 從入口開始遞歸分析,并生成依賴圖譜
  3. 将各個引用子產品打包為一個立即執行函數
  4. 将最終的bundle檔案寫入bundle.js中

完整代碼見:https://github.com/LuckyWinty/blog/tree/master/code/bundleBuild

以上是打包的基本機制,而webpack的打包過程,會基于這些基本步驟進行擴充,主要有以下步驟:

  1. 初始化參數 從配置檔案和 Shell 語句中讀取與合并參數,得出最終的參數
  2. 開始編譯 用上一步得到的參數初始Compiler對象,加載所有配置的插件,通 過執行對象的run方法開始執行編譯
  3. 确定入口 根據配置中的 Entry 找出所有入口檔案
  4. 編譯子產品 從入口檔案出發,調用所有配置的 Loader 對子產品進行編譯,再找出該子產品依賴的子產品,再遞歸本步驟直到所有入口依賴的檔案都經過了本步驟的處理
  5. 完成子產品編譯 在經過第4步使用 Loader 翻譯完所有子產品後, 得到了每個子產品被編譯後的最終内容及它們之間的依賴關系
  6. 輸出資源:根據入口和子產品之間的依賴關系,組裝成一個個包含多個子產品的 Chunk,再将每個 Chunk 轉換成一個單獨的檔案加入輸出清單中,這是可以修改輸出内容的最後機會
  7. 輸出完成:在确定好輸出内容後,根據配置确定輸出的路徑和檔案名,将檔案的内容寫入檔案系統中。

整個流程概括為3個階段,初始化、編譯、輸出。而在每個階段中又會發生很多事件,Webpack會将這些事件廣播出來供Plugin使用。具體鈎子,可以看官方文檔:https://webpack.js.org/api/compiler-hooks/#hooks

這一塊,我寫了另一篇更詳細的文章,可以看這裡:

Webpack4打包機制原了解析

Webpack Loader

Loader 就像一個翻譯員,能将源檔案經過轉化後輸出新的結果,并且一個檔案還可以鍊式地經過多個翻譯員翻譯。

概念:

  • 一個Loader 的職責是單一的,隻需要完成一種轉換
  • 一個Loader 其實就是一個Node.js 子產品,這個子產品需要導出一個函數

開發Loader形式

  1. 基本形式
module.exports = function (source ) {
    return source;
}           

複制

  1. 調用第三方子產品
const sass= require('node-sass');
module.exports = function (source) {
  return sass(source);
}           

複制

由于 Loader 運作在 Node.js 中,是以我們可以調用任意 Node.js 自帶的 API ,或者安裝第三方子產品進行調用

  1. 調用Webpack的Api
//擷取使用者為 Loader 傳入的 options
const loaderUtils =require ('loader-utils');
module.exports = (source) => {
    const options= loaderUtils.getOptions(this);
    return source;
}
//傳回sourceMap
module.exports = (source)=> {
    this.callback(null, source, sourceMaps);
    //當我們使用 this.callback 傳回内容時 ,該 Loader 必須傳回 undefined,
    //以讓 Webpack 知道該 Loader 傳回的結果在 this.callback 中,而不是 return中
    return;
}
// 異步
module.exports = (source) => {
    const callback = this.async()
    someAsyncOperation(source, (err, result, sourceMaps, ast) => {
        // 通過 callback 傳回異步執行後的結果
        callback(err, result, sourceMaps, ast)
    })
}
//緩存加速
module.exports = (source) => {
    //關閉該 Loader 的緩存功能
    this.cacheable(false)
    return source
}           

複制

source參數是compiler 傳遞給 Loader 的一個檔案的原内容,這個函數需要傳回處理後的内容,這裡為了簡單起見,直接将原内容傳回了,相當于該Loader 有做任何轉換.這裡結合了webpack的api和第三方子產品之後,可以說loader可以做的事情真的非常非常多了...

更多的webpack Api可以看官方文檔:https://webpack.js.org/api/loaders

Webpack Plugin

專注處理 webpack 在編譯過程中的某個特定的任務的功能子產品,可以稱為插件概念:

  • 是一個獨立的子產品
  • 子產品對外暴露一個 js 函數
  • 函數的原型 (prototype) 上定義了一個注入 compiler 對象的 apply 方法 apply 函數中需要有通過 compiler 對象挂載的 webpack 事件鈎子,鈎子的回調中能拿到目前編譯的 compilation 對象,如果是異步編譯插件的話可以拿到回調 callback
  • 完成自定義子編譯流程并處理 complition 對象的内部資料
  • 如果異步編譯插件的話,資料處理完成後執行 callback 回調。

開發基本形式

// 1、BasicPlugin.js 檔案(獨立子產品)
    // 2、子產品對外暴露的 js 函數
    class BasicPlugin{
        //在構造函數中擷取使用者為該插件傳入的配置
        constructor(pluginOptions) {
            this.options = pluginOptions;
        }
        //3、原型定義一個 apply 函數,并注入了 compiler 對象
        apply(compiler) {
            //4、挂載 webpack 事件鈎子(這裡挂載的是 emit 事件)
            compiler.plugin('emit', function (compilation, callback) {
                // ... 内部進行自定義的編譯操作
                // 5、操作 compilation 對象的内部資料
                console.log(compilation);
                // 6、執行 callback 回調
                callback();
            });
        }
    }
    // 7、暴露 js 函數
    module.exports = BasicPlugin;           

複制

Webpack 啟動後,在讀取配置的過程中會先執行 new BasicPlugin(options )初始化一個 BasicPlugin 并獲得其執行個體。在初始化 Compiler 對象後,再調用 basicPlugin.apply (compiler )為插件執行個體傳入 compiler 對象。插件執行個體在擷取到 compiler 對象後,就可以通過 compiler. plugin (事件名稱 ,回調函數)監聽到 Webpack 廣播的事件,并且可以通過 compiler 對象去操作 Webpack。

Compiler對象

compiler 對象是 webpack 的編譯器對象,compiler 對象會在啟動 webpack 的時候被一次性地初始化,compiler 對象中包含了所有 webpack 可自定義操作的配置,例如 loader 的配置,plugin 的配置,entry 的配置等各種原始 webpack 配置等

Webpack 深入淺出之公司級分享總結(内附完整ppt)

webpack部分源碼:https://github.com/webpack/webpack/blob/10282ea20648b465caec6448849f24fc34e1ba3e/lib/webpack.js#L30

Compilation 對象

compilation 執行個體繼承于 compiler,compilation 對象代表了一次單一的版本 webpack 建構和生成編譯資源的過程。當運作 webpack 開發環境中間件時,每當檢測到一個檔案變化,一次新的編譯将被建立,進而生成一組新的編譯資源以及新的 compilation 對象。一個 compilation 對象包含了 目前的子產品資源、編譯生成資源、變化的檔案、以及 被跟蹤依賴的狀态資訊。編譯對象也提供了很多關鍵點回調供插件做自定義處理時選擇使用。

Compiler 和 Compilation 的差別在于:Compiler 代表了整個 Webpack 從啟動到關閉的生命周期,而 Compilation 隻代表一次新的編譯。

Tapable & Tapable 執行個體

webpack 的插件架構主要基于 Tapable 實作的,Tapable 是 webpack 項目組的一個内部庫,主要是抽象了一套插件機制。它類似于 NodeJS 的 EventEmitter 類,專注于自定義事件的觸發和操作。除此之外, Tapable 允許你通過回調函數的參數通路事件的生産者。

Webpack 深入淺出之公司級分享總結(内附完整ppt)

webpack本質上是一種事件流的機制,它的工作流程就是将各個插件串聯起來,而實作這一切的核心就是Tapable,webpack中最核心的負責編譯的Compiler和負責建立bundles的Compilation都是Tapable的執行個體,Tapable 能夠讓我們為 javaScript 子產品添加并應用插件。它可以被其它子產品繼承或混合。

Tapable 模型

Tapable 簡化後的模型,就是我們熟悉的釋出訂閱者模式

class SyncHook{
   constructor(){
      this.hooks = {}
   }
   
   tap(name,fn){
    if(!this.hooks[name])this.hooks[name] = []
     this.hooks[name].push(fn)
   }

   call(name){
     this.hooks[name].forEach(hook=>hook(...arguments))
   }
}           

複制

loader 部分也出過完整文章,可以看這裡:webpack 插件機制分析及開發調試

Loader & Plugin 開發調試

npm link

  1. 確定正在開發的本地 Loader 子產品的 package.json 已經配置好(最主要的main字段的入口檔案指向要正确)
  2. 在本地的 Npm 子產品根目錄下執行 npm link,将本地子產品注冊到全局
  3. 在項目根目錄下執行 npm link loader-name ,将第 2 步注冊到全局的本地 Npm 子產品連結到項目的 node moduels 下,其中的 loader-name 是指在第 1 步的package.json 檔案中配置的子產品名稱
Webpack 深入淺出之公司級分享總結(内附完整ppt)

Npm link 專門用于開發和調試本地的 Npm 子產品,能做到在不釋出子產品的情況下, 将本地的一個正在開發的子產品的源碼連結到項目的 node_modules 目錄下,讓項目可以直接使 用本地的 Npm 子產品。由于是通過軟連結的方式實作的,編輯了本地的 Npm 子產品的代碼,是以在項目中也能使用到編輯後的代碼。

Resolveloader

ResolveLoader 用于配置 Webpack 如何尋找 Loader ,它在預設情況下隻會去 node_modules 目錄下尋找。為了讓 Webpack 加載放在本地項目中的 Loader,需要修改 resolveLoader.modules。

Webpack 深入淺出之公司級分享總結(内附完整ppt)

關于HMR的在這:webpack中的HMR(熱更新)原理剖析

關于性能優化的在這:Webpack性能優化總結大全

建構工具選擇

針對不同的場景,選擇最合适的工具

Webpack 深入淺出之公司級分享總結(内附完整ppt)

通過對比,不難看出,Webpack和Rollup在不同場景下,都能發揮自身優勢作用。webpack作為打包工具,但是在定義子產品輸出的時候,webpack确不支援ESM,webpack插件系統龐大,确實有支援子產品級的Tree-Shacking的插件,如webpack-deep-scope-analysis-plugin。但是粒度更細化的,一個子產品裡面的某個方法,本來如果沒有被引用的話也可以去掉的,就不行了....這個時候,就要上rollup了。rollup它支援程式流分析,能更加正确的判斷項目本身的代碼是否有副作用,其實就是rollup的tree-shaking更幹淨。是以我們的結論是rollup 比較适合打包 js 的 sdk 或者封裝的架構等,例如,vue 源碼就是 rollup 打包的。而 webpack 比較适合打包一些應用,例如 SPA 或者同構項目等等。

結論:在開發應用時使用 Webpack,開發庫時使用 Rollup

PS:公衆号背景回複 webpack 即可擷取本次分享的完整ppt

相關熱門推薦

Webpack4打包機制原了解析

webpack 插件機制分析及開發調試

webpack中的HMR(熱更新)原理剖析

Webpack性能優化總結大全

參考資料

補充學習資料:https://github.com/LuckyWinty/blog

Loader: https://juejin.im/post/5a698a316fb9a01c9f5b9ca0

Tapable: https://juejin.im/post/5abf33f16fb9a028e46ec352

webpack:

ebook:webpack深入淺出,公衆号回複 ebook 即可擷取

極客時間:玩轉webpack

最後

  • 歡迎加我微信(winty230),拉你進技術群,長期交流學習...
  • 歡迎關注「前端Q」,認真學前端,做個有專業的技術人...
Webpack 深入淺出之公司級分享總結(内附完整ppt)
Webpack 深入淺出之公司級分享總結(内附完整ppt)

在看點這裡

Webpack 深入淺出之公司級分享總結(内附完整ppt)