作为一名踏足前端时间不长的小开发必须得聊一聊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 函数(使用ES6class实现);
  • 2、在它的原型上定义 apply 方法;
  • 3、指定一个触及到 webpack 本身的事件钩子(此处触及compilation钩子:编译(compilation)创建之后,执行插件);
  • 4、在钩子事件中操作index.html(将cdnscript标签插入到index.html中);
  • 5、在apply方法执行完之前将cdn的参数放入webpack外部扩展externals中;
  • 6、在实现功能后调用 webpack 提供的 callback

实现步骤:

1、创建一个具名 JavaScript 函数(使用ES6class实现)

  创建类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

  这一步主要是要实现 cdnscript标签插入到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-pluginbeforeAssetTagGeneration对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开发的更多相关文章

  1. 简单webpack plugin 开发

    重要是学习下怎么开发webpack plugin,同时记录下 插件模型 webpack 是一个插件,可以是javascript class ,或者具名 class 定义apply 方法 指定一个绑定到 ...

  2. 如何开发webpack plugin

    继上回介绍了如何开发webpack loader 之后.趁热打铁,来继续看下webpack另一个核心组成:plugin. 下面也和loader一样,让我们一起从基本的官方文档着手看起. loader和 ...

  3. 揭秘webpack plugin

    前言 Plugin(插件) 是 webpack 生态的的一个关键部分.它为社区提供了一种强大的方法来扩展 webpack 和开发 webpack 的编译过程.这篇文章将尝试探索 webpack plu ...

  4. [Cordova] Plugin开发架构

    [Cordova] Plugin开发架构 问题情景 开发Cordova Plugin的时候,侦错Native Code是一件让人困扰的事情,因为Cordova所提供的错误讯息并没有那么的完整.常常需要 ...

  5. [Cordova] Plugin开发入门

    [Cordova] Plugin开发入门 Overview Cordova的设计概念,是在APP上透过Web控件来呈现Web页面,让Web开发人员可以操作熟悉的语言.工具来开发APP.使用Web页面来 ...

  6. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(三)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/06/2340661.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

  7. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(四)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/08/2343294.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

  8. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(一)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/02/2336147.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

  9. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(二)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/06/2339490.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

随机推荐

  1. pyhon 自动化 logger

    #!/Users/windows8.1/PycharmProjects/pythonapi# @Software: PyCharm Community Edition# -*- coding: utf ...

  2. Java NIO:FileChannel数据传输

    调用方式 FileChannel dstChannel; FileChannel srcChannel; dstChannel.transferFrom(srcChannel,0,srcChannel ...

  3. 实验4 汇编应用编程和c语言程序反汇编分析

    1. 实验任务1 教材「实验9 根据材料编程」(P187-189)编程:在屏幕中间分别显示绿色.绿底红色.白底蓝色的字符串'welcome to masm!'. 解题思路:根据学习的知识,我知道该页在 ...

  4. js上 九.多分支语句

    9-3.if...else if ...else语句 多分支的if语句,多选一. 格式:

  5. Web服务器-服务器开发-返回浏览器需要的页面 (3.3.2)

    @ 目录 1.说明 2.代码 关于作者 1.说明 使用正则表达式,匹配客户端的请求头 获取到请求的路径 返回对应请求路径的文字 可以使用打开对应文件的方式去返回对应的文件 2.代码 from sock ...

  6. 记一次真实的webpack优化经历

    前言 公司目前现有的一款产品是使用vue v2.0框架实现的,配套的打包工具为webpack v3.0.整个项目大概有80多个vue文件,也算不上什么大型项目. 只不过每次头疼的就是打包所耗费的时间平 ...

  7. .NET Core 使用MediatR CQRS模式 读写分离

    前言 CQRS(Command Query Responsibility Segregation)命令查询职责分离模式,它主要从我们业务系统中进行分离出我们(Command 增.删.改)和(Query ...

  8. MySQL误删除用户怎么解决

    前言:在不考虑到原来用户对关联数据库的授权问题的情况下,有以下两种思路解决 #1.安全模式修改 第一步:关闭数据库服务: [root@db01 ~]#/etc/init.d/mysqld stop 第 ...

  9. [数据库]000 - 🍳Sysbench 数据库压力测试工具

    000 - Sysbench 数据库压力测试工具 sysbench 是一个开源的.模块化的.跨平台的多线程性能测试工具,可以用来进行CPU.内存.磁盘I/O.线程.数据库的性能测试.目前支持的数据库有 ...

  10. 单细胞分析实录(2): 使用Cell Ranger得到表达矩阵

    Cell Ranger是一个"傻瓜"软件,你只需提供原始的fastq文件,它就会返回feature-barcode表达矩阵.为啥不说是gene-cell,举个例子,cell has ...