Detaillierte Einführung in das Plugin in Webpack5

Was macht das Plugin?

Plugins können Webpack erweitern und benutzerdefiniertes Build-Verhalten hinzufügen, sodass Webpack ein breiteres Aufgabenspektrum ausführen und über stärkere Build-Funktionen verfügen kann.

So funktioniert das Plugin

Webpack ist wie eine Produktionslinie. Es muss eine Reihe von Verarbeitungsprozessen durchlaufen, bevor die Quelldateien in Ausgabeergebnisse umgewandelt werden können. Jeder Verarbeitungsprozess in dieser Produktionslinie hat eine einzige Verantwortung und es bestehen Abhängigkeiten zwischen mehreren Prozessen. Erst nachdem die aktuelle Verarbeitung abgeschlossen ist, kann sie an den nächsten Prozess übergeben werden. Ein Plug-In ist wie eine in die Produktionslinie eingefügte Funktion, die zu einem bestimmten Zeitpunkt Ressourcen in der Produktionslinie verarbeitet .

webpack nutzt Tapable, um diese komplexe Produktionslinie zu organisieren. Webpack sendet Ereignisse während des Betriebs. Das Plug-in muss nur die Ereignisse abhören, die ihm wichtig sind, und kann sich dieser Produktionslinie anschließen, um den Betrieb der Produktionslinie zu ändern. Der Ereignisflussmechanismus von Webpack gewährleistet die Ordnung der Plug-Ins und macht das gesamte System sehr skalierbar.

Aus Sicht der Codelogik löst Webpack während des Codekompilierungsprozesses eine Reihe von Tapable-Hook-Ereignissen aus. Das Plug-In sucht den entsprechenden Hook und hängt seine eigene Aufgabe daran, dh registriert das Ereignis dass beim Erstellen des Webpacks die vom Plug-in registrierten Ereignisse ausgeführt werden, wenn der Hook ausgelöst wird.

Haken im Webpack

Die Essenz von Hooks ist: Ereignisse . Um unseren direkten Eingriff und die Steuerung des Kompilierungsprozesses zu erleichtern, kapselt Webpack verschiedene Schlüsselereignisse, die während des Kompilierungsprozesses ausgelöst werden, in Ereignisschnittstellen und macht sie verfügbar. Diese Schnittstellen werden anschaulich als Hooks bezeichnet. Die Entwicklung von Plug-Ins ist untrennbar mit diesen Hooks verbunden.

Tapable

Tapable bietet eine einheitliche Plug-in-Schnittstellentypdefinition (Hook) für Webpack, die Kernfunktionsbibliothek von Webpack. Derzeit gibt es im Webpack zehn Arten von Hooks, die im Tapable-Quellcode zu sehen sind:

// https://github.com/webpack/tapable/blob/master/lib/index.js
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

Tapable stellt Plugins außerdem drei Methoden zur Verfügung, mit denen verschiedene Arten von benutzerdefiniertem Build-Verhalten eingefügt werden können:
tap: Sie können synchrone Hooks und asynchrone Hooks registrieren.
tapAsync: Registrieren Sie einen asynchronen Hook im Callback-Modus.
tapPromise: Registrieren Sie asynchrone Hooks im Promise-Modus.

Plugin-Build-Objekt

Compiler

Die vollständige Konfiguration der Webpack-Umgebung wird im Compiler-Objekt gespeichert. Dabei handelt es sich um ein eindeutiges Objekt, das bei jedem Start eines Webpack-Builds nur einmal erstellt wird.

Dieses Objekt wird erstellt, wenn Webpack zum ersten Mal gestartet wird. Über das Compilerobjekt können wir auf die Hauptumgebungskonfiguration von Webapck zugreifen, z. B. Loader, Plugins und andere Konfigurationsinformationen.

Es verfügt über die folgenden Haupteigenschaften:
Compiler.options kann dieses Mal beim Starten von Webpack auf alle Konfigurationsdateien zugreifen, einschließlich, aber nicht beschränkt auf, vollständige Konfigurationsinformationen wie Loader, Eintrag, Ausgabe, Plugin usw.
Compiler.inputFileSystem und Compiler.OutputFileSystem können Dateioperationen ausführen, die fs in Nodejs entsprechen.
Compiler.Hooks können verschiedene Arten von Tapable-Hooks registrieren, sodass unterschiedliche Logik in den Compiler-Lebenszyklus implantiert werden kann.

