揭秘webpack plugin

时间: 2019-12-23阅读: 599标签: 插件

前言

Plugin(插件) 是 webpack 生态的的一个关键部分。它为社区提供了一种强大的方法来扩展 webpack 和开发 webpack 的编译过程。这篇文章将尝试探索 webpack plugin,揭秘它的工作原理,以及如何开发一个 plugin。


一、Plugin 的作用

关于 Plugin 的作用,引用一下 webpack 官方的介绍:

Plugins expose the full potential of the webpack engine to third-party developers. Using staged build callbacks, developers can introduce their own behaviors into the webpack build process.

翻译成“人话”就是:
通过插件我们可以扩展 webpack,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力。


二、Plugin 工作原理

Plugin 的工作原理,简单来说,就是webpack在编译过程中提供了一些生命周期钩子,我们的插件会在这些钩子事件中注入我们要执行的任务(注册处理器),当 webpack 执行构建的时候,它的 Tapable 事件流会自动调用这些钩子,从而触发我们注入的方法,执行我们的自定义任务。


三、webpack 的一些底层逻辑

开发一个 plugin 比开发一个 loader 更高级一些,因为我们会用到一些 webpack 比较底层的内部组件。因此我们需要了解一些 webpack 的底层逻辑。

webpack 内部执行流程

一次完整的 webpack 打包大致是这样的过程:

将命令行参数与 webpack 配置文件 合并、解析得到参数对象。

参数对象传给 webpack 执行得到 Compiler 对象。

执行 Compiler 的 run方法开始编译。每次执行 run 编译都会生成一个 Compilation 对象。

触发 Compiler 的 make方法分析入口文件,调用 compilation 的 buildModule 方法创建主模块对象。

生成入口文件 AST(抽象语法树),通过 AST 分析和递归加载依赖模块。

所有模块分析完成后,执行 compilation 的 seal 方法对每个 chunk 进行整理、优化、封装。

最后执行 Compiler 的 emitAssets 方法把生成的文件输出到 output 的目录中。

webpack 底层基本流程图


webpack 内部的一些钩子

在 webpack 编译打包的过程中,会触发一些关键事件,为了方便我们直接介入和控制编译过程,webpack 把这些事件封装成接口暴露了出来,这些接口被叫做 hooks(钩子)。开发插件,离不开这些钩子。

Tapable

Tapable 是 webpack 的核心功能库,它为 webpack 提供了统一的插件接口(钩子)类型定义。webpack 中目前有十种 hooks,在 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");

Tapable 还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:

tap:注册钩子,同步钩子和异步钩子都可以使用。

tapAsync:回调方式注册异步钩子。

tapPromise:Promise方式注册异步钩子。

webpack 里有几个非常重要的对象,他们是 Compiler, Compilation 和 JavaScriptParser。这些对象都继承了 Tapable 类,身上都挂着丰富的钩子,用于注册和调用插件。

Compiler Hooks

Compiler 编译器模块是创建编译实例的主引擎。大多数面向用户的插件都首先在 Compiler 上注册。

compiler上暴露的一些常用的钩子:

钩子类型什么时候调用
runAsyncSeriesHook在编译器开始读取记录前执行
compileSyncHook在一个新的compilation创建之前执行
compilationSyncHook在一次compilation创建后执行插件
makeAsyncParallelHook完成一次编译之前执行
emitAsyncSeriesHook在生成文件到output目录之前执行,回调参数: compilation
afterEmitAsyncSeriesHook在生成文件到output目录之后执行
assetEmittedAsyncSeriesHook生成文件的时候执行,提供访问产出文件信息的入口,回调参数:file,info
doneAsyncSeriesHook一次编译完成后执行,回调参数:stats

Compilation Hooks

Compilation 是 Compiler 用来创建一次新的编译过程的模块。一个 Compilation 实例可以访问所有模块和它们的依赖。在一次编译阶段,模块被加载、封装、优化、分块、散列和还原。
Compilation 也继承了 Tapabl 并提供了很多生命周期钩子。

Compilation 上暴露的一些常用的钩子:

钩子类型什么时候调用
buildModuleSyncHook在模块开始编译之前触发,可以用于修改模块
succeedModuleSyncHook当一个模块被成功编译,会执行这个钩子
finishModulesAsyncSeriesHook当所有模块都编译成功后被调用
sealSyncHook当一次compilation停止接收新模块时触发
optimizeDependenciesSyncBailHook在依赖优化的开始执行
optimizeSyncHook在优化阶段的开始执行
optimizeModulesSyncBailHook在模块优化阶段开始时执行,插件可以在这个钩子里执行对模块的优化,回调参数:modules
optimizeChunksSyncBailHook代码块优化阶段开始时执行,插件可以在这个钩子里执行对代码块的优化,回调参数:chunks
optimizeChunkAssetsAsyncSeriesHook优化任何代码块资源,这些资源存放在 compilation.assets 上。一个 chunk 有一个 files 属性,它指向由一个chunk创建的所有文件。任何额外的 chunk 资源都存放在 compilation.additionalChunkAssets 上。回调参数:chunks
optimizeAssetsAsyncSeriesHook优化所有存放在 compilation.assets 的所有资源。回调参数:assets

JavaScriptParser Hooks

Parser 解析器实例在 Compiler 编译器中产生,用于解析 webpack 正在处理的每个模块。我们可以用它提供的 Tapable 钩子自定义解析过程。

JavascriptParser 上暴露的一些常用的钩子:

钩子类型什么时候调用
evaluateSyncBailHook在计算表达式的时候调用。
statementSyncBailHook为代码片段中每个已解析的语句调用的通用钩子
importSyncBailHook为代码片段中每个import语句调用,回调参数:statement,source
exportSyncBailHook为代码片段中每个export语句调用,回调参数:statement
callSyncBailHook解析一个call方法的时候调用,回调参数:expression
programSyncBailHook解析一个表达式的时候调用,回调参数:expression

对webpack底层逻辑和tapable钩子有了这些了解后,我们就可以进一步尝试开发一个插件了。


四、如何开发一个webpack plugin

plugin 的基本结构

一个 webpack plugin 由如下部分组成:

一个命名的 Javascript 方法或者 JavaScript 类。

它的原型上需要定义一个叫做 apply 的方法。

注册一个事件钩子。

操作webpack内部实例特定数据

功能完成后,调用webpack提供的回调。

一个基本的 plugin 代码结构大致长这个样子:

// plugins/MyPlugin.js

class MyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('My Plugin', (stats) => {
      console.log('Bravo!');
    });
  }
}

module.exports = MyPlugin;

这就是一个最简单的 webpack 插件了,它注册了 Compiler 上的异步串行钩子 done,在钩子中注入了一条控制台打印的语句。根据上文钩子的介绍我们可以知道,done 会在一次编译完成后执行。所以这个插件会在每次打包结束,向控制台首先输出这句 Bravo!。


开发一个文件清单插件

我希望每次webpack打包后,自动产生一个打包文件清单,上面要记录文件名、文件数量等信息。

思路:

显然这个操作需要在文件生成到dist目录之前进行,所以我们要注册的是Compiler上的emit钩子。

emit 是一个异步串行钩子,我们用 tapAsync 来注册。

在 emit 的回调函数里我们可以拿到 compilation 对象,所有待生成的文件都在它的 assets 属性上。

通过 compilation.assets 获取我们需要的文件信息,并将其整理为新的文件内容准备输出。

然后往 compilation.assets 添加这个新的文件。

插件完成后,最后将写好的插件放到 webpack 配置中,这个包含文件清单的文件就会在每次打包的时候自动生成了。

实现:

// plugins/FileListPlugin.js

class FileListPlugin {
    constructor (options) {
        // 获取插件配置项
        this.filename = options && options.filename ? options.filename : 'FILELIST.md';
    }

