@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 +8 -7
- package/package.json +1 -1
- package/src/index.js +27 -8
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
|
|
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.
|
|
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.
|
|
103
|
+
<use href="{{ craft.vite.asset('src/icons/sprite-light.svg') }}#svg-sun"></use>
|
|
103
104
|
</svg>
|
|
104
105
|
```
|
|
105
106
|
|
|
106
|
-
In dev
|
|
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
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('
|
|
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
|
|
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
|
-
|
|
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({
|
|
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 {
|
|
118
|
-
manifest[
|
|
134
|
+
for (const { key, fileName } of emitted) {
|
|
135
|
+
manifest[key] = {
|
|
119
136
|
file: fileName,
|
|
120
|
-
src:
|
|
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
|
-
|
|
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
|
|