likes
comments
collection
share

wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

作者站长头像
站长
· 阅读数 51

田间的风吹老了岁月,老舍笔下的茶馆写的是近代史,真的写尽了当时的苦态,可能现在的地铁写的是现代史吧。时光飞逝,很快就工作两三年了。昨天做项目的时候,引入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组件正常使用,需要手动修改。 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

那时候就开始思考,能不能通过工具进行处理,然后进行百度,还别说真的可以,有现成的插件的,可以直接生成,但是插件它只能生成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脚本吧,通过运行脚本执行,方便你我,想用的都可以用,方便你我。 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

规范输入指令化

之前是通过不同的指令执行不同的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`);
}

输出优化提示

用户输入路径,不知道会是那个,那就做个优化,提示一下它吧输出一下目前找的路径,然后发现我插件进行了优化了图片,但是优化了多少却不知道,还要最后去看文件的属性里面的文件大小,那就输出优化前后的文件大小,让用户知道这个插件做的优化,优化的图片数量。 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

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 优化配置文件。

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

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

wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

进行测试

项目中进行使用 执行前 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

执行后 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

如果输出的文件中不存在该文件名称,则不加copy,如果存在则在中间加后缀.copy wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

效果

这是批量处理的效果,以及输出的提示,还有优化的大小 优化前后的大小对比(特地找个大量的图片来过测试) 优化前的总大小 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

优化后的总大小 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

优化了将近一半

wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

优化前后的图像对比 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够 是没啥差别点的。

生成React组件

wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够 文件大小 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

生成Vue组件 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够 文件大小 wsksvg — SVG 转换与优化工具wsksvg它不仅能够,实现对svg的优化,包括png,jpg图片的优化,还能够

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

最后

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

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

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

总结

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

转载自:https://juejin.cn/post/7408742389647310889
评论
请登录