@onedarnleyroad/vite-plugin-svg-sprite 2.0.0-beta.8 → 2.1.0
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 +46 -13
- package/package.json +14 -3
- package/src/index.js +9 -5
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @onedarnleyroad/vite-plugin-svg-sprite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@onedarnleyroad/vite-plugin-svg-sprite)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
A Vite plugin that builds **content-hashed SVG sprite sheets** at build time and registers them in **Vite's manifest** — built for server-rendered apps like **Craft CMS** (via [nystudio107's Vite plugin](https://nystudio107.com/docs/vite/)), and works with any manifest consumer. Organise icons into folders to get one sprite per folder, and reference them from templates with no JavaScript.
|
|
4
7
|
|
|
5
8
|
## Features
|
|
6
9
|
|
|
@@ -109,28 +112,32 @@ With Craft CMS + [nystudio107/craft-vite](https://nystudio107.com/docs/vite/):
|
|
|
109
112
|
|
|
110
113
|
#### A reusable, accessible macro
|
|
111
114
|
|
|
112
|
-
Rather than hand-writing the `<svg><use>` wrapper each time, wrap it in a Twig macro.
|
|
115
|
+
Rather than hand-writing the `<svg><use>` wrapper each time, wrap it in a Twig macro. The two things you set most often — the icon **name** and its **classes** — are positional; the rest (subfolder, accessible label, extra attributes) goes in an `options` hash. Icons default to decorative (`aria-hidden="true"`), and switch to a labelled `role="img"` when the icon carries meaning on its own:
|
|
113
116
|
|
|
114
117
|
```twig
|
|
115
|
-
{% macro icon(name,
|
|
116
|
-
{% set
|
|
117
|
-
{% set
|
|
118
|
+
{% macro icon(name, classList = '', options = {}) %}
|
|
119
|
+
{% set folder = options.folder ?? null %}
|
|
120
|
+
{% set label = options.label ?? null %}
|
|
121
|
+
{% set file = folder ? "sprite-#{folder}.svg" : 'sprite.svg' %}
|
|
122
|
+
{% set a11y = label
|
|
118
123
|
? { role: 'img', 'aria-label': label }
|
|
119
124
|
: { 'aria-hidden': 'true', focusable: 'false' } %}
|
|
120
|
-
<svg{{ attr({ class:
|
|
121
|
-
<use href="{{ craft.vite.entry(
|
|
125
|
+
<svg{{ attr({ class: classList }|merge(a11y)|merge(options.attrs ?? {})) }}>
|
|
126
|
+
<use href="{{ craft.vite.entry(file) }}#svg-{{ name }}"></use>
|
|
122
127
|
</svg>
|
|
123
128
|
{% endmacro %}
|
|
124
129
|
```
|
|
125
130
|
|
|
126
131
|
```twig
|
|
127
|
-
{{ icon('arrow') }}
|
|
128
|
-
{{ icon('sun', folder: 'light') }}
|
|
129
|
-
{{ icon('search', label: 'Search') }}
|
|
130
|
-
<button aria-label="Close">{{ icon('x') }}</button>
|
|
132
|
+
{{ icon('arrow', 'icon') }} {# root sprite → entry('sprite.svg') #}
|
|
133
|
+
{{ icon('sun', 'icon', { folder: 'light' }) }} {# subfolder → entry('sprite-light.svg') #}
|
|
134
|
+
{{ icon('search', 'icon', { label: 'Search' }) }} {# meaningful → role="img" + aria-label #}
|
|
135
|
+
<button aria-label="Close">{{ icon('x', 'icon') }}</button> {# icon-only control: label the button #}
|
|
131
136
|
```
|
|
132
137
|
|
|
133
|
-
Reach for `aria-hidden="true"` only when the icon is decorative — i.e. sitting next to visible text. A standalone, meaning-bearing icon needs an accessible name (via `label`, or on the enclosing control such as a button), or it's invisible to assistive tech. (`attr()` is Craft's built-in attribute renderer.)
|
|
138
|
+
Reach for `aria-hidden="true"` only when the icon is decorative — i.e. sitting next to visible text. A standalone, meaning-bearing icon needs an accessible name (via `options.label`, or on the enclosing control such as a button), or it's invisible to assistive tech. (`'icon'` is a CSS class you define to size the `<svg>` — sprite symbols have no intrinsic dimensions; the walkthrough below covers sizing and colour. `attr()` is Craft's built-in attribute renderer.)
|
|
139
|
+
|
|
140
|
+
> **Full Craft CMS walkthrough:** [SVG icon sprites in Craft CMS with Vite](https://github.com/onedarnleyroad/craftcms/wiki/SVG-icon-sprites-in-Craft-CMS-with-Vite) — end-to-end setup, the macro, sizing & colour gotchas, and sprites vs. inlining.
|
|
134
141
|
|
|
135
142
|
## Development workflow
|
|
136
143
|
|
|
@@ -144,7 +151,7 @@ Every edit under `inputDir` triggers a rebuild (a second or two), updates `manif
|
|
|
144
151
|
|
|
145
152
|
### Without manifest
|
|
146
153
|
|
|
147
|
-
You can reference
|
|
154
|
+
You can reference a sprite file directly without a manifest, but its filename is always content-hashed — there's no option to turn hashing off — so you'd have to discover the emitted name yourself (e.g. by globbing `assets/sprite-*.svg`). The manifest-based flow is strongly recommended.
|
|
148
155
|
|
|
149
156
|
## Migrating from v1
|
|
150
157
|
|
|
@@ -167,6 +174,32 @@ svgSprite({
|
|
|
167
174
|
|
|
168
175
|
Passing `outputFile` in v2 throws an error. Update your templates to read the sprite URL from Vite's manifest instead of hard-coding the path.
|
|
169
176
|
|
|
177
|
+
## How this differs from other SVG sprite plugins
|
|
178
|
+
|
|
179
|
+
Several Vite plugins generate SVG sprites; they mostly split by *how the sprite reaches the page*:
|
|
180
|
+
|
|
181
|
+
- **Runtime injection** (e.g. [`vite-plugin-svg-icons`](https://github.com/vbenjs/vite-plugin-svg-icons)) injects the sprite into the DOM via a virtual import. Great for SPAs; needs JavaScript to run.
|
|
182
|
+
- **Import-based** plugins expose each icon as a JS module or framework component. Great when your icons live in JS/JSX.
|
|
183
|
+
- **This plugin is build-time and manifest-driven.** It emits content-hashed `.svg` files and registers them in Vite's manifest under stable logical keys, so a **server-rendered** template (Craft/Twig, Laravel/Blade, etc.) resolves the hashed URL with no JavaScript — e.g. `craft.vite.entry('sprite.svg')`. It also emits one sprite per subfolder.
|
|
184
|
+
|
|
185
|
+
If you want runtime/HMR injection or a virtual import, one of the others will suit you better. If you render HTML on the server and resolve assets through a Vite manifest, this is built for that.
|
|
186
|
+
|
|
187
|
+
## FAQ
|
|
188
|
+
|
|
189
|
+
**Does it work with Craft CMS?** Yes — it's the primary target. With [nystudio107's Craft Vite plugin](https://nystudio107.com/docs/vite/), reference a sprite with `craft.vite.entry('sprite.svg')` — see [Using the sprite](#using-the-sprite), or the full [Craft + Vite walkthrough](https://github.com/onedarnleyroad/craftcms/wiki/SVG-icon-sprites-in-Craft-CMS-with-Vite).
|
|
190
|
+
|
|
191
|
+
**How do I reference an icon in a Twig template?** Use `<use href="{{ craft.vite.entry('sprite.svg') }}#svg-{name}">`, or the reusable [`icon()` macro](#a-reusable-accessible-macro).
|
|
192
|
+
|
|
193
|
+
**Can I have more than one sprite?** Yes — each subfolder of `inputDir` becomes its own sprite, keyed `sprite-{folder}.svg`. See [Subfolders](#subfolders).
|
|
194
|
+
|
|
195
|
+
**Can two icons share a filename across folders?** Yes. `arrow.svg` and `brand/arrow.svg` both become `<symbol id="svg-arrow">`, but in *separate* sprite files — and you reference each by its own sprite URL (`entry('sprite.svg')#svg-arrow` vs `entry('sprite-brand.svg')#svg-arrow`), so the file URL disambiguates them and they render fine on the same page. The ids only repeat *across* files, never within one; the only thing to avoid is inlining multiple sprites into a single document and referencing by bare `#svg-arrow`.
|
|
196
|
+
|
|
197
|
+
**Does it run during `vite dev` / support HMR?** No — it's build-only. Run `vite build --watch` alongside your dev server for a live-ish loop.
|
|
198
|
+
|
|
199
|
+
**Does it optimise SVGs?** No — it does light cleanup (strips the XML prolog / comments / empty `<defs>`, collapses whitespace, namespaces IDs), but it doesn't touch path data, so bring source SVGs that are already production-ready. Each icon needs a `viewBox` — the plugin strips `width`/`height` and scales from it.
|
|
200
|
+
|
|
201
|
+
**Why are the manifest keys `sprite.svg` and `sprite-light.svg`, not paths?** Because nystudio107's `entry()` resolves keys with a substring match, and a path-style `light/sprite.svg` would collide with the root `sprite.svg`. Basename keys avoid that.
|
|
202
|
+
|
|
170
203
|
## License
|
|
171
204
|
|
|
172
205
|
MIT © [One Darnley Road](https://onedarnleyroad.com)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onedarnleyroad/vite-plugin-svg-sprite",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Vite plugin that builds an SVG sprite sheet from a directory of SVG files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -15,14 +15,25 @@
|
|
|
15
15
|
"vite-plugin",
|
|
16
16
|
"svg",
|
|
17
17
|
"sprite",
|
|
18
|
-
"svg-sprite"
|
|
18
|
+
"svg-sprite",
|
|
19
|
+
"svg-symbols",
|
|
20
|
+
"icons",
|
|
21
|
+
"manifest",
|
|
22
|
+
"craft-cms",
|
|
23
|
+
"nystudio107"
|
|
19
24
|
],
|
|
20
25
|
"author": "One Darnley Road <hello@onedarnleyroad.com>",
|
|
21
26
|
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/onedarnleyroad/vite-plugin-svg-sprite.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/onedarnleyroad/vite-plugin-svg-sprite#readme",
|
|
32
|
+
"bugs": "https://github.com/onedarnleyroad/vite-plugin-svg-sprite/issues",
|
|
22
33
|
"publishConfig": {
|
|
23
34
|
"access": "public"
|
|
24
35
|
},
|
|
25
36
|
"peerDependencies": {
|
|
26
|
-
"vite": ">=
|
|
37
|
+
"vite": ">=3.0.0"
|
|
27
38
|
}
|
|
28
39
|
}
|
package/src/index.js
CHANGED
|
@@ -142,11 +142,15 @@ export default function svgSpritePlugin(options = {}) {
|
|
|
142
142
|
writeBundle(outputOptions) {
|
|
143
143
|
if (!viteConfig?.build?.manifest) return
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
// Vite 5+ writes the default manifest to .vite/manifest.json; Vite 3/4 write
|
|
146
|
+
// manifest.json at the outDir root. Try both when no custom path is configured.
|
|
147
|
+
const manifestNames = typeof viteConfig.build.manifest === 'string'
|
|
148
|
+
? [viteConfig.build.manifest]
|
|
149
|
+
: ['.vite/manifest.json', 'manifest.json']
|
|
150
|
+
const manifestPath = manifestNames
|
|
151
|
+
.map(name => resolve(outputOptions.dir, name))
|
|
152
|
+
.find(path => existsSync(path))
|
|
153
|
+
if (!manifestPath) return
|
|
150
154
|
|
|
151
155
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
152
156
|
|