还在为 SVG 烦恼?我写了个 CLI 工具,一键打包,性能拉满!(已开源)
作者:[King-GD]
GitHub: github.com/King-GD/svg…
哈喽,各位掘友们好!作为一名前端开发者,和 SVG 打交道是家常便饭。它轻量、可缩放、还能用 CSS 控制样式,简直是图标方案的完美选择。但随着项目越来越大,SVG 图标一多,管理和使用上的“痛点”也随之而来。
最近,我就被这些痛点折磨得不轻,索性一怒之下,自己动手写了个 CLI 工具来彻底解决它们。没想到,从一个几十行的脚本开始,我最终把它打磨成了一个可以发布到 NPM 的完整开源项目:svg-quick。
今天,我想和大家分享的,不仅仅是这个工具,更是从一个想法到一个开源产品的完整心路历程。
🤔 我们究竟在烦恼 SVG 的什么?
在使用 SVG 图标时,你是否也遇到过这些问题?
1. 性能问题:如果每个图标都作为独立的 .svg 文件请求,上百个图标就会导致上百个 HTTP 请求,这对性能是灾难性的。
2. 打包效率:在大型项目中,Webpack/Vite 每次构建都要实时去处理成百上千个 SVG 文件(比如用 svgr 转换),这会明显拖慢本就“弥足珍贵”的构建速度。
3. 最终体积:将 SVG 作为 React/Vue 组件直接导入,每个图标都会被包裹一层组件的额外代码,积少成多,最终产物体积会悄悄膨胀。
4. 灵活性差:有时候我想在 <img> 里用,有时候想在 CSS background 里用,有时候又想给 ECharts 这类图表库用。不同的场景需要不同格式的图标,来回转换非常繁琐。
这些问题,每一个都像一根小刺,扎在日常开发的幸福感上。
💡 解决方案的进化之路
V1.0: 一个满足自己的脚本
一切始于一个简单的 Node.js 脚本。思路很直接:
-
扫描一个指定文件夹里的所有
.svg文件。 -
读取每个文件的内容。
-
用
svgo压缩优化。 -
把所有优化后的 SVG 字符串,塞到一个 JS 对象里。
-
最后生成一个
index.js文件,导出这个大对象和几个辅助函数。
这个脚本解决了“多文件请求”和“重复优化”的问题,让我可以很方便地在项目里使用。但它的问题也很明显:路径硬编码、无法复用、每次换项目都得复制粘贴改代码。
V2.0: 走向 CLI 工具
为了让这个脚本能在任何项目中开箱即用,我决定把它改造成一个真正的命令行工具。
我引入了 yargs 来解析命令行参数,核心命令很快就成型了:
svg-quick --input ./icons --output ./dist
这比之前的脚本强大多了!我可以指定任意的输入和输出目录,它变成了一个可复用的工具。我当时觉得已经很完美了,直到我遇到了现代前端工程化的“灵魂拷问”—— Tree-shaking。
V3.0: 拥抱 Tree-shaking 和专业化
我意识到,之前把所有图标都打包到一个大对象里的做法,有一个致命缺陷:
// 之前产物的核心
export const icons = {
user: '<svg>...',
setting: '<svg>...',
// ... 500 个图标
};
当我在代码里只 import 并使用 user 图标时,打包工具(Webpack/Vite)无法判断 icons 对象里其他 499 个图标是无用的,最终会导致所有图标都被打包进产物!
这绝对不能忍!于是,我重构了整个工具的输出结构。
新的产物变成了这样:
// Tree-shakeable 的产物
export const user = '<svg>...</svg>';
export const setting = '<svg>...</svg>';
// ... 每个图标都是一个独立的 const 导出
这样一来,import { user } from '...'; 就能让打包工具精确地“摇掉”所有未使用的图标,完美实现了按需打包。
同时,我还增加了更多专业化的功能:
-
多种输出模式 (
--mode): 用户可以选择只生成按需打包的版本 (treeshakeable),或者只生成全量版本 (full),甚至两者都要 (all)。 -
类型定义: 自动生成
.d.ts文件,在 TypeScript 项目中提供完美的类型提示和自动补全。 -
代码美化: 内置
Prettier,确保生成的代码风格统一且美观。
至此,svg-quick 已经从一个自用的“小作坊”脚本,进化成了一个功能完备、设计考究的“正规军”。
🚀 正式介绍:svg-quick
现在,你可以通过 npx 轻松使用它,无需安装:
npx svg-quick --input <源目录> --output <目标目录> --mode <输出模式>
它能为你带来什么?
-
⚡️ 极致性能: 预构建模式,提升构建速度,减小产物体积。
-
🌳 完美按需打包: 确保最终产物只包含你用到的图标。
-
✨ 高度灵活性: 多种输出模式,满足任何使用场景。
-
💻 优秀的开发体验: 统一的 API 和完整的 TypeScript 支持。
-
🔧 强大的优化: 内置
svgo,自动压缩优化。
📖 产物使用示例 (Using the Output)
根据你选择的 --mode,产物的使用方式也不同,这体现了性能与灵活性的权衡。
模式一:按需打包 (treeshakeable 模式)
这是追求极致性能的推荐用法。此模式下生成的模块只包含按图标名导出的 const 变量。
📖 产物使用示例 (Using the Output)
Treeshakeable 模式 (推荐)
当 mode 设置为 treeshakeable 时,你可以这样使用:
// 在你的 React / Vue / Svelte 组件中
import { user, arrowLeft } from '../generated-icons';
// 'user' 和 'arrowLeft' 都是优化后的 SVG 字符串
// 你可以轻松地封装成自己的 <Icon> 组件
// React 示例:
const Icon = ({ svgString, ...props }) => (
<span {...props} dangerouslySetInnerHTML={{ __html: svgString }} />
);
const App = () => (
<div>
<Icon svgString={user} style={{ color: 'blue' }} />
<Icon svgString={arrowLeft} style={{ color: 'red' }} />
</div>
);
优势: 打包工具构建你的应用时,只有被 import 的 user 和 arrowLeft 图标会被包含进来,实现完美的按需打包。
Full 模式
当 mode 设置为 full 时,你可以使用实用工具函数:
// 导入实用工具函数,支持动态获取图标
import { getIconData, getIconSrc } from '../generated-icons';
// 获取 SVG 字符串
const userIconSvg = getIconData('user');
// 获取 base64 编码的 data URL,可直接用于 <img> 标签或 CSS background
const userIconSrc = getIconSrc('user');
// 在组件中使用
const DynamicIcon = ({ iconName }) => {
const iconSrc = getIconSrc(iconName);
return iconSrc ? <img src={iconSrc} alt={iconName} /> : null;
};
// 或者在 CSS 中使用
const iconUrl = getIconSrc('user');
// background-image: url(data:image/svg+xml;base64,...)
在 ECharts 中使用图标 (需要 Full 模式)
// 导入 ECharts 和图标工具函数 (需要使用 full 模式生成)
import * as echarts from 'echarts';
import { getIconSrc } from '../generated-icons';
// 在 ECharts 配置中使用图标
const option = {
// 1. 在系列数据中使用图标作为 symbol
series: [
{
type: 'scatter',
data: [
{ value: [10, 20], symbol: getIconSrc('user') },
{ value: [30, 40], symbol: getIconSrc('star') },
],
symbolSize: 30,
},
],
// 2. 在图例中使用自定义图标
legend: {
data: [
{
name: '用户数据',
icon: getIconSrc('user'),
},
{
name: '收藏数据',
icon: getIconSrc('star'),
},
],
},
// 3. 在工具箱中使用自定义图标
toolbox: {
feature: {
myTool: {
show: true,
title: '自定义工具',
icon: getIconSrc('settings'),
onclick: function () {
console.log('自定义工具被点击');
},
},
},
},
// 4. 在标记点中使用图标
series: [
{
type: 'line',
data: [120, 200, 150, 80, 70, 110, 130],
markPoint: {
data: [
{
type: 'max',
symbol: getIconSrc('arrowUp'),
symbolSize: 25,
},
{
type: 'min',
symbol: getIconSrc('arrowDown'),
symbolSize: 25,
},
],
},
},
],
};
// 初始化图标
const chart = echarts.init(document.getElementById('chart'));
chart.setOption(option);
注意:使用此模式会生成一个包含所有图标的全量
icons对象和辅助函数。因此,无论你实际用了几个,所有图标都会被打包进最终产物。
🌟 写在最后
从一个解决自己痛点的小脚本,到一个功能完善并发布到 NPM 的开源工具,这个过程让我收获良多。最大的感悟是,“轮子”不一定要惊天动地,能解决一个真实、具体、哪怕很小的问题,它就是有价值的。
svg-quick 现在已经非常稳定,如果你也曾为 SVG 的管理而烦恼,不妨试一试它。
如果你觉得这个小工具对你有帮助,欢迎给我的 GitHub 仓库点个 Star 🌟!如果你有任何想法或建议,也非常欢迎提 Issue 或 PR。
感谢阅读!
项目链接
- GitHub (求 Star ⭐): github.com/King-GD/svg…
- NPM: www.npmjs.com/package/svg…