Dokumentation zu Compiler-Hooks

Zusammenstellung

Das Kompilierungsobjekt stellt den Aufbau einer Ressource dar und die Kompilierungsinstanz hat Zugriff auf alle Module und ihre Abhängigkeiten.

Ein Kompilierungsobjekt kompiliert alle Module im Build-Abhängigkeitsdiagramm. Während der Kompilierungsphase werden Module geladen, versiegelt, optimiert, in Chunks aufgeteilt, gehasht und wiederhergestellt.

Es verfügt über die folgenden Haupteigenschaften:
Compilation.modules hat Zugriff auf alle Module, und jede gepackte Datei ist ein Modul.
Compilation.chunks Chunk ist ein Codeblock, der aus mehreren Modulen besteht. Die durch die Eintragsdatei eingeführten Ressourcen bilden einen Block, und die durch Code getrennten Module bilden einen weiteren Block.
Compilation.assets kann auf die Ergebnisse aller durch diese Verpackung generierten Dateien zugreifen.
Compilation.hooks kann verschiedene Arten von angreifbaren Hooks registrieren, die zum Hinzufügen und Ändern von Logik während der Kompilierungsmodulphase verwendet werden.

Dokumentation zu Kompilierungs-Hooks

Lebenszyklus

Fügen Sie hier eine Bildbeschreibung ein

Entwickeln Sie ein Plug-in

  1. Wenn das Webpack die Konfiguration liest, führt new TestPlugin() die Plug-in-Konstruktormethode aus
  2. Webpack erstellt Compilerobjekt
  3. Durchlaufen Sie alle Plug-Ins und rufen Sie die Apply-Methode des Plug-Ins auf
  4. Führen Sie den Rest des Kompilierungsprozesses aus (initiieren Sie jedes Hook-Ereignis).

Plugins/test-plugin.js:

class TestPlugin {
    
    
  constructor() {
    
    
    console.log("TestPlugin constructor()");
  }
  apply(compiler) {
    
    
    console.log("TestPlugin apply()");
  }
}
module.exports = TestPlugin;

Die Verwendung von Plug-Ins muss in webpack.config.js konfiguriert werden:

const TestPlugin = require("./plugins/test-plugin")
……
plugins: [
	new TestPlugin()
]

Registrieren Sie Hooks

class TestPlugin {
    
    
  constructor() {
    
    
    console.log("TestPlugin constructor()");
  }
  apply(compiler) {
    
    
    console.log("TestPlugin apply()");

    // 从文档可知, compile hook 是 SyncHook, 也就是同步钩子, 只能用tap注册
    compiler.hooks.compile.tap("TestPlugin", (compilationParams) => {
    
    
      console.log("compiler.compile()");
    });

    // 从文档可知, make 是 AsyncParallelHook, 也就是异步并行钩子, 特点就是异步任务同时执行
    // 可以使用 tap、tapAsync、tapPromise 注册。
    // 如果使用tap注册的话,进行异步操作是不会等待异步操作执行完成的。
    compiler.hooks.make.tap("TestPlugin", (compilation) => {
    
    
      compilation.hooks.seal.tap("TestPlugin",()=>{
    
    
        console.log("TestPlugin seal")
      })
      setTimeout(() => {
    
    
        console.log("compiler.make() 111");
      }, 2000);
    });

    // 使用tapAsync、tapPromise注册,进行异步操作会等异步操作做完再继续往下执行
    compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {
    
    
      setTimeout(() => {
    
    
        console.log("compiler.make() 222");
        // 必须调用
        callback();
      }, 1000);
    });

    compiler.hooks.make.tapPromise("TestPlugin", (compilation) => {
    
    
      console.log("compiler.make() 333");
      // 必须返回promise
      return new Promise((resolve) => {
    
    
        resolve();
      });
    });

    // 从文档可知, emit 是 AsyncSeriesHook, 也就是异步串行钩子,特点就是异步任务顺序执行
    compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
    
    
      setTimeout(() => {
    
    
        console.log("compiler.emit() 111");
        callback();
      }, 3000);
    });

    compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
    
    
      setTimeout(() => {
    
    
        console.log("compiler.emit() 222");
        callback();
      }, 2000);
    });

    compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
    
    
      setTimeout(() => {
    
    
        console.log("compiler.emit() 333");
        callback();
      }, 1000);
    });
  }
}

