webpack-qc-iconfont-plugin

webpack-qc-iconfont-plugin是一个webpack插件,可以轻松地帮你将阿里icon的图标项目下载至本地

开发初衷

  • 之前已经发布过gulp的版本了,但是在webpack流行的时代,我还是觉得webpack插件版还是很有必有的,于是在我加班加点的研究下,我终于实现了webpack插件版
  • 为啥子要加班加点呢,因为我很懒,到目前为止都没有完整的看过官方的文档,所以基础很差,因此连更半夜的才把这个插件完成了,请原谅我的懒,真的,我一看文档就感觉要睡着了,不知道有没有同感的同学

实现原理

  • 鉴于之前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;
    }
    };
    }
    }
    })
    }
    1. 代码逻辑基本理顺了,下面就是webpack插件一些简单知识了
    • 涉及知识点:

      • ES6 class 构造函数,什么是构造函数这里不多讲,类似于后端类
      • webpack 事件钩子 tapable ,这个看下官方文档,初步认识即可,类似于后端的委托代{过}{滤}理
      • webpack 的 compilercompilation, 这个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;
  1. 这样一个简单的webpack插件基架就完成了,剩下的就是根据我刚才分析写出 IconfontDownloadCssIconfontDownloadFontFile 的逻辑
  2. 最后为了便于其他开发者可以个性化的使用,我们应该为我们的插件提供钩子事件,这里就需要用到 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'
}),
]
};

Options

  • url

    • 类型:String
    • 默认:无,该参数是必须(没有将会报错)
    • 描述:为阿里图标中 - 我的图标项目 - 中获取的css代码url
    • 基础用法:new WebpackQcIconfontPlugin({url: '//at.alicdn.com/t/font_xxxxxxx_xxxxxx.css' })
  • isDev

    • 类型:String,
    • 默认:true
    • 描述:当前是否为开发模式
  • fontPath

    • 类型:String
    • 默认:'./iconfont/iconfont'
    • 描述:下载的字体图标文件保存路径,只有在 isDev 为false,也就是生产环境才有效
  • iconPrefix

    • 类型:String
    • 默认:与源文件保持一致 .icon-
    • 描述:字体图标统一前缀,如设置为 { iconPrefix: '.cu-icon-' },则图标调用为:<i class="iconfont cu-icon-XXX"></i>
  • keepIconFontStyle

    • 类型:Boolean
    • 默认:undefined,即未开启,不保留
    • 描述:是否保留css源文件中的 .iconfont{/*...*/} 中的样式,该属性多用于与vant等类似已有自己字体图标相关初始设置的组件库配合使用,如您没有与类似组件使用,建议开启或自定义一个,否则您的图标将不会有初始样式
  • fontExt

    • 类型:Array
    • 默认:['.eot', '.ttf', '.svg', '.woff', '.woff2'] ,即全部下载
    • 描述:需要下载的字体图标格式扩展名,只有在 isDev 为false时有效
  • template

    • 类型:String
    • 默认:index.html
    • 描述:生成的图标css将自动注入模板文件,图标生成后会根据该配置自动注入到模板文件中,无需手动调用,如不需要自动注入,可以将该值设置为 null
    • 补充:开发模式下会css会以 <style> ... </style> 形式注入,生成模式下会以 <link rel="stylesheet" href="./iconfont.css"> 方式注入

总结

  • 详细的用法与源码在 我的github webpack-qc-iconfont-plugin 中有,有需要小伙伴可以去下载
  • 最后分析下插件的不足之处:
    • 没有实现自动根据webpack mode环境判断生产和开发环境,学艺不精,找了很多文档,仍不知道如何实现,若有大神愿意指点迷津将非常感谢
    • 插件模板文件那一块总感觉有点什么说不上来问题,本来想实现根据webpack的出口配置,自动实现,不需要配置的,但是发现好像理想丰满,现实骨感
  • 我学到的知识
    • 深入了解了 webpack 插件的实现原理,初步掌握了 compilercompilation
    • 半懵半懂的了解了 tapable
    • 看懂了未来前端的前景会越来越好,因为能实现的东西越来越多,查阅资料的同时也重新认识了一次前端,发现很多新东西和自己不曾了解的代码写法
    • 最后祝愿每一位努力奋斗的前端小伙伴们越来越好,在前端这条路上不断奋进和学习,不要轻易放弃哟!

注:此前该文发布与52pojie,由于很多小伙伴反应没有账号看不到文章,故转到我的博客再发一次,鉴于此以后我发文都尽量一式两份~~~~~哈哈哈

作者:leona

原文链接:https://www.cnblogs.com/leona-d/p/12697281.html

版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接

