wsksvg — SVG 转换与优化工具

田间的风吹老了岁月,老舍笔下的茶馆写的是近代史,真的写尽了当时的苦态,可能现在的地铁写的是现代史吧。时光飞逝,很快就工作两三年了。昨天做项目的时候,引入svg图像转换为组件的时候,觉得很麻烦,自己手动转换,效率低,而且无脑,关键还不能直接用,有时候UI切的svg的图是没办法直接转为组件的,还要手动修改,这让我这个底层打工仔很生气。一怒之下,写了个插件,进行转换。然后就篇文章分享一下给大家,同时大家也可以直接使用。

介绍

在现代Web开发中,SVG因其无损缩放和丰富的交互性而受到广泛欢迎。然而,手动将SVG文件转换为React或Vue组件不仅耗时,还容易出错。为了解决这个问题,我开发了wsksvg。这个工具不仅可以优化SVG文件,还能将其转换为格式化的无状态React组件或Vue组件。它支持单文件处理,也支持批量处理,通过一个安装命令和一条指令大大提高开发效率,为底层开发者节省时间。

功能和特点

以下是其主要功能和特点:

1.SVG 优化:

使用 svgo 进行 SVG 文件的优化,包括调整颜色、移除不必要的属性等。
根据 --vue 或 --react 选项,生成 Vue 或 React 组件。

2.组件生成:

Vue 组件:将 SVG 转换为 Vue 单文件组件(.vue)。
React 组件:将 SVG 转换为 React 组件(.tsx)。

3.图像优化:

支持 PNG、JPG 和 JPEG 格式图像的优化。
使用 sharp 进行图像尺寸调整和优化。

4.输入输出处理:

支持处理单个文件或目录下的所有 SVG、PNG、JPG、JPEG 文件。
输出路径可以指定,也可以不指定,默认在输入路径相同目录下生成。

特点

  • 灵活配置:根据选项生成 Vue 或 React 组件,或仅优化 SVG 文件。
  • 自动路径处理:支持处理文件和目录,并自动创建输出目录。
  • 增强 SVG 功能:保留 SVG 原始颜色和尺寸,移除不必要的属性。
  • 详细日志:打印原始和优化后的文件大小,帮助用户了解优化效果。
  • 错误处理:对不支持的文件类型和处理错误有明确的错误提示。

这个工具为开发人员提供了便捷的方式来优化图像并生成组件,适用于需要处理大量 SVG 文件和图像资源的项目。

使用说明

1.安装

npm install -g wsksvg

2.使用列子

  wsksvg audio-file-raw.svg 
  wsksvg audio-file-raw.jpg
  wsksvg audio-file-raw.png
  wsksvg ./rawSvg
  wsksvg ./rawSvg ./test  //默认优化
  wsksvg ./rawSvg ./testVue --vue  //生成vue组件
  wsksvg ./rawSvg ./testReact --react //生成react 组件
  wsksvg  ./raw  //支持模糊匹配文件名称
  ./rawSvg 输入文件路径  ./test 输出文件路径

灵感来源

做公司项目的时候,不同项目之间总是使用相同的图标,或者只是颜色不同的图标,还有大小不一样的图标,因为不同项目间的,没办法共用,我就开始写个图标组件库,动态的改变图标的颜色,使用svg图片是比较合适的,符合我的项目需求,但是单纯的svg图标无法实现动态图标,那要转化为组件,我用的是react,那我要转化为React组件。刚开始不以为然,命名传参,返回svg图像,但UI切的SVG图,并不什么时候都能用够直接使用的,需要处理,因为React组件正常使用,需要手动修改。
在这里插入图片描述

那时候就开始思考,能不能通过工具进行处理,然后进行百度,还别说真的可以,有现成的插件的,可以直接生成,但是插件它只能生成React的组件, 不能生成Vue的组件,那就不能在不同框架下进行使用,有时候我也只想对图片进行优化,有些图标不需要改变颜色,尺寸,也不需要点击交互,单纯的优化。那它就不满足我的需求了。就开始尝试自己写一个。

实现过程

SVG 转React组件

寻找解决方案,发现svgr插件能够进行处理,那就自己写个文件进行处理,首先明白自己的需求,我想把一个文件夹下面的所有的SVG图片,都编译成react组件,那就是读取一个文件夹下的所有svg,那就要用到node fs 实现文件的读取,
然后进行格式转化通过–这个插件来处理,然后查看这个插件的官网,进行编译,然后写到另外一个文件下,汇总到index.ts文件中,共我导出使用。

import { transform } from '@svgr/core';
import { resolve, dirname, extname, basename } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import camelCase from 'camelcase';
import { optimize } from 'svgo';
import svgoConfig from '../svgo.config.js';
import svgoRawConfig from '../svgo.raw.config.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

const entryDir = resolve(__dirname, '../rawSvg');
const outDir = resolve(__dirname, '../src/ReactIcons');

// Ensure output directory exists
if (!fs.existsSync(outDir)) {
  fs.mkdirSync(outDir, { recursive: true });
}

// Read all SVG files from the input directory
const files = fs.readdirSync(entryDir, 'utf-8');
const indexFileName = 'index.ts';
const prefix = '';
const suffix = '';

// Process SVG files
const batches = files.filter(f => extname(f) === '.svg').map(async file => {
  try {
    const svgFileName = basename(file, '.svg');
    const componentName = `${prefix}${camelCase(svgFileName, { pascalCase: true })}${suffix}`;
    const reactFileName = `${componentName}.tsx`;
    const svgContent = fs.readFileSync(resolve(entryDir, file), 'utf-8');

    // Choose appropriate SVGO configuration
    const config = file.includes('-raw.svg') ? svgoRawConfig : svgoConfig;
    const result = optimize(svgContent, config);

    // Transform SVG to React component
    const jsxCode = await transform(result.data, {
      plugins: ['@svgr/plugin-jsx', '@svgr/plugin-prettier'],
      icon: true,
      typescript: true,
    }, {
      componentName: componentName,
    });

    // Write transformed SVG to a file
    fs.writeFileSync(resolve(outDir, reactFileName), jsxCode, 'utf-8');
    return { fileName: reactFileName, componentName };
  } catch (error) {
    console.error(`Error processing file ${file}:`, error);
    throw error;
  }
});

// Generate index file with exports
const arr = await Promise.all(batches);
const indexFileContent = arr.map(a => `export { default as ${a.componentName} } from './${a.componentName}';`).join('\n');
fs.writeFileSync(resolve(outDir, indexFileName), indexFileContent, 'utf-8');

console.log('SVG to React components conversion completed successfully.');

React都转了也不差Vue的

思考React的都能实现,React用的是jsx语法,Vue3支持,但Vue2不支持,Vue的语法,不就是模板语法嘛,编写模板语法,然后在把SVG写到模板语法中。

import { optimize } from 'svgo';
import fs from 'fs';
import path from 'path';
import camelCase from 'camelcase';
import { fileURLToPath } from 'url';

import svgoConfig from '../svgo.config.js';
import svgoRawConfig from '../svgo.raw.config.js';

// 获取 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const entryDir = path.resolve(__dirname, '../rawSvg');
const outDir = path.resolve(__dirname, '../src/VueIcons');

// 确保输出目录存在
if (!fs.existsSync(outDir)) {
  fs.mkdirSync(outDir, { recursive: true });
}

// 读取所有 SVG 文件
const files = fs.readdirSync(entryDir, 'utf-8');

// 处理 SVG 文件
const batches = files.filter(f => path.extname(f) === '.svg').map(async file => {
  try {
    const svgFileName = path.basename(file, '.svg');
    const componentName = camelCase(svgFileName, { pascalCase: true });
    const vueFileName = `${componentName}.vue`;
    const svgContent = fs.readFileSync(path.resolve(entryDir, file), 'utf-8');

    // 选择适当的 SVGO 配置
    const config = file.includes('-raw.svg') ? svgoRawConfig : svgoConfig;
    const result = optimize(svgContent, config);

    // 创建 Vue 组件模板
    const vueCode = `
<template>
  <svg xmlns="http://www.w3.org/2000/svg" v-html="icon" ></svg>
</template>

<script setup>

const icon = \`${result.data}\`;
</script>

<style scoped>
svg {
  width: 1em;
  height: 1em;
}
</style>
    `;
    // 将 Vue 组件写入文件
    fs.writeFileSync(path.resolve(outDir, vueFileName), vueCode, 'utf-8');
    return { fileName: vueFileName, componentName };
  } catch (error) {
    console.error(`Error processing file ${file}:`, error);
    throw error;
  }
});
// 生成 index.ts 文件
const arr = await Promise.all(batches);
const indexFileContent = arr.map(a => `export { default as ${a.componentName} } from './${a.componentName}.vue';`).join('\n');
fs.writeFileSync(path.resolve(outDir, 'index.ts'), indexFileContent, 'utf-8');

console.log('SVG to Vue components conversion completed successfully.');


只想优化SVG图像

有时候我又在想,不是很想转化,这个插件能够进行优化,在转化,那我想只是优化一下,输出的还是svg图片,查看文档,发现配置,进行配置优化,输出。当然一些svg是不需要改变颜色的,写死的,那这个优化配置就不能一样了。就进行判断,通过文件命名吧。

import { optimize } from 'svgo';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

// 获取 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const entryDir = path.resolve(__dirname, '../rawSvg'); // 输入目录
const outDir = path.resolve(__dirname, '../src/optimizedSvg'); // 输出目录

// 确保输出目录存在
if (!fs.existsSync(outDir)) {
  fs.mkdirSync(outDir, { recursive: true });
}

// 读取所有 SVG 文件
const files = fs.readdirSync(entryDir, 'utf-8');

// 处理 SVG 文件
const processFiles = files.filter(f => path.extname(f) === '.svg').map(async file => {
  try {
    const filePath = path.resolve(entryDir, file);
    const svgContent = fs.readFileSync(filePath, 'utf-8');

    // 优化 SVG 内容
    const result = optimize(svgContent, {
      multipass: true, // 多次优化
      // 其他配置选项可以在这里添加
    });

    // 写入优化后的 SVG 文件
    const outputPath = path.resolve(outDir, file);
    fs.writeFileSync(outputPath, result.data, 'utf-8');
    console.log(`Optimized ${file}`);
  } catch (error) {
    console.error(`Error processing file ${file}:`, error);
  }
});

// 等待所有文件处理完成
await Promise.all(processFiles);

console.log('SVG optimization completed successfully.');

进行自动化实现(写个插件或者服务)

写了三个脚本,文件,但是只能在这个项目中使用,还是写个服务吧,用户上传svg文件,在页面中进行选择,是优化,还是生成react组件代码还是Vue的一点复制,或者下载文件,那就完美了,部署在github的静态管理中,我就能够随时用了(我觉得我这个想法挺好),但是写一个上传需要一个服务,写一个服务简单,但是我的服务器快到期了,没钱续费了(年年打工,年年穷,越干前端,越是穷,只能解决温饱,不能致富啊),再者我想分享一下这个插件,觉得挺不错的,不敢给网友们用,主要担心有些人拿它做测试把我服务器搞溃,本来就不富裕的人,变得更穷了。那就写个npm脚本吧,通过运行脚本执行,方便你我,想用的都可以用,方便你我。
在这里插入图片描述

规范输入指令化

之前是通过不同的指令执行不同的js脚本文件,文件路径都是写死的,生成的react,跟vue也是写死的,那样肯定不行,不灵活,而且不方便,那就让用户输入吧,输入要转化的文件,或者文件夹,路径跟名称,还有输出的路径跟文件夹名字,同时也要指定生成React组件,还是Vue组件,还是只进行优化。**然后在测试过程中,觉得,我只想输入个文件名字或者路径,跟给个默认的执行比较好,这样体验感才会上去,那就默认如果没有给一个新的文件夹,在同一个文件下就进行一个.copy的扩展,如果没有指定是生成组件,还是优化,那就默认进行优化。**这样的一个过程。体验感不错,然后就这样规定。

