问题
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