图解Webpack——实现Plugin

简介: 图解Webpack——实现Plugin

面试工作加分项!

Plugin是webpack生态系统的重要组成部分,其目的是解决loader无法实现的其他事,可用于执行范围更广的任务,为webpack带来很大的灵活性。目前存在的plugin并不能完全满足所有的开发需求,所以定制化符合自己需求的plugin成为学习webpack的必经之路。下面将逐步阐述plugin开发中几个关键技术点并实现plugin。


640.jpg

一、Webpack构建流程


在实现自己的Plugin之前,需要了解一下Webpack的构建流程(下图)。Webpack的构建流程类似于一条生产线(webpack的事件流机制),在特定时机会广播对应的事件,插件就可以监听这些事件的发生,从而在特定的时机做特定的事情。

640.png

上图中展示了Webpack构建流程的三个阶段及每个阶段广播出来的事件。


  1. 初始化阶段:启动构建;紧接着从配置文件和Shell语句中读取并合并参数,得到最终参数;用上一步得到的参数初始化Compiler对象并加载所有配置的插件。
  2. 从Entey出发,针对每个Module串行调用对应的Loader去翻译文件的内容,再找到该Module依赖的Module,递归地进行编译处理,得到每个模块被翻译后的最终内容及它们之间的依赖关系。
  3. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再将每个Chunk转换成一个单独的文件加入到输出列表中,最终将所需输出文件的内容写入文件系统中。


二、编写Plugin



编写Plugin主要分为四个步骤:

  • 创建一个具名JavaScript函数
  • 在其原型上定义apply方法
  • 给webpack构建流程中相关事件挂载事件钩子进行监听
  • 钩子函数触发后,利用compiler、compilation等进行相关操作,达到需要的效果


2.1 基本结构


class MyPlugin {
    constructor(options) {}
    apply(compiler) {
        console.log(compiler);
        compiler.hooks.emit.tap('MyPlugin', () => {
            console.log('plugin is used!!!');
        })
    }
}
module.exports = MyPlugin;


2.2 apply方法


apply 方法在安装插件时,会被 webpack compiler 调用一次。apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。


2.3 钩子函数


在webpack整个编译过程中暴露出来大量的Hook供内部/外部插件使用,将Hook注册后即可实现对整个webpack事件流中对应事件的监听。这些钩子函数的核心是Tapable,该包暴露出很多钩子类,利用这些类创建了上述钩子函数。常见的钩子主要有以下几种:

640.png


暴露在compiler和compilation上的钩子函数均是基于上述钩子构建。

640.png

2.4 钩子函数注册方式

640.png


在plugin中,钩子注册方式有三种:tap、tapAsync、tapPromise,根据钩子函数的类型采用相应的方法进行注册。同步钩子函数利用tap注册,异步钩子函数利用tap、tapAsync、tapPromise进行注册。


  1. tap
    tap可以用来注册同步钩子也能用来注册异步钩子。


compiler.hooks.compile.tap('MyPlugin', compilationParams => {
    console.log('以同步方式触及compile钩子')
});
  1. tapAsync
    tapAsync能够用来注册异步钩子,并通过callback告知Webpack异步逻辑执行完毕。
compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
    console.log('tapAsync 异步');
    callback();
});
  1. tapPromise
    tapPromise能够用来注册异步钩子,其通过返回Promise来告知Webpack异步逻辑执行完毕。(实现方式有两种,一种是通过返回Promse函数,另一种是利用async实现)。


// 方式一
compiler.hooks.run.tapPromise('MyPlugin', (compiler) => {
    return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
        console.log('tapPromise 异步')
    })
})
// 方式二
compiler.hooks.run.tapPromise('MyPlugin', async (compiler) => {
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log('tapPromise 异步 async')
})


2.5 Compiler和Compilation


Compiler和Compilation是Plugin和Webpack之间的桥梁,所以了解其具体含义至关重要,其含义如下:


  • Compiler 对象包含了 Webpack 环境的所有配置信息,包含options、loaders、plugins等信息。这个对象在 Webpack 启动时被实例化,它是全局唯一的,可以简单地将它理解为 Webpack 实例。


  • Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack以开发模式运行时,每当检测到一个文件发生变化,便有一次新的 Compilation 被 创建。Compilation对象也提供了很多事件回调供插件进行扩展。通过 Compilation也能读取到 Compiler 对象。


三、自定义钩子函数


640.png

难道钩子函数只有官方给的哪些吗?肯定不是的,我们能够自己按照自己的需求实现一个钩子函数,实现该钩子函数主要分为以下几个步骤:


  1. 调用tabable包,选择合适的钩子
  2. 将自己定义的钩子函数挂载到compiler或compilation上
  3. 在需要的位置注册该钩子函数
  4. 在你所需要的时机触发对应的钩子函数


