CcbeanBlog CcbeanBlog
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Ccbean

靡不有初,鲜克有终
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 邂逅Webpack
  • Webpack的配置和处理CSS资源
  • Webpack加载和处理其它资源
  • Webpak模块化原理
  • Webpack中的source-map
  • Webpack中的babel
  • Webpack中的DevServer和HMR
  • Webpack环境分离和代码分离
  • Webpack中的DLL、Terser和ScopeHoisting
  • Webpack中的TreeShaking以及其它优化
  • Webpack打包分析
  • Webpack自定义Loader
  • Webpack自定义Plugin
    • Compiler和Compilation
    • Webpack和Tapable
    • 自定义插件
  • 分析React和Vue脚手架
  • Gulp的使用
  • Rollup的使用
  • Vite的使用
  • Webpack5使用学习
ccbean
2022-06-25
目录

Webpack自定义Plugin

# Webpack自定义Plugin

# Compiler和Compilation

webpack有两个非常重要的类:Compiler和Compilation。

Compiler和Compilation的区别如下:

  • Compiler:在Webpack构建之初就会创建此对象,并且在webpack的整个生命周期中都会存在(before - run - beforeCompiler - compile - make - finishMake - afterCompiler),只要是webpack编译,都会创建一个Compiler,也就是每次执行如npm run build时会创建Compiler对象
  • Compilation是到准备编译模块(如main.js),才会创建Compilation对象,主要存在于 compile之后 - make之前,且是make阶段主要使用的对象。

那么为什么要引入Compilation,只使用Compiler是否可以?

答案是不可以的。Compiler在整个生命周期中只使用一个对象即可。但是Compilation是每次编译都会创建新的。

比如webpack开启了watch,只要是原代码发生了变化,就需要重新编译模块;此时重新编译,如果再创建Compiler对象显然是不合理的,Compiler对象在初始化过程中做了很多操作,详见createCompiler。原代码发生改变,Compiler是可以继续使用的。

那么这个重新编译的工作,就可以创建一个新的Compilation对象来做编译。它们两个的生命周期不同,使用阶段也就不同。

# Webpack和Tapable

Compiler和Compilation是通过注入插件的方式,来监听webpack的所有生命周期。

插件的注入离不开各种各样的Hook,而他们的Hook是如何得到的呢?

其实是创建了Tapable库中的各种Hook的实例;所以,如果我们想要学习自定义插件,最好先了解Tapable (opens new window)

Tapable是官方编写和维护的一个库;这个库对外提供了很多Hook类,可以使用这些类创建插件的Hook。

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
} = require("tapable");

在Webpack文档中可以看到这些由Tapable提供的生命周期Hook,包括compiler 钩子 (opens new window)和compilation 钩子 (opens new window)

Hook 的类型可以按照 事件回调的运行逻辑 或者 触发事件的方式 来分类。

事件回调的运行逻辑可分为四种:

  • Basic:基础类型,单纯地调用注册的事件回调,并不关心其内部的运行逻辑;
  • Bail:保险类型,当一个事件在运行时返回值不为undefined时,就会停止后面事件回调的执行;
  • Waterfall:瀑布类型,如果当前执行的事件回调返回值不为undefined时,会将这次返回的结果作为下一个回调事件的第一个参数;
  • Loop:循环类型,如果当前执行事件回调函数的返回值不为undefined,重新从第一个注册的事件回调执行,直到当前执行的事件回调没有返回值。

触发事件的方式可分同步和异步两种:

  • Sync:Sync开头的Hook类只能用tap方法注册事件回调,这类事件回调是同步执行的;如果使用tapAsync或tapPromise方法注册则会报错。
  • Async:Async开头的Hook类,无法用call方法触发事件,必须用callAsync或者promise方法触发,这两个方法都能触发tap、tapAsync、tapPromise注册的事件回调。按照串行和并行,异步Hook类又分为:
    • AsyncSeries:按照顺序执行,当前事件回调如果是异步的,那么会等异步执行完毕才会执行下一个事件回调;
    • AsyncParelle:并行执行所有的事件回调。

# 自定义插件

在之前的学习中,已经使用了非常多的Plugin,如CleanWebpackPlugin、HTMLWebpackPlugin、MiniCSSExtractPlugin、CompressionPlugin。

这些Plugin被注册到webpack的生命周期中的过程如下:

  1. 在webpack函数的createCompiler方法中,注册了所有的插件;
  2. 在注册插件时,会调用插件函数或者插件对象的apply方法;
  3. 插件方法会接收compiler对象,我们可以通过compiler对象来注册Hook的事件;
  4. 某些插件也会传入一个compilation的对象,我们也可以监听compilation的Hook事件;

这里示例演示,将静态文件上传到指定服务器。

那么上传时机在资源输出到目录后,应该监听afterEmit (opens new window)钩子,这个钩子会在输出 asset 到 output 目录之后执行。

创建插件/plugins/AutoUploadWebpackPlugin.js

const { NodeSSH } = require('node-ssh');

class AutoUploadWebpackPlugin {
  constructor(options) {
    this.options = options;
    this.ssh = new NodeSSH();
  }

  // 插件必须实现apply方法
  apply(compiler) {
    // 监听输出 asset 到 output 目录之后执行的Hook
    compiler.hooks.afterEmit.tapAsync('AutoUploadWebpackPlugin', async (compilation, callback) => {
      // 1. 获取打包输出目录
      const outputPath = compilation.outputOptions.path;
      console.log('打包了', outputPath)

      // 2. 创建SSH连接
      await this.connectServer()

      // 3. 删除服务端之前的资源文件
      const serverDir = this.options.remotePath;
      this.ssh.execCommand(`rm -rf ${serverDir}/*`);

      // 4. 上传文件
      this.uploadFiles(outputPath, serverDir)

      // 5. 断开SSH连接
      this.ssh.dispose();

      callback();
    });
  }

  async connectServer() {
    try {
      await this.ssh.connect({
        host: this.options.host,
        username: this.options.username,
        password: this.options.password
      });
      console.log('连接成功~');
    } catch (error) {
      console.log('连接失败:', error);
    }
  }

  async uploadFiles(localPath, remotePath) {
    const status = await this.ssh.putDirectory(localPath, remotePath, {
      recursive: true,
      concurrency: 10
    });
    console.log('传送到服务器: ', status ? "成功": "失败");
  }
}

module.exports = AutoUploadWebpackPlugin;

在Webpack中配置插件:

const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AutoUploadWebpackPlugin = require('./plugins/AutoUploadWebpackPlugin');

module.exports = {
  mode: "development",
  devtool: "source-map",
  context: path.resolve(__dirname, "."),
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "./build"),
    filename: "bundle.js",
  },
  module: {
    rules: [
    ],
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new AutoUploadWebpackPlugin({
      host: '127.0.0.1',
      username: 'ccbean',
      password: '**********',
      remotePath: '/root/server'
    })
  ],
};

开发插件本质就是监听Webpack生命周期,然后进行相关的功能开发。

编辑 (opens new window)
上次更新: 2022/06/30, 22:20:12
Webpack自定义Loader
分析React和Vue脚手架

← Webpack自定义Loader 分析React和Vue脚手架→

最近更新
01
阅读精通正则表达式总结
09-29
02
项目搭建规范的配置
07-15
03
Vite的使用
07-03
更多文章>
Theme by Vdoing | Copyright © 2018-2023 Ccbeango
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式