深入淺出webpack -- loader和plugin原理及區別

麥樂樂發表於2020-10-12

一、loader原理

1、概念

loader就像一個翻譯員,能將原始檔翻譯後輸出新的結果,並且一個檔案可以鏈式的經過幾個翻譯員。

以.scss檔案為例子:

  • 先將.scss檔案內容交給sass-loader翻譯為css
  • 在將翻譯後的css交給css-loader,找出css中依賴的資源,壓縮css
  • 再將css-loader輸出的內容交給style-loader,轉化為通過指令碼載入的JavaScript程式碼
const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
// console.log(path.resolve('webpack.config.js'))
module.exports = {
    mode:'development',
    entry: './app.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js'
    },
    module: {
        rules: [
         ...
          {
              test: /\.scss$/,
              use: [
                
                  {
                    loader: 'style-loader'
                  },
                  {
                    loader: 'css-loader'
                  },
                  {
                    loader: 'style-loader'
                  }
              ]
              
          }
        ]
    },
  
}

webpack是執行在Node.js上面的,一個Loader其實就是一個模組,需要匯出一個函式。

2、自己來實現一個Loader:

a、簡單實現

新建一個test.wy檔案,內容如下

c(89)

根目錄下建一個資料夾  

index.js的內容

module.exports = function(source) {
    return source.replace('c', 'console.log');
}

webpack.common.js中增加配置

     {
          test: /\.wy$/,
          loader: './wy-loader'
        }

index.js中引入  import './test.wy'


npm run build 

內容會被轉化為: 

給loader設定屬性:

{
   test: /\.wy$/,
    loader: './wy-loader',
    options: {
       name: '麥樂'
     }
}

wy-loader/index.js

b、使用一個外掛,獲取配置的屬性:

const loaderUtils = require('loader-utils')
module.exports = function(source) {
  const options = loaderUtils.getOptions(this)
  console.log(source, options)
  return source;
}

 上面的loader只是返回了原內容轉換後的內容,在某些情況下還需要返回其它的內容。

以babel-loader轉換es6為例子,需要輸出轉化後的es5和程式碼對應的Source Map,這種情況需要這樣寫:

module.exports = function(source) {
  this.callback(null, source, sourceMaps)
  return;
}

這樣就告訴webpack內容在callback中不在返回值中。 

c、非同步loader

如果處理結果是非同步拿到的,可以這樣來寫loader:

module.exports = function(source) {
  var callback = this.async()
  someAsyncOperation(source, function(err, result, sourceMaps, ast) {
    callback(err, result, sourceMaps, ast)
  })  
}

以二進位制的格式輸入給 loader 

webpack傳遞給Loader的資料格式是utf-8的字串,有的時候需要處理二進位制檔案,例如file-loader, 這是就需要webpack為Loader傳入二進位制的資料。

const loaderUtils = require('loader-utils')
module.exports = function(source) {
  console.log(source)
  return source;
}
module.exports.raw = true // 將source變成buffer型別

d、快取加速

在某些情況下有寫轉換非常耗時,如果每次構建都執行重複的操作,構建會變得很緩慢,webpack會快取所有Loader處理的結果,在需要處理的檔案和其依賴的檔案 沒有發生變化時,不會重新呼叫Loader去執行轉換。如果讓webpack不快取處理的結果,可以這樣:

module.exports = function(source) {
  // 關閉快取功能
  this.cacheable(false)
  return source;
}

e、載入本地loader

自己開發好的Loader,需要測試下能不能正常執行,配置到webpack中,才可以正確的使用Loader。例如上面的style-loader, 引入的時候是訪問的node_modules中的。這樣就需要把編寫好的loader釋出到npm才可以正常測試,這就會很麻煩。解決問題的辦法有兩種:

  • npm link
  • ResolveLoader

假如本地的 Loader 在專案目錄中的 ./wy-loader中,則需要如下配置:

module.exports = {
  resolveLoader:{
    // 去哪些目錄下尋找 Loader,有先後順序之分
    modules: ['node_modules','./wy-loader'],
  }
}

f、其它 Loader API

除了以上提到的在 Loader 中能呼叫的 Webpack API 外,還存在以下常用 API:

  • this.context:當前處理檔案的所在目錄,假如當前 Loader 處理的檔案是 /src/main.js,則 this.context 就等於 /src

  • this.resource:當前處理檔案的完整請求路徑,包括 querystring,例如 /src/main.js?name=1

  • this.resourcePath:當前處理檔案的路徑,例如 /src/main.js

  • this.resourceQuery:當前處理檔案的 querystring。

  • this.target:等於 Webpack 配置中的 Target

  • this.loadModule:當 Loader 在處理一個檔案時,如果依賴其它檔案的處理結果才能得出當前檔案的結果時, 就可以通過 this.loadModule(request: string, callback: function(err, source, sourceMap, module)) 去獲得 request 對應檔案的處理結果。

  • this.resolve:像 require 語句一樣獲得指定檔案的完整路徑,使用方法為 resolve(context: string, request: string, callback: function(err, result: string))

  • this.addDependency:給當前處理檔案新增其依賴的檔案,以便再其依賴的檔案發生變化時,會重新呼叫 Loader 處理該檔案。使用方法為 addDependency(file: string)

  • this.addContextDependency:和 addDependency 類似,但 addContextDependency 是把整個目錄加入到當前正在處理檔案的依賴中。使用方法為 addContextDependency(directory: string)

  • this.clearDependencies:清除當前正在處理檔案的所有依賴,使用方法為 clearDependencies()

  • this.emitFile:輸出一個檔案,使用方法為 emitFile(name: string, content: Buffer|string, sourceMap: {...})

其它沒有提到的 API 可以去 Webpack 官網 檢視。

二、plugin原理

1、概念

 

2、自己實現plugin

Webpack 通過 Plugin 機制讓其更加靈活,以適應各種應用場景。 在 Webpack 執行的生命週期中會廣播出許多事件,Plugin 可以監聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結果。

一個簡單的plugin

class BasicPlugin{
  // 在建構函式中獲取使用者給該外掛傳入的配置
  constructor(options){
  }

  // Webpack 會呼叫 BasicPlugin 例項的 apply 方法給外掛例項傳入 compiler 物件
  apply(compiler){
    compiler.plugin('compilation',function(compilation) {
    })
  }
}

// 匯出 Plugin
module.exports = BasicPlugin;

使用

const BasicPlugin = require('./BasicPlugin.js');
module.export = {
  plugins:[
    new BasicPlugin(options),
  ]
}

 Webpack 啟動後,在讀取配置的過程中會先執行 new BasicPlugin(options) 初始化一個 BasicPlugin 獲得其例項。 在初始化 compiler 物件後,再呼叫 basicPlugin.apply(compiler) 給外掛例項傳入 compiler 物件。 外掛例項在獲取到 compiler 物件後,就可以通過 compiler.plugin(事件名稱, 回撥函式) 監聽到 Webpack 廣播出來的事件。 並且可以通過 compiler 物件去操作 Webpack。

 

相關文章