YYDS: Webpack Plugin开发
source link: http://www.cnblogs.com/wawoweb/p/14299978.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
目录
- 二、开发一个webpack plugin
-
- 1、创建一个具名 JavaScript 函数(使用ES6的class实现)
- 2、在它的原型上定义 apply 方法
- 3、指定一个触及到 webpack 本身的事件钩子
- 4、在钩子事件中操作index.html
- 5、设置webpack的外部扩展externals
-
- configureWebpack中配置:
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记得 一键三连 哟!
文章参考
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK