icon.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. const fs = require("fs");
  2. const path = require("path");
  3. const AdmZip = require("adm-zip");
  4. // 清理所有临时文件
  5. function cleanupTempDir() {
  6. const tempDir = path.join(".cool", "temp");
  7. if (fs.existsSync(tempDir)) {
  8. try {
  9. fs.rmSync(tempDir, { recursive: true, force: true });
  10. } catch (error) {
  11. console.warn(`❌ 清理临时目录失败: ${ tempDir }`, error);
  12. }
  13. }
  14. }
  15. // 确保临时目录存在
  16. function ensureTempDir() {
  17. const tempDir = path.join(".cool", "temp");
  18. if (!fs.existsSync(tempDir)) {
  19. fs.mkdirSync(tempDir, { recursive: true });
  20. }
  21. }
  22. // 创建icons目录和子目录
  23. function ensureDistDir(folderName = "") {
  24. const iconsPath = folderName ? path.join("icons", folderName) : "icons";
  25. if (!fs.existsSync(iconsPath)) {
  26. fs.mkdirSync(iconsPath, { recursive: true });
  27. }
  28. }
  29. // 读取zip文件列表
  30. function getZipFiles() {
  31. const iconsDir = path.join(".cool", "icons");
  32. if (!fs.existsSync(iconsDir)) {
  33. console.error(`❌ 目录不存在: ${ iconsDir }`);
  34. return [];
  35. }
  36. return fs.readdirSync(iconsDir).filter((item) => {
  37. const filePath = path.join(iconsDir, item);
  38. const stats = fs.statSync(filePath);
  39. return stats.isFile() && item.endsWith(".zip");
  40. });
  41. }
  42. // 解压zip文件到临时目录
  43. function extractZipFile(zipPath, folderName) {
  44. try {
  45. const zip = new AdmZip(zipPath);
  46. const tempDir = path.join(".cool", "temp", folderName);
  47. // 确保临时目录存在
  48. if (!fs.existsSync(tempDir)) {
  49. fs.mkdirSync(tempDir, { recursive: true });
  50. }
  51. // 解压到临时目录
  52. zip.extractAllTo(tempDir, true);
  53. // 检查是否有额外的顶层文件夹
  54. const extractedItems = fs.readdirSync(tempDir);
  55. // 如果只有一个项目且是文件夹,则可能是额外的包装文件夹
  56. if (extractedItems.length === 1) {
  57. const singleItem = extractedItems[0];
  58. const singleItemPath = path.join(tempDir, singleItem);
  59. const stats = fs.statSync(singleItemPath);
  60. if (stats.isDirectory()) {
  61. // 检查这个文件夹是否包含我们需要的文件
  62. const innerItems = fs.readdirSync(singleItemPath);
  63. const hasIconFiles = innerItems.some(
  64. (item) =>
  65. item.endsWith(".ttf") || item.endsWith(".json") || item.endsWith(".css")
  66. );
  67. if (hasIconFiles) {
  68. return singleItemPath;
  69. }
  70. }
  71. }
  72. return tempDir;
  73. } catch (error) {
  74. console.error(`❌ 解压失败: ${ zipPath }`, error);
  75. return null;
  76. }
  77. }
  78. // 将TTF文件转换为base64
  79. function ttfToBase64(ttfPath) {
  80. try {
  81. const ttfBuffer = fs.readFileSync(ttfPath);
  82. return ttfBuffer.toString("base64");
  83. } catch (error) {
  84. console.error(`❌ 读取TTF文件失败: ${ ttfPath }`, error);
  85. return null;
  86. }
  87. }
  88. // 生成TypeScript文件
  89. function generateTypeScript(originalFolderName, camelCaseName, iconData, iconPrefix) {
  90. const tsContent = `export const ${ camelCaseName } = {\n${ iconData
  91. .map((item) => `\t"${ iconPrefix }${ item.name }": "${ item.unicode }"`)
  92. .join(",\n") }\n};\n`;
  93. const outputPath = path.join("icons", originalFolderName, "index.ts");
  94. fs.writeFileSync(outputPath, tsContent);
  95. }
  96. // 生成SCSS文件
  97. function generateSCSS(originalFolderName, base64Data) {
  98. const scssContent = `@font-face {\n\tfont-family: "${ toCamelCase(originalFolderName) }";\n\tsrc: url("data:font/ttf;base64,${ base64Data }") format("woff");\n}\n`;
  99. const outputPath = path.join("icons", originalFolderName, "index.scss");
  100. fs.writeFileSync(outputPath, scssContent);
  101. }
  102. // 从CSS文件提取图标数据(用于remixicon等)
  103. function extractIconsFromCSS(cssPath) {
  104. try {
  105. const cssContent = fs.readFileSync(cssPath, "utf8");
  106. const iconData = [];
  107. // 匹配CSS中的图标规则,例如:.ri-home-line:before { content: "\ee2b"; }
  108. const regex = /\.ri-([^:]+):before\s*{\s*content:\s*"\\([^"]+)"/g;
  109. let match;
  110. while ((match = regex.exec(cssContent)) !== null) {
  111. const iconName = match[1];
  112. const unicode = match[2];
  113. iconData.push({
  114. name: iconName,
  115. unicode: unicode
  116. });
  117. }
  118. return iconData;
  119. } catch (error) {
  120. console.error(`❌ 读取CSS文件失败: ${ cssPath }`, error);
  121. return [];
  122. }
  123. }
  124. // 读取和处理图标数据
  125. function processIconData(jsonPath) {
  126. try {
  127. const jsonData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
  128. return jsonData.glyphs.map((item) => ({
  129. name: item.font_class,
  130. unicode: item.unicode
  131. }));
  132. } catch (error) {
  133. console.error(`❌ 读取JSON文件失败: ${ jsonPath }`, error);
  134. return [];
  135. }
  136. }
  137. // 读取iconfont图标前缀
  138. function getIconPrefix(jsonPath) {
  139. try {
  140. const jsonData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
  141. return jsonData.css_prefix_text;
  142. } catch (error) {
  143. console.error(`❌ 读取JSON文件失败: ${jsonPath}`, error);
  144. return [];
  145. }
  146. }
  147. // 将连字符转换为驼峰命名的函数
  148. function toCamelCase(str) {
  149. return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
  150. }
  151. // 处理单个zip文件
  152. function processZipFile(zipFileName) {
  153. const originalFolderName = path.basename(zipFileName, ".zip");
  154. const folderName = toCamelCase(originalFolderName); // 转换为驼峰命名用于变量名
  155. const zipPath = path.join(".cool", "icons", zipFileName);
  156. // 解压zip文件 (使用原始文件夹名称)
  157. const tempDir = extractZipFile(zipPath, originalFolderName);
  158. if (!tempDir) {
  159. return null;
  160. }
  161. // 图标库名称
  162. const ptName = ["iconfont", "remixicon"];
  163. // 获取文件路径
  164. const getFilePath = (ext) => {
  165. let filePath = null;
  166. for (const name of ptName) {
  167. const tempPath = path.join(tempDir, `${ name }.${ ext }`);
  168. if (fs.existsSync(tempPath)) {
  169. filePath = tempPath;
  170. break;
  171. }
  172. }
  173. return filePath;
  174. };
  175. // 在解压后的目录中查找文件
  176. const jsonPath = getFilePath("json");
  177. const cssPath = getFilePath("css");
  178. const ttfPath = getFilePath("ttf");
  179. if (!ttfPath) {
  180. console.warn(`⚠️跳过 ${ folderName }: 缺少 TTF 文件`);
  181. return null;
  182. }
  183. let iconData = [];
  184. let iconPrefix = "";
  185. // 优先使用JSON文件
  186. if (jsonPath) {
  187. iconData = processIconData(jsonPath);
  188. if (originalFolderName !== "iconfont") {
  189. iconPrefix = getIconPrefix(jsonPath);
  190. }
  191. }
  192. // 如果没有则尝试CSS文件
  193. else if (cssPath) {
  194. iconData = extractIconsFromCSS(cssPath);
  195. } else {
  196. console.warn(`⚠️ 跳过 ${ folderName }: 缺少 ${ jsonPath } 或 ${ cssPath }`);
  197. return null;
  198. }
  199. if (iconData.length === 0) {
  200. console.warn(`⚠️ ${ folderName }: 没有找到图标数据`);
  201. return null;
  202. }
  203. console.log(`✅ ${ zipFileName } 找到 ${ iconData.length } 个图标`);
  204. // 转换TTF为base64
  205. const base64Data = ttfToBase64(ttfPath);
  206. if (!base64Data) {
  207. console.error(`❌ ${ folderName }: TTF转换失败`);
  208. return null;
  209. }
  210. // 为该文件夹创建icons子目录 (使用原始文件夹名称)
  211. ensureDistDir(originalFolderName);
  212. // 生成TypeScript文件 (使用驼峰命名作为变量名,原始名称作为路径)
  213. generateTypeScript(originalFolderName, folderName, iconData, iconPrefix);
  214. // 生成SCSS文件 (使用原始名称作为路径和字体名称)
  215. generateSCSS(originalFolderName, base64Data);
  216. return { originalName: originalFolderName, camelName: folderName };
  217. }
  218. // 生成主index.ts文件
  219. function generateIndexTS(actualFolders) {
  220. const imports = actualFolders
  221. .map((folder) => {
  222. const camelName = toCamelCase(folder);
  223. return `import { ${ camelName } } from "./${ folder }";`;
  224. })
  225. .join("\n");
  226. const exports = `export const icons = {\n${ actualFolders
  227. .map((folder) => `\t${ toCamelCase(folder) }`)
  228. .join(",\n") }\n};\n`;
  229. const content = `${ imports }\n\n${ exports }`;
  230. fs.writeFileSync("icons/index.ts", content);
  231. }
  232. // 生成主index.scss文件
  233. function generateIndexSCSS(actualFolders) {
  234. const imports = actualFolders.map((folder) => `@import "./${ folder }/index.scss";`).join("\n");
  235. fs.writeFileSync("icons/index.scss", imports + "\n");
  236. }
  237. // 扫描icons目录下的实际文件夹
  238. function getActualIconFolders() {
  239. const iconsDir = "icons";
  240. if (!fs.existsSync(iconsDir)) {
  241. return [];
  242. }
  243. return fs.readdirSync(iconsDir).filter((item) => {
  244. const itemPath = path.join(iconsDir, item);
  245. const stats = fs.statSync(itemPath);
  246. return stats.isDirectory();
  247. });
  248. }
  249. // 主函数
  250. function main() {
  251. console.log("🚀 开始处理字体文件...\n");
  252. // 确保临时目录存在
  253. ensureTempDir();
  254. // 确保icons目录存在
  255. ensureDistDir();
  256. try {
  257. // 获取所有zip文件
  258. const zipFiles = getZipFiles();
  259. // 处理每个zip文件
  260. const processedFolders = [];
  261. for (const zipFile of zipFiles) {
  262. const result = processZipFile(zipFile);
  263. if (result) {
  264. processedFolders.push(result);
  265. }
  266. }
  267. // 扫描icons目录下的实际文件夹
  268. const actualFolders = getActualIconFolders();
  269. if (actualFolders.length > 0) {
  270. // 生成主index文件
  271. generateIndexTS(actualFolders);
  272. generateIndexSCSS(actualFolders);
  273. }
  274. if (processedFolders.length > 0) {
  275. const folderNames = processedFolders.map((f) =>
  276. typeof f === "string" ? f : f.originalName
  277. );
  278. console.log(
  279. `\n🎉 成功处理了 ${ processedFolders.length } 个字体包: ${ folderNames.join(", ") }`
  280. );
  281. }
  282. } catch (error) {
  283. console.error("❌ 脚本执行出错:", error);
  284. } finally {
  285. cleanupTempDir();
  286. }
  287. }
  288. // 运行脚本
  289. if (require.main === module) {
  290. main();
  291. }