YYDS: Webpack Plugin开发

作为一名踏足前端时间不长的小开发必须得聊一聊webpack,刚开始接触webpack时第一反应这是啥(⊙_⊙)? 怎么这么复杂,感觉好难呀,算了先不管这些!时间是个好东西呀,随着对前端工程化的实践和理解慢慢加深,跟webpack接触越来越多,最终还是被ta折服,不禁高呼一声“webpack yyds(永远滴神)!”
去年年中就想写一些关于webpack的文章,由于各种原因耽搁了(主要是觉得对webpack理解还不够,不敢妄自下笔);临近年节,时间也有些了,与其 "摸鱼"不如摸摸webpack,整理一些"年货"分享给需要的xdm!后续会继续写一些【 Webpack】系列文章,xdm监督···
导读
本文主要通过实现一个cdn优化的插件CdnPluginInject介绍下webpack的插件plugin开发的具体流程,中间会涉及到html-webpack-plugin插件的使用、vue/cli3+项目中webpack插件的配置以及webpack相关知识点的说明。全文大概2800+字,预计耗时5~10分钟,希望xdm看完有所学、有所思、有所输出!
注意:文章中实例基于vue/cli3+工程展开!
一、cdn常规使用
index.html:
<head>
···
</head>
<body>
<div id="app"></div>
<script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>
···
</body>
vue.config.js:
module.exports = {
···
configureWebpack: {
···
externals: {
'vuex': 'Vuex',
'vue-router': 'VueRouter',
···
}
},
二、开发一个webpack plugin
webpack官网如此介绍到:插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。创建插件比创建 loader 更加高级,因为你将需要理解一些 webpack 底层的内部特性来实现相应的钩子!
一个插件由以下构成:
- 一个具名 JavaScript 函数。
- 在它的原型上定义 apply 方法。
- 指定一个触及到 webpack 本身的 事件钩子。
- 操作 webpack 内部的实例特定数据。
- 在实现功能后调用 webpack 提供的 callback。
// 一个 JavaScript class
class MyExampleWebpackPlugin {
// 将 `apply` 定义为其原型方法,此方法以 compiler 作为参数
apply(compiler) {
// 指定要附加到的事件钩子函数
compiler.hooks.emit.tapAsync(
'MyExampleWebpackPlugin',
(compilation, callback) => {
console.log('This is an example plugin!');
console.log('Here’s the `compilation` object which represents a single build of assets:', compilation);
// 使用 webpack 提供的 plugin API 操作构建结果
compilation.addModule(/* ... */);
callback();
}
);
}
}
三、cdn优化插件实现
思路:
- 1、创建一个具名
JavaScript函数(使用ES6的class实现); - 2、在它的原型上定义
apply方法; - 3、指定一个触及到 webpack 本身的事件钩子(此处触及
compilation钩子:编译(compilation)创建之后,执行插件); - 4、在钩子事件中操作
index.html(将cdn的script标签插入到index.html中); - 5、在
apply方法执行完之前将cdn的参数放入webpack的外部扩展externals中; - 6、在实现功能后调用
webpack提供的callback;
实现步骤:
1、创建一个具名 JavaScript 函数(使用ES6的class实现)
创建类cdnPluginInject,添加类的构造函数接收传递过来的参数;此处我们定义接收参数的格式如下:
modules:[
{
name: "xxx", //cdn包的名字
var: "xxx", //cdn引入库在项目中使用时的变量名
path: "http://cdn.url/xxx.js" //cdn的url链接地址
},
···
]
定义类的变量modules接收传递的cdn参数的处理结果:
class CdnPluginInject {
constructor({
modules,
}) {
// 如果是数组,将this.modules变换成对象形式
this.modules = Array.isArray(modules) ? { ["defaultCdnModuleKey"]: modules } : modules;
}
···
}
module.exports = CdnPluginInject;
2、在它的原型上定义 apply 方法
插件是由一个构造函数(此构造函数上的 prototype 对象具有
apply方法)的所实例化出来的。这个apply方法在安装插件时,会被 webpack compiler 调用一次。apply方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象
cdnPluginInject.js代码如下:
class CdnPluginInject {
constructor({
modules,
}) {
// 如果是数组,将this.modules变换成对象形式
this.modules = Array.isArray(modules) ? { ["defaultCdnModuleKey"]: modules } : modules;
}
//webpack plugin开发的执行入口apply方法
apply(compiler) {
···
}
module.exports = CdnPluginInject;
3、指定一个触及到 webpack 本身的事件钩子
此处触及compilation钩子:编译(compilation)创建之后,执行插件。

compilation 是 compiler 的一个hooks函数, compilation 会创建一次新的编译过程实例,一个 compilation 实例可以访问所有模块和它们的依赖,在获取到这些模块后,根据需要对其进行操作处理!
class CdnPluginInject {
constructor({
modules,
}) {
// 如果是数组,将this.modules变换成对象形式
this.modules = Array.isArray(modules) ? { ["defaultCdnModuleKey"]: modules } : modules;
}
//webpack plugin开发的执行入口apply方法
apply(compiler) {
//获取webpack的输出配置对象
const { output } = compiler.options;
//处理output.publicPath, 决定最终资源相对于引用它的html文件的相对位置
output.publicPath = output.publicPath || "/";
if (output.publicPath.slice(-1) !== "/") {
output.publicPath += "/";
}
//触发compilation钩子函数
compiler.hooks.compilation.tap("CdnPluginInject", compilation => {
···
}
}
module.exports = CdnPluginInject;
4、在钩子事件中操作index.html
这一步主要是要实现 将cdn的script标签插入到index.html中 ;如何实现呢?在vue项目中webpack进行打包时其实是使用html-webpack-plugin生成.html文件的,所以我们此处也可以借助html-webpack-plugin对html文件进行操作插入cdn的script标签。
// 4.1 引入html-webpack-plugin依赖
const HtmlWebpackPlugin = require("html-webpack-plugin");
class CdnPluginInject {
constructor({
modules,
}) {
// 如果是数组,将this.modules变换成对象形式
this.modules = Array.isArray(modules) ? { ["defaultCdnModuleKey"]: modules } : modules;
}
//webpack plugin开发的执行入口apply方法
apply(compiler) {
//获取webpack的输出配置对象
const { output } = compiler.options;
//处理output.publicPath, 决定最终资源相对于引用它的html文件的相对位置
output.publicPath = output.publicPath || "/";
if (output.publicPath.slice(-1) !== "/") {
output.publicPath += "/";
}
//触发compilation钩子函数
compiler.hooks.compilation.tap("CdnPluginInject", compilation => {
// 4.2 html-webpack-plugin中的hooks函数,当在资源生成之前异步执行
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration
.tapAsync("CdnPluginInject", (data, callback) => { // 注册异步钩子
//获取插件中的cdnModule属性(此处为undefined,因为没有cdnModule属性)
const moduleId = data.plugin.options.cdnModule;
// 只要不是false(禁止)就行
if (moduleId !== false) {
// 4.3得到所有的cdn配置项
let modules = this.modules[
moduleId || Reflect.ownKeys(this.modules)[0]
];
if (modules) {
// 4.4 整合已有的js引用和cdn引用
data.assets.js = modules
.filter(m => !!m.path)
.map(m => {
return m.path;
})
.concat(data.assets.js);
// 4.5 整合已有的css引用和cdn引用
data.assets.css = modules
.filter(m => !!m.style)
.map(m => {
return m.style;
})
.concat(data.assets.css);
}
}
// 4.6 返回callback函数
callback(null, data);
});
}
}
module.exports = CdnPluginInject;
接下来逐步对上述实现进行分析:
- 4.1、引入html-webpack-plugin依赖,这个不用多说;
- 4.2、调用
html-webpack-plugin中的hooks函数,在html-webpack-plugin中资源生成之前异步执行;这里由衷的夸夸html-webpack-plugin的作者了,ta在开发html-webpack-plugin时就在插件中内置了很多的hook函数供开发者在调用插件的不同阶段嵌入不同操作;因此,此处我们可以使用html-webpack-plugin的beforeAssetTagGeneration对html进行操作; - 4.3、 在
beforeAssetTagGeneration中,获取得到所有的需要进行cdn引入的配置数据; - 4.4、 整合已有的js引用和cdn引用;通过
data.assets.js可以获取到compilation阶段所有生成的js资源(最终也是插入index.html中)的链接/路径,并且将需要配置的cdn的path数据(cdn的url)合并进去; - 4.5、 整合已有的css引用和cdn引用;通过
data.assets.css可以获取到compilation阶段所有生成的css资源(最终也是插入index.html中)的链接/路径,并且将需要配置的css类型cdn的path数据(cdn的url)合并进去; - 4.6、 返回callback函数,目的是告诉
webpack该操作已经完成,可以进行下一步了;
5、设置webpack的外部扩展externals
在apply方法执行完之前还有一步必须完成:将cdn的参数配置到外部扩展externals中;可以直接通过compiler.options.externals获取到webpack中externals属性,经过操作将cdn配置中数据配置好就ok了。
6、 callback;
返回callback,告诉webpack CdnPluginInject插件已经完成;
// 4.1 引入html-webpack-plugin依赖
const HtmlWebpackPlugin = require("html-webpack-plugin");
class CdnPluginInject {
constructor({
modules,
}) {
// 如果是数组,将this.modules变换成对象形式
this.modules = Array.isArray(modules) ? { ["defaultCdnModuleKey"]: modules } : modules;
}
//webpack plugin开发的执行入口apply方法
apply(compiler) {
//获取webpack的输出配置对象
const { output } = compiler.options;
//处理output.publicPath, 决定最终资源相对于引用它的html文件的相对位置
output.publicPath = output.publicPath || "/";
if (output.publicPath.slice(-1) !== "/") {
output.publicPath += "/";
}
//触发compilation钩子函数
compiler.hooks.compilation.tap("CdnPluginInject", compilation => {
// 4.2 html-webpack-plugin中的hooks函数,当在资源生成之前异步执行
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration
.tapAsync("CdnPluginInject", (data, callback) => { // 注册异步钩子
//获取插件中的cdnModule属性(此处为undefined,因为没有cdnModule属性)
const moduleId = data.plugin.options.cdnModule;
// 只要不是false(禁止)就行
if (moduleId !== false) {
// 4.3得到所有的cdn配置项
let modules = this.modules[
moduleId || Reflect.ownKeys(this.modules)[0]
];
if (modules) {
// 4.4 整合已有的js引用和cdn引用
data.assets.js = modules
.filter(m => !!m.path)
.map(m => {
return m.path;
})
.concat(data.assets.js);
// 4.5 整合已有的css引用和cdn引用
data.assets.css = modules
.filter(m => !!m.style)
.map(m => {
return m.style;
})
.concat(data.assets.css);
}
}
// 4.6 返回callback函数
callback(null, data);
});
// 5.1 获取externals
const externals = compiler.options.externals || {};
// 5.2 cdn配置数据添加到externals
Reflect.ownKeys(this.modules).forEach(key => {
const mods = this.modules[key];
mods
.forEach(p => {
externals[p.name] = p.var || p.name; //var为项目中的使用命名
});
});
// 5.3 externals赋值
compiler.options.externals = externals; //配置externals
// 6 返回callback
callback();
}
}
module.exports = CdnPluginInject;
至此,一个完整的webpack插件CdnPluginInject就开发完成了!接下来使用着试一试。
四、cdn优化插件使用
在vue项目的vue.config.js文件中引入并使用CdnPluginInject:
cdn配置文件CdnConfig.js:
/*
* 配置的cdn
* @name: 第三方库的名字
* @var: 第三方库在项目中的变量名
* @path: 第三方库的cdn链接
*/
module.exports = [
{
name: "moment",
var: "moment",
path: "https://cdn.bootcdn.net/ajax/libs/moment.js/2.27.0/moment.min.js"
},
···
];
configureWebpack中配置:
const CdnPluginInject = require("./CdnPluginInject");
const cdnConfig = require("./CdnConfig");
module.exports = {
···
configureWebpack: config => {
//只有是生产山上线打包才使用cdn配置
if(process.env.NODE.ENV =='production'){
config.plugins.push(
new CdnPluginInject({
modules: CdnConfig
})
)
}
}
···
}
chainWebpack中配置:
const CdnPluginInject = require("./CdnPluginInject");
const cdnConfig = require("./CdnConfig");
module.exports = {
···
chainWebpack: config => {
//只有是生产山上线打包才使用cdn配置
if(process.env.NODE.ENV =='production'){
config.plugin("cdn").use(
new CdnPluginInject({
modules: CdnConfig
})
)
}
}
···
}
通过使用CdnPluginInject:
- 1、通过配置实现对cdn优化的管理和维护;
- 2、实现针对不同环境做cdn优化配置(开发环境直接使用本地安装依赖进行调试,生产环境适应cdn方式优化加载);
五、小结
看完后肯定有webpack大佬有一丝丝疑惑,这个插件不就是 webpack-cdn-plugin 的乞丐版!CdnPluginInject只不过是本人根据webpack-cdn-plugin源码的学习,结合自己项目实际所需修改的仿写版本,相较于webpack-cdn-plugin将cdn链接的生成进行封装,CdnPluginInject是直接将cdn链接进行配置,对于选择cdn显配置更加简单。想要进一步学习的xdm可以看看webpack-cdn-plugin的源码,经过作者的不断的迭代更新,其提供的可配置参数更加丰富,功能更加强大(再次膜拜)。
重点:整理不易,觉得还可以的xdm记得 一键三连 哟!
文章参考
YYDS: Webpack Plugin开发的更多相关文章
- 简单webpack plugin 开发
重要是学习下怎么开发webpack plugin,同时记录下 插件模型 webpack 是一个插件,可以是javascript class ,或者具名 class 定义apply 方法 指定一个绑定到 ...
- 如何开发webpack plugin
继上回介绍了如何开发webpack loader 之后.趁热打铁,来继续看下webpack另一个核心组成:plugin. 下面也和loader一样,让我们一起从基本的官方文档着手看起. loader和 ...
- 揭秘webpack plugin
前言 Plugin(插件) 是 webpack 生态的的一个关键部分.它为社区提供了一种强大的方法来扩展 webpack 和开发 webpack 的编译过程.这篇文章将尝试探索 webpack plu ...
- [Cordova] Plugin开发架构
[Cordova] Plugin开发架构 问题情景 开发Cordova Plugin的时候,侦错Native Code是一件让人困扰的事情,因为Cordova所提供的错误讯息并没有那么的完整.常常需要 ...
- [Cordova] Plugin开发入门
[Cordova] Plugin开发入门 Overview Cordova的设计概念,是在APP上透过Web控件来呈现Web页面,让Web开发人员可以操作熟悉的语言.工具来开发APP.使用Web页面来 ...
- MS CRM 2011的自定义和开发(11)——插件(plugin)开发(三)
http://www.cnblogs.com/StoneGarden/archive/2012/02/06/2340661.html MS CRM 2011的自定义和开发(11)——插件(plugin ...
- MS CRM 2011的自定义和开发(11)——插件(plugin)开发(四)
http://www.cnblogs.com/StoneGarden/archive/2012/02/08/2343294.html MS CRM 2011的自定义和开发(11)——插件(plugin ...
- MS CRM 2011的自定义和开发(11)——插件(plugin)开发(一)
http://www.cnblogs.com/StoneGarden/archive/2012/02/02/2336147.html MS CRM 2011的自定义和开发(11)——插件(plugin ...
- MS CRM 2011的自定义和开发(11)——插件(plugin)开发(二)
http://www.cnblogs.com/StoneGarden/archive/2012/02/06/2339490.html MS CRM 2011的自定义和开发(11)——插件(plugin ...
随机推荐
- 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 《Stereo R-CNN based 3D Object Detection for Autonomous Driving》论文解读
论文链接:https://arxiv.org/pdf/1902.09738v2.pdf 这两个月忙着做实验 博客都有些荒废了,写篇用于3D检测的论文解读吧,有理解错误的地方,烦请有心人指正). 博客原 ...
- PHP远程代码执行漏洞:CVE-2019-11043
漏洞详情: Nginx上fastcgi_split_path_info在处理带有 %0a 的请求时,会因为遇到换行符 \n 导致PATH_INFO为空.而php-fpm在处理PATH_INFO为空的情 ...
- OpenSNS后台文件上传漏铜分析
前言 这几天正在想找个文件上传漏洞分析一波,以加深对文件上传漏洞的理解,正好看到FreeBuf的一片文章记对OpenSNS的一次代码审计,由于其只对漏洞进行复现,故在此进行代码层面的分析. 漏洞分析 ...
- ecshop v2 v3 EXP
import requests import binascii def get_v2Payload(code): '''Ecshop V2.x payload''' code = "{$ab ...
- hugging face-基于pytorch-bert的中文文本分类
1.安装hugging face的transformers pip install transformers 2.下载相关文件 字表: wget http://52.216.242.246/model ...
- 一个shell程序
使用vi写一个shell程序 touch cdlog echo "cd /app/mycrm/log" >> cdlog chmod +x cdlog 执行: ...
- ASP.NET Core WebAPI实现本地化多语言(单资源文件)
在Startup ConfigureServices 注册本地化所需要的服务AddLocalization和 Configure<RequestLocalizationOptions> p ...
- spark 系列之一 RDD的使用
spark中常用的两种数据类型,一个是RDD,一个是DataFrame,本篇主要介绍RDD的一些应用场景见代码本代码的应用场景是在spark本地调试(windows环境) /** * 创建 spark ...
- Redis 设计与实现 4:字典
Redis 中,字典是基础结构.Redis 数据库数据.过期时间.哈希类型都是把字典作为底层结构. 字典的结构 哈希表 哈希表的实现代码在:dict.h/dictht ,Redis 的字典用哈希表的方 ...