1. 程式人生 > >[轉] webpack之plugin內部運行機制

[轉] webpack之plugin內部運行機制

ram markdown 允許 for body info 特性 clas 方式

簡介

webpack作為當前最為流行的模塊打包工具,幾乎所有的主流前端開發框架(React、Vue等)都會將其作為默認的模塊加載和打包工具。通過簡單的配置項,使用各種相關的loader和plugin,我們就可以實現自動的模塊依賴分析並打包,從而大大降低了前端項目的開發復雜度,明顯提高了前端項目的開發效率。

其中,plugin是webpack核心支柱功能,通過plugin(插件)webpack可以實現loader所不能完成的復雜功能,使用plugin豐富的自定義API以及生命周期事件,可以控制webpack編譯流程的每個環節,實現對webpack的自定義功能擴展。

webpack基礎概念

entry

entry表示webpack編譯的入口文件,通常由html通過script標簽引入。

entry的配置參見官方文檔說明。

loader

loader 用於對模塊的源代碼進行轉換。其類似於其他構建工具中“任務(task)”,並提供了處理前端構建步驟的強大方法。loader 可以將文件從不同的語言(如 TypeScript)轉換為 JavaScript,或將內聯圖像轉換為 data URL,或者將CSS文件、markdown等非js文件轉換為js文件並require進來!

loader特性

技術分享圖片

loader使用方法

  • webpack.config.js
1 2 3 4 5 6 7 8 module.exports = { module: { rules: [ {test: /\.css$/, use: ‘css-loader‘}, {test: /\.ts$/, use: ‘ts-loader‘} ] } };
  • require 語句中使用
1 require(‘style-loader!css-loader?modules!./styles.css‘);

module

Module是webpack的中的核心實體,要加載的一切和所有的依賴都是Module,總之一切都是Module。

  • webpack支持的模塊類型:
    • ES2015模塊,使用import來加載
    • CommonJS模塊,使用 require()加載。例如:Node.js模塊
    • AMD模塊,使用require 加載。例如:require.js支持的異步加載模塊
    • css/sass/less 模塊文件
    • image等其它非js模塊文件

webpack 通過 loader 可以支持各種語言和預處理器編寫模塊。loader 描述了webpack 如何處理非js模塊,可將這些非js模塊進行轉換,然後可以通過普通的js模塊加載方式來使用。

chunk

chunk 是webpack使用code splitting後的產物,也就是按需加載的分塊,裝載了不同的 module

module 和 chunk 的關系圖:

技術分享圖片

webpack將chunk分為三種類型

  • entry chunk
    入口代碼塊包含了 webpack 運行時需要的一些函數,如 webpackJsonp, __webpack_require__ 等以及依賴的一系列模塊

  • normal chunk
    普通代碼塊沒有包含運行時需要的代碼,主要指代那些應用運行時動態加載的模塊,其結構有加載方式決定,如基於異步的方式可能會包含 webpackJsonp 的調用。

  • initial chunk
    initial chunk本質上還是normal chunk,不過其會在應用初始化時完成加載,往往這個類型的chunk由CommonsChunkPlugin生成。
    與入口代碼塊對應的一個概念是入口模塊(module 0),如果入口代碼塊中包含了入口模塊 webpack 會立即執行這個模塊,否則會等待包含入口模塊的代碼塊,包含入口模塊的代碼塊其實就是initial chunk

code splitting

利用webpack提供的code splitting功能可生成不同類型的chunk,具體請參照另外一篇文章《基於webpack實現react組件的按需加載》

如何編寫一個webpack plugin

plugin創建

plugin是一個具有 apply方法的 js對象。 apply方法會被 webpack的 compiler(編譯器)對象調用,並且 compiler 對象可在整個 compilation(編譯)生命周期內訪問。

webpack插件的組成:

  • 一個JavaScript函數或者class(ES6語法)。
  • 在它的原型上定義一個apply方法。
  • 指定掛載的webpack事件鉤子。
  • 處理webpack內部實例的特定數據。
  • 功能完成後調用webpack提供的回調

