vite-plugin-svg-spritegen 1.2.1 → 1.2.3

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/dist/index.js CHANGED
@@ -1,43 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const path = require("node:path");
4
+ const node_crypto = require("node:crypto");
4
5
  const chokidar = require("chokidar");
5
6
  const vite = require("vite");
6
- const fastGlob = require("fast-glob");
7
7
  const node_fs = require("node:fs");
8
+ const fastGlob = require("fast-glob");
8
9
  const nodeHtmlParser = require("node-html-parser");
9
10
  const svgo = require("svgo");
10
11
  const buildSvgMap = async (inputConfigs) => {
11
12
  const svgMap = /* @__PURE__ */ new Map();
12
- for (const inputConfig of inputConfigs) {
13
- const { pattern, baseDir, getSymbolId } = inputConfig;
14
- const matchPaths = await fastGlob(pattern, { cwd: baseDir });
15
- for (const matchPath of matchPaths) {
16
- const resolvedInputConfig = {
17
- ...inputConfig,
18
- matchPath,
19
- filePath: path.join(baseDir, matchPath)
20
- };
21
- const symbolId = getSymbolId(resolvedInputConfig);
22
- if (svgMap.has(symbolId)) {
23
- console.log(
24
- `Duplicate SVG symbol id "${symbolId}. Symbol ids should be unique across all inputs."`
25
- );
26
- continue;
27
- }
28
- svgMap.set(symbolId, { ...resolvedInputConfig, symbolId });
29
- }
30
- }
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
+ );
31
39
  return svgMap;
32
40
  };
