@onedarnleyroad/vite-plugin-svg-sprite 2.0.0-beta.0 → 2.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,7 @@ A Vite plugin that builds SVG sprite sheets from a directory of SVG files — wi
9
9
  - **Vite manifest integration** — works out of the box with [nystudio107's Craft Vite plugin](https://nystudio107.com/docs/vite/) and any other manifest consumer
10
10
  - **Multiple sprites from subfolders** — organise icons into `light/`, `dark/`, etc. and get one sprite per folder
11
11
  - Namespaces internal IDs to prevent conflicts across icons
12
+ - **Preserves outer `<svg>` attributes** on the generated `<symbol>` — `fill="currentColor"`, `stroke`, `class`, `role`, `aria-*`, `data-*`, `style` (including CSS custom properties), etc. Only the truly wrapper-specific attributes are stripped: `xmlns`, `version`, `width`, `height`, and `id` (we set our own).
12
13
  - Preserves existing `<title>` elements, or falls back to the filename
13
14
  - Strips comments and empty `<defs>` blocks
14
15
  - Dev server serves sprites from memory with full-reload on change
@@ -82,28 +83,28 @@ Each SVG becomes a `<symbol>` with `id="svg-{filename}"`. For example, `arrow.sv
82
83
 
83
84
  ### With Vite manifest (recommended)
84
85
 
85
- With `build.manifest: true` enabled, each sprite is registered in `manifest.json` keyed by its logical name:
86
+ With `build.manifest: true` enabled, each sprite is registered in `manifest.json` keyed by its `inputDir`-relative path:
86
87
 
87
88
  ```json
88
89
  {
89
- "sprite.svg": { "file": "assets/sprite-DAskUDYW.svg", "src": "sprite.svg", "isEntry": false },
90
- "sprite-light.svg": { "file": "assets/sprite-light-xV7q1sYy.svg", "src": "sprite-light.svg", "isEntry": false }
90
+ "src/icons/sprite.svg": { "file": "assets/sprite-DAskUDYW.svg", "src": "src/icons/sprite.svg", "isEntry": false },
91
+ "src/icons/sprite-light.svg": { "file": "assets/sprite-light-xV7q1sYy.svg", "src": "src/icons/sprite-light.svg", "isEntry": false }
91
92
  }
92
93
  ```
93
94
 
94
- With Craft CMS + [nystudio107/craft-vite](https://nystudio107.com/docs/vite/):
95
+ With Craft CMS + [nystudio107/craft-vite](https://nystudio107.com/docs/vite/), use `craft.vite.asset()` — **not `entry()`**. The `asset()` helper is dev-server-aware; `entry()` always reads the manifest, so in dev it would serve stale built files:
95
96
 
96
97
  ```twig
97
98
  <svg aria-hidden="true">
98
- <use href="{{ craft.vite.entry('sprite.svg') }}#svg-arrow"></use>
99
+ <use href="{{ craft.vite.asset('src/icons/sprite.svg') }}#svg-arrow"></use>
99
100
  </svg>
100
101
 
101
102
  <svg aria-hidden="true">
102
- <use href="{{ craft.vite.entry('sprite-light.svg') }}#svg-sun"></use>
103
+ <use href="{{ craft.vite.asset('src/icons/sprite-light.svg') }}#svg-sun"></use>
103
104
  </svg>
104
105
  ```
105
106
 
106
- In dev mode, `craft.vite.entry('sprite.svg')` returns the raw path (`/sprite.svg`), which the plugin's dev middleware serves. In production it returns the hashed path from the manifest.
107
+ In dev, `asset()` returns the Vite dev-server URL (e.g. `https://…:3000/src/icons/sprite.svg`), which the plugin's in-memory middleware serves. In production it returns the hashed URL from the manifest.
107
108
 
108
109
  ### Without manifest
109
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onedarnleyroad/vite-plugin-svg-sprite",
3
- "version": "2.0.0-beta.0",
3
+ "version": "2.0.0-beta.4",
4
4
  "description": "Vite plugin that builds an SVG sprite sheet from a directory of SVG files",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.js CHANGED
@@ -3,7 +3,21 @@ import { resolve, basename, extname, join } from 'path'
3
3
  import { createHash } from 'crypto'
4
4
 
5
5
  function hashContent(content) {
6
- return createHash('sha256').update(content).digest('base64url').slice(0, 8)
6
+ return createHash('sha256').update(content).digest('hex').slice(0, 8)
7
+ }
8
+
9
+ const STRIP_ATTRS = new Set(['xmlns', 'version', 'width', 'height', 'id'])
10
+
11
+ function extractOuterAttrs(openTag) {
12
+ const attrRe = /\s+([a-zA-Z_][a-zA-Z0-9_:-]*)="([^"]*)"/g
13
+ const kept = []
14
+ let m
15
+ while ((m = attrRe.exec(openTag)) !== null) {
16
+ const [, attr, value] = m
17
+ if (STRIP_ATTRS.has(attr) || attr.startsWith('xmlns:')) continue
18
+ kept.push(`${attr}="${value}"`)
19
+ }
20
+ return kept.join(' ')
7
21
  }
8
22
 
9
23
  function buildSymbol(svg, name) {
@@ -21,7 +35,8 @@ function buildSymbol(svg, name) {
21
35
  .replace(/\bhref="#([^"]+)"/g, `href="#${name}-$1"`)
22
36
  .replace(/\bxlink:href="#([^"]+)"/g, `xlink:href="#${name}-$1"`)
23
37
 
24
- const viewBox = namespaced.match(/viewBox="([^"]+)"/)?.[1] ?? '0 0 24 24'
38
+ const openTag = namespaced.match(/<svg\b[^>]*>/i)?.[0] ?? ''
39
+ const outerAttrs = extractOuterAttrs(openTag)
25
40
 
26
41
  const inner = namespaced
27
42
  .replace(/<svg[^>]*>/i, '')
@@ -30,7 +45,8 @@ function buildSymbol(svg, name) {
30
45
  .replace(/>\s+</g, '><')
31
46
  .trim()
32
47
 
33
- return `<symbol id="svg-${name}" viewBox="${viewBox}"><title>${title}</title>${inner}</symbol>`
48
+ const attrs = `id="svg-${name}"${outerAttrs ? ' ' + outerAttrs : ''}`
49
+ return `<symbol ${attrs}><title>${title}</title>${inner}</symbol>`
34
50
  }
35
51
 
36
52
  function buildSpriteSvg(dir, files) {
@@ -98,8 +114,9 @@ export default function svgSpritePlugin(options = {}) {
98
114
  const source = buildSpriteSvg(group.dir, group.files)
99
115
  const base = group.logicalName.replace(/\.svg$/, '')
100
116
  const hashedFileName = join(outputDir, `${base}-${hashContent(source)}.svg`)
117
+ const key = join(inputDir, group.logicalName)
101
118
  this.emitFile({ type: 'asset', fileName: hashedFileName, source })
102
- emitted.push({ logicalName: group.logicalName, fileName: hashedFileName })
119
+ emitted.push({ key, fileName: hashedFileName })
103
120
  }
104
121
  },
105
122
 
@@ -114,10 +131,10 @@ export default function svgSpritePlugin(options = {}) {
114
131
 
115
132
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
116
133
 
117
- for (const { logicalName, fileName } of emitted) {
118
- manifest[logicalName] = {
134
+ for (const { key, fileName } of emitted) {
135
+ manifest[key] = {
119
136
  file: fileName,
120
- src: logicalName,
137
+ src: key,
121
138
  isEntry: false,
122
139
  }
123
140
  }
@@ -131,7 +148,8 @@ export default function svgSpritePlugin(options = {}) {
131
148
  const rebuild = () => {
132
149
  sprites.clear()
133
150
  for (const group of collectSpriteGroups(inputDir, prefix)) {
134
- sprites.set(group.logicalName, buildSpriteSvg(group.dir, group.files))
151
+ const key = join(inputDir, group.logicalName)
152
+ sprites.set(key, buildSpriteSvg(group.dir, group.files))
135
153
  }
136
154
  }
137
155
  rebuild()
@@ -143,6 +161,7 @@ export default function svgSpritePlugin(options = {}) {
143
161
  if (!match) return next()
144
162
  res.setHeader('Content-Type', 'image/svg+xml')
145
163
  res.setHeader('Cache-Control', 'no-cache')
164
+ res.setHeader('Access-Control-Allow-Origin', '*')
146
165
  res.end(match)
147
166
  })
148
167