Introducción detallada al complemento en Webpack5

¿Qué hace el complemento?

Los complementos pueden ampliar el paquete web y agregar comportamientos de compilación personalizados, lo que permite que el paquete web realice una gama más amplia de tareas y tenga capacidades de compilación más sólidas.

Cómo funciona el complemento

Webpack es como una línea de producción: necesita pasar por una serie de procesos de procesamiento antes de que los archivos fuente puedan convertirse en resultados. Cada proceso de procesamiento en esta línea de producción tiene una única responsabilidad y existen dependencias entre múltiples procesos. Solo después de que se completa el procesamiento actual se puede pasar al siguiente proceso. Un complemento es como una función insertada en la línea de producción, que procesa recursos en la línea de producción en un momento específico .

webpack utiliza Tapable para organizar esta compleja línea de producción. Webpack transmitirá eventos durante la operación, el complemento solo necesita escuchar los eventos que le interesan y puede unirse a la línea de producción para cambiar la operación de la línea de producción. El mecanismo de flujo de eventos de webpack garantiza el orden de los complementos, lo que hace que todo el sistema sea muy escalable.

Desde la perspectiva de la lógica del código, webpack activará una serie de eventos de gancho Tapable durante el proceso de compilación del código. Lo que hace el complemento es encontrar el gancho correspondiente y colgar su propia tarea en él, es decir, registrar el evento, por lo que que cuando se compila el paquete web, los eventos registrados por el complemento se ejecutarán cuando se active el gancho.

Ganchos dentro de Webpack

La esencia de los ganchos es: eventos . Para facilitar nuestra intervención directa y control del proceso de compilación, webpack encapsula varios eventos clave activados durante el proceso de compilación en interfaces de eventos y los expone. Estas interfaces se denominan vívidamente ganchos. El desarrollo de complementos es inseparable de estos ganchos.

Tapable

Tapable proporciona una definición de tipo de interfaz de complemento unificada (gancho) para webpack, que es la biblioteca de funciones principales de webpack. Actualmente hay diez tipos de ganchos en el paquete web, que se pueden ver en el código fuente de Tapable:

// 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 también expone tres métodos de complementos para inyectar diferentes tipos de comportamientos de compilación personalizados:
tap: puede registrar ganchos sincrónicos y asincrónicos.
tapAsync: registra un enlace asincrónico en modo de devolución de llamada.
tapPromise: registra ganchos asincrónicos en modo Promesa.

Objeto de construcción de complemento

Compilador

La configuración completa del entorno Webpack se almacena en el objeto del compilador. Es un objeto único que solo se crea una vez cada vez que se inicia la compilación de un paquete web.

Este objeto se creará cuando Webpack se inicie por primera vez. Podemos acceder a la configuración del entorno principal de Webapck, como el cargador, el complemento y otra información de configuración a través del objeto compilador.

Tiene las siguientes propiedades principales:
compiler.options puede acceder a todos los archivos de configuración al iniciar el paquete web esta vez, incluida, entre otras, información de configuración completa, como cargadores, entradas, salidas, complementos, etc.
compiler.inputFileSystem y compiler.outputFileSystem pueden realizar operaciones de archivos, que son equivalentes a fs en Nodejs.
compiler.hooks puede registrar diferentes tipos de ganchos tapables, de modo que se pueda implantar una lógica diferente en el ciclo de vida del compilador.

documentación de ganchos del compilador

Compilacion

El objeto de compilación representa la construcción de un recurso y la instancia de compilación tiene acceso a todos los módulos y sus dependencias.

Un objeto de compilación compilará todos los módulos en el gráfico de dependencia de compilación. Durante la fase de compilación, los módulos se cargan, sellan, optimizan, fragmentan, procesan y restauran.

Tiene las siguientes propiedades principales:
compilación.modules tiene acceso a todos los módulos y cada archivo empaquetado es un módulo.
El fragmento de compilación.chunks es un bloque de código compuesto por varios módulos. Los recursos introducidos por el archivo de entrada forman un fragmento y los módulos separados por código son otro fragmento.
Compilation.assets puede acceder a los resultados de todos los archivos generados por este paquete.
Compilation.hooks puede registrar diferentes tipos de Hooks que se pueden tocar, que se utilizan para agregar y modificar la lógica durante la fase del módulo de compilación.

documentación de ganchos de compilación

ciclo vital

Insertar descripción de la imagen aquí

Desarrollar un complemento

  1. Cuando el paquete web lee la configuración, el nuevo TestPlugin() ejecutará el método constructor del complemento.
  2. webpack crea un objeto compilador
  3. Recorra todos los complementos y llame al método de aplicación del complemento
  4. Ejecute el resto del proceso de compilación (inicie cada evento de gancho)

complementos/test-plugin.js:

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

El uso de complementos debe configurarse en webpack.config.js:

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

Registrar ganchos

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;

Iniciar la depuración

Vea los datos del compilador y del objeto de compilación mediante la depuración.

Instrucciones de configuración de package.json:

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

Ejecutar comando:npm run debug

En este momento, la consola genera el siguiente contenido:

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

Abra el navegador Chrome y presione F12 para abrir la consola de depuración del navegador. En este momento, la consola mostrará un ícono verde:
Insertar descripción de la imagen aquí
haga clic en el ícono verde para ingresar al modo de depuración.

Utilice el punto de interrupción del depurador donde el código debe depurarse y el código dejará de ejecutarse, lo que le permitirá depurar y ver los datos.

BannerWebpackPlugin

Función: agregar comentarios al archivo de salida empaquetado.

Agregue comentarios antes de empaquetar la salida: debe usar el gancho compilador.hooks.emit , que se activa antes de empaquetar la salida. Compilation.assets puede obtener todos los archivos de recursos que se generarán.

// 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;

Complemento CleanWebpack

Función: borre el último contenido empaquetado antes de que el paquete web empaquete la salida, es decirclean: true;

¿Cómo ejecutar antes de la salida del paquete?
Debe utilizar compiler.hooks.emitun gancho, que se activa antes de empaquetar la salida.

¿Cómo borrar el último contenido empaquetado?
Obtenga el directorio de salida del paquete: a través del objeto del compilador.
Borrar contenido mediante operaciones de archivos: opere archivos a través de compilador.outputFileSystem.

// 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;

AnalizarWebpackPlugin

Función: analiza el tamaño de los recursos empaquetados del paquete web y genera el archivo de análisis.

¿Dónde hacerlo? compiler.hooks.emitSe activa antes de empaquetar la salida. Necesitamos analizar el tamaño del recurso y agregar el archivo md analizado.

// 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

Función: el archivo de tiempo de ejecución generado por el empaquetado del paquete web es demasiado pequeño y el rendimiento del envío de solicitudes adicionales no es bueno, por lo que debe integrarse en js para reducir la cantidad de solicitudes.

Debe html-webpack-pluginlograrse con la ayuda de inyectar el tiempo de ejecución en línea en el complemento html-webpack antes de generar index.html y eliminar los archivos de tiempo de ejecución redundantes.

// 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;

Documentación de referencia

Supongo que te gusta

Origin blog.csdn.net/zag666/article/details/131948452
Recomendado
Clasificación