// 该代码中有注册的同步钩子函数和异步钩子函数和其触发过程
const { SyncHook, AsyncSeriesHook } = require('tapable');
class MyHookPlugin {
    constructor() {}
    apply(compiler) {
        // 挂载
        compiler.hooks.myHook = new SyncHook(['arg1', 'arg2']);
        compiler.hooks.myAsyncHook = new AsyncSeriesHook(['arg1', 'arg2']);
        // 注册
        // 同步
        compiler.hooks.myHook.tap('myHook', (arg1, arg2) => {
            console.log('自己定义的钩子函数被触发', arg1, arg2);
        })
        // 异步1
        compiler.hooks.myAsyncHook.tapAsync('myHook', (arg1, arg2, callback) => {
            console.log('异步钩子 tapAsync ', arg1, arg2);
            callback();
        });
        // 异步2
        compiler.hooks.myAsyncHook.tapPromise('myHook', (arg1, arg2) => {
            return new Promise((resolve) => {
                resolve({arg1, arg2});
            }).then((context) => {
                console.log('异步钩子 tapPromise', context)
            })
        });
        compiler.hooks.environment.tap('myHookPlugin', () => {
            // 触发
            // 同步
            compiler.hooks.myHook.call(1, 2);
            // 异步1
            compiler.hooks.myAsyncHook.callAsync(1, 2, err => {
                console.log('触发完毕……   callAsync')
            })
            // 异步2
            compiler.hooks.myAsyncHook.promise(1, 2).then(err => {
                console.log('触发完毕……   promise')
            })
        })
    }
}
module.exports = MyHookPlugin;


四、实现Plugin



本节是Plugin实战,在生成资源到output目录之前生成资源清单。


class MyPlugin {
    constructor(options) {
    }
    apply(compiler) {
        compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
            console.log('plugin is used!!!', "emit事件");
            const mainfest = {}
            for (const name of Object.keys(compilation.assets)) {
                // compilation.assets[name].size()获取输出文件的大小;compilation.assets[name].source()获取内容
                mainfest[name] = compilation.assets[name].size();
                console.log(compilation.assets[name].source())
            }
            compilation.assets['mainfest.json'] = {
                source() {
                    return JSON.stringify(mainfest);
                },
                size() {
                    return this.source().length;
                }
            };
            callback();
        });
    }
}
module.exports = MyPlugin;


注:本文只是起到抛砖引玉的作用,希望各位大佬多多指点。

相关文章
|
JSON 前端开发 JavaScript
浅谈一下 webpack 以及 loader 和 plugin
浅谈一下 webpack 以及 loader 和 plugin
180 0
|
19天前
|
测试技术 开发者
如何确保 Webpack plugin 与其他插件的兼容性?
【10月更文挑战第23天】确保 Webpack plugin 与其他插件的兼容性需要从多个方面进行考虑和努力。通过遵循规范、进行充分测试、保持沟通协作等方式,
|
19天前
|
算法 测试技术 开发者
编写 Webpack plugin 时需要注意什么?
【10月更文挑战第23天】 编写 Webpack plugin 需要综合考虑多个方面的因素。只有在充分理解和掌握这些要点的基础上,才能编写出高质量、可靠且实用的 Plugin,为 Webpack 构建过程带来更多的价值和便利。
|
19天前
|
搜索推荐 测试技术 开发者
写一个 webpack plugin
【10月更文挑战第23天】编写一个 Webpack plugin 需要对 Webpack 的工作原理和机制有深入的了解,同时需要具备良好的编程能力和逻辑思维。通过合理设计和实现,Plugin 可以为我们的 Webpack 构建过程带来更多的灵活性和个性化。
|
1月前
|
前端开发 JavaScript
Webpack 常用 Loader 和 Plugin
【10月更文挑战第12天】Webpack 是一个强大的模块打包工具,能够将各种资源模块进行打包和处理。Loader 用于转换模块的源代码,如 `babel-loader` 将 ES6+ 代码转换为 ES5,`css-loader` 处理 CSS 文件等。Plugin 扩展 Webpack 功能,如 `HtmlWebpackPlugin` 自动生成 HTML 文件,`UglifyJsPlugin` 压缩 JavaScript 代码。通过合理配置和使用 Loader 和 Plugin,可以构建高效、优化的项目。
21 2
|
1月前
|
移动开发 JavaScript 前端开发
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
这篇文章主要介绍了如何通过配置Webpack的插件,如HtmlWebpackPlugin、uglifyjs-webpack-plugin和webpack-dev-server,来简化前端开发流程。
39 0
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
|
2月前
|
设计模式 前端开发 JavaScript
webpack实战之手写一个loader和plugin
该文章详细讲解了如何从零开始编写一个自定义的Webpack Loader和Plugin,包括它们的工作原理、开发步骤以及如何将自定义的Loader和Plugin集成到Webpack配置中。
webpack实战之手写一个loader和plugin
|
6月前
|
自然语言处理 JavaScript 前端开发
webpack实战——手写常用plugin
webpack实战——手写常用plugin
|
6月前
|
前端开发 JavaScript
Webpack中的Loader和Plugin:理解与使用
Webpack中的Loader和Plugin:理解与使用
|
6月前
|
前端开发 JavaScript
webpack 核心武器:loader 和 plugin 的使用指南(下)
webpack 核心武器:loader 和 plugin 的使用指南(下)
webpack 核心武器:loader 和 plugin 的使用指南(下)