vite-plugin-svg-spritegen 1.4.5 → 1.5.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 +18 -15
- package/dist/index.js +293 -267
- package/dist/index.mjs +260 -263
- package/dist/write-sprite.d.ts +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|

|
|
2
2
|

|
|
3
|
-

|
|
3
|
+

|
|
4
4
|
|
|
5
5
|
# vite-plugin-svg-spritegen
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
## Installation
|
|
8
|
+
|
|
9
|
+
v2.x is Vite 8+ only. Use v1.x for Vite 5-7
|
|
10
|
+
|
|
9
11
|
```bash
|
|
10
12
|
npm install -D vite-plugin-svg-spritegen
|
|
11
13
|
```
|
|
12
14
|
|
|
13
15
|
## Usage
|
|
16
|
+
|
|
14
17
|
```typescript
|
|
15
18
|
// vite.config.js
|
|
16
19
|
import { svgSpritegen } from 'vite-plugin-svg-spritegen';
|
|
17
20
|
|
|
18
21
|
export default {
|
|
19
22
|
plugins: [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
svgSpritegen({
|
|
24
|
+
input: [
|
|
25
|
+
{
|
|
26
|
+
baseDir: 'src/assets/icons',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
baseDir: 'node_modules/lucide-static/icons',
|
|
30
|
+
removeAttrs: ['class', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
outputDir: 'src/assets/icons',
|
|
34
|
+
}),
|
|
32
35
|
],
|
|
33
36
|
};
|
|
34
37
|
```
|
|
@@ -53,4 +56,4 @@ export const IconBase: FC<SVGProps<SVGSVGElement> & { icon: IconName }> = ({ ico
|
|
|
53
56
|
|
|
54
57
|
```jsx
|
|
55
58
|
<Icon icon="plus" />
|
|
56
|
-
```
|
|
59
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -1,278 +1,304 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const resolvedInputConfig = {
|
|
22
|
-
...inputConfig,
|
|
23
|
-
content,
|
|
24
|
-
matchPath,
|
|
25
|
-
filePath
|
|
26
|
-
};
|
|
27
|
-
const symbolId = getSymbolId(resolvedInputConfig);
|
|
28
|
-
if (svgMap.has(symbolId)) {
|
|
29
|
-
console.log(
|
|
30
|
-
`Duplicate SVG symbol id "${symbolId}. Symbol ids should be unique across all inputs."`
|
|
31
|
-
);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
svgMap.set(symbolId, { ...resolvedInputConfig, symbolId });
|
|
35
|
-
})
|
|
36
|
-
);
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
return svgMap;
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __create = Object.create;
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
14
|
+
key = keys[i];
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: ((k) => from[k]).bind(null, key),
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
40
21
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
//#endregion
|
|
27
|
+
let node_path = require("node:path");
|
|
28
|
+
node_path = __toESM(node_path);
|
|
29
|
+
let chokidar = require("chokidar");
|
|
30
|
+
let vite = require("vite");
|
|
31
|
+
let node_fs = require("node:fs");
|
|
32
|
+
let fast_glob = require("fast-glob");
|
|
33
|
+
fast_glob = __toESM(fast_glob);
|
|
34
|
+
let node_html_parser = require("node-html-parser");
|
|
35
|
+
let svgo = require("svgo");
|
|
36
|
+
//#region src/build-svg-map.ts
|
|
37
|
+
var buildSvgMap = async (inputConfigs) => {
|
|
38
|
+
const svgMap = /* @__PURE__ */ new Map();
|
|
39
|
+
await Promise.all(inputConfigs.map(async (inputConfig) => {
|
|
40
|
+
const { pattern, baseDir, getSymbolId } = inputConfig;
|
|
41
|
+
const matchedPaths = await (0, fast_glob.default)(pattern, { cwd: baseDir });
|
|
42
|
+
await Promise.all(matchedPaths.map(async (matchPath) => {
|
|
43
|
+
const filePath = node_path.default.join(baseDir, matchPath);
|
|
44
|
+
const content = await node_fs.promises.readFile(filePath, "utf8");
|
|
45
|
+
const resolvedInputConfig = {
|
|
46
|
+
...inputConfig,
|
|
47
|
+
content,
|
|
48
|
+
matchPath,
|
|
49
|
+
filePath
|
|
50
|
+
};
|
|
51
|
+
const symbolId = getSymbolId(resolvedInputConfig);
|
|
52
|
+
if (svgMap.has(symbolId)) {
|
|
53
|
+
console.log(`Duplicate SVG symbol id "${symbolId}. Symbol ids should be unique across all inputs."`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
svgMap.set(symbolId, {
|
|
57
|
+
...resolvedInputConfig,
|
|
58
|
+
symbolId
|
|
59
|
+
});
|
|
60
|
+
}));
|
|
61
|
+
}));
|
|
62
|
+
return svgMap;
|
|
53
63
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await writeIfChanged(filePath, content);
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/write-if-changed.ts
|
|
66
|
+
var writeIfChanged = (filePath, content) => {
|
|
67
|
+
try {
|
|
68
|
+
const currentContent = (0, node_fs.readFileSync)(filePath, "utf8");
|
|
69
|
+
if (content && currentContent !== content) {
|
|
70
|
+
(0, node_fs.writeFileSync)(filePath, content, "utf8");
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
(0, node_fs.writeFileSync)(filePath, content ?? "", "utf8");
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
69
78
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/write-types.ts
|
|
81
|
+
var writeTypes = async (filePath, svgMap) => {
|
|
82
|
+
await writeIfChanged(filePath, [
|
|
83
|
+
"/* eslint-disable */",
|
|
84
|
+
"",
|
|
85
|
+
"// This file is autogenerated. Do not commit to source control.",
|
|
86
|
+
"",
|
|
87
|
+
"export type IconName = (typeof iconNames)[number];",
|
|
88
|
+
"",
|
|
89
|
+
"export const iconNames = [",
|
|
90
|
+
...[...svgMap.keys()].map((name) => ` '${name}',`),
|
|
91
|
+
"] as const;",
|
|
92
|
+
""
|
|
93
|
+
].join("\n"));
|
|
82
94
|
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
convertColors: { currentColor: true },
|
|
99
|
-
...svg.svgoOverrides
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
"removeDimensions",
|
|
104
|
-
{
|
|
105
|
-
name: "removeAttrs",
|
|
106
|
-
params: { attrs: "(stroke-width|stroke-linecap|stroke-linejoin|class)" }
|
|
107
|
-
}
|
|
108
|
-
]
|
|
109
|
-
};
|
|
110
|
-
svg.svgo = svg.svgo ?? svgoDefault;
|
|
111
|
-
content2 = svgo.optimize(content2, {
|
|
112
|
-
...svg.svgo,
|
|
113
|
-
plugins: [
|
|
114
|
-
...svg.svgo.plugins ?? [],
|
|
115
|
-
...svg.removeAttrs.length ? [
|
|
116
|
-
{
|
|
117
|
-
name: "removeAttrs",
|
|
118
|
-
params: {
|
|
119
|
-
attrs: `(${svg.removeAttrs.join("|")})`
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
] : [],
|
|
123
|
-
...svg.svgoPlugins
|
|
124
|
-
]
|
|
125
|
-
}).data;
|
|
126
|
-
}
|
|
127
|
-
const svgEl = nodeHtmlParser.parse(content2).querySelector("svg");
|
|
128
|
-
if (!svgEl) {
|
|
129
|
-
throw new Error(`SVG file at path "${svg.filePath}" does not contain an svg tag.`);
|
|
130
|
-
}
|
|
131
|
-
svgEl.tagName = "symbol";
|
|
132
|
-
svgEl.setAttribute("id", svg.symbolId);
|
|
133
|
-
svgEl.removeAttribute("xmlns");
|
|
134
|
-
svgEl.removeAttribute("xmlns:xlink");
|
|
135
|
-
svgEl.removeAttribute("version");
|
|
136
|
-
const defs = svgEl.querySelector("defs");
|
|
137
|
-
if (defs) {
|
|
138
|
-
defs.childNodes.forEach((def) => definitions.push(def.toString()));
|
|
139
|
-
svgEl.removeChild(defs);
|
|
140
|
-
}
|
|
141
|
-
symbols.push(svgEl.toString());
|
|
142
|
-
}
|
|
143
|
-
const lines = [
|
|
144
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
145
|
-
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">'
|
|
146
|
-
];
|
|
147
|
-
if (definitions.length) {
|
|
148
|
-
lines.push(" <defs>", ...definitions.map((d) => ` ${d}`), " </defs>");
|
|
149
|
-
}
|
|
150
|
-
lines.push(...symbols.map((s) => ` ${s}`), "</svg>");
|
|
151
|
-
const content = lines.join("\n");
|
|
152
|
-
writeIfChanged(spritePath, content);
|
|
153
|
-
return content;
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/write-gitignore.ts
|
|
97
|
+
var writeGitignore = async (filePath, spriteName, typesName) => {
|
|
98
|
+
const content = [
|
|
99
|
+
"# svg spritegen",
|
|
100
|
+
spriteName,
|
|
101
|
+
typesName
|
|
102
|
+
].join("\n");
|
|
103
|
+
try {
|
|
104
|
+
const currentContent = await node_fs.promises.readFile(filePath, "utf8");
|
|
105
|
+
if (currentContent.includes(content)) return;
|
|
106
|
+
else await node_fs.promises.writeFile(filePath, currentContent + "\n" + content, "utf8");
|
|
107
|
+
} catch (e) {
|
|
108
|
+
await node_fs.promises.writeFile(filePath, content, "utf8");
|
|
109
|
+
}
|
|
154
110
|
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/write-sprite.ts
|
|
113
|
+
var buildSpriteContent = (svgMap, spritePath) => {
|
|
114
|
+
const symbols = [];
|
|
115
|
+
const definitions = [];
|
|
116
|
+
const svgSorted = [...svgMap].sort((a, b) => String(a[0]).localeCompare(b[0])).map(([_, svg]) => svg);
|
|
117
|
+
for (const svg of svgSorted) {
|
|
118
|
+
if (spritePath && spritePath === svg.filePath) continue;
|
|
119
|
+
let content = svg.content;
|
|
120
|
+
if (svg.svgo !== false) {
|
|
121
|
+
const svgoDefault = { plugins: [
|
|
122
|
+
{
|
|
123
|
+
name: "preset-default",
|
|
124
|
+
params: { overrides: {
|
|
125
|
+
removeViewBox: false,
|
|
126
|
+
convertColors: { currentColor: true },
|
|
127
|
+
...svg.svgoOverrides
|
|
128
|
+
} }
|
|
129
|
+
},
|
|
130
|
+
"removeDimensions",
|
|
131
|
+
{
|
|
132
|
+
name: "removeAttrs",
|
|
133
|
+
params: { attrs: "(stroke-width|stroke-linecap|stroke-linejoin|class)" }
|
|
134
|
+
}
|
|
135
|
+
] };
|
|
136
|
+
svg.svgo = svg.svgo ?? svgoDefault;
|
|
137
|
+
content = (0, svgo.optimize)(content, {
|
|
138
|
+
...svg.svgo,
|
|
139
|
+
plugins: [
|
|
140
|
+
...svg.svgo.plugins ?? [],
|
|
141
|
+
...svg.removeAttrs.length ? [{
|
|
142
|
+
name: "removeAttrs",
|
|
143
|
+
params: { attrs: `(${svg.removeAttrs.join("|")})` }
|
|
144
|
+
}] : [],
|
|
145
|
+
...svg.svgoPlugins
|
|
146
|
+
]
|
|
147
|
+
}).data;
|
|
148
|
+
}
|
|
149
|
+
const svgEl = (0, node_html_parser.parse)(content).querySelector("svg");
|
|
150
|
+
if (!svgEl) throw new Error(`SVG file at path "${svg.filePath}" does not contain an svg tag.`);
|
|
151
|
+
svgEl.tagName = "symbol";
|
|
152
|
+
svgEl.setAttribute("id", svg.symbolId);
|
|
153
|
+
svgEl.removeAttribute("xmlns");
|
|
154
|
+
svgEl.removeAttribute("xmlns:xlink");
|
|
155
|
+
svgEl.removeAttribute("version");
|
|
156
|
+
const defs = svgEl.querySelector("defs");
|
|
157
|
+
if (defs) {
|
|
158
|
+
defs.childNodes.forEach((def) => definitions.push(def.toString()));
|
|
159
|
+
svgEl.removeChild(defs);
|
|
160
|
+
}
|
|
161
|
+
symbols.push(svgEl.toString());
|
|
162
|
+
}
|
|
163
|
+
const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"0\" height=\"0\">"];
|
|
164
|
+
if (definitions.length) lines.push(" <defs>", ...definitions.map((d) => ` ${d}`), " </defs>");
|
|
165
|
+
lines.push(...symbols.map((s) => ` ${s}`), "</svg>");
|
|
166
|
+
return lines.join("\n");
|
|
163
167
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
srcExclude: [],
|
|
169
|
-
whitelist: []
|
|
168
|
+
var writeSprite = (spritePath, svgMap) => {
|
|
169
|
+
const content = buildSpriteContent(svgMap, spritePath);
|
|
170
|
+
writeIfChanged(spritePath, content);
|
|
171
|
+
return content;
|
|
170
172
|
};
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/index.ts
|
|
175
|
+
var defaultMatchPattern = /(\S*icon\S*):\s?"(?<icon>.+?)"/gi;
|
|
176
|
+
var inputConfigDefaults = {
|
|
177
|
+
pattern: "**/*.svg",
|
|
178
|
+
baseDir: "./",
|
|
179
|
+
prefix: "",
|
|
180
|
+
removeAttrs: [],
|
|
181
|
+
svgoPlugins: [],
|
|
182
|
+
getSymbolId: (config) => config.prefix + config.matchPath.replace(/\.[^/.]+$/, "").replaceAll("/", "-").toLowerCase()
|
|
183
|
+
};
|
|
184
|
+
var stripUnusedDefaults = {
|
|
185
|
+
enabled: true,
|
|
186
|
+
matchPattern: defaultMatchPattern,
|
|
187
|
+
srcInclude: ["**/*.[jt]sx", "**/*.md?(x)"],
|
|
188
|
+
srcExclude: [],
|
|
189
|
+
whitelist: []
|
|
190
|
+
};
|
|
191
|
+
var SPRITE_VIRTUAL_ID = "\0virtual:svg-spritegen-sprite";
|
|
192
|
+
var SPRITE_URL_PLACEHOLDER = "__SVG_SPRITEGEN_SPRITE_URL_PLACEHOLDER__";
|
|
193
|
+
var toFilterArray = (p) => [p].flat().filter((x) => x != null);
|
|
171
194
|
function svgSpritegen(config) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
195
|
+
const inputConfigsResolved = (Array.isArray(config.input) ? config.input : [config.input ?? inputConfigDefaults]).map((inputConfig) => {
|
|
196
|
+
const baseDir = node_path.default.resolve(process.cwd(), inputConfig.baseDir ?? inputConfigDefaults.baseDir);
|
|
197
|
+
return {
|
|
198
|
+
...inputConfigDefaults,
|
|
199
|
+
...inputConfig,
|
|
200
|
+
baseDir
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
const stripUnusedResolved = { ...stripUnusedDefaults };
|
|
204
|
+
if (typeof config.stripUnused === "object") Object.assign(stripUnusedResolved, config.stripUnused);
|
|
205
|
+
else if (config.stripUnused === false) stripUnusedResolved.enabled = false;
|
|
206
|
+
const outputPath = node_path.default.resolve(process.cwd(), config.outputDir);
|
|
207
|
+
const spriteNameResolved = config.spriteFileName ?? "sprite.svg";
|
|
208
|
+
const typesFilePath = (0, vite.normalizePath)(node_path.default.join(outputPath, config.typesFileName ?? "types.ts"));
|
|
209
|
+
const spriteFilePath = (0, vite.normalizePath)(node_path.default.join(outputPath, spriteNameResolved));
|
|
210
|
+
const gitignoreFilePath = (0, vite.normalizePath)(node_path.default.join(outputPath, ".gitignore"));
|
|
211
|
+
const matchPatternRe = new RegExp(stripUnusedResolved.matchPattern, "g");
|
|
212
|
+
const idIncludes = toFilterArray(stripUnusedResolved.srcInclude);
|
|
213
|
+
const idExcludes = ["**/node_modules/**", ...toFilterArray(stripUnusedResolved.srcExclude)];
|
|
214
|
+
let watcher;
|
|
215
|
+
let allSvgs;
|
|
216
|
+
const referencedSvgs = /* @__PURE__ */ new Map();
|
|
217
|
+
let isBuild = false;
|
|
218
|
+
let viteBase = "/";
|
|
219
|
+
let spriteRefId;
|
|
220
|
+
return {
|
|
221
|
+
name: "svg-spritegen",
|
|
222
|
+
enforce: "pre",
|
|
223
|
+
configResolved(config) {
|
|
224
|
+
isBuild = config.command === "build";
|
|
225
|
+
viteBase = config.base ?? "/";
|
|
226
|
+
},
|
|
227
|
+
async buildStart() {
|
|
228
|
+
allSvgs = await buildSvgMap(inputConfigsResolved);
|
|
229
|
+
await writeTypes(typesFilePath, allSvgs);
|
|
230
|
+
if (config.gitignore !== false) await writeGitignore(gitignoreFilePath, "sprite.svg", "types.ts");
|
|
231
|
+
if (isBuild) return;
|
|
232
|
+
writeSprite(spriteFilePath, allSvgs);
|
|
233
|
+
const onWatch = (changedPath) => {
|
|
234
|
+
if (changedPath === spriteFilePath) return;
|
|
235
|
+
buildSvgMap(inputConfigsResolved).then((newAllSvgFiles) => {
|
|
236
|
+
writeSprite(spriteFilePath, newAllSvgFiles);
|
|
237
|
+
});
|
|
238
|
+
};
|
|
239
|
+
watcher = (0, chokidar.watch)(inputConfigsResolved.map((c) => c.baseDir), { ignoreInitial: true }).on("add", onWatch).on("change", onWatch).on("unlink", onWatch);
|
|
240
|
+
},
|
|
241
|
+
resolveId: {
|
|
242
|
+
filter: { id: new RegExp(`${spriteNameResolved.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`) },
|
|
243
|
+
async handler(source, importer) {
|
|
244
|
+
if (!isBuild) return null;
|
|
245
|
+
if (source === SPRITE_VIRTUAL_ID) return null;
|
|
246
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
247
|
+
if (resolved && (0, vite.normalizePath)(resolved.id) === spriteFilePath) return SPRITE_VIRTUAL_ID;
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
load(id) {
|
|
252
|
+
if (id === SPRITE_VIRTUAL_ID) return `export default ${JSON.stringify(SPRITE_URL_PLACEHOLDER)};`;
|
|
253
|
+
return null;
|
|
254
|
+
},
|
|
255
|
+
transform: {
|
|
256
|
+
filter: {
|
|
257
|
+
id: {
|
|
258
|
+
include: idIncludes,
|
|
259
|
+
exclude: idExcludes
|
|
260
|
+
},
|
|
261
|
+
code: matchPatternRe
|
|
262
|
+
},
|
|
263
|
+
handler(code) {
|
|
264
|
+
if (!isBuild || !stripUnusedResolved.enabled) return null;
|
|
265
|
+
const matches = [...code.matchAll(matchPatternRe)].flatMap(({ groups }) => groups?.icon ?? []);
|
|
266
|
+
for (const match of matches) {
|
|
267
|
+
if (referencedSvgs.has(match)) continue;
|
|
268
|
+
const svg = allSvgs.get(match);
|
|
269
|
+
if (svg) referencedSvgs.set(match, svg);
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
buildEnd() {
|
|
275
|
+
if (!isBuild) return;
|
|
276
|
+
for (const id of stripUnusedResolved.whitelist) {
|
|
277
|
+
if (referencedSvgs.has(id)) continue;
|
|
278
|
+
const svg = allSvgs.get(id);
|
|
279
|
+
if (svg) referencedSvgs.set(id, svg);
|
|
280
|
+
}
|
|
281
|
+
const sourceMap = stripUnusedResolved.enabled ? referencedSvgs : allSvgs;
|
|
282
|
+
spriteRefId = this.emitFile({
|
|
283
|
+
type: "asset",
|
|
284
|
+
name: spriteNameResolved,
|
|
285
|
+
source: buildSpriteContent(sourceMap, spriteFilePath)
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
renderChunk: {
|
|
289
|
+
filter: { code: SPRITE_URL_PLACEHOLDER },
|
|
290
|
+
handler(code) {
|
|
291
|
+
if (!spriteRefId) return null;
|
|
292
|
+
const fileName = this.getFileName(spriteRefId);
|
|
293
|
+
const url = `${viteBase.endsWith("/") ? viteBase : `${viteBase}/`}${fileName}`;
|
|
294
|
+
return code.replaceAll(SPRITE_URL_PLACEHOLDER, url);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
async closeBundle() {
|
|
298
|
+
await watcher?.close();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
276
301
|
}
|
|
302
|
+
//#endregion
|
|
277
303
|
exports.default = svgSpritegen;
|
|
278
304
|
exports.svgSpritegen = svgSpritegen;
|
package/dist/index.mjs
CHANGED
|
@@ -1,278 +1,275 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { hash } from "node:crypto";
|
|
3
2
|
import { watch } from "chokidar";
|
|
4
|
-
import { normalizePath
|
|
3
|
+
import { normalizePath } from "vite";
|
|
5
4
|
import { promises, readFileSync, writeFileSync } from "node:fs";
|
|
6
5
|
import fastGlob from "fast-glob";
|
|
7
6
|
import { parse } from "node-html-parser";
|
|
8
7
|
import { optimize } from "svgo";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
return svgMap;
|
|
8
|
+
//#region src/build-svg-map.ts
|
|
9
|
+
var buildSvgMap = async (inputConfigs) => {
|
|
10
|
+
const svgMap = /* @__PURE__ */ new Map();
|
|
11
|
+
await Promise.all(inputConfigs.map(async (inputConfig) => {
|
|
12
|
+
const { pattern, baseDir, getSymbolId } = inputConfig;
|
|
13
|
+
const matchedPaths = await fastGlob(pattern, { cwd: baseDir });
|
|
14
|
+
await Promise.all(matchedPaths.map(async (matchPath) => {
|
|
15
|
+
const filePath = path.join(baseDir, matchPath);
|
|
16
|
+
const content = await promises.readFile(filePath, "utf8");
|
|
17
|
+
const resolvedInputConfig = {
|
|
18
|
+
...inputConfig,
|
|
19
|
+
content,
|
|
20
|
+
matchPath,
|
|
21
|
+
filePath
|
|
22
|
+
};
|
|
23
|
+
const symbolId = getSymbolId(resolvedInputConfig);
|
|
24
|
+
if (svgMap.has(symbolId)) {
|
|
25
|
+
console.log(`Duplicate SVG symbol id "${symbolId}. Symbol ids should be unique across all inputs."`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
svgMap.set(symbolId, {
|
|
29
|
+
...resolvedInputConfig,
|
|
30
|
+
symbolId
|
|
31
|
+
});
|
|
32
|
+
}));
|
|
33
|
+
}));
|
|
34
|
+
return svgMap;
|
|
38
35
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/write-if-changed.ts
|
|
38
|
+
var writeIfChanged = (filePath, content) => {
|
|
39
|
+
try {
|
|
40
|
+
const currentContent = readFileSync(filePath, "utf8");
|
|
41
|
+
if (content && currentContent !== content) {
|
|
42
|
+
writeFileSync(filePath, content, "utf8");
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
writeFileSync(filePath, content ?? "", "utf8");
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
51
50
|
};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/write-types.ts
|
|
53
|
+
var writeTypes = async (filePath, svgMap) => {
|
|
54
|
+
await writeIfChanged(filePath, [
|
|
55
|
+
"/* eslint-disable */",
|
|
56
|
+
"",
|
|
57
|
+
"// This file is autogenerated. Do not commit to source control.",
|
|
58
|
+
"",
|
|
59
|
+
"export type IconName = (typeof iconNames)[number];",
|
|
60
|
+
"",
|
|
61
|
+
"export const iconNames = [",
|
|
62
|
+
...[...svgMap.keys()].map((name) => ` '${name}',`),
|
|
63
|
+
"] as const;",
|
|
64
|
+
""
|
|
65
|
+
].join("\n"));
|
|
67
66
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/write-gitignore.ts
|
|
69
|
+
var writeGitignore = async (filePath, spriteName, typesName) => {
|
|
70
|
+
const content = [
|
|
71
|
+
"# svg spritegen",
|
|
72
|
+
spriteName,
|
|
73
|
+
typesName
|
|
74
|
+
].join("\n");
|
|
75
|
+
try {
|
|
76
|
+
const currentContent = await promises.readFile(filePath, "utf8");
|
|
77
|
+
if (currentContent.includes(content)) return;
|
|
78
|
+
else await promises.writeFile(filePath, currentContent + "\n" + content, "utf8");
|
|
79
|
+
} catch (e) {
|
|
80
|
+
await promises.writeFile(filePath, content, "utf8");
|
|
81
|
+
}
|
|
80
82
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
svgEl.removeChild(defs);
|
|
138
|
-
}
|
|
139
|
-
symbols.push(svgEl.toString());
|
|
140
|
-
}
|
|
141
|
-
const lines = [
|
|
142
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
143
|
-
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">'
|
|
144
|
-
];
|
|
145
|
-
if (definitions.length) {
|
|
146
|
-
lines.push(" <defs>", ...definitions.map((d) => ` ${d}`), " </defs>");
|
|
147
|
-
}
|
|
148
|
-
lines.push(...symbols.map((s) => ` ${s}`), "</svg>");
|
|
149
|
-
const content = lines.join("\n");
|
|
150
|
-
writeIfChanged(spritePath, content);
|
|
151
|
-
return content;
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/write-sprite.ts
|
|
85
|
+
var buildSpriteContent = (svgMap, spritePath) => {
|
|
86
|
+
const symbols = [];
|
|
87
|
+
const definitions = [];
|
|
88
|
+
const svgSorted = [...svgMap].sort((a, b) => String(a[0]).localeCompare(b[0])).map(([_, svg]) => svg);
|
|
89
|
+
for (const svg of svgSorted) {
|
|
90
|
+
if (spritePath && spritePath === svg.filePath) continue;
|
|
91
|
+
let content = svg.content;
|
|
92
|
+
if (svg.svgo !== false) {
|
|
93
|
+
const svgoDefault = { plugins: [
|
|
94
|
+
{
|
|
95
|
+
name: "preset-default",
|
|
96
|
+
params: { overrides: {
|
|
97
|
+
removeViewBox: false,
|
|
98
|
+
convertColors: { currentColor: true },
|
|
99
|
+
...svg.svgoOverrides
|
|
100
|
+
} }
|
|
101
|
+
},
|
|
102
|
+
"removeDimensions",
|
|
103
|
+
{
|
|
104
|
+
name: "removeAttrs",
|
|
105
|
+
params: { attrs: "(stroke-width|stroke-linecap|stroke-linejoin|class)" }
|
|
106
|
+
}
|
|
107
|
+
] };
|
|
108
|
+
svg.svgo = svg.svgo ?? svgoDefault;
|
|
109
|
+
content = optimize(content, {
|
|
110
|
+
...svg.svgo,
|
|
111
|
+
plugins: [
|
|
112
|
+
...svg.svgo.plugins ?? [],
|
|
113
|
+
...svg.removeAttrs.length ? [{
|
|
114
|
+
name: "removeAttrs",
|
|
115
|
+
params: { attrs: `(${svg.removeAttrs.join("|")})` }
|
|
116
|
+
}] : [],
|
|
117
|
+
...svg.svgoPlugins
|
|
118
|
+
]
|
|
119
|
+
}).data;
|
|
120
|
+
}
|
|
121
|
+
const svgEl = parse(content).querySelector("svg");
|
|
122
|
+
if (!svgEl) throw new Error(`SVG file at path "${svg.filePath}" does not contain an svg tag.`);
|
|
123
|
+
svgEl.tagName = "symbol";
|
|
124
|
+
svgEl.setAttribute("id", svg.symbolId);
|
|
125
|
+
svgEl.removeAttribute("xmlns");
|
|
126
|
+
svgEl.removeAttribute("xmlns:xlink");
|
|
127
|
+
svgEl.removeAttribute("version");
|
|
128
|
+
const defs = svgEl.querySelector("defs");
|
|
129
|
+
if (defs) {
|
|
130
|
+
defs.childNodes.forEach((def) => definitions.push(def.toString()));
|
|
131
|
+
svgEl.removeChild(defs);
|
|
132
|
+
}
|
|
133
|
+
symbols.push(svgEl.toString());
|
|
134
|
+
}
|
|
135
|
+
const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"0\" height=\"0\">"];
|
|
136
|
+
if (definitions.length) lines.push(" <defs>", ...definitions.map((d) => ` ${d}`), " </defs>");
|
|
137
|
+
lines.push(...symbols.map((s) => ` ${s}`), "</svg>");
|
|
138
|
+
return lines.join("\n");
|
|
152
139
|
};
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
prefix: "",
|
|
158
|
-
removeAttrs: [],
|
|
159
|
-
svgoPlugins: [],
|
|
160
|
-
getSymbolId: (config) => config.prefix + config.matchPath.replace(/\.[^/.]+$/, "").replaceAll("/", "-").toLowerCase()
|
|
140
|
+
var writeSprite = (spritePath, svgMap) => {
|
|
141
|
+
const content = buildSpriteContent(svgMap, spritePath);
|
|
142
|
+
writeIfChanged(spritePath, content);
|
|
143
|
+
return content;
|
|
161
144
|
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/index.ts
|
|
147
|
+
var defaultMatchPattern = /(\S*icon\S*):\s?"(?<icon>.+?)"/gi;
|
|
148
|
+
var inputConfigDefaults = {
|
|
149
|
+
pattern: "**/*.svg",
|
|
150
|
+
baseDir: "./",
|
|
151
|
+
prefix: "",
|
|
152
|
+
removeAttrs: [],
|
|
153
|
+
svgoPlugins: [],
|
|
154
|
+
getSymbolId: (config) => config.prefix + config.matchPath.replace(/\.[^/.]+$/, "").replaceAll("/", "-").toLowerCase()
|
|
168
155
|
};
|
|
156
|
+
var stripUnusedDefaults = {
|
|
157
|
+
enabled: true,
|
|
158
|
+
matchPattern: defaultMatchPattern,
|
|
159
|
+
srcInclude: ["**/*.[jt]sx", "**/*.md?(x)"],
|
|
160
|
+
srcExclude: [],
|
|
161
|
+
whitelist: []
|
|
162
|
+
};
|
|
163
|
+
var SPRITE_VIRTUAL_ID = "\0virtual:svg-spritegen-sprite";
|
|
164
|
+
var SPRITE_URL_PLACEHOLDER = "__SVG_SPRITEGEN_SPRITE_URL_PLACEHOLDER__";
|
|
165
|
+
var toFilterArray = (p) => [p].flat().filter((x) => x != null);
|
|
169
166
|
function svgSpritegen(config) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
167
|
+
const inputConfigsResolved = (Array.isArray(config.input) ? config.input : [config.input ?? inputConfigDefaults]).map((inputConfig) => {
|
|
168
|
+
const baseDir = path.resolve(process.cwd(), inputConfig.baseDir ?? inputConfigDefaults.baseDir);
|
|
169
|
+
return {
|
|
170
|
+
...inputConfigDefaults,
|
|
171
|
+
...inputConfig,
|
|
172
|
+
baseDir
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
const stripUnusedResolved = { ...stripUnusedDefaults };
|
|
176
|
+
if (typeof config.stripUnused === "object") Object.assign(stripUnusedResolved, config.stripUnused);
|
|
177
|
+
else if (config.stripUnused === false) stripUnusedResolved.enabled = false;
|
|
178
|
+
const outputPath = path.resolve(process.cwd(), config.outputDir);
|
|
179
|
+
const spriteNameResolved = config.spriteFileName ?? "sprite.svg";
|
|
180
|
+
const typesFilePath = normalizePath(path.join(outputPath, config.typesFileName ?? "types.ts"));
|
|
181
|
+
const spriteFilePath = normalizePath(path.join(outputPath, spriteNameResolved));
|
|
182
|
+
const gitignoreFilePath = normalizePath(path.join(outputPath, ".gitignore"));
|
|
183
|
+
const matchPatternRe = new RegExp(stripUnusedResolved.matchPattern, "g");
|
|
184
|
+
const idIncludes = toFilterArray(stripUnusedResolved.srcInclude);
|
|
185
|
+
const idExcludes = ["**/node_modules/**", ...toFilterArray(stripUnusedResolved.srcExclude)];
|
|
186
|
+
let watcher;
|
|
187
|
+
let allSvgs;
|
|
188
|
+
const referencedSvgs = /* @__PURE__ */ new Map();
|
|
189
|
+
let isBuild = false;
|
|
190
|
+
let viteBase = "/";
|
|
191
|
+
let spriteRefId;
|
|
192
|
+
return {
|
|
193
|
+
name: "svg-spritegen",
|
|
194
|
+
enforce: "pre",
|
|
195
|
+
configResolved(config) {
|
|
196
|
+
isBuild = config.command === "build";
|
|
197
|
+
viteBase = config.base ?? "/";
|
|
198
|
+
},
|
|
199
|
+
async buildStart() {
|
|
200
|
+
allSvgs = await buildSvgMap(inputConfigsResolved);
|
|
201
|
+
await writeTypes(typesFilePath, allSvgs);
|
|
202
|
+
if (config.gitignore !== false) await writeGitignore(gitignoreFilePath, "sprite.svg", "types.ts");
|
|
203
|
+
if (isBuild) return;
|
|
204
|
+
writeSprite(spriteFilePath, allSvgs);
|
|
205
|
+
const onWatch = (changedPath) => {
|
|
206
|
+
if (changedPath === spriteFilePath) return;
|
|
207
|
+
buildSvgMap(inputConfigsResolved).then((newAllSvgFiles) => {
|
|
208
|
+
writeSprite(spriteFilePath, newAllSvgFiles);
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
watcher = watch(inputConfigsResolved.map((c) => c.baseDir), { ignoreInitial: true }).on("add", onWatch).on("change", onWatch).on("unlink", onWatch);
|
|
212
|
+
},
|
|
213
|
+
resolveId: {
|
|
214
|
+
filter: { id: new RegExp(`${spriteNameResolved.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`) },
|
|
215
|
+
async handler(source, importer) {
|
|
216
|
+
if (!isBuild) return null;
|
|
217
|
+
if (source === SPRITE_VIRTUAL_ID) return null;
|
|
218
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
219
|
+
if (resolved && normalizePath(resolved.id) === spriteFilePath) return SPRITE_VIRTUAL_ID;
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
load(id) {
|
|
224
|
+
if (id === SPRITE_VIRTUAL_ID) return `export default ${JSON.stringify(SPRITE_URL_PLACEHOLDER)};`;
|
|
225
|
+
return null;
|
|
226
|
+
},
|
|
227
|
+
transform: {
|
|
228
|
+
filter: {
|
|
229
|
+
id: {
|
|
230
|
+
include: idIncludes,
|
|
231
|
+
exclude: idExcludes
|
|
232
|
+
},
|
|
233
|
+
code: matchPatternRe
|
|
234
|
+
},
|
|
235
|
+
handler(code) {
|
|
236
|
+
if (!isBuild || !stripUnusedResolved.enabled) return null;
|
|
237
|
+
const matches = [...code.matchAll(matchPatternRe)].flatMap(({ groups }) => groups?.icon ?? []);
|
|
238
|
+
for (const match of matches) {
|
|
239
|
+
if (referencedSvgs.has(match)) continue;
|
|
240
|
+
const svg = allSvgs.get(match);
|
|
241
|
+
if (svg) referencedSvgs.set(match, svg);
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
buildEnd() {
|
|
247
|
+
if (!isBuild) return;
|
|
248
|
+
for (const id of stripUnusedResolved.whitelist) {
|
|
249
|
+
if (referencedSvgs.has(id)) continue;
|
|
250
|
+
const svg = allSvgs.get(id);
|
|
251
|
+
if (svg) referencedSvgs.set(id, svg);
|
|
252
|
+
}
|
|
253
|
+
const sourceMap = stripUnusedResolved.enabled ? referencedSvgs : allSvgs;
|
|
254
|
+
spriteRefId = this.emitFile({
|
|
255
|
+
type: "asset",
|
|
256
|
+
name: spriteNameResolved,
|
|
257
|
+
source: buildSpriteContent(sourceMap, spriteFilePath)
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
renderChunk: {
|
|
261
|
+
filter: { code: SPRITE_URL_PLACEHOLDER },
|
|
262
|
+
handler(code) {
|
|
263
|
+
if (!spriteRefId) return null;
|
|
264
|
+
const fileName = this.getFileName(spriteRefId);
|
|
265
|
+
const url = `${viteBase.endsWith("/") ? viteBase : `${viteBase}/`}${fileName}`;
|
|
266
|
+
return code.replaceAll(SPRITE_URL_PLACEHOLDER, url);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
async closeBundle() {
|
|
270
|
+
await watcher?.close();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
274
273
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
svgSpritegen
|
|
278
|
-
};
|
|
274
|
+
//#endregion
|
|
275
|
+
export { svgSpritegen as default, svgSpritegen };
|
package/dist/write-sprite.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-svg-spritegen",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Vite plugin that generates an svg spritesheet, optionally removing unused svg files.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"svgo": "^3.3.2"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"vite": ">=
|
|
57
|
+
"vite": ">=8.0.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^20.14.12",
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
"eslint-plugin-prettier": "^5.1.3",
|
|
68
68
|
"prettier": "^3.2.5",
|
|
69
69
|
"typescript": "^5.4.5",
|
|
70
|
-
"vite": "^
|
|
70
|
+
"vite": "^8.0.11"
|
|
71
71
|
}
|
|
72
72
|
}
|