webpack-qc-iconfont-plugin是一个webpack插件,可以轻松地帮你将阿里icon的图标项目下载至本地
鉴于之前gulp版本的,有同学说看不懂,于是乎我决定在该版进行一个原理讲解,大佬勿喷,小女子只是分享下学习心得。
实现这个插件首先你得研究阿里提供的css代码,这里我提供一个供大家伙学习使用的 //at.alicdn.com/t/font_1425510_3v068prmkkw.css
研究这个你会发现,它其实就是一个css文件,保存下来就可以了。因此我们可以发起请求将css文件下载到本地,npm官方提供了相当多请求封装包,什么request/http/download...,你可以随便挑一个你喜欢的。
文件请求成功后,因为我们想要可以自定义前缀,删除部分不需要的代码,这个时候需要用到正则,这里以插件源码来说明:
let rawData = body;
if (!isDev) rawData = rawData.replace(new RegExp(urlPrefix, ‘g‘), fontPath) // 当为生成环境时,将css中的在线url css地址替换为本地字体文件路径 fontPath
var result = ‘/* 字体图标,来源路径:"‘ + url + ‘"*/ \r\n‘; // 添加字体图标来源url注释,以便于快速定位样式来源
var delUnnecessary = rawData.replace(/\.iconfont[\s\S]*?\}/, ‘‘); // 利用正则删除 .iconfont { ... } 图标初始化代码
var iconCss = delUnnecessary.match(/\.icon\-[\s\S]*?\}/g); // 利用正则匹配出所有 .icon-XXX { ... } 的图标样式代码
var handlerData = keepIconFontStyle ? rawData : delUnnecessary; // 根据配置的 keepIconFontStyle 识别是否需要删除 .iconfont { ... } 图标初始化代码
result += handlerData.replace(/\.icon\-[\s\S]*?\}/g, ‘‘); // 利用正则删除原有的 .icon-XXX { ... } 的图标样式代码
// 在循环匹配出的 iconCss 重新生成正确前缀的 .icon-XXX { ... } 的图标样式代码
for (var i in iconCss) {
var item = iconCss[i];
if (iconPrefix) item = item.replace(/\.icon\-/, iconPrefix);
result += item + ‘\r\n‘;
}
// 最后删除多余的空行
result = result.replace(/\r{2,}/g, ‘\r‘);
result = result.replace(/\n{2,}/g, ‘\n‘);
这样css文件就生成结束了,接下来就根据开发或生产环境决定是否需要下载css中引用的字体图标文件,这里通过对阿里提供的css文件分析,找到字体图标文件路径,如下:
//at.alicdn.com/t/font_1425510_3v068prmkkw.eot
//at.alicdn.com/t/font_1425510_3v068prmkkw.woff
//at.alicdn.com/t/font_1425510_3v068prmkkw.woff2
//at.alicdn.com/t/font_1425510_3v068prmkkw.ttf
//at.alicdn.com/t/font_1425510_3v068prmkkw.svg
与提供的css文件比较:
//at.alicdn.com/t/font_1425510_3v068prmkkw.css
经过对比,发现字体图标文件路径,就是提供的css文件换个扩展名而已,因此,我们可以把需要下载的字体图标扩展名写成一个数组,利用递归请求文件路径下载字体图标文件到本地
最后,webpack版有模板文件的说法,为此需要将我们生成的css文件注入到模板文件中,这里以插件源码讲解:
if (template) { // 判断 template 配置存在的时候进行注入
compiler.hooks.emit.tap(pluginName, compilation => { // 注册webpack插件compiler emit hook
for (var filename in compilation.assets) { // 循环 compilation.assets 准备输出的资源列表
if (filename === template) { // 找到模板文件
const htmlData = compilation.assets[filename].source() // 获取模板文件字符串数据
if (!htmlData) return
const headLinkCss = ‘<link rel="stylesheet" href="./‘ + cssName + ‘">‘ // 生成本地路径的 head link css标签
// 询问该数据中已是否存在head link css标签,这里的判断是由于该插件代码会被先后执行两次,这需要你对webpack的compiler 与 compilation有初步的认识,compiler会在整个webpack生命周期中存在,而compilation是在每次编译时执行,因此前后会执行一次compiler 和 compilation,就是两次,因此未避免向生产环境注入两次head link css,我们需要进行过滤
const findHeadLinkCss = htmlData.includes(headLinkCss)
const htmlArr = htmlData.split(‘</head>‘) // 根据 ‘</head>‘ 分割数据为数组
let htmlHeadBefore = htmlArr[0] // 获取第一个数据
if (isDev) {
// 如果是开发环境,则将生成的css以 ‘<style> ... </style>‘ 形式注入到模板文件中,以便开发调试
const iconCss = compilation.assets[cssName].source()
if (!iconCss) return
htmlHeadBefore += ‘<style>‘ + iconCss + ‘</style>‘
} else if (!findHeadLinkCss) {
// 如果是生成环境,且模板文件中不存在head link css标签,便将前面生成的head link css标签注入到数据中
htmlHeadBefore += headLinkCss
}
// 最后链接前后代码
const handledHtml = htmlHeadBefore + ‘</head>‘ + htmlArr[1]
// 替换掉准备输出的模板资源文件
compilation.assets[template] = {
source: function () {
return handledHtml;
},
size: function () {
return handledHtml.length;
}
};
}
}
})
}
tapable
,这个看下官方文档,初步认识即可,类似于后端的委托代{过}{滤}理compiler
与 compilation
, 这个webpack官方提供的事件,主要基于 tapable
编写// 声明插件构造函数
class WebpackQcIconfontPlugin {
// 构造函数本身
constructor(options) {
// 用来对传入的options进行处理,统一的处理便于日后的维护,也是你自己后面编写文档是查看options 属性一个非常好的窗口
this.options = options || {};
if (!this.options.url) throw new Error(‘[‘ + pluginName + ‘] Missing options url!‘);
... ...
}
// 构造函数的原型函数apply,webpack插件需要
apply(compiler) {
// 这里我们获取到 webpack 插件为我们提供的事件对象 compiler
// 对即将使用到的options进行获取,我通常习惯将他们重新获取赋值,在这里,而不是直接在代码中使用大量的 options.XXX,原因是当我需要去除或修改一个options属性时我找的难受,其次是我可以清晰知道这个函数使用了那些options属性,我的options属性将影响到哪些函数
const options = this.options
const isDev = options.isDev
const fontExtList = options.fontExtList
const cssName = options.cssName
const template = options.template
// 注册一个 compiler.hooks.compilation 钩子
compiler.hooks.compilation.tap(pluginName, compilation => {
// 执行声明的 IconfontDownloadCss 与 IconfontDownloadFontFile 构造函数,之所以将他们分别构造是为了逻辑清晰,增强代码可读性
new IconfontDownloadCss().apply(compilation, options);
if (!isDev && fontExtList && fontExtList.length > 0) new IconfontDownloadFontFile().apply(compilation, options);
})
}
}
module.exports = WebpackQcIconfontPlugin;
IconfontDownloadCss
与 IconfontDownloadFontFile
的逻辑tapable
了,详细的还请小白移步官网查阅,我就不详细解说了,这里简单概述下该插件是如何编写的事件// 声明一个异步事件,这里因为返回参数一致,所以用了统一的声明方式,简单快速便捷
const asyncHooks = new HookMap(key => new AsyncParallelHook([‘result‘, ‘callback‘]))
IconfontDownloadCss
构造函数中:
// 判断是否存在 iconfontCssCreateEnd 的注册事件
const iconCssCreateEndHooks = asyncHooks.get(‘iconfontCssCreateEnd‘)
if (iconCssCreateEndHooks) {
// 存在使用 iconCssCreateEndHooks.callAsync 执行注册事件
iconCssCreateEndHooks.callAsync(result, handledData => {
if (!handledData) return callback()
resultHandle(handledData)
})
} else {
// 不存在则执行默认方法
resultHandle(result)
}
// 注册事件时使用如下方法,和官网是一致的:
WebpackQcIconfontPlugin.getHooks.for(‘iconfontCssCreateEnd‘).tapAsync(pluginName, (result, cb) => {
result += ‘1111111‘
cb(result)
})
IconfontDownloadFontFile
构造函数中:
// 判断是否存在 iconfontFileDownloadEnd 的注册事件
const iconfontFileDownloadEndHooks = asyncHooks.get(‘iconfontFileDownloadEnd‘)
if (iconfontFileDownloadEndHooks) {
存在使用 iconfontFileDownloadEndHooks.callAsync 执行注册事件
iconfontFileDownloadEndHooks.callAsync(fontFileList, handledData => {
if (!handledData) return callback()
resultHandle(handledData)
})
} else {
// 不存在则执行默认方法
resultHandle(fontFileList)
}
// 注册事件时使用如下方法:
WebpackQcIconfontPlugin.getHooks.for(‘iconfontFileDownloadEnd‘).tapAsync(pluginName, (fontFileList, cb) => {
const testFile = ‘测试使用的文件而已‘
fontFileList.push({
filename: ‘test.text‘,
data: {
source: function () {
return testFile;
},
size: function () {
return testFile.length;
}
}
})
cb(fontFileList)
})
npm install webpack-qc-iconfont-plugin
webpack.config.js
文件中进行调用:
// 引入插件
const WebpackQcIconfontPlugin = require(‘iconfont-webpack-plugin‘)
module.exports = {
plugins: [
// 插件调用代码
new WebpackQcIconfontPlugin({
url: ‘//at.alicdn.com/t/font_xxxxxxx_xxxxxx.css‘,
isDev: true,
fontPath: ‘./iconfont/iconfont‘,
iconPrefix: ‘.cu-icon-‘,
keepIconFontStyle: false,
fontExt: [‘.eot‘, ‘.ttf‘, ‘.svg‘, ‘.woff‘, ‘.woff2‘],
template: ‘index.html‘
}),
]
};
url
new WebpackQcIconfontPlugin({url: ‘//at.alicdn.com/t/font_xxxxxxx_xxxxxx.css‘ })
isDev
true
fontPath
‘./iconfont/iconfont‘
isDev
为false,也就是生产环境才有效iconPrefix
.icon-
{ iconPrefix: ‘.cu-icon-‘ }
,则图标调用为:<i class="iconfont cu-icon-XXX"></i>
keepIconFontStyle
.iconfont{/*...*/}
中的样式,该属性多用于与vant等类似已有自己字体图标相关初始设置的组件库配合使用,如您没有与类似组件使用,建议开启或自定义一个,否则您的图标将不会有初始样式fontExt
isDev
为false时有效template
index.html
<style> ... </style>
形式注入,生成模式下会以 <link rel="stylesheet" href="./iconfont.css">
方式注入compiler
与 compilation
tapable
注:此前该文发布与52pojie,由于很多小伙伴反应没有账号看不到文章,故转到我的博客再发一次,鉴于此以后我发文都尽量一式两份~~~~~哈哈哈
作者:leona
原文链接:https://www.cnblogs.com/leona-d/p/12697281.html
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接
【Web】阿里icon图标webpack插件(webpack-qc-iconfont-plugin)详解
原文:https://www.cnblogs.com/leona-d/p/12697281.html