基于react-native-vector-icons与 iconfont 的生成自定义Icon组件的工具
前言
原本项目中使用了 react-native-vector-icons 中已有的图标,但是领导希望应用内的 icon与UI同学的保持一致,所以需要使用自定义icon的能力,但是每次UI同学更新完icon库之后,都要手动去维护本地的Iocn的名字比较麻烦,所以产生了写一个脚本自动修改本地图标的想法。
首先我们的图标是维护在阿里巴巴矢量图标库上的;
阿里巴巴矢量图标库链接https://www.iconfont.cn
准备工作
项目导入依赖 react-native-vector-icons
从 阿里巴巴矢量图标库下载iconfont的压缩包并解压,下载方式如下图:
使用
将末尾的代码放在项目的根目录下,然后执行下面命令即可生成
注意: iOS 需要将写到 ios的文件在Xcode中添加到项目中
命令:node task-iconfont.js component=路径 iconNames=路径 androidFolder=路径 iOSFolder=路径 fileName=文件名 ./src/…解压后路径
参数说明:
component:(可选参数)自定义icon组件的路径 ,默认:src/icon-out/IconSet.tsx
iconNames:(可选参数)全部icon名称的路径,默认:无不输出
androidFolder:(可选参数)android 源文件存放位置;默认:android/app/src/main/assets/fonts/
iOSFolder:(可选参数)ios 源文件存放位置;默认:ios/fonts/
fileName: (可选参数)源文件的文件名(默认为读取到的源文件的文件名)
./src/…解压后路径: (必填参数)下载回来的压缩包解压后的文件夹
task-iconfont.js 实现
const fs = require('fs');
const childProcess = require('child_process');
const path = require('path');
const chalk = require('chalk');
const util = require('util');
const defaultConfig = {
component: 'src/icon-out/IconSet.tsx',
iconNames: undefined,
androidFolder: 'android/app/src/main/assets/fonts/',
iOSFolder: 'ios/fonts/',
fileName: undefined,
};
const run = () => {
const argv = process.argv;
const [, , ...params] = argv;
if (!params) {
Logger.error('参数错误!');
}
const config = initializeConfig(params);
if (config) {
const json = JSON.parse(fs.readFileSync(config.res.json, { encoding: 'utf8', mode: 438 }));
writeIconSet(config.component, json, config.fileName);
const ttf = fs.readFileSync(config.res.ttf);
writeTtf(config.androidFolder, config.fileName, ttf);
writeTtf(config.iOSFolder, config.fileName, ttf);
if (config.iconNames) {
writeIconNames(config.iconNames, json, config.component);
}
}
};
/**
*
* @param {string} ttf
* @param {string[]} params
* @returns {{
* res:{ttf:string;json:string};
* component: string;
* iconNames:string;
* androidFolder:string;
* iOSFolder:string;
* fileName: string
* }}
*/
const initializeConfig = (params) => {
const result = {
...defaultConfig,
};
for (let index = 0, len = params.length; index < len; index++) {
const str = params[index].trim();
if (str.indexOf('=') !== -1) {
const [key, value] = str.split('=');
result[key] = value;
} else {
try {
const stat = fs.statSync(str, { encoding: 'utf8' });
if (stat.isDirectory()) {
const ttf = findFileBySuffix(str, '.ttf');
const json = findFileBySuffix(path.dirname(ttf), '.json');
if (ttf && json) {
result.res = { ttf, json };
}
} else {
Logger.error('未知参数: ' + str);
return undefined;
}
} catch (error) {
Logger.error('未知参数: ' + str);
return undefined;
}
}
}
if (!result.res) {
Logger.error('文件自动查找失败;请确认文件夹内是否存在 *.json 和 *.ttf 文件!');
return undefined;
}
if (!result.fileName) {
result.fileName = path.basename(result.res.ttf);
}
return result;
};
/**
* 遍历查找 ttf文件
* @param {string} folder 要查询的文件夹
* @param {string} extname 后缀
* @returns {string|undefined}
*/
const findFileBySuffix = (folder, extname) => {
const files = fs.readdirSync(folder, { encoding: 'utf8' });
for (let index = 0, len = files.length; index < len; index++) {
const currFile = path.join(folder, files[index]);
const stat = fs.statSync(currFile, { encoding: 'utf8' });
if (stat.isDirectory()) {
const find = findFileBySuffix(currFile, extname);
if (find) {
return find;
}
} else {
if (path.extname(currFile).toLocaleLowerCase() === extname) {
return currFile;
}
}
}
return undefined;
};
/**
* 写入组件文件
* @param {string} outFile 输出文件路径
* @param {{font_family:string;glyphs:{font_class:string; unicode_decimal:string}}} json 数据源
* @param {string} name 文件名称
*/
const writeIconSet = (outFile, json, name) => {
const folder = path.dirname(outFile);
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder);
}
const option = { encoding: 'utf8', mode: 438 };
const header = `import { createIconSet } from 'react-native-vector-icons';`;
const footer = `
const Icon = createIconSet(glyphMap, '${json.font_family}', '${name}');
export default Icon;
export type IconName = keyof typeof glyphMap;
`;
const glyphMap = {};
for (let i = 0, len = json.glyphs.length; i < len; i++) {
const { font_class, unicode_decimal } = json.glyphs[i];
glyphMap[font_class] = unicode_decimal;
}
const body = `const glyphMap=${JSON.stringify(glyphMap)};`;
fs.writeFile(outFile, `${header}${body}${footer}`, option, () => {
// && git add ${outFile}
childProcess.exec(`eslint --fix ${outFile}`, (error) => {
if (error) {
Logger.warn(error);
} else {
Logger.info(`${outFile} 写入成功!`);
}
});
});
};
const writeIconNames = (outFile, json, iconset) => {
const folder = path.dirname(outFile);
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder);
}
const option = { encoding: 'utf8', mode: 438 };
let relative = removeExtname(path.relative(path.dirname(outFile), iconset));
relative = relative.startsWith('..') ? relative : `.${path.sep}${relative}`;
const header = `import {IconName} from '${relative}';`;
const footer = `
export default icons;
`;
const icons = [];
for (let i = 0, len = json.glyphs.length; i < len; i++) {
const { font_class } = json.glyphs[i];
icons.push(font_class);
}
const body = `const icons:IconName[]=${JSON.stringify(icons)};`;
fs.writeFile(outFile, `${header}${body}${footer}`, option, () => {
childProcess.exec(`eslint --fix ${outFile}`, (error) => {
if (error) {
Logger.warn(error);
} else {
Logger.info(`${outFile} 写入成功!`);
}
});
});
};
/**
* 写入文件
* @param {string} outFolder 路径
* @param {string} fileName 文件名称
* @param {Buffer} data 数据
*/
const writeTtf = (outFolder, fileName, data) => {
if (outFolder) {
if (!fs.existsSync(outFolder)) {
fs.mkdirSync(path.dirname(outFolder));
}
const outFile = path.join(outFolder, fileName);
fs.writeFile(outFile, data, (err) => {
if (err) {
Logger.warn(err);
return;
}
childProcess.exec(`eslint --fix ${outFile}`, (error) => {
if (error) {
Logger.warn(error);
} else {
Logger.info(`${outFile} 写入成功!`);
}
});
});
}
};
/**
*
* @param {string} file 文件路径
* @returns 去掉后缀之后的路径
*/
const removeExtname = (file) => {
return file.substring(0, file.length - path.extname(file).length);
};
/**
* 输出日志 工具类
*/
const Logger = {
error: (message, ...optionalParams) => {
const format = util.format.call(this, message, ...optionalParams);
process.stdout.write(chalk.red(format + '\n'));
},
warn: (message, ...optionalParams) => {
const format = util.format.call(this, message, ...optionalParams);
process.stdout.write(chalk.yellow(format + '\n'));
},
info: (message, ...optionalParams) => {
const format = util.format.call(this, message, ...optionalParams);
process.stdout.write(chalk.green(format + '\n'));
},
};
run();