plugin_svg.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /**
  2. * @description: 注册svg图标
  3. */
  4. import { readdirSync, readFileSync } from 'node:fs'
  5. import { join } from 'node:path'
  6. import type { RsbuildPlugin } from '@rsbuild/core'
  7. export type SvgSpriteLoaderOptions = {
  8. path: string
  9. // default: icon-[name]
  10. symbolId?: string
  11. }
  12. // 正则表达式定义
  13. const svgTitle = /<svg([^>]*)>/
  14. const clearHeightWidth = /(width|height)="([^"]*)"/g
  15. const hasViewBox = /viewBox="[^"]*"/
  16. const clearReturn = /[\r\n]/g
  17. // 查找并处理 SVG 文件
  18. function svgFind(directoryPath: string, idPrefix: string): string[] {
  19. const svgs: string[] = []
  20. const directs = readdirSync(directoryPath, { withFileTypes: true })
  21. for (const dirent of directs) {
  22. if (dirent.isDirectory()) {
  23. svgs.push(...svgFind(join(directoryPath, dirent.name, '/'), idPrefix))
  24. } else if (dirent.name.endsWith('.svg')) {
  25. const svgContent = readFileSync(join(directoryPath, dirent.name), 'utf-8')
  26. .replace(clearReturn, '')
  27. .replace(svgTitle, ($1, $2: string) => {
  28. let width = 0
  29. let height = 0
  30. let content = $2.replace(
  31. clearHeightWidth,
  32. (match: string, prop: string, value: number) => {
  33. if (prop === 'width') width = value
  34. else if (prop === 'height') height = value
  35. return '' // 移除匹配的宽度和高度属性
  36. },
  37. )
  38. // 添加 viewBox 属性,如果不存在
  39. if (!hasViewBox.test(content)) {
  40. content += `viewBox="0 0 ${width} ${height}"`
  41. }
  42. const name = idPrefix.replace(
  43. '[name]',
  44. dirent.name.replace('.svg', ''),
  45. )
  46. return `<symbol id="${name}" ${content}>`
  47. })
  48. .replace('</svg>', '</symbol>')
  49. .replaceAll('prefix_', () => {
  50. return idPrefix.replace('[name]', dirent.name.replace('.svg', ''))
  51. })
  52. svgs.push(svgContent)
  53. }
  54. }
  55. return svgs
  56. }
  57. // 创建 SVG 字符串
  58. function createSvg(path: string, prefix: string): string {
  59. if (path === '') return ''
  60. const res = svgFind(path, prefix)
  61. return res.join('')
  62. }
  63. export const pluginSvgSpriteLoader = (
  64. options: SvgSpriteLoaderOptions,
  65. ): RsbuildPlugin => ({
  66. name: 'plugin-svg-sprite-loader',
  67. setup(api) {
  68. const str = createSvg(options.path, options.symbolId || 'icon-[name]')
  69. api.modifyRsbuildConfig((config) => {
  70. // 监听svg文件夹的变化
  71. const watchFilesConfig = {
  72. type: 'reload-server' as any,
  73. paths: [options.path],
  74. }
  75. if (config.dev)
  76. if (Array.isArray(config.dev?.watchFiles)) {
  77. config.dev.watchFiles.push(watchFilesConfig)
  78. } else {
  79. config.dev.watchFiles = [watchFilesConfig]
  80. }
  81. })
  82. api.modifyHTMLTags(({ headTags, bodyTags }) => {
  83. bodyTags.unshift({
  84. tag: 'svg',
  85. attrs: {
  86. xmlns: 'http://www.w3.org/2000/svg',
  87. 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
  88. style: 'position: absolute; width: 0; height: 0',
  89. },
  90. children: str,
  91. })
  92. return { headTags, bodyTags }
  93. })
  94. },
  95. })