33
- const writeIfChanged = async (filePath, content) => {
41
+ const writeIfChanged = (filePath, content) => {
34
42
  try {
35
- const currentContent = await node_fs.promises.readFile(filePath, "utf8");
43
+ const currentContent = node_fs.readFileSync(filePath, "utf8");
36
44
  if (content && currentContent !== content) {
37
- await node_fs.promises.writeFile(filePath, content, "utf8");
45
+ node_fs.writeFileSync(filePath, content, "utf8");
46
+ return true;
38
47
  }
48
+ return false;
39
49
  } catch (e) {
40
- await node_fs.promises.writeFile(filePath, content ?? "", "utf8");
50
+ node_fs.writeFileSync(filePath, content ?? "", "utf8");
51
+ return true;
41
52
  }
42
53
  };
43
54
  const writeTypes = async (filePath, svgMap) => {
@@ -69,48 +80,45 @@ const writeGitignore = async (filePath, spriteName, typesName) => {
69
80
  await node_fs.promises.writeFile(filePath, content, "utf8");
70
81
  }
71
82
  };
72
- const writeSprite = async (spritePath, svgMap) => {
83
+ const writeSprite = (spritePath, svgMap) => {
73
84
  const symbols = [];
74
85
  const definitions = [];
75
- await Promise.all(
76
- [...svgMap.values()].map(async (inputConfig) => {
77
- const { svgo: svgo$1, svgoPlugins = [], removeAttrs, symbolId, filePath } = inputConfig;
78
- if (spritePath === filePath) return;
79
- let svgContent = await node_fs.promises.readFile(filePath, "utf8");
80
- if (svgo$1 !== false) {
81
- svgContent = svgo.optimize(svgContent, {
82
- ...svgo$1,
83
- plugins: [
84
- ...svgo$1.plugins ?? [],
85
- ...removeAttrs.length ? [
86
- {
87
- name: "removeAttrs",
88
- params: {
89
- attrs: `(${removeAttrs.join("|")})`
90
- }
86
+ for (const svg of svgMap.values()) {
87
+ if (spritePath === svg.filePath) continue;
88
+ let content2 = svg.content;
89
+ if (svg.svgo !== false) {
90
+ content2 = svgo.optimize(content2, {
91
+ ...svg.svgo,
92
+ plugins: [
93
+ ...svg.svgo.plugins ?? [],
94
+ ...svg.removeAttrs.length ? [
95
+ {
96
+ name: "removeAttrs",
97
+ params: {
98
+ attrs: `(${svg.removeAttrs.join("|")})`
91
99
  }
92
- ] : [],
93
- ...svgoPlugins
94
- ]
95
- }).data;
96
- }
97
- const svg = nodeHtmlParser.parse(svgContent).querySelector("svg");
98
- if (!svg) {
99
- throw new Error(`SVG file at path "${filePath}" does not contain an svg tag.`);
100
- }
101
- svg.tagName = "symbol";
102
- svg.setAttribute("id", symbolId);
103
- svg.removeAttribute("xmlns");
104
- svg.removeAttribute("xmlns:xlink");
105
- svg.removeAttribute("version");
106
- const defs = svg.querySelector("defs");
107
- if (defs) {
108
- defs.childNodes.forEach((def) => definitions.push(def.toString()));
109
- svg.removeChild(defs);
110
- }
111
- symbols.push(svg.toString());
112
- })
113
- );
100
+ }
101
+ ] : [],
102
+ ...svg.svgoPlugins
103
+ ]
104
+ }).data;
105
+ }
106
+ const svgEl = nodeHtmlParser.parse(svg.content).querySelector("svg");
107
+ if (!svgEl) {
108
+ throw new Error(`SVG file at path "${svg.filePath}" does not contain an svg tag.`);
109
+ }
110
+ svgEl.tagName = "symbol";
111
+ svgEl.setAttribute("id", svg.symbolId);
112
+ svgEl.removeAttribute("xmlns");
113
+ svgEl.removeAttribute("xmlns:xlink");
114
+ svgEl.removeAttribute("version");
115
+ const defs = svgEl.querySelector("defs");
116
+ if (defs) {
117
+ defs.childNodes.forEach((def) => definitions.push(def.toString()));
118
+ svgEl.removeChild(defs);
119
+ }
120
+ symbols.push(svgEl.toString());
121
+ }
114
122
  const lines = [
115
123
  '<?xml version="1.0" encoding="UTF-8"?>',
116
124
  '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">'
@@ -120,7 +128,7 @@ const writeSprite = async (spritePath, svgMap) => {
120
128
  }
121
129
  lines.push(...symbols.map((s) => ` ${s}`), "</svg>");
122
130
  const content = lines.join("\n");
123
- await writeIfChanged(spritePath, lines.join("\n"));
131
+ writeIfChanged(spritePath, content);
124
132
  return content;
125
133
  };
126
134
  const svgoDefault = {
@@ -182,9 +190,10 @@ function svgSpritegen(config) {
182
190
  const srcExcludeResolved = ["node_modules/**", srcExclude].flat().filter((e) => e !== null);
183
191
  const srcFilter = vite.createFilter(srcInclude, srcExcludeResolved);
184
192
  let watcher;
185
- let allSvgFiles;
186
- const referencedSvgFiles = /* @__PURE__ */ new Map();
193
+ let allSvgs;
194
+ const referencedSvgs = /* @__PURE__ */ new Map();
187
195
  let isBuild = false;
196
+ let finalSpriteContent = "";
188
197
  return {
189
198
  name: "svg-spritegen",
190
199
  enforce: "post",
@@ -192,20 +201,20 @@ function svgSpritegen(config) {
192
201
  isBuild = config2.command === "build";
193
202
  },
194
203
  async buildStart() {
195
- allSvgFiles = await buildSvgMap(inputConfigsResolved);
196
- await writeTypes(typesFilePath, allSvgFiles);
204
+ allSvgs = await buildSvgMap(inputConfigsResolved);
205
+ await writeTypes(typesFilePath, allSvgs);
197
206
  if (config.gitignore !== false) {
198
207
  await writeGitignore(gitignoreFilePath, "sprite.svg", "types.ts");
199
208
  }
200
209
  if (isBuild) {
201
210
  if (stripUnusedResolved.enabled) {
202
- await writeIfChanged(spriteFilePath);
211
+ writeIfChanged(spriteFilePath);
203
212
  } else {
204
- await writeSprite(spriteFilePath, allSvgFiles);
213
+ writeSprite(spriteFilePath, allSvgs);
205
214
  }
206
215
  return;
207
216
  }
208
- await writeSprite(spriteFilePath, allSvgFiles);
217
+ writeSprite(spriteFilePath, allSvgs);
209
218
  const onWatch = (path2) => {
210
219
  if (path2 === spriteFilePath) return;
211
220
  buildSvgMap(inputConfigsResolved).then((newAllSvgFiles) => {
@@ -222,20 +231,37 @@ function svgSpritegen(config) {
222
231
  ({ groups }) => (groups == null ? void 0 : groups.icon) ?? []
223
232
  );
224
233
  matches.push(...stripUnusedResolved.whitelist);
225
- console.log(matches);
226
234
  for (const match of matches) {
227
- if (referencedSvgFiles.has(match)) continue;
228
- const svgPath = allSvgFiles.get(match);
229
- if (!svgPath) continue;
230
- referencedSvgFiles.set(match, svgPath);
235
+ if (referencedSvgs.has(match)) continue;
236
+ const svg = allSvgs.get(match);
237
+ if (!svg) continue;
238
+ referencedSvgs.set(match, svg);
231
239
  }
232
240
  },
241
+ outputOptions(options) {
242
+ if (!stripUnusedResolved.enabled) return null;
243
+ finalSpriteContent = writeSprite(spriteFilePath, referencedSvgs);
244
+ return {
245
+ ...options,
246
+ assetFileNames: (asset) => {
247
+ var _a;
248
+ const name = typeof options.assetFileNames === "string" ? options.assetFileNames : ((_a = options.assetFileNames) == null ? void 0 : _a.call(options, asset)) ?? "assets/[name]-[hash][extname]";
249
+ if (asset.names.includes(spriteNameResolved)) {
250
+ return name.replace(
251
+ "[hash]",
252
+ node_crypto.hash("sha256", finalSpriteContent, "hex").substring(0, 8)
253
+ );
254
+ } else {
255
+ return name;
256
+ }
257
+ }
258
+ };
259
+ },
233
260
  async generateBundle(_options, bundle) {
234
261
  if (stripUnusedResolved.enabled) {
235
- const spriteContent = await writeSprite(spriteFilePath, referencedSvgFiles);
236
262
  Object.values(bundle).forEach((file) => {
237
263
  if (file.type === "asset" && file.names.includes(spriteNameResolved)) {
238
- file.source = spriteContent;
264
+ file.source = finalSpriteContent;
239
265
  }
240
266
  });
241
267
  }
package/dist/index.mjs CHANGED
@@ -1,41 +1,52 @@
1
1
  import path from "node:path";
2
+ import { hash } from "node:crypto";
2
3
  import { watch } from "chokidar";
3
4
  import { normalizePath, createFilter } from "vite";
5
+ import { promises, readFileSync, writeFileSync } from "node:fs";
4
6
  import fastGlob from "fast-glob";
5
- import { promises } from "node:fs";
6
7
  import { parse } from "node-html-parser";
7
8
  import { optimize } from "svgo";
8
9
  const buildSvgMap = async (inputConfigs) => {
9
10
  const svgMap = /* @__PURE__ */ new Map();
10
- for (const inputConfig of inputConfigs) {
11
- const { pattern, baseDir, getSymbolId } = inputConfig;
12
- const matchPaths = await fastGlob(pattern, { cwd: baseDir });
13
- for (const matchPath of matchPaths) {
14
- const resolvedInputConfig = {
15
- ...inputConfig,
16
- matchPath,
17
- filePath: path.join(baseDir, matchPath)
18
- };
19
- const symbolId = getSymbolId(resolvedInputConfig);
20
- if (svgMap.has(symbolId)) {
21
- console.log(
22
- `Duplicate SVG symbol id "${symbolId}. Symbol ids should be unique across all inputs."`
23
- );
24
- continue;
25
- }
26
- svgMap.set(symbolId, { ...resolvedInputConfig, symbolId });
27
- }
28
- }
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
+ );
29
37
  return svgMap;
30
38
  };
31
- const writeIfChanged = async (filePath, content) => {
39
+ const writeIfChanged = (filePath, content) => {
32
40
  try {
33
- const currentContent = await promises.readFile(filePath, "utf8");
41
+ const currentContent = readFileSync(filePath, "utf8");
34
42
  if (content && currentContent !== content) {
35
- await promises.writeFile(filePath, content, "utf8");
43
+ writeFileSync(filePath, content, "utf8");
44
+ return true;
36
45
  }
46
+ return false;
37
47
  } catch (e) {
38
- await promises.writeFile(filePath, content ?? "", "utf8");
48
+ writeFileSync(filePath, content ?? "", "utf8");
49
+ return true;
39
50
  }
40
51
  };
41
52
  const writeTypes = async (filePath, svgMap) => {
@@ -67,48 +78,45 @@ const writeGitignore = async (filePath, spriteName, typesName) => {
67
78
  await promises.writeFile(filePath, content, "utf8");
68
79
  }
69
80
  };
70
- const writeSprite = async (spritePath, svgMap) => {
81
+ const writeSprite = (spritePath, svgMap) => {
71
82
  const symbols = [];
72
83
  const definitions = [];
73
- await Promise.all(
74
- [...svgMap.values()].map(async (inputConfig) => {
75
- const { svgo, svgoPlugins = [], removeAttrs, symbolId, filePath } = inputConfig;
76
- if (spritePath === filePath) return;
77
- let svgContent = await promises.readFile(filePath, "utf8");
78
- if (svgo !== false) {
79
- svgContent = optimize(svgContent, {
80
- ...svgo,
81
- plugins: [
82
- ...svgo.plugins ?? [],
83
- ...removeAttrs.length ? [
84
- {
85
- name: "removeAttrs",
86
- params: {
87
- attrs: `(${removeAttrs.join("|")})`
88
- }
84
+ for (const svg of svgMap.values()) {
85
+ if (spritePath === svg.filePath) continue;
86
+ let content2 = svg.content;
87
+ if (svg.svgo !== false) {
88
+ content2 = optimize(content2, {
89
+ ...svg.svgo,
90
+ plugins: [
91
+ ...svg.svgo.plugins ?? [],
92
+ ...svg.removeAttrs.length ? [
93
+ {
94
+ name: "removeAttrs",
95
+ params: {
96
+ attrs: `(${svg.removeAttrs.join("|")})`
89
97
  }
90
- ] : [],
91
- ...svgoPlugins
92
- ]
93
- }).data;
94
- }
95
- const svg = parse(svgContent).querySelector("svg");
96
- if (!svg) {
97
- throw new Error(`SVG file at path "${filePath}" does not contain an svg tag.`);
98
- }
99
- svg.tagName = "symbol";
100
- svg.setAttribute("id", symbolId);
101
- svg.removeAttribute("xmlns");
102
- svg.removeAttribute("xmlns:xlink");
103
- svg.removeAttribute("version");
104
- const defs = svg.querySelector("defs");
105
- if (defs) {
106
- defs.childNodes.forEach((def) => definitions.push(def.toString()));
107
- svg.removeChild(defs);
108
- }
109
- symbols.push(svg.toString());
110
- })
111
- );
98
+ }
99
+ ] : [],
100
+ ...svg.svgoPlugins
101
+ ]
102
+ }).data;
103
+ }
104
+ const svgEl = parse(svg.content).querySelector("svg");
105
+ if (!svgEl) {
106
+ throw new Error(`SVG file at path "${svg.filePath}" does not contain an svg tag.`);
107
+ }
108
+ svgEl.tagName = "symbol";
109
+ svgEl.setAttribute("id", svg.symbolId);
110
+ svgEl.removeAttribute("xmlns");
111
+ svgEl.removeAttribute("xmlns:xlink");
112
+ svgEl.removeAttribute("version");
113
+ const defs = svgEl.querySelector("defs");
114
+ if (defs) {
115
+ defs.childNodes.forEach((def) => definitions.push(def.toString()));
116
+ svgEl.removeChild(defs);
117
+ }
118
+ symbols.push(svgEl.toString());
119
+ }
112
120
  const lines = [
113
121
  '<?xml version="1.0" encoding="UTF-8"?>',
114
122
  '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">'
@@ -118,7 +126,7 @@ const writeSprite = async (spritePath, svgMap) => {
118
126
  }
119
127
  lines.push(...symbols.map((s) => ` ${s}`), "</svg>");
120
128
  const content = lines.join("\n");
121
- await writeIfChanged(spritePath, lines.join("\n"));
129
+ writeIfChanged(spritePath, content);
122
130
  return content;
123
131
  };
124
132
  const svgoDefault = {
@@ -180,9 +188,10 @@ function svgSpritegen(config) {
180
188
  const srcExcludeResolved = ["node_modules/**", srcExclude].flat().filter((e) => e !== null);
181
189
  const srcFilter = createFilter(srcInclude, srcExcludeResolved);
182
190
  let watcher;
183
- let allSvgFiles;
184
- const referencedSvgFiles = /* @__PURE__ */ new Map();
191
+ let allSvgs;
192
+ const referencedSvgs = /* @__PURE__ */ new Map();
185
193
  let isBuild = false;
194
+ let finalSpriteContent = "";
186
195
  return {
187
196
  name: "svg-spritegen",
188
197
  enforce: "post",
@@ -190,20 +199,20 @@ function svgSpritegen(config) {
190
199
  isBuild = config2.command === "build";
191
200
  },
192
201
  async buildStart() {
193
- allSvgFiles = await buildSvgMap(inputConfigsResolved);
194
- await writeTypes(typesFilePath, allSvgFiles);
202
+ allSvgs = await buildSvgMap(inputConfigsResolved);
203
+ await writeTypes(typesFilePath, allSvgs);
195
204
  if (config.gitignore !== false) {
196
205
  await writeGitignore(gitignoreFilePath, "sprite.svg", "types.ts");
197
206
  }
198
207
  if (isBuild) {
199
208
  if (stripUnusedResolved.enabled) {
200
- await writeIfChanged(spriteFilePath);
209
+ writeIfChanged(spriteFilePath);
201
210
  } else {
202
- await writeSprite(spriteFilePath, allSvgFiles);
211
+ writeSprite(spriteFilePath, allSvgs);
203
212
  }
204
213
  return;
205
214
  }
206
- await writeSprite(spriteFilePath, allSvgFiles);
215
+ writeSprite(spriteFilePath, allSvgs);
207
216
  const onWatch = (path2) => {
208
217
  if (path2 === spriteFilePath) return;
209
218
  buildSvgMap(inputConfigsResolved).then((newAllSvgFiles) => {
@@ -220,20 +229,37 @@ function svgSpritegen(config) {
220
229
  ({ groups }) => (groups == null ? void 0 : groups.icon) ?? []
221
230
  );
222
231
  matches.push(...stripUnusedResolved.whitelist);
223
- console.log(matches);
224
232
  for (const match of matches) {
225
- if (referencedSvgFiles.has(match)) continue;
226
- const svgPath = allSvgFiles.get(match);
227
- if (!svgPath) continue;
228
- referencedSvgFiles.set(match, svgPath);
233
+ if (referencedSvgs.has(match)) continue;
234
+ const svg = allSvgs.get(match);
235
+ if (!svg) continue;
236
+ referencedSvgs.set(match, svg);
229
237
  }
230
238
  },
239
+ outputOptions(options) {
240
+ if (!stripUnusedResolved.enabled) return null;
241
+ finalSpriteContent = writeSprite(spriteFilePath, referencedSvgs);
242
+ return {
243
+ ...options,
244
+ assetFileNames: (asset) => {
245
+ var _a;
246
+ const name = typeof options.assetFileNames === "string" ? options.assetFileNames : ((_a = options.assetFileNames) == null ? void 0 : _a.call(options, asset)) ?? "assets/[name]-[hash][extname]";
247
+ if (asset.names.includes(spriteNameResolved)) {
248
+ return name.replace(
249
+ "[hash]",
250
+ hash("sha256", finalSpriteContent, "hex").substring(0, 8)
251
+ );
252
+ } else {
253
+ return name;
254
+ }
255
+ }
256
+ };
257
+ },
231
258
  async generateBundle(_options, bundle) {
232
259
  if (stripUnusedResolved.enabled) {
233
- const spriteContent = await writeSprite(spriteFilePath, referencedSvgFiles);
234
260
  Object.values(bundle).forEach((file) => {
235
261
  if (file.type === "asset" && file.names.includes(spriteNameResolved)) {
236
- file.source = spriteContent;
262
+ file.source = finalSpriteContent;
237
263
  }
238
264
  });
239
265
  }
package/dist/types.d.ts CHANGED
@@ -13,6 +13,7 @@ export interface ResolvedInputConfig extends Required<InputConfig> {
13
13
  symbolId: string;
14
14
  matchPath: string;
15
15
  filePath: string;
16
+ content: string;
16
17
  }
17
18
  export type SvgMap = Map<string, ResolvedInputConfig>;
18
19
  export interface StripUnusedConfig {
@@ -1 +1 @@
1
- export declare const writeIfChanged: (filePath: string, content?: string) => Promise<void>;
1
+ export declare const writeIfChanged: (filePath: string, content?: string) => boolean;
@@ -1,2 +1,2 @@
1
1
  import type { SvgMap } from './types';
2
- export declare const writeSprite: (spritePath: string, svgMap: SvgMap) => Promise<string>;
2
+ 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.2.1",
3
+ "version": "1.2.3",
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",