module.exports = TestPlugin;

Beginnen Sie mit dem Debuggen

Zeigen Sie die Compiler- und Kompilierungsobjektdaten durch Debuggen an.

Konfigurationsanweisungen für package.json:

{
    
    
  "name": "source",
  "version": "1.0.0",
  "scripts": {
    
    
    "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
  },
}

Führen Sie den Befehl aus:npm run debug

Zu diesem Zeitpunkt gibt die Konsole den folgenden Inhalt aus:

PS C:\Users\86176\Desktop\source> npm run debug

> source@1.0.0 debug
> node --inspect-brk ./node_modules/webpack-cli/bin/cli.js

Debugger listening on ws://127.0.0.1:9229/629ea097-7b52-4011-93a7-02f83c75c797
For help, see: https://nodejs.org/en/docs/inspecto

Öffnen Sie den Chrome-Browser und drücken Sie F12, um die Browser-Debugging-Konsole zu öffnen. Zu diesem Zeitpunkt zeigt die Konsole ein grünes Symbol an:
Fügen Sie hier eine Bildbeschreibung ein
Klicken Sie auf das grüne Symbol, um in den Debugging-Modus zu wechseln.

Verwenden Sie den Debugger-Haltepunkt an der Stelle, an der der Code debuggt werden muss. Der Code wird dann nicht mehr ausgeführt, sodass Sie debuggen und die Daten anzeigen können.

BannerWebpackPlugin

Funktion: Kommentare zur gepackten Ausgabedatei hinzufügen.

Fügen Sie Kommentare hinzu, bevor Sie die Ausgabe packen: Sie müssen den Hook „compiler.hooks.emit “ verwenden , der vor dem Packen der Ausgabe ausgelöst wird. Compilation.assets kann alle Ressourcendateien abrufen, die ausgegeben werden.

// plugins/banner-webpack-plugin.js
class BannerWebpackPlugin {
    
    
  constructor(options = {
     
     }) {
    
    
    this.options = options;
  }

  apply(compiler) {
    
    
    // 需要处理文件
    const extensions = ["js", "css"];

    // emit是异步串行钩子
    compiler.hooks.emit.tapAsync("BannerWebpackPlugin", (compilation, callback) => {
    
    
      // compilation.assets包含所有即将输出的资源
      // 通过过滤只保留需要处理的文件
      const assetPaths = Object.keys(compilation.assets).filter((path) => {
    
    
        const splitted = path.split(".");
        return extensions.includes(splitted[splitted.length - 1]);
      });

      assetPaths.forEach((assetPath) => {
    
    
        const asset = compilation.assets[assetPath];

        const source = `/*
		* Author: ${
      
      this.options.author}
		*/\n${
      
      asset.source()}`;

        // 覆盖资源
        compilation.assets[assetPath] = {
    
    
          // 资源内容
          source() {
    
    
            return source;
          },
          // 资源大小
          size() {
    
    
            return source.length;
          },
        };
      });

      callback();
    });
  }
}

module.exports = BannerWebpackPlugin;

CleanWebpackPlugin

Funktion: Löschen Sie den zuletzt gepackten Inhalt, bevor Webpack die Ausgabe verpacktclean: true;

Wie wird vor der Verpackungsausgabe ausgeführt?
Sie müssen compiler.hooks.emiteinen Hook verwenden, der vor dem Packen der Ausgabe ausgelöst wird.

Wie lösche ich den zuletzt verpackten Inhalt?
Rufen Sie das Verpackungsausgabeverzeichnis ab: über das Compilerobjekt.
Inhalt durch Dateioperationen löschen: Dateien über Compiler.outputFileSystem verwalten.

// plugins/clean-webpack-plugin.js
class CleanWebpackPlugin {
    
    
  apply(compiler) {
    
    
    // 获取操作文件的对象
    const fs = compiler.outputFileSystem;
    // emit是异步串行钩子
    compiler.hooks.emit.tapAsync("CleanWebpackPlugin", (compilation, callback) => {
    
    
      // 获取输出文件目录
      const outputPath = compiler.options.output.path;
      // 删除目录所有文件
      const err = this.removeFiles(fs, outputPath);
      // 执行成功err为undefined,执行失败err就是错误原因
      callback(err);
    });
  }