    apply(compiler) {
        // 注册 compiler 上的 emit 钩子
        compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
            
            // 通过 compilation.assets 获取文件数量
            let len = Object.keys(compilation.assets).length;

            // 添加统计信息
            let content = `# ${len} file${len>1?'s':''} emitted by webpack\n\n`;

            // 通过 compilation.assets 获取文件名列表
            for(let filename in compilation.assets) {
                content += `- ${filename}\n`;
            }

            // 往 compilation.assets 中添加清单文件
            compilation.assets[this.filename] = {
                // 写入新文件的内容
                source: function() {
                    return content;
                },
                // 新文件大小(给 webapck 输出展示用)
                size: function() {
                    return content.length;
                }
            }

            // 执行回调,让 webpack 继续执行
            cb();
        })
    }
}

module.exports = FileListPlugin;

测试:

在 webpack.config.js 中配置我们自己写的plugin:

plugins: [
    new MyPlugin(),
    new FileListPlugin({
        filename: '_filelist.md'
    })
]

npm run build 执行,可以看到生成了 _filelist.md 文件:


打开 dist 目录,可以看到_filelist.md 文件中列出了 webpack 打包后的文件:


成功!


总结

本文总结了 webpack plugin 的工作原理、wepack底层执行的基本流程以及介绍了 tapable 和常用的 hooks,最后通过两个小例子演示了如何自己开发一个webpack插件。

开发插件并非难如登天的事情,当遇到通过配置无法解决的问题,又一时找不到好的插件时,不如试试自己编写一个插件来解决,相信我,你会越来越强的!

本文的源码均可在这里获取:https://github.com/yc111/webpack-plugin

原文:https://www.cnblogs.com/champyin/p/12198515.html

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/7363

关闭

jQuery图片剪裁插件Cropper.js的使用

1.引入必要的js和css核心文件;2.构建html,可以将图片或canvas直接包裹到一个块级元素中。small时预览框;3.初始化插件及相关参数

chrome插件开发

上手调试:打开chrome://extensions/ 开启开发者模式,选择加载已解压的扩展程序,选中自己的项目文件即可上手调试;引入vue 会出现 安全性问题,需要在manifest.json中设置

原生 js 实现一个有动画效果的进度条插件 progress

一个用于装载进度条内容的 div (且叫做 container)。然后在 container 里面动态生成三个元素,一个是做为背景的 div (且叫做 progress),一个是做为显示进度的 div (且叫做 bar),还有一个是显示文字的 span (且叫做 text)。

总结18个webpack插件

何为插件(Plugin)?专注处理 webpack 在编译过程中的某个特定的任务的功能模块,可以称为插件。Plugin 是一个扩展器,它丰富了 webpack 本身,针对是 loader 结束后,webpack 打包的整个过程

浏览器插件_常用谷歌浏览器插件推荐

常用谷谷歌浏览器确实没有其它国产软件的内置功能丰富。但是 Google 浏览器的的优点恰恰就体现在拥有超简约的界面,以及支持众多强大好用的扩展程序,用户能够按照自己的喜好去个性化定制浏览器。今天我就给大家介绍几款自己常用的插件。

使用CodeMirror插件遇到的问题

结果你会发现,可以复制代码,没问题,但是不可以粘贴复制的代码,那问题到底出现在了哪里呢?首先呢,排除clipboard插件的问题,这个很简单,随便找个例子测试一下就行了

使用jquery-intro插件做页面引导

设置参数: 设置多个格式 json格式:key:value 可设置参数;下载jquery-intro;设置方法(多个参数设置);在页面中引用jquery-intro;定义引用的区块;配置jquery-intro的显示

BlockUI详细用法_Jquery中ajax加载提示插件blickUI

BlockUI 插件是用于进行AJAX操作时模拟同步传输时锁定浏览器操作。当它被激活时,它会阻止使用者与页面(或页面的一部分)进行交互,直至它被取消。BlockUI以在DOM中添加元素的方法来实现阻止用户与浏览器交互的外观和行为

Vue中过滤器及自定义插件

想不出来,把官方的拿过来Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)

webpack插件ProvidePlugin的使用方法和eslint配置

配置 webpack.config.js文件里 plugins 属性,配置完以后就可以在代码里直接使用 _ ,而不再需要 import; 注意:(如果不配置eslint,浏览器就会报错:\\\'_\\\' is not defined no-undef)

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!