Options:
  --vue           Generate Vue components from SVG files.
  --react         Generate React components from SVG files.
  -h, --help      Display this help message.
Examples:
  wsksvg ./rawSvg
  wsksvg ./rawSvg ./test
  wsksvg ./rawSvg ./testVue --vue
  wsksvg ./rawSvg ./testReact --react

  ./rawSvg Input file path  ./test Output file path

懒得拼全名字模糊输入

输入路径的名称的时候,看到哪里只有这一个文件名,懒得打全名称,能不能进行模糊处理,同时也可以指定了摸个命名一样的进行处理,觉得这个想法不错,就进行了处理一下。效果不错。

   wsksvg raw 也能够匹配到    wsksvg rawSvg.svg

svg都能优化,png,跟jpg也做一下优化吧

看到我的项目上不仅有svg图片,也有png跟jpg,能不能也把它们一起优化算了。然后引入了sharp 插件,emm,感觉效果可以,这下一个文件下的图片都进行了优化。哈哈哈哈哈哈哈,体验感也上去了,静态文件小了,加载图片的速度也加快了。

sharp 是一个用于图像处理的高性能 Node.js 库。它提供了广泛的功能来处理和优化图像,特别适合在服务器端进行图像操作。

// 处理 PNG 或 JPG 文件
async function processImageFile(filePath: string, output: string) {
  const fileName = path.basename(filePath, path.extname(filePath));
  const fileDir = path.dirname(filePath);
  const originalBuffer = fs.readFileSync(filePath);

  // 计算原始文件大小
  const originalSize = Buffer.byteLength(originalBuffer);

  // 生成输出目录路径
  const outputDir = output ? path.resolve(process.cwd(), output) : fileDir;
  ensureDirectoryExists(outputDir); // 确保输出目录存在

  // 生成输出文件路径
  const extname = path.extname(filePath);
  let outputPath = path.resolve(outputDir, `${fileName}${extname}`);

  // 检查输出路径是否已存在
  if (fs.existsSync(outputPath)) {
    outputPath = path.resolve(outputDir, `${fileName}.copy${extname}`);
  }

  // 计算优化后的文件缓冲区
  const optimizedBuffer = await sharp(originalBuffer)
    .resize({ withoutEnlargement: true })  // 根据需要调整大小
    .toBuffer();

  // 写入优化后的文件
  fs.writeFileSync(outputPath, optimizedBuffer);

  // 打印优化信息
  const newSize = Buffer.byteLength(optimizedBuffer);
  console.log(`Optimized ${path.basename(filePath)} -> ${path.basename(outputPath)}`);
  console.log(`Original Size: ${originalSize} bytes`);
  console.log(`Optimized Size: ${newSize} bytes`);
}

输出优化提示

用户输入路径,不知道会是那个,那就做个优化,提示一下它吧输出一下目前找的路径,然后发现我插件进行了优化了图片,但是优化了多少却不知道,还要最后去看文件的属性里面的文件大小,那就输出优化前后的文件大小,让用户知道这个插件做的优化,优化的图片数量。
在这里插入图片描述

SVG跟组件采用不同过的优化策略

为什么会这样呢,因为如果采用同一种优化策略,SVG图像无法显示
SVG优化