【Web】阿里icon图标webpack插件(webpack-qc-iconfont-plugin)详解的更多相关文章

  1. Spring Boot的Maven插件Spring Boot Maven plugin详解

    Spring Boot的Maven插件(Spring Boot Maven plugin)能够以Maven的方式为应用提供Spring Boot的支持,即为Spring Boot应用提供了执行Mave ...

  2. 前端html、CSS快速编写代码插件-Emmet使用方法技巧详解

    前端html.CSS快速编写代码插件-Emmet使用方法技巧详解   Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生.它使用仿CSS选择器的语法来 ...

  3. 提高Java代码质量的Eclipse插件之Checkstyle的使用详解

    提高Java代码质量的Eclipse插件之Checkstyle的使用详解 CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发人员遵守某些编码规范的工具.它能够自动化代 ...

  4. js插件---videojs中文文档详解

    js插件---videojs中文文档详解 一.总结 一句话总结: js插件网上都有很多参考资料,使用起来也非常简单 二.lavarel中使用实例 <video id="example_ ...

  5. web缓存服务器varnish-4.1.6的部署及配置详解

    web缓存服务器varnish-4.1.6的部署及配置详解 1.安装varnish4.1.6安装依赖 yum install -y autoconf automake jemalloc-devel l ...

  6. Elasticsearch之sense插件安装之后的浏览详解

    前提博客是 Elasticsearch之sense插件的安装(图文详解) 立马,可以看到 http://192.168.80.145:5601/app/sense 以后更新

  7. 使用Navicat或者其他数据库工具连接阿里云EDS(数据库服务器)实例过程详解

    使用Navicat或者其他数据库工具连接阿里云EDS(数据库服务器)实例过程详解 背景:这几天从阿里云上面购买了云服务器,最垃圾的那种,还送oss和EDS数据库服务器,只不过EDS数据库服务器只有一个 ...

  8. 【前端必会】不知道webpack插件? webpack插件源码分析BannerPlugin

    背景 不知道webpack插件是怎么回事,除了官方的文档外,还有一个很直观的方式,就是看源码. 看源码是一个挖宝的行动,也是一次冒险,我们可以找一些代码量不是很大的源码 比如webpack插件,我们就 ...

  9. webpack学习(五)配置详解

    配置详解 //使用插件html-webpack-plugin打包合并html //使用插件extract-text-webpack-plugin打包独立的css //使用UglifyJsPlugin压 ...

随机推荐

  1. 何为引用法---细谈C++引用

    何为引用...给已有的变量取别名 ; int &a = num;//此处 &不是取地址 而是标明 a是引用变量(a 是 num的别名) 注意: 1.引用必须初始化 2.引用一旦初始化 ...

  2. MDI设置父子窗体

    1.新建父窗体,设置窗体属性:IsMdicontainer设置成true; 2.拖入button控件,修改button中的text. 3.点击button控件设置代码: //1.窗体实例化 Form ...

  3. [极客大挑战 2019]PHP1

    知识点:PHP序列化与反序列化,最下方有几个扩展可以看一下 他说备份了,就肯定扫目录,把源文件备份扫出来 dirsearch扫目录扫到www.zip压缩包

  4. 记录一次云主机部署openstack的血泪史

    看见这个部署成功的留下了激动的泪水 经过长时间的BUG苦肝终于成功部署成功  部署的环境2vCPU 8GB 阿里云主机,部署成功以后内存占用确实蛮高的 记录这一次踩坑,给后来者避免踩坑时间,个人踩坑踩 ...

  5. Windows下利用Chrome调试IOS设备页面

    本文介绍如何在 Windows 系统中连接 iOS设备 并对 Web 页面进行真机调试 必须前提 iOS设备.数据线 Node.js 环境 Chrome 浏览器 环境准备 安装Node环境 参考Nod ...

  6. hdu2203kmp匹配

    拼接字符串即可解决移位的问题: 代码如下: #include<bits/stdc++.h> using namespace std; typedef unsigned int ui; ty ...

  7. Node/Python 工具搭建cmder和nrm

    一.安装cmder cmder是windows下的一款终端工具,支持很多linux命令,用起来还是很爽的. 1.安装 http://cmder.net/ 直接在官网下载,解压即可. 2.cmder配置 ...

  8. 大O 表示法

    大O表示法 指出了算法有多快.例如,假设列表包含n个元素.简单查找需要检查每个元素,因此需要执行n次操作.使用大O表示法,这个运行时间为O(n).单位秒呢?没有——大O表示法指的并非以秒为单位的速度. ...

  9. 非常诡异的IIS下由配置文件加上svg的mime头导致整个网站的静态文件访问报错误

    调试了两天遇到一个非常诡异的问题 一个系统稳定运行了很多年,是用mvc5+WIN2008R2  + .NET 4.5 +IIS环境下运行,非常稳定,最近想迁移到一台新的服务器,为了少麻烦在阿里云上买了 ...

  10. 本地不安装Oracle时,PLSQL的配置

    这篇我在csdn上写过的,文章地址:https://blog.csdn.net/weixin_40404606/article/details/101940542