vite-plugin-svg-spritegen 1.4.5 → 1.4.6

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
@@ -1,34 +1,37 @@
1
1
  ![npm](https://img.shields.io/npm/v/vite-plugin-svg-spritegen?style=flat)
2
2
  ![GitHub](https://img.shields.io/github/license/jayalfredprufrock/vite-plugin-svg-spritegen?style=flat)
3
- ![npm](https://img.shields.io/npm/dy/vite-plugin-svg-spritegen?style=flat)
3
+ ![npm](https://img.shields.io/npm/dy/vite-plugin-svg-spritegen?style=flat)
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
- svgSpritegen({
21
- input: [
22
- {
23
- baseDir: 'src/assets/icons',
24
- },
25
- {
26
- baseDir: 'node_modules/lucide-static/icons',
27
- removeAttrs: ['class', 'stroke-width', 'stroke-linecap', 'stroke-linejoin']
28
- }
29
- ],
30
- outputDir: 'src/assets/icons'
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
- "use strict";
2
- Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const path = require("node:path");
4
- const node_crypto = require("node:crypto");
5
- const chokidar = require("chokidar");
6
- const vite = require("vite");
7
- const node_fs = require("node:fs");
8
- const fastGlob = require("fast-glob");
9
- const nodeHtmlParser = require("node-html-parser");
10
- const svgo = require("svgo");
11
- const buildSvgMap = async (inputConfigs) => {
12
- const svgMap = /* @__PURE__ */ new Map();
13
- await Promise.all(
14
- inputConfigs.map(async (inputConfig) => {
15
- const { pattern, baseDir, getSymbolId } = inputConfig;
16
- const matchedPaths = await fastGlob(pattern, { cwd: baseDir });
17
- await Promise.all(
18
- matchedPaths.map(async (matchPath) => {
19
- const filePath = path.join(baseDir, matchPath);
20
- const content = await node_fs.promises.readFile(filePath, "utf8");
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
- const writeIfChanged = (filePath, content) => {
42
- try {
43
- const currentContent = node_fs.readFileSync(filePath, "utf8");
44
- if (content && currentContent !== content) {
45
- node_fs.writeFileSync(filePath, content, "utf8");
46
- return true;
47
- }
48
- return false;
49
- } catch (e) {
50
- node_fs.writeFileSync(filePath, content ?? "", "utf8");
51
- return true;
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
- const writeTypes = async (filePath, svgMap) => {
55
- const iconNames = [...svgMap.keys()];
56
- const content = [
57
- "/* eslint-disable */",
58
- "",
59
- "// This file is autogenerated. Do not commit to source control.",
60
- "",
61
- "export type IconName = (typeof iconNames)[number];",
62
- "",
63
- "export const iconNames = [",
64
- ...iconNames.map((name) => ` '${name}',`),
65
- "] as const;",
66
- ""
67
- ].join("\n");
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
- const writeGitignore = async (filePath, spriteName, typesName) => {
71
- const content = ["# svg spritegen", spriteName, typesName].join("\n");
72
- try {
73
- const currentContent = await node_fs.promises.readFile(filePath, "utf8");
74
- if (currentContent.includes(content)) {
75
- return;
76
- } else {
77
- await node_fs.promises.writeFile(filePath, currentContent + "\n" + content, "utf8");
78
- }
79
- } catch (e) {
80
- await node_fs.promises.writeFile(filePath, content, "utf8");
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
- const writeSprite = (spritePath, svgMap) => {
84
- const symbols = [];
85
- const definitions = [];
86
- const svgSorted = [...svgMap].sort((a, b) => String(a[0]).localeCompare(b[0])).map(([_, svg]) => svg);
87
- for (const svg of svgSorted) {
88
- if (spritePath === svg.filePath) continue;
89
- let content2 = svg.content;
90
- if (svg.svgo !== false) {
91
- const svgoDefault = {
92
- plugins: [
93
- {
94
- name: "preset-default",
95
- params: {
96
- overrides: {
97
- removeViewBox: false,
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
- const defaultMatchPattern = /(\S*icon\S*):\s?"(?<icon>.+?)"/gi;
156
- const inputConfigDefaults = {
157
- pattern: "**/*.svg",
158
- baseDir: "./",
159
- prefix: "",
160
- removeAttrs: [],
161
- svgoPlugins: [],
162
- getSymbolId: (config) => config.prefix + config.matchPath.replace(/\.[^/.]+$/, "").replaceAll("/", "-").toLowerCase()
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
- const stripUnusedDefaults = {
165
- enabled: true,
166
- matchPattern: defaultMatchPattern,
167
- srcInclude: ["**/*.[jt]sx", "**/*.md?(x)"],
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
- const inputConfigs = Array.isArray(config.input) ? config.input : [config.input ?? inputConfigDefaults];
173
- const inputConfigsResolved = inputConfigs.map((inputConfig) => {
174
- const baseDir = path.resolve(process.cwd(), inputConfig.baseDir ?? inputConfigDefaults.baseDir);
175
- return {
176
- ...inputConfigDefaults,
177
- ...inputConfig,
178
- baseDir
179
- };
180
- });
181
- const stripUnusedResolved = { ...stripUnusedDefaults };
182
- if (typeof config.stripUnused === "object") {
183
- Object.assign(stripUnusedResolved, config.stripUnused);
184
- } else if (config.stripUnused === false) {
185
- stripUnusedResolved.enabled = false;
186
- }
187
- const outputPath = path.resolve(process.cwd(), config.outputDir);
188
- const spriteNameResolved = config.spriteFileName ?? "sprite.svg";
189
- const typesFilePath = vite.normalizePath(path.join(outputPath, config.typesFileName ?? "types.ts"));
190
- const spriteFilePath = vite.normalizePath(path.join(outputPath, spriteNameResolved));
191
- const gitignoreFilePath = vite.normalizePath(path.join(outputPath, ".gitignore"));
192
- const { srcInclude, srcExclude } = stripUnusedResolved;
193
- const srcExcludeResolved = ["node_modules/**", srcExclude].flat().filter((e) => e !== null);
194
- const srcFilter = vite.createFilter(srcInclude, srcExcludeResolved);
195
- let watcher;
196
- let allSvgs;
197
- const referencedSvgs = /* @__PURE__ */ new Map();
198
- let isBuild = false;
199
- let finalSpriteContent = "";
200
- return {
201
- name: "svg-spritegen",
202
- enforce: "post",
203
- configResolved(config2) {
204
- isBuild = config2.command === "build";
205
- },
206
- async buildStart() {
207
- allSvgs = await buildSvgMap(inputConfigsResolved);
208
- await writeTypes(typesFilePath, allSvgs);
209
- if (config.gitignore !== false) {
210
- await writeGitignore(gitignoreFilePath, "sprite.svg", "types.ts");
211
- }
212
- if (isBuild) {
213
- if (stripUnusedResolved.enabled) {
214
- writeIfChanged(spriteFilePath);
215
- } else {
216
- writeSprite(spriteFilePath, allSvgs);
217
- }
218
- return;
219
- }
220
- writeSprite(spriteFilePath, allSvgs);
221
- const onWatch = (path2) => {
222
- if (path2 === spriteFilePath) return;
223
- buildSvgMap(inputConfigsResolved).then((newAllSvgFiles) => {
224
- writeSprite(spriteFilePath, newAllSvgFiles);
225
- });
226
- };
227
- const inputPaths = inputConfigsResolved.map((config2) => config2.baseDir);
228
- watcher = chokidar.watch(inputPaths, { ignoreInitial: true }).on("add", onWatch).on("change", onWatch).on("unlink", onWatch);
229
- },
230
- moduleParsed(info) {
231
- if (!isBuild || !stripUnusedResolved.enabled || !srcFilter(info.id) || !info.code) return;
232
- const matchPattern = new RegExp(stripUnusedResolved.matchPattern, "g");
233
- const matches = [...info.code.matchAll(matchPattern)].flatMap(
234
- ({ groups }) => (groups == null ? void 0 : groups.icon) ?? []
235
- );
236
- matches.push(...stripUnusedResolved.whitelist);
237
- for (const match of matches) {
238
- if (referencedSvgs.has(match)) continue;
239
- const svg = allSvgs.get(match);
240
- if (!svg) continue;
241
- referencedSvgs.set(match, svg);
242
- }
243
- },
244
- outputOptions(options) {
245
- if (!stripUnusedResolved.enabled) return null;
246
- finalSpriteContent = writeSprite(spriteFilePath, referencedSvgs);
247
- return {
248
- ...options,
249
- assetFileNames: (asset) => {
250
- var _a;
251
- const name = typeof options.assetFileNames === "string" ? options.assetFileNames : ((_a = options.assetFileNames) == null ? void 0 : _a.call(options, asset)) ?? "assets/[name]-[hash][extname]";
252
- if (asset.names.includes(spriteNameResolved)) {
253
- return name.replace(
254
- "[hash]",
255
- node_crypto.hash("sha256", finalSpriteContent, "hex").substring(0, 8)
256
- );
257
- } else {
258
- return name;
259
- }
260
- }
261
- };
262
- },
263
- async generateBundle(_options, bundle) {
264
- if (stripUnusedResolved.enabled) {
265
- Object.values(bundle).forEach((file) => {
266
- if (file.type === "asset" && file.names.includes(spriteNameResolved)) {
267
- file.source = finalSpriteContent;
268
- }
269
- });
270
- }
271
- },
272
- async closeBundle() {
273
- await (watcher == null ? void 0 : watcher.close());
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, createFilter } from "vite";
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
- const buildSvgMap = async (inputConfigs) => {
10
- const svgMap = /* @__PURE__ */ new Map();
11
- await Promise.all(
12
- inputConfigs.map(async (inputConfig) => {
13
- const { pattern, baseDir, getSymbolId } = inputConfig;
14
- const matchedPaths = await fastGlob(pattern, { cwd: baseDir });
15
- await Promise.all(
16
- matchedPaths.map(async (matchPath) => {
17
- const filePath = path.join(baseDir, matchPath);
18
- const content = await promises.readFile(filePath, "utf8");
19
- const resolvedInputConfig = {
20
- ...inputConfig,
21
- content,
22
- matchPath,
23
- filePath
24
- };
25
- const symbolId = getSymbolId(resolvedInputConfig);
26
- if (svgMap.has(symbolId)) {
27
- console.log(
28
- `Duplicate SVG symbol id "${symbolId}. Symbol ids should be unique across all inputs."`
29
- );
30
- return;
31
- }
32
- svgMap.set(symbolId, { ...resolvedInputConfig, symbolId });
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
- const writeIfChanged = (filePath, content) => {
40
- try {
41
- const currentContent = readFileSync(filePath, "utf8");
42
- if (content && currentContent !== content) {
43
- writeFileSync(filePath, content, "utf8");
44
- return true;
45
- }
46
- return false;
47
- } catch (e) {
48
- writeFileSync(filePath, content ?? "", "utf8");
49
- return true;
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
- const writeTypes = async (filePath, svgMap) => {
53
- const iconNames = [...svgMap.keys()];
54
- const content = [
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
- ...iconNames.map((name) => ` '${name}',`),
63
- "] as const;",
64
- ""
65
- ].join("\n");
66
- await writeIfChanged(filePath, content);
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
- const writeGitignore = async (filePath, spriteName, typesName) => {
69
- const content = ["# svg spritegen", spriteName, typesName].join("\n");
70
- try {
71
- const currentContent = await promises.readFile(filePath, "utf8");
72
- if (currentContent.includes(content)) {
73
- return;
74
- } else {
75
- await promises.writeFile(filePath, currentContent + "\n" + content, "utf8");
76
- }
77
- } catch (e) {
78
- await promises.writeFile(filePath, content, "utf8");
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
- const writeSprite = (spritePath, svgMap) => {
82
- const symbols = [];
83
- const definitions = [];
84
- const svgSorted = [...svgMap].sort((a, b) => String(a[0]).localeCompare(b[0])).map(([_, svg]) => svg);
85
- for (const svg of svgSorted) {
86
- if (spritePath === svg.filePath) continue;
87
- let content2 = svg.content;
88
- if (svg.svgo !== false) {
89
- const svgoDefault = {
90
- plugins: [
91
- {
92
- name: "preset-default",
93
- params: {
94
- overrides: {
95
- removeViewBox: false,
96
- convertColors: { currentColor: true },
97
- ...svg.svgoOverrides
98
- }
99
- }
100
- },
101
- "removeDimensions",
102
- {
103
- name: "removeAttrs",
104
- params: { attrs: "(stroke-width|stroke-linecap|stroke-linejoin|class)" }
105
- }
106
- ]
107
- };
108
- svg.svgo = svg.svgo ?? svgoDefault;
109
- content2 = optimize(content2, {
110
- ...svg.svgo,
111
- plugins: [
112
- ...svg.svgo.plugins ?? [],
113
- ...svg.removeAttrs.length ? [
114
- {
115
- name: "removeAttrs",
116
- params: {
117
- attrs: `(${svg.removeAttrs.join("|")})`
118
- }
119
- }
120
- ] : [],
121
- ...svg.svgoPlugins
122
- ]
123
- }).data;
124
- }
125
- const svgEl = parse(content2).querySelector("svg");
126
- if (!svgEl) {
127
- throw new Error(`SVG file at path "${svg.filePath}" does not contain an svg tag.`);
128
- }
129
- svgEl.tagName = "symbol";
130
- svgEl.setAttribute("id", svg.symbolId);
131
- svgEl.removeAttribute("xmlns");
132
- svgEl.removeAttribute("xmlns:xlink");
133
- svgEl.removeAttribute("version");
134
- const defs = svgEl.querySelector("defs");
135
- if (defs) {
136
- defs.childNodes.forEach((def) => definitions.push(def.toString()));
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
- const defaultMatchPattern = /(\S*icon\S*):\s?"(?<icon>.+?)"/gi;
154
- const inputConfigDefaults = {
155
- pattern: "**/*.svg",
156
- baseDir: "./",
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
- const stripUnusedDefaults = {
163
- enabled: true,
164
- matchPattern: defaultMatchPattern,
165
- srcInclude: ["**/*.[jt]sx", "**/*.md?(x)"],
166
- srcExclude: [],
167
- whitelist: []
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
- const inputConfigs = Array.isArray(config.input) ? config.input : [config.input ?? inputConfigDefaults];
171
- const inputConfigsResolved = inputConfigs.map((inputConfig) => {
172
- const baseDir = path.resolve(process.cwd(), inputConfig.baseDir ?? inputConfigDefaults.baseDir);
173
- return {
174
- ...inputConfigDefaults,
175
- ...inputConfig,
176
- baseDir
177
- };
178
- });
179
- const stripUnusedResolved = { ...stripUnusedDefaults };
180
- if (typeof config.stripUnused === "object") {
181
- Object.assign(stripUnusedResolved, config.stripUnused);
182
- } else if (config.stripUnused === false) {
183
- stripUnusedResolved.enabled = false;
184
- }
185
- const outputPath = path.resolve(process.cwd(), config.outputDir);
186
- const spriteNameResolved = config.spriteFileName ?? "sprite.svg";
187
- const typesFilePath = normalizePath(path.join(outputPath, config.typesFileName ?? "types.ts"));
188
- const spriteFilePath = normalizePath(path.join(outputPath, spriteNameResolved));
189
- const gitignoreFilePath = normalizePath(path.join(outputPath, ".gitignore"));
190
- const { srcInclude, srcExclude } = stripUnusedResolved;
191
- const srcExcludeResolved = ["node_modules/**", srcExclude].flat().filter((e) => e !== null);
192
- const srcFilter = createFilter(srcInclude, srcExcludeResolved);
193
- let watcher;
194
- let allSvgs;
195
- const referencedSvgs = /* @__PURE__ */ new Map();
196
- let isBuild = false;
197
- let finalSpriteContent = "";
198
- return {
199
- name: "svg-spritegen",
200
- enforce: "post",
201
- configResolved(config2) {
202
- isBuild = config2.command === "build";
203
- },
204
- async buildStart() {
205
- allSvgs = await buildSvgMap(inputConfigsResolved);
206
- await writeTypes(typesFilePath, allSvgs);
207
- if (config.gitignore !== false) {
208
- await writeGitignore(gitignoreFilePath, "sprite.svg", "types.ts");
209
- }
210
- if (isBuild) {
211
- if (stripUnusedResolved.enabled) {
212
- writeIfChanged(spriteFilePath);
213
- } else {
214
- writeSprite(spriteFilePath, allSvgs);
215
- }
216
- return;
217
- }
218
- writeSprite(spriteFilePath, allSvgs);
219
- const onWatch = (path2) => {
220
- if (path2 === spriteFilePath) return;
221
- buildSvgMap(inputConfigsResolved).then((newAllSvgFiles) => {
222
- writeSprite(spriteFilePath, newAllSvgFiles);
223
- });
224
- };
225
- const inputPaths = inputConfigsResolved.map((config2) => config2.baseDir);
226
- watcher = watch(inputPaths, { ignoreInitial: true }).on("add", onWatch).on("change", onWatch).on("unlink", onWatch);
227
- },
228
- moduleParsed(info) {
229
- if (!isBuild || !stripUnusedResolved.enabled || !srcFilter(info.id) || !info.code) return;
230
- const matchPattern = new RegExp(stripUnusedResolved.matchPattern, "g");
231
- const matches = [...info.code.matchAll(matchPattern)].flatMap(
232
- ({ groups }) => (groups == null ? void 0 : groups.icon) ?? []
233
- );
234
- matches.push(...stripUnusedResolved.whitelist);
235
- for (const match of matches) {
236
- if (referencedSvgs.has(match)) continue;
237
- const svg = allSvgs.get(match);
238
- if (!svg) continue;
239
- referencedSvgs.set(match, svg);
240
- }
241
- },
242
- outputOptions(options) {
243
- if (!stripUnusedResolved.enabled) return null;
244
- finalSpriteContent = writeSprite(spriteFilePath, referencedSvgs);
245
- return {
246
- ...options,
247
- assetFileNames: (asset) => {
248
- var _a;
249
- const name = typeof options.assetFileNames === "string" ? options.assetFileNames : ((_a = options.assetFileNames) == null ? void 0 : _a.call(options, asset)) ?? "assets/[name]-[hash][extname]";
250
- if (asset.names.includes(spriteNameResolved)) {
251
- return name.replace(
252
- "[hash]",
253
- hash("sha256", finalSpriteContent, "hex").substring(0, 8)
254
- );
255
- } else {
256
- return name;
257
- }
258
- }
259
- };
260
- },
261
- async generateBundle(_options, bundle) {
262
- if (stripUnusedResolved.enabled) {
263
- Object.values(bundle).forEach((file) => {
264
- if (file.type === "asset" && file.names.includes(spriteNameResolved)) {
265
- file.source = finalSpriteContent;
266
- }
267
- });
268
- }
269
- },
270
- async closeBundle() {
271
- await (watcher == null ? void 0 : watcher.close());
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
- export {
276
- svgSpritegen as default,
277
- svgSpritegen
278
- };
274
+ //#endregion
275
+ export { svgSpritegen as default, svgSpritegen };
@@ -1,2 +1,3 @@
1
1
  import type { SvgMap } from './types';
2
+ export declare const buildSpriteContent: (svgMap: SvgMap, spritePath?: string) => string;
2
3
  export declare const writeSprite: (spritePath: string, svgMap: SvgMap) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-svg-spritegen",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
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": ">=5.2.0"
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": "^5.2.11"
70
+ "vite": "^8.0.11"
71
71
  }
72
72
  }