共计 5309 个字符,预计需要花费 14 分钟才能阅读完成。
大家有没有遇到过这些问题:
- webpack 打包之后的文件没有压缩
- 动态文件要手动拷贝到输入目录
- 代码中写了很多环境判断的多余代码
上一篇「webpack 外围个性」loader 说到 webpack 的 loader
机制,本文次要聊一聊另外一个外围个性:插件(plugin
)。
插件机制就是为了实现我的项目中除了资源模块打包以外的其余自动化工作,解决上述的问题。
plugin
是用来扩大 webpack 性能的,通过在构建流程里注入钩子实现,它为 webpack 带来了很大的灵活性。
plugin 绝对于 loader 有哪些区别?
loader
是转换器,将一种文件编译转换为另一个文件,操作的是文件。例如:将 .less
文件转换成 .css
文件。
plugin
是扩展器,它是针对 loader
完结之后,打包的整个过程。它并不间接操作文件,而是基于事件机制工作。在 webpack 构建流程中的特定时机会播送对应的事件,插件能够监听这些事件的产生,在特定的机会做对应的事件。包含:打包优化,资源管理,注入环境变量。
plugin 该怎么配置呢?
例如 HtmlWebpackPlugin
能够为咱们生成一个 HTML 文件,其中包含应用 script
标签的 body 中的所有模块。看下如何配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpackConfig = {
...
plugins: [new HtmlWebpackPlugin()]
};
舒适提醒:
loader
在 module.rules 中配置,作为模块的解析规定,类型为数组。每一项都是一个对象,外部蕴含了 test(类型文件)、loader、options(参数)等属性。plugin
则独自配置,类型为数组,每一项是一个plugin
的实例,参数都通过构造函数传入。
有哪些常见的 plugin?
上面整顿的插件列表来自 webpack 中武官网,大家看见不相熟的 plugin
能够点击名称跳转,看一看,理解一下具体玩法。
名称 | 形容 |
---|---|
AggressiveSplittingPlugin | 将原来的 chunk 分成更小的 chunk |
BabelMinifyWebpackPlugin | 应用 babel-minify 进行压缩 |
BannerPlugin | 在每个生成的 chunk 顶部增加 banner |
CommonsChunkPlugin | 提取 chunks 之间共享的通用模块 |
CompressionWebpackPlugin | 事后筹备的资源压缩版本,应用 Content-Encoding 提供拜访服务 |
ContextReplacementPlugin | 重写 require 表达式的推断上下文 |
CopyWebpackPlugin | 将单个文件或整个目录复制到构建目录 |
DefinePlugin | 容许在编译时 (compile time) 配置的全局常量 |
DllPlugin | 为了极大缩小构建工夫,进行 dll 分包 |
EnvironmentPlugin | DefinePlugin 中 process.env 键的简写形式。 |
ExtractTextWebpackPlugin | 从 bundle 中提取文本(CSS)到独自的文件 |
HotModuleReplacementPlugin | 启用模块热替换(Enable Hot Module Replacement – HMR) |
HtmlWebpackPlugin | 简略创立 HTML 文件,用于服务器拜访 |
I18nWebpackPlugin | 为 bundle 减少国际化反对 |
IgnorePlugin | 从 bundle 中排除某些模块 |
LimitChunkCountPlugin | 设置 chunk 的最小 / 最大限度,以微调和管制 chunk |
LoaderOptionsPlugin | 用于从 webpack 1 迁徙到 webpack 2 |
MinChunkSizePlugin | 确保 chunk 大小超过指定限度 |
NoEmitOnErrorsPlugin | 在输入阶段时,遇到编译谬误跳过 |
NormalModuleReplacementPlugin | 替换与正则表达式匹配的资源 |
NpmInstallWebpackPlugin | 在开发时主动装置短少的依赖 |
ProvidePlugin | 不用通过 import/require 应用模块 |
SourceMapDevToolPlugin | 对 source map 进行更细粒度的管制 |
EvalSourceMapDevToolPlugin | 对 eval source map 进行更细粒度的管制 |
UglifyjsWebpackPlugin | 能够管制我的项目中 UglifyJS 的版本 |
ZopfliWebpackPlugin | 通过 node-zopfli 将资源事后压缩的版本 |
怎么写一个 plugin?
在说怎么写插件之前,先简略介绍几个好玩的货色:tapable
、compiler
和 compilation
。##### 参考 webpack 视频解说:进入学习
tapable
tapable
是一个相似于 nodejs
的 EventEmitter
的库, 次要是管制钩子函数的公布与订阅。当然,tapable
提供的 hook
机制比拟全面,分为同步和异步两个大类(异步中又辨别异步并行和异步串行),而依据事件执行的终止条件的不同,由衍生出 Bail
/ Waterfall
/ Loop
类型。
根本应用:
const {SyncHook} = require('tapable');
const hook = new SyncHook(['name']);
hook.tap('hello', (name) => {console.log(`hello ${name}`);
});
hook.tap('hi', (name) => {console.log(`hi ${name}`);
});
hook.call('july');
// hello july
// hi july
小结:
tapable
根本逻辑是,先通过类实例的 tap
办法注册对应 hook
的处理函数,而后通过 call
办法触发回调函数。
compiler
compiler
对象蕴含 webpack 环境所有配置信息,蕴含 options、loaders 和 plugins 等信息。能够简略了解为 webpack 实例。代表整个 webpack 从启动到敞开的生命周期。
compile
的外部实现:
class Compiler extends Tapable {constructor(context) {super();
this.hooks = {/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
...
};
...
}
小结:
compile
继承了 tapable
,而后在实例上绑定了一个 hook
对象。
compilation
compilation
对象蕴含了以后的模块资源、编译生成资源和变动的文件等。仅代表一次新的编译。
compilation
的实现:
class Compilation extends Tapable {/** * Creates an instance of Compilation. * @param {Compiler} compiler the compiler which created the compilation */
constructor(compiler) {super();
this.hooks = {/** @type {SyncHook<Module>} */
buildModule: new SyncHook(["module"]),
/** @type {SyncHook<Module>} */
rebuildModule: new SyncHook(["module"]),
/** @type {SyncHook<Module, Error>} */
failedModule: new SyncHook(["module", "error"]),
/** @type {SyncHook<Module>} */
succeedModule: new SyncHook(["module"]),
/** @type {SyncHook<Dependency, string>} */
addEntry: new SyncHook(["entry", "name"]),
/** @type {SyncHook<Dependency, string, Error>} */
};
}
}
当运行 webpack 开发环境中间件时,每当检测到一个文件变动,就会创立一个新的 compilation
,从而生成一组新的编译资源。一个 compilation
对象体现了以后的模块资源、编译生成资源、变动的文件、以及被跟踪依赖的状态信息。compilation
对象也提供了很多要害机会的回调,以供插件做自定义解决时抉择应用。
热身
写一个最根底的 plugin
:
// 一个 JavaScript 命名函数。function SimplePlugin() {}
// 在插件函数的 prototype 上定义一个 `apply` 办法。SimplePlugin.prototype.apply = function (compiler) {
// 指定一个挂载到 webpack 本身的事件钩子。compiler.plugin("webpacksEventHook", function (compilation /* 解决 webpack 外部实例的特定数据。*/, callback) {console.log("This is an simple plugin!!!");
// 性能实现后调用 webpack 提供的回调。callback();});
};
webpack 启动后,做了上面几件事件:
- 在读取配置的过程中先执行
new SimplePlugin()
,初始化一个 SimplePlugin 并取得其实例。 - 在初始化
compiler
对象后,再调用SimplePlugin.apply(compiler)
为插件实例传入compiler
对象。 - 插件实例在获取到
compiler
对象后,就能够通过compiler.plugin("webpacksEventHook", function(compilation, callback){})
监听到 webpack 播送的事件,并且通过compiler
对象去操作 webpack。
实战
上面写一个实用的插件。
作用是在 webpack 马上敞开时做一些事件。例如告知用打包实现,是否执行胜利。或者执行一些 script
脚本。咱们将其命名为 AfterWebpackPlugin
。用法如下:
module.exports = {
plugins: [
// 别离传入胜利和失败时的回调函数
new AfterWebpackPlugin(() => {// 能够告诉用户构建胜利,执行公布脚本},
(err) => {
// 构建失败时,抛出异样
console.error(err);
}
),
],
};
这里实现过程须要借助以下两个钩子:
- done:在胜利构建并且输入文件后,webpack 马上退出时产生。
- failed:在构建异样时导致构建失败,webpack 马上退出时产生。
实现代码如下:
class AfterWebpackPlugin {constructor(doneCb, failedCb) {
// 传入回调函数
this.doneCb = doneCb;
this.failedCb = failedCb;
}
apply(compiler) {compiler.plugin("done", (res) => {
// webpack 生命周期 `done` 中的回调
this.doneCb(res);
});
compiler.plugin("failed", (err) => {
// webpack 生命周期 `failed` 中的回调
this.failedCb(err);
});
}
}
module.exports = AfterWebpackPlugin;
开发插件小结:
- 留神在 webpack 生命周期中找到适合的钩子去实现性能。
- 留神了解 webpack 生命周期中各个钩子的轻微区别。
拓展
webpack 输入阶段 会产生的事件及解释如下:
事件名 | 解释 |
---|---|
should-emit | 所有须要输入的文件曾经生成,询问插件有哪些文件须要输入,有哪些不须要输入 |
emit | 确定好要输入哪些文件后,执行文件输入,能够在这里获取和批改输入的内容 |
after-emit | 文件输入结束 |
done | 胜利实现一次残缺的编译和输入流程 |
failed | 如果在编译和输入的流程遇到异样,导致 webpack 退出,就会间接跳转到本步骤,插件能够在本事件中获取具体谬误起因 |
系列文章
- 「webpack 外围个性」loader
- 「webpack 外围个性」插件(plugin)
- 「webpack 外围个性」模块热替换(HMR)
感激
- 如果本文对你有帮忙,就点个赞反对下吧!感激浏览。