如何用Vite Glob Import动态生成React SVG图标组件并正常构建?
解决方案
问题根源
你遇到的核心问题是:import.meta.glob({ eager: true }) 在Vite库构建阶段,Rollup无法完全静态解析动态导入的SVG资源,导致最终打包产物里没有包含这些图标组件;同时当前导出的是一个对象,组件库的消费方也无法通过树摇按需引入单个图标。
方案一:优化Vite配置与导出逻辑,让Rollup静态解析Glob导入
1. 调整Vite配置
修改vite-plugin-svgr的配置,确保SVG被正确编译为React组件,同时让Rollup能识别Glob导入的静态资源:
import react from '@vitejs/plugin-react'; import { resolve } from 'path'; import { defineConfig } from 'vite'; import dts from 'vite-plugin-dts'; import tsConfigPaths from 'vite-tsconfig-paths'; import * as packageJson from './package.json'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; import svgr from 'vite-plugin-svgr'; export default defineConfig(() => ({ plugins: [ cssInjectedByJsPlugin(), // 调整svgr配置,明确处理路径,开启named导出 svgr({ include: './src/assets/icons/*.svg', svgrOptions: { exportType: 'named', }, }), react(), tsConfigPaths() as any, dts({ include: ['src'], // 让dts插件识别动态生成的组件类型 rollupTypes: true, }), ], build: { lib: { entry: resolve('src', 'index.ts'), name: 'red-mist', formats: ['es', 'cjs'], fileName: (format) => `my-lib.${format}.js`, }, rollupOptions: { external: [...Object.keys(packageJson.peerDependencies)], input: { index: resolve('src', 'index.ts'), css: resolve('src', 'index.css'), }, output: { preserveModules: true, // 保留模块结构,利于树摇 preserveModulesRoot: 'src', }, }, }, }));
2. 修改图标组件导出代码
把动态导出的对象改成具名导出,让Rollup能静态识别每个组件:
// src/components/Icons/index.ts import type { FC, SVGProps } from 'react'; const svgs = import.meta.glob('./../../assets/icons/*.svg', { eager: true, import: 'ReactComponent', // 直接导入ReactComponent,简化逻辑 }); const exports: Record<string, FC<SVGProps<SVGSVGElement>>> = {}; for (const [path, component] of Object.entries(svgs)) { // 从路径提取文件名,生成规范组件名(比如"user.svg" -> "UserIcon") const fileName = path.split('/').pop()?.replace('.svg', '') || ''; const componentName = fileName.charAt(0).toUpperCase() + fileName.slice(1) + 'Icon'; exports[componentName] = component as FC<SVGProps<SVGSVGElement>>; } // 导出所有组件(支持按需引入) export const { ...Icons } = exports; // 兼容原有默认导出用法 export default exports;
3. 在入口文件index.ts中导出图标组件
// src/index.ts export * from './components/Icons';
方案二:构建前预生成图标组件文件(最可靠)
如果方案一仍有问题,直接用Node脚本在构建前自动生成静态的图标组件文件,彻底避免动态导入的问题:
1. 编写预生成脚本(scripts/generate-icons.ts)
import fs from 'fs'; import path from 'path'; const iconsDir = path.resolve(__dirname, '../src/assets/icons'); const outputFile = path.resolve(__dirname, '../src/components/Icons/generated.tsx'); // 读取icons文件夹下的所有SVG文件 const svgFiles = fs.readdirSync(iconsDir).filter(file => file.endsWith('.svg')); // 生成导入语句 const importStatements = svgFiles.map(file => { const fileName = file.replace('.svg', ''); const componentAlias = fileName.charAt(0).toUpperCase() + fileName.slice(1); return `import { ReactComponent as ${componentAlias} } from './../../assets/icons/${file}';`; }).join('\n'); // 生成具名导出语句 const exportStatements = svgFiles.map(file => { const fileName = file.replace('.svg', ''); const componentAlias = fileName.charAt(0).toUpperCase() + fileName.slice(1); const componentName = `${componentAlias}Icon`; return `export const ${componentName} = ${componentAlias};`; }).join('\n'); // 生成默认导出 const defaultExport = `export default { ${svgFiles.map(file => { const fileName = file.replace('.svg', ''); const componentName = fileName.charAt(0).toUpperCase() + fileName.slice(1) + 'Icon'; return componentName; }).join(', ')} };`; // 拼接完整代码并写入文件 const content = ` ${importStatements} ${exportStatements} ${defaultExport} `.trim(); fs.writeFileSync(outputFile, content); console.log('图标组件文件已生成');
2. 在package.json中添加预构建脚本
{ "scripts": { "prebuild": "tsx scripts/generate-icons.ts", "build": "vite build" } }
3. 在Icons/index.ts中导入生成的组件
// src/components/Icons/index.ts export * from './generated'; export { default } from './generated';
这个方案的优势是:所有图标组件都是静态导入,构建时Rollup能完全解析,类型生成也不会有问题,消费方可以直接按需引入单个图标。
验证方法
- 执行
npm run build后,检查打包产物中的my-lib.es.js或my-lib.cjs.js,确认包含图标组件的代码 - 在测试项目中安装组件库,尝试导入单个图标组件(比如
import { UserIcon } from 'red-mist'),验证是否能正常渲染
内容的提问来源于stack exchange,提问作者counterbeing
相关产品推荐
相关产品推荐