這裏以我們常用的 UglifyJsPlugin 為分析示例。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class UglifyJsPlugin { apply(compiler) { const options = this.options; options.test = options.test || /\.js($|\?)/i; ...... //綁定compilation事件 compiler.plugin("compilation", (compilation) => { if(options.sourceMap) { compilation.plugin("build-module", (module) => { // to get detailed location info about errors module.useSourceMap = true; }); } //綁定optimize-chunk-assets事件 compilation.plugin("optimize-chunk-assets", (chunks, callback) => { const files = []; chunks.forEach((chunk) => files.push.apply(files, chunk.files)); ...... callback(); }); }); } } module.exports = UglifyJsPlugin;

plugin用法

由於plugin可以攜帶參數/選項,你必須在wepback配置中,向plugins屬性傳入 new實例。這裏介紹兩種常用的使用plugin的方式。

webpack.config.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const HtmlWebpackPlugin = require(‘html-webpack-plugin‘); const webpack = require(‘webpack‘); module.exports = { module: { loaders: [ { test: /\.(js|jsx)$/, loader: ‘babel-loader‘ } ] }, plugins: [ new webpack.optimize.UglifyJsPlugin(), //訪問內置的插件 new HtmlWebpackPlugin({template: ‘./src/index.html‘}) //訪問第三方插件 ] };

Node.js

目前webpack官方強烈建議在配置文件中使用plugins屬性配置項來使用各種plugin,而在Node.js中通過compiler.apply來使用plugin這種方式並不推薦。

1 2 3 4 5 6 7 8 9 const webpack = require(‘webpack‘); //運行時(runtime)訪問 webpack const configuration = require(‘./webpack.config.js‘); let compiler = webpack(configuration); compiler.apply(new webpack.optimize.UglifyJsPlugin()); compiler.run(function(err, stats) { // ... });

webpack整體運行流程圖

TODO

技術分享圖片

Tapable

webpack整體是一個插件架構,所有的功能都以插件的方式集成在構建流程中,通過發布訂閱事件來觸發各個插件執行。webpack核心使用Tapable 來實現插件(plugins)的binding(綁定)和applying(應用)。

tapable介紹

tapable是webpack官方開發維護的一個小型庫,能夠讓我們為javascript模塊添加並應用插件。 它可以被其它模塊繼承或混合。它類似於NodeJS的 EventEmitter 類,專註於自定義事件發射操作。 除此之外, Tapable 允許你通過回調函數的參數訪問事件的生產者。

tapable核心函數

技術分享圖片

compiler(編譯器)和compilation(編譯)

在webpack插件開發中最重要的兩個核心概念就是 compilercompilation 。理解他們是擴展webpack功能的關鍵。

compiler

webpack官方對compiler的解釋如下:

The compiler object represents the fully configured webpack environment. This object is built once upon starting webpack, and is configured with all operational settings including options, loaders, and plugins. When applying a plugin to the webpack environment, the plugin will receive a reference to this compiler. Use the compiler to access the main webpack environment

compiler對象代表的是配置完備的Webpack環境。 compiler對象只在Webpack啟動時構建一次,由Webpack組合所有的配置項構建生成。

功能核心

Compiler 繼承自前面我們介紹的Tapable類,其混合了 Tapable 類以吸收其功能來註冊和調用自身的插件。 大多數面向用戶的插件,都是首先在 Compiler 上註冊的。

技術分享圖片

compiler事件鉤子(Event Hooks)

webpack官方列出了Compiler的所有事件鉤子,點擊查看

我們這裏只介紹一些關鍵性的事件,大致按照webpack流程的執行順序:

技術分享圖片

compiler源碼

  • 點擊查看

compilation

webpack官方解釋如下:

A compilation object represents a single build of versioned assets. While running webpack development middleware, a new compilation will be created each time a file change is detected, thus generating a new set of compiled assets. A compilation surfaces information about the present state of module resources, compiled assets, changed files, and watched dependencies. The compilation also provides many callback points at which a plugin may choose to perform custom actions.

compilation 對象代表了一次單一的版本構建和生成資源。當運行 webpack 開發環境中間件時,每當檢測到一個文件變化,一次新的編譯將被創建,從而生成一組新的編譯資源。一個編譯對象表現了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息。編譯對象也提供了很多關鍵點事件回調供插件做自定義處理時選擇使用。

功能核心

Compilation對象負責組織整個編譯過程,包含了每個構建環節所對應的方法。前面提到的Compiler(編譯器)的run方法中調用compiler方法開始編譯,在編譯過程中創建了一個Compilation對象。

技術分享圖片

Compilation事件鉤子(Event Hooks)

  • 獲取Compilation對象
1 2 3 4 compiler.plugin("compilation", function(compilation) { //主要的編譯實例 //隨後所有的方法都從 compilation.plugin 上得來 });
  • Compilation(編譯)事件鉤子

技術分享圖片

  • Compilation綁定事件示例:
1 2 3 4 5 6 7 8 9 10 //這裏一般只有一個塊,除非你在配置中指定了多個入口 compilation.plugin(‘optimize-chunks‘, function(chunks) { //這裏一般只有一個塊,除非你在配置中指定了多個入口 chunks.forEach(function (chunk) { //塊含有模塊的循環引用 chunk.modules.forEach(function (module){ //module.loaders, module.rawRequest, module.dependencies 等。 }); }); });

compilation源碼

  • 點擊查看

二者關系

  • compiler 對象代表的是不變的webpack環境,是針對webpack的
  • compilation 對象針對的是隨時可變的項目文件,只要文件有改動,compilation就會被重新創建。

Module

module 是 webpack 構建的核心實體,也是所有 module 的 父類,它有幾種不同子類:NormalModule , MultiModule , ContextModule , DelegatedModule 等。但這些核心實體都是在構建中都會去調用對應方法,也就是build()

build方法核心源代碼點擊查看

Template

Template是用來生成結果代碼的。入口是compilationcreateChunkAssets方法。

1 2 3 4 5 6 //如果是入口,則使用MainTemplate生成結果,否則使用ChunkTemplate. if(chunk.entry) { source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates); } else { source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates); }

Template子類

  • MainTemplate.js 用於生成項目入口文件
  • ChunkTemplate.js 用於生成異步加載的js代碼
  • ModuleTemplate.js 用於生成某個模塊的代碼
  • HotUpdateChunkTemplate.js 熱更新chunk

Template源代碼

ModuleTemplate源代碼

參考資料

    • 命令行輸入webpack的時候都發生了什麽
    • Webpack 源碼(一)—— Tapable 和 事件流
    • webpack 源碼解析
    • 細說 webpack 之流程篇
    • 從 Bundle 文件看 Webpack 模塊機制
    • 如何寫一個webpack插件(一)
    • How to write a plugin?
    • Webpack中hash與chunkhash的區別,以及js與css的hash指紋解耦方案
    • 插件 API

[轉] webpack之plugin內部運行機制