// SVGO 配置
const svgoConfig = {
  js2svg: {
    indent: 2,
    pretty: true,
  },
  plugins: [
    {
      name: 'preset-default',
      params: {
        overrides: {
          removeViewBox: false, // 保持 viewBox
          inlineStyles: {
            onlyMatchedOnce: false,
          },
        },
      },
    },
    {
      name: 'convertStyleToAttrs',
      params: {
        onlyMatchedOnce: false,
      },
    },
    {
      name: 'removeAttrs',
      params: {
        attrs: ['svg:style'], // 可选:移除内联样式,但保留 width 和 height
      },
    },
    {
      name: 'addAttributesToSVGElement',
      params: {
        attributes: [{
          width: '1em',
          height: '1em',
          'aria-hidden': true,
          focusable: 'false',
        }]
      }
    }
  ],
};

组件优化SVG

const svgoComConfig = {
  js2svg: {
    indent: 2,
    pretty: true,
  },
  plugins: [
    {
      name: 'preset-default',
      params: {
        overrides: {
          removeViewBox: false,
          inlineStyles: {
            onlyMatchedOnce: false,
          },
        },
      },
    },
    'removeXMLNS',
    'convertStyleToAttrs',
    {
      name: 'convertColors',
      params: {
        attrs: ['svg:style'], // 可选:移除内联样式,但保留 width 和 height
      },
    },
    {
      name: 'removeAttrs',
      params: { attrs: ['opacity'] }, // 移除不需要的 opacity 属性,但保留 width 和 height
    },
    {
      name: 'addAttributesToSVGElement',
      params: {
        attributes: [{
          'aria-hidden': true,
          focusable: 'false',
          // 不设置 width 和 height,以保持原始大小
        }]
      }
    }
  ],
};

也许不同的需求不同,后面会进行一个扩展,扩展成可以自定义优化文件。 这是svgo 优化配置文件。

总是报错,那就弄个帮助说明

不小心输入出错了,提示报错,感觉如果没有个帮助说明不太好,别人不知道怎么用,就在做一下优化提示,给出一下例子。让用户知道怎么用。

在这里插入图片描述

进行测试

项目中进行使用
执行前
在这里插入图片描述

执行后
在这里插入图片描述

如果输出的文件中不存在该文件名称,则不加copy,如果存在则在中间加后缀.copy
在这里插入图片描述

效果

这是批量处理的效果,以及输出的提示,还有优化的大小
优化前后的大小对比(特地找个大量的图片来过测试)
优化前的总大小
在这里插入图片描述

优化后的总大小
在这里插入图片描述

优化了将近一半

在这里插入图片描述

优化前后的图像对比
在这里插入图片描述
是没啥差别点的。

生成React组件

在这里插入图片描述
在这里插入图片描述
文件大小
在这里插入图片描述

生成Vue组件
在这里插入图片描述
在这里插入图片描述
文件大小
在这里插入图片描述

可以看到都比之前少了60%总体上!!!!

最后

其实开发一个插件并不能,很多事情都可以进行自动化的进行处理,这些没什么技术含量的工作,刚开始我也是手动的将SVG图片转为React组件,反正也就那样,无所事事,没觉得有什么,但有时候思考一下,能不能通过工具实现这种无聊的工作呢,而且自己去尝试,去尝试的过程中,会引发自己的大量思考,从而它也是一种提升。 有人肯定会说,能实现就行了,没必要去思考那么多,有那时间还不如去摸鱼。我也想说,哈哈哈,不过真的停下来思考一下,实现过程并不难,这个插件我一天不到就实现了,而且能解决项目这种转化的问题,也剩下更多的摸鱼时间,也更多的时间去做自己喜欢做的事情,反正总工时不能变,打工人倔强。

如果你也觉得这个插件不错的化,帮助到你了,可以给个点赞,给我的github点个Star。

github地址:https://github.com/wskang12138/wsk-icons

总结

将SVG图像转换为组件可以显著提高开发效率,但手动处理这些转换往往耗时且容易出错。为了简化这一过程,wsksvg工具它不仅能够优化SVG、PNG、JPG图片,还可以自动将SVG文件转换为格式化的无状态React或Vue组件。通过这个工具,用户可以轻松实现单文件或批量处理,减少了手动转换的繁琐步骤,提高了工作效率,同时也支持根据需求选择不同的处理方式。

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值