背景
在微信小程序开发过程中难免会遇到须要应用多色 icon 的场景,我的项目中的 icon 个别寄存在 iconfont 上。
iconfont 有三种援用形式(参考 https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.d8cf4382a&helptype=code)
- unicode 援用:因为是字体,所以不反对多色
- font-class 援用:实质上还是应用的字体,所以多色图标还是不反对的
- symbol 援用:反对多色,应用 svg 渲染
然而微信小程序 iconfont 并不反对 <svg>
标签,只反对以 background 模式渲染.svg 文件。基于此,能够应用 Node 编写脚本,将 iconfont 我的项目里的多色 icon 下载到我的项目中,生成
.arrow-down--colorful {background: url('./arrow-down.[hash].svg');
}
// 或者有 CDN 的能够
.arrow-down--colorful {background: url('//[cdn]/imgs/arrow-down.[hash].svg');
}
步骤
步骤一、上传 icon 至 iconfont
留神在上传时,带色彩的图标须要有固定后缀来辨别。例如一般 icon 为 icon-name,则带色彩的 icon 须要为 icon-name–colorful
步骤二、脚本下载 icon_**.css
应用 Node 下载我的项目对应的 css 脚本,格局为:
@font-face {font-family: "iconfont";
src: url('//at.alicdn.com/t/font_**.eot?t=1571134856668');
src: url('//at.alicdn.com/t/font_385238_**.eot?t=1571134856668#iefix') format('embedded-opentype'),
url('data:application/x-font-woff2;**') format('woff2'),
url('//at.alicdn.com/t/font_**.woff?t=**') format('woff'),
url('//at.alicdn.com/t/font_**.ttf?t=**') format('truetype'),
url('//at.alicdn.com/t/font_385238_**.svg?t=**#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
...
}
.icon-name:before {content: "\e600";}
.icon-name--colorful:before {content: "\e653";}
删除其中无用的 font 格局,以及带 –colorful 的 css 定义。应用脚本将其格式化为
@font-face {font-family: "iconfont";
src: url('data:application/x-font-woff2;**') format('woff2');
}
.iconfont {
font-family: "iconfont" !important;
...
}
.icon-name:before {content: "\e600";}
步骤三、脚本下载 icon_**.js
应用 Node 下载我的项目对应的 js 脚本,格局为:
!function(o){var t,l='<svg><symbol id="icon-name”viewBox="0 0 1024 1024"><path …>…</path></symbol>symbol id="icon-name—colorful”viewBox="0 0 1024 1024"><path …>…</path></symbol></svg>’,…}(window);
提取出其中带色彩 (id 以 –colorful 结尾) 的 <symbol></symbol> 标签,应用 xml 解析工具,失去
<path d="..." fill="..." ></path><path d="..." fill="..." ></path>
留神:如果步骤一不能实现依照 –colorful 辨别是否是多色 icon,则须要剖析各个 path 的色彩是否统一来辨别是否是多色。
拼接上 <svg></svg>
标签
function getHash(cont: string) {
return cryptoNode
.createHash('md5')
.update(cont)
.digest('hex')
.substr(0, 8);
}
步骤四、生成 hash 值及图片文件
最终失去的 svgStr 的 hash 值
function getHash(cont: string) {
return cryptoNode
.createHash('md5')
.update(cont)
.digest('hex')
.substr(0, 8);
}
应用 hash 值是为了防止更换 icon 然而不换名字的场景
生成本地图片文件
const hash = getHash(svgStr);
const fileName = `${iconId}.${hash}.svg`;
const filePath = `${stylePath}${fileName}`;
fs.writeFileSync(filePath, svgStr);
步骤五、将图片上传至 CDN 并删除本地文件(可选)
如果有 CDN 资源能够将图片上传至 CDN,能够节约打包出的我的项目体积
uploadFileToCDN(filePath);
/** 将 pathWithHash 文件上传到 CDN */
async function uploadFileToCDN(pathWithHash: string) {return new Promise((resolve, reject) => {
exec(`${此处为上传至 CDN 的命令行}`,
(error: any, stdout: any, stderr: any) => {if (error) {console.error(`exec error: ${error}`);
reject(error);
return;
}
resolve('');
/** 删除文件 */
fs.unlinkSync(pathWithHash);
}
);
});
}
步骤六、生成残缺的 css 内容并写入本地
给步骤二的 css 文件拼接上带色彩图片的 css 内容
@font-face {font-family: "iconfont";
src: url('data:application/x-font-woff2;**') format('woff2');
}
.iconfont {
font-family: "iconfont" !important;
...
}
.icon-name:before {content: "\e600";}
.icon-name--colorful:before {background: url('cdnUrl');
}
并写入本地
fs.writeFileSync(cssPath, fileContent.replace(/"/g,"'"), 'utf8');
残缺的脚本文件示例
const http = require('http');
const cryptoNode = require('crypto');
const {exec} = require('child_process');
const xml2js = require('xml2js');
const fs = require('fs');
const config = {url: '//at.alicdn.com/t/font_***.js'};
// 上传至 CDN 后的前缀
const cdnPath = '***';
/** 生成的 css 文件 path */
const cssPath = 'src/styles/iconfont.scss';
const stylePath = 'src/styles/';
const {parseString} = xml2js;
// 替换 iconfont.scss
const iconfontUrl = config.url.replace(/.*\/\//, 'http://');
const iconfontUrl_css = iconfontUrl.replace('.js', '.css');
let fileContent = '';
type IPath = {
$: {[key: string]: string;
};
};
type ISymbol = {
$: {
id: string;
viewBox: string;
};
path: IPath[];};
/** 读取 css 文件,去掉近程连贯 */
async function generateFile() {const cssData = await readRemoteFile(iconfontUrl_css);
fileContent = cssData.replace(/[^,;]*at.alicdn.com.*\*\//g, '');
// 替换掉 woff
fileContent = fileContent.replace(/[^)]*at.alicdn.com.*\),/g, ';');
// 替换掉 colorful
fileContent = fileContent.replace(/[^}]*colorful[^}]*}/g, '');
// 替换 src
fileContent = fileContent.replace('url(', 'src: url(');
// 加换行
fileContent = fileContent.replace('{font-family', '{\n font-family');
// 加换行
fileContent = fileContent.replace(') format(', ')\n format(');
// 去除最初一个换行
fileContent = fileContent.replace(/\n$/, '');
// 有色彩的图标生成 background: url('cdnUrl')
const fontXml = await fetchXml(iconfontUrl);
fontXml.svg.symbol.forEach((item: ISymbol) => {
const iconId = item.$.id;
if (/colorful/.test(iconId)) {
let svgStr = '';
item.path.forEach((path: IPath) => {const keys = Object.keys(path.$);
let attrStr = '';
keys.forEach(k => {attrStr += `${k}="${path.$[k]}" `;
});
svgStr = `${svgStr}<path ${attrStr}></path>`;
});
svgStr = `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em">${svgStr}</svg>`;
const hash = getHash(svgStr);
const fileName = `${iconId}.${hash}.svg`;
const filePath = `${stylePath}${fileName}`;
fs.writeFileSync(filePath, svgStr);
uploadFileToCDN(filePath);
svgStr = `\n.${iconId} {\n background: url('${cdnPath}${fileName}');\n}\n`;
fileContent += svgStr;
}
});
}
function getHash(cont: string) {
return cryptoNode
.createHash('md5')
.update(cont)
.digest('hex')
.substr(0, 8);
}
async function uploadAndUpdate() {
// 写文件
fs.writeFileSync(cssPath, fileContent.replace(/"/g,"'"), 'utf8');
}
generateFile().then(() => {uploadAndUpdate();
});
function readRemoteFile(remoteUrl: string): Promise<string> {
let _data = '';
return new Promise(resolve => {http.get(remoteUrl, (res: any) => {
// 数据
res.on('data', function(chunk: string) {_data += chunk;});
res.on('end', function() {resolve(_data);
});
});
});
}
async function fetchXml(url: string): Promise<{
svg: {symbol: ISymbol[];
};
}> {const data = await readRemoteFile(url);
const matches = String(data).match(/'<svg>(.+?)<\/svg>'/);
return new Promise((resolve, reject) => {parseString(`<svg>${matches ? matches[1] : ''}</svg>`, (err: any, result: any) => {if (err) {reject(err);
} else {resolve(result);
}
});
});
}
/** 将 pathWithHash 文件上传到 CDN */
async function uploadFileToCDN(pathWithHash: string) {return new Promise((resolve, reject) => {exec(`${此处为上传至 CDN 的命令行}`, (error: any, stdout: any, stderr: any) => {if (error) {console.error(`exec error: ${error}`);
reject(error);
return;
}
resolve('');
/** 删除文件 */
fs.unlinkSync(pathWithHash);
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
});
}
再在 package.json
中的 scripts 中配置
"icon": "ts-node scripts/iconfont.ts"
则每次更新 iconfont 后,更新对应的 url,再运行 npm run icon
即可