  removeFiles(fs, path) {
    
    
    try {
    
    
      // 读取当前目录下所有文件
      const files = fs.readdirSync(path);

      // 遍历文件,删除
      files.forEach((file) => {
    
    
        // 获取文件完整路径
        const filePath = `${
      
      path}/${
      
      file}`;
        // 分析文件
        const fileStat = fs.statSync(filePath);
        // 判断是否是文件夹
        if (fileStat.isDirectory()) {
    
    
          // 是文件夹需要递归遍历删除下面所有文件
          this.removeFiles(fs, filePath);
        } else {
    
    
          // 不是文件夹就是文件,直接删除
          fs.unlinkSync(filePath);
        }
      });

      // 最后删除当前目录
      fs.rmdirSync(path);
    } catch (e) {
    
    
      // 将产生的错误返回出去
      return e;
    }
  }
}

module.exports = CleanWebpackPlugin;

AnalyzeWebpackPlugin

Funktion: Analysieren Sie die Größe der im Webpack gepackten Ressourcen und geben Sie die Analysedatei aus.

Wo kann man das machen? compiler.hooks.emitEs wird vor der Verpackungsausgabe ausgelöst. Wir müssen die Ressourcengröße analysieren und die analysierte MD-Datei hinzufügen.

// plugins/analyze-webpack-plugin.js
class AnalyzeWebpackPlugin {
    
    
  apply(compiler) {
    
    
    // emit是异步串行钩子
    compiler.hooks.emit.tap("AnalyzeWebpackPlugin", (compilation) => {
    
    
      // Object.entries将对象变成二维数组。二维数组中第一项值是key,第二项值是value
      const assets = Object.entries(compilation.assets);

      let source = "# 分析打包资源大小 \n| 名称 | 大小 |\n| --- | --- |";

      assets.forEach(([filename, file]) => {
    
    
        source += `\n| ${
      
      filename} | ${
      
      file.size()} |`;
      });

      // 添加资源
      compilation.assets["analyze.md"] = {
    
    
        source() {
    
    
          return source;
        },
        size() {
    
    
          return source.length;
        },
      };
    });
  }
}

module.exports = AnalyzeWebpackPlugin;

InlineChunkWebpackPlugin

Funktion: Die durch das Webpack-Paket generierte Laufzeitdatei ist zu klein und die Leistung beim Senden zusätzlicher Anforderungen ist nicht gut. Daher muss sie in js eingebunden werden, um die Anzahl der Anforderungen zu verringern.

Dies muss html-webpack-pluginerreicht werden, indem die Inline-Laufzeit vor der Ausgabe von index.html in das HTML-Webpack-Plugin eingefügt und die redundanten Laufzeitdateien gelöscht werden.

// plugins/inline-chunk-webpack-plugin.js
const HtmlWebpackPlugin = require("safe-require")("html-webpack-plugin");

class InlineChunkWebpackPlugin {
    
    
  constructor(tests) {
    
    
    this.tests = tests;
  }

  apply(compiler) {
    
    
    compiler.hooks.compilation.tap("InlineChunkWebpackPlugin", (compilation) => {
    
    
      const hooks = HtmlWebpackPlugin.getHooks(compilation);

      hooks.alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => {
    
    
        assets.headTags = this.getInlineTag(assets.headTags, compilation.assets);
        assets.bodyTags = this.getInlineTag(assets.bodyTags, compilation.assets);
      });

      hooks.afterEmit.tap("InlineChunkHtmlPlugin", () => {
    
    
        Object.keys(compilation.assets).forEach((assetName) => {
    
    
          if (this.tests.some((test) => assetName.match(test))) {
    
    
            delete compilation.assets[assetName];
          }
        });
      });
    });
  }

  getInlineTag(tags, assets) {
    
    
    return tags.map((tag) => {
    
    
      if (tag.tagName !== "script") return tag;

      const scriptName = tag.attributes.src;

      if (!this.tests.some((test) => scriptName.match(test))) return tag;

      return {
    
     tagName: "script", innerHTML: assets[scriptName].source(), closeTag: true };
    });
  }
}

module.exports = InlineChunkWebpackPlugin;

Referenzdokumentation

Guess you like

Origin blog.csdn.net/zag666/article/details/131948452