当前位置 博文首页 > 哇喔WEB: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+
工程展开!
index.html:
<head>
···
</head>
<body>
<div ></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官网如此介绍到:插件向第三方开发者提供了 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中配置:
bkconst 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记得 一键三连 哟!
文章参考
- 揭秘webpack-plugin
- webpack-cdn-plugin的github
下一篇:没有了
最新 更多<<
哇喔WEB:YYDS: Webpack Plugin开发 路边两盏灯:【Azure Developer】解决Azure Key Vault管理Storag SpringLeee:使用 gRPCurl 调试.NET 5的gPRC服务 itbsl:深入理解Redis之简单动态字符串 メSerendipity:浏览器performance工具介绍及内存问题表现与监控 xiaokantianse:19.java设计模式之备忘录模式 扣代码工具 hta版 吃西瓜的星星:以事实驳斥:改进你的c#代码的5个技巧(四) 用hta实现的桌面漂浮flash 翁智华:MySQL全面瓦解17:触发器相关 hta编写的消费记录程序 尘世风:Linux Shell 编程基础详解——吐血整理,墙裂推荐! hta编写的常用工具箱(常用工具快捷方式等) 晓飞的算法工程笔记:Objects as Points:预测目标中心,无需NMS 做一个困难重重的hta下载者 为少:如何构建一个多人(.io) Web 游戏,第 2 部分 XMLDOM下载者生成器代码(xmldown.hta) 腾讯云原生:K8s 平台可以如何处理 Pod 预授权问题 javascript操作xml(增删改查)例子代码hta版 Artech:ASP.NET Core错误处理中间件[2]: 开发者异常页面 离22周岁距离多长时间的计算[适合没到22周岁的朋友] hta实现的定时关机小程序 悦光阴:Azure App object和Service Principal 什么是hta(HTML Application) 华为云开发者社区:技术实践丨React Native 项目 Web 端同构 A notepad made in HTA(hta实现的记事本) 码农译站:改进你的c#代码的5个技巧(四) 关键字排名(Keyword Ranking) 削微寒:摆脱 996——GitHub 热点速览 v.21.03 DOM浏览器(方便需要dom操作的朋友)