# 图标生成字体文件
# 背景
公司内部使用字体图标,是在阿里的字体图标矢量库 (opens new window)中找好图标,添加到组里,再下载下来,把文件提交到我们的gitlab仓库维护起来。
这样当然不方便。
先在npm上搜索了下,没找到类似的方案,但把svg图标生成字体与css的库有,也有把图标放在本地某个文件夹就不用再管的loader,后者是webfonts-loader (opens new window)。
于是我们讨论了一个方案:
搭建一台服务器,所有的
svg图标都放上面解析
DOM中用到的图标名称请求服务器资源,下载到本地某个目录
按照
webfonts-loader的文档,配置这个目录,之后应该开发与打包都OK
整体思路很简单,实现起来却有些波折。
# 技术验证
首先进行技术验证。
# 新建webpack工程
我用webpack4创建了一个新的工程,里面添加了vue-loader,在其中一个vue文件里这样使用图标(因为我们主要技术栈是vue,先不考虑其它的):
<my-font name="go"></my-font>
<my-font name="search"></my-font>
# 自定义loader,解析DOM,下载图标
自定义一个loader,放在vue-loader的后面,也就是先它执行,对应的webpack的配置如下:
rules: [
{
test: /\.vue$/,
use: ['vue-loader', myfontLoader]
},
}
我的loader作用就是用来解析vue的DOM元素,用正则表达式匹配出my-font的name,再从服务器下载对应的图标到本地目录。
# 使用webfonts-loader
- 配置文件
webfonts-loader的配置文件myfont.font.js是这样的:
module.exports = {
'files': [
'./myfont/*.svg'
],
'fontName': 'myfonticons',
'classPrefix': 'myfonticon-',
'baseSelector': '.myfonticon',
'types': ['eot', 'woff', 'woff2', 'ttf', 'svg'],
'fileName': 'app.[fontname].[hash].[ext]'
};
其实就是下载到myfont文件夹。
webpack配置webfonts-loader除了上述配置文件,还需要有额外的webpack配置:
{
test: /\.font\.js/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: false
}
},
'webfonts-loader'
]
}
- 引入文件
还需要在入口文件中引入一个
js:
require('./myfont.font');
它的原理是,引用了这个文件后,就可以使用webfonts-loader得到配置文件,生成css提供给下一个loader——css-loader,再使用MiniCssExtractPlugin.loader生成单独的css文件。因为它监听配置的files文件夹,所以当文件夹内文件变更后,会触发webpack重新编译,编译后会进行热更新。
# 测试
经过测试,开发与打包都没有问题。
# 迁移到vue
# 配置
我们项目是使用vue-cli创建的,因为内置了webpack,封装了相当多的功能,所以只能通过它暴露的接口修改webpack配置。
chainWebpack: (config) => {
config.module
.rule('kissfont')
.test(/kiss\.font\.js/)
// .use(MiniCssExtractPlugin.loader)
// .loader(MiniCssExtractPlugin.loader)
// .end()
.use('css-loader')
.loader('css-loader')
.end()
.use('webfonts-loader')
.loader('webfonts-loader')
.end();
config.module.rule('vue').use(myfontLoader).loader(myfontLoader).end();
},
# 缺点
问题来了,如果使用MiniCssExtractPlugin.loader会报错。
注释掉后,倒是不报了,但还有另一个问题,首次是因为临时文件夹下面并没有图标,会报错。我的解决方案是每次先加个默认图标。
# 折腾
如果我能在图标下载完成后,再生成css与字体文件,是不是更好点?我们知道,webpack的插件提供了大量的钩子函数,在loader中下载完图标,完全可以在插件中干这件事情。
于是开始折腾。
在
webfonts-loader代码中,找到用来生成css和字体文件的库——@vusion/webfonts-generator。使用
html-webpack-plugin的钩子函数html-webpack-plugin-before-html-processing,在它期间生成css与字体把生成的
css注入到页面的css标签
这时,又出现一个问题:无法热更新。主要是开发环境下会有这种需求。
我进行了下列尝试:
把
css改为用js动态插入到页面,仍使用html-webpack-plugin来注入代码。失败。开发环境下,
webpack把生成的代码是放内存里了,我找到生成css的地方,并将它放到对应的内存(compiler.outputFileSystem,使用的是memory-fs)中。失败。服务启动时代理我存放
css的临时目录(config.devServer.contentBase(utils.getTmpDir())),这样目录下文件有更新,页面就会刷新。发现确实是进行刷新,而不是热更新。失败。
怎么办呢?
研究热更新的原理,还是我的css生成的时机不对,不在编译阶段,就没法热更新。可如果在编译阶段,我又无法知道什么时候所有的图标都下载完成。
陷入僵局。
没办法,只能参考webfonts-loader,在入口文件中引入css(import 'xx.css'),再加个style-loader和css-loader专门处理这个css,这样,当这个css有变化时,也能热更新了。
兜兜转转,没想到还是回归了webfonts-loader的思路,好尴尬。
# 过程中遇到的问题
# 多进程
parallel如果配置为true时,会根据CPU的核心数量开启多进程,如果本地存储变量做为中转记录的话,会失效,只能存在临时文件,或者process.env中,后者有个时机问题,只能在进程开始前存入,否则依然会失效。
# cache-loader缓存
vue文件被默认添加了cache-loader,它在加载一次后,会导致下次跟它相关的loader不会再触发,应该是使用了pitch函数的缘故。
为了打包的图标css能够纯净,需要每次打包都下载需要的图标,这时就得禁止cache-loader。
const vueRule = config.module.rule('vue');
vueRule.uses.delete('cache-loader');
vueRule.use(fontLoader).loader(fontLoader).options(options).end();
# require缓存
在loader中,添加对配置文件的监听:
this.addDependency(dir);
但配置文件的读取,是用require('xx.js')这样做的,它有个问题,执行过一次后,就加到内存里了,不会再读取这个文件,于是需要在合适的时候清除一遍缓存:
delete require.cache[require.resolve(dir)];
# 总结
通过这个功能,学习了webpack的插件和loader的使用,对cache-loader、thread-loader、html-webpack-plugin以及热更新都有了些了解。
最终的插件和使用可以见这里 (opens new window)