react+vite中大量svg使用

402 阅读2分钟

问题

react18+vite中用到了大量svg,如何能够方便的使用?

实现1:vite-plugin-svgr

svgr是一个将svg转为React组件的通用工具,可以搭配@svgr/plugin-svgo进行优化

pnpm add vite-plugin-svgr -D

vite.config.ts

import svgr from 'vite-plugin-svgr'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react(),
   svgr({
      include: "src/assets/svg/*.svg",
      // svgrOptions: {
      //   // 配置选项(可选)
      //   icon: true,    // 允许修改 SVG 尺寸/颜色
      //   expandProps: 'end', // 将 props 展开到根元素
      // }
    })],

})

vite-env.d.ts

// 如果使用了ts( 其实影响不大
/// <reference types="vite-plugin-svgr/client" />

项目中使用

import  ChromeSvg  from '@/assets/svg/chrome.svg'
const testFC = () => {
    return <div><ChromeSvg /></div>
}

实现2:封装成react组件

@/assets/svg/interface.ts

export type SvgIconProps = {
  size?: number
  color?: string
}

ChromeSvgFC.tsx


import type { SvgIconProps } from './interface'
export const ChromeSvgFC: React.FC<SvgIconProps> = ({ size = 16 }) => {
    return <svg viewBox="0 0 1024 1024"
        p-id="2094" width={size} height={size}>
        <path d="M123.648 178.346667C361.642667-98.602667 802.986667-43.946667 967.936 279.68h-396.501333c-71.424 0-117.546667-1.621333-167.509334 24.661333-58.709333 30.933333-102.997333 88.234667-118.485333 155.52L123.648 178.389333z" fill="#EA4335" p-id="2095"></path>
        <path d="M341.674667 512c0 93.866667 76.330667 170.24 170.154666 170.24 93.866667 0 170.154667-76.373333 170.154667-170.24s-76.330667-170.24-170.154667-170.24c-93.866667 0-170.154667 76.373333-170.154666 170.24z" fill="#4285F4" p-id="2096"></path>
        <path d="M577.877333 734.848c-95.530667 28.373333-207.274667-3.114667-268.501333-108.8-46.762667-80.64-170.24-295.765333-226.346667-393.557333-196.565333 301.226667-27.136 711.808 329.685334 781.866666l165.12-279.509333z" fill="#34A853" p-id="2097"></path>
        <path d="M669.866667 341.76a233.130667 233.130667 0 0 1 43.008 286.634667c-40.576 69.973333-170.154667 288.682667-232.96 394.581333 367.658667 22.656 635.733333-337.664 514.645333-681.258667H669.866667z" fill="#FBBC05" p-id="2098"></path>
    </svg>
}

import  ChromeSvg  from '@/assets/svg/ChromeSvgFC.tsx'
const testFC = () => {
    return <div><ChromeSvgFC size={16}/></div>
}

快速生成tsx文件和统一导出

scripts/svgReactGenerate.js

读取@/assets/svg下所有的svg文件,生成对应的tsx文件

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

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

// 配置参数
const SVG_DIR = path.join(__dirname, '../src/assets/svg') // SVG 存放目录

// 更改内容
function generateSingleFile (content, fileName) {

    const updateContent = content.replace(/width="[^"]*"/g, 'width={size}')
        .replace(/height="[^"]*"/g, 'height={size}')
        .replace(/style="[^"]*"/g, '')
        .replace(/t="[^"]*"/g, '')
        .replace(/class="[^"]*"/g, '')
        .replace(/xmlns="[^"]*"/g, '')
        .replace(/xmlns:xlink="[^"]*"/g, '')

    return `
    import type { SvgIconProps } from './interface'
        export const ${fileName}: React.FC<SvgIconProps> = ({ size = 16 }) => {
    return ${updateContent}
    }
    `
}
// 读取生成文件
function fileOperate (inputPath, targetPath, fileName) {
    if (!inputPath || !targetPath) return
    fs.readFile(inputPath, 'utf8', (err, data) => {
        if (err) {
            console.error('读取文件时出错:', err);
            return;
        }

        const processedContent = generateSingleFile(data, fileName)

        fs.writeFileSync(targetPath, processedContent, 'utf8', (err) => {
            if (err) {
                console.error('写入文件时出错:', err);
                return;
            }
            console.log('文件处理完成,新文件已生成:', targetPath);
        });
    })


}

// main
function generateSvgReactFC () {
    try {
        // 获取所有 SVG 文件名
        const files = fs.readdirSync(SVG_DIR)
            .filter(file => file.endsWith('.svg'))
            .map(file => file.replace('.svg', ''))

        files.forEach(file => {
            const newName = `${file.replace(/^./, str => str.toUpperCase())}SvgFC`
            const inputPath = path.join(SVG_DIR, `${file}.svg`)
            const targetPath = path.join(SVG_DIR, `${newName}.tsx`)
            fileOperate(inputPath, targetPath, newName)
        })

    } catch {
        console.log("错误");

    }
}

generateSvgReactFC()

@/assets/svg/index.ts统一导入导出文件生成

// scripts/svgGenerate.js
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url';

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

// 配置参数
const SVG_DIR = path.join(__dirname, '../src/assets/svg') // SVG 存放目录
const OUTPUT_FILE = path.join(SVG_DIR, 'index.ts') // 输出文件路径

function generateExports () {
    try {
        // 获取所有 SVG 文件名
        const svgFiles = fs.readdirSync(SVG_DIR)
            .filter(file => file.endsWith('.svg'))
            .map(file => file.replace('.svg', ''))
        const shouldExportsSvgName = []
        // 生成svg导出内容
        const exportsContent = svgFiles
            .map(file => {
                const componentName = `${file.replace(/^./, str => str.toUpperCase())}Svg`
                shouldExportsSvgName.push(componentName)
                return `import  ${componentName}  from './${file}.svg'`
            })
            .join('\n')
        const exportSVGSentence = `export {${shouldExportsSvgName.join(",")}}`


        const reactFiles = fs.readdirSync(SVG_DIR)
            .filter(file => file.endsWith('SvgFC.tsx'))
            .map(file => file.replace('.tsx', ''))
        // 生成react导出内容
        const exportsReactContent = reactFiles
            .map(file => `export * from './${file}'`)
            .join('\n')

        // 写入文件
        fs.writeFileSync(OUTPUT_FILE, `// Auto-generated by generate.js\n${exportsContent}\n\n${exportsReactContent}\n\n${exportSVGSentence}`)
        console.log('SVG exports generated successfully!')

    } catch (error) {
        console.error('Error generating SVG exports:', error)
    }
}

generateExports()

// 修改 generate.js 添加监听功能
// "gen:svg:watch": "node scripts/svg-exporter/generate.js --watch"
// const chokidar = require('chokidar')

// if (process.argv.includes('--watch')) {
//   console.log('Watching SVG directory...')
//   chokidar.watch(SVG_DIR).on('all', (event, path) => {
//     if (event === 'add' || event === 'unlink') {
//       generateExports()
//     }
//   })
// }

// 修改 generate.js 中的命名转换逻辑
// function formatComponentName(fileName) {
//     return fileName
//       .split('-')
//       .map(word => word[0].toUpperCase() + word.slice(1))
//       .join('') + 'Icon'
//   }

参考

Deepseek