@[toc]
作为一名踏足前端工夫不长的小开发必须得聊一聊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记得 一键三连 哟!
文章参考
- 揭秘webpack-plugin
- webpack-cdn-plugin的github
发表回复