vite-awesome-svg-loader 1.3.5 → 1.4.1

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
@@ -99,14 +99,40 @@ viteAwesomeSvgLoader({
99
99
  // A list of files or directories to preserve line width of.
100
100
  preserveLineWidthList: [/config-demo\/preserve-line-width\//, /config-demo\/all\//],
101
101
 
102
+ // A list of files to skip while preserving line width. Overrides preserveLineWidthList.
103
+ skipPreserveLineWidthList: [/line-width-not-preserved\.svg/],
104
+
102
105
  // A list of files or directories to preserve color of
103
106
  setCurrentColorList: [/config-demo\/set-current-color\//, /config-demo\/all\//],
104
107
 
105
- // A list of files to skip while transforming
106
- skipTransformsList: [/skip-transforms\.svg/],
108
+ // A list of files to skip while replacing colors. Overrides setCurrentColorList.
109
+ skipSetCurrentColorList: [/colors-not-preserved\.svg/],
110
+
111
+ // A list of files to skip while transforming. File skip-transforms.svg is present in every directory.
112
+ skipTransformsList: [/skip-transforms\.svg/, /ignore-elements-orig\.svg/],
107
113
 
108
- // A list of files to skip loading of
114
+ // A list of files to skip loading of. File skip-loading.svg is present in every directory.
109
115
  skipFilesList: [/skip-loading\.svg/],
116
+
117
+ // A list of selectors to skip while preserving line width
118
+ skipPreserveLineWidthSelectors: [
119
+ // It can be a list of CSS selectors like this one. Every element in every file will be checked against it.
120
+ '*[data-original-line-width="true"], *[data-original-line-width="true"] *',
121
+
122
+ // Or it can be configured on per-file basis:
123
+ {
124
+ files: [/ignore-elements\.svg/, /some-other-file\.svg/],
125
+ selectors: ['*[data-original-line-width="true"], *[data-original-line-width="true"] *'],
126
+ },
127
+ ],
128
+
129
+ // These two options are not recommended due to architectural and performance considerations (see JSDoc):
130
+
131
+ // A list of selectors to skip while replacing colors. Same format as above.
132
+ skipSetCurrentColorSelectors: ['*[data-original-color="true"], *[data-original-color="true"] *'],
133
+
134
+ // A list of selectors to skip while transforming. Same format as above.
135
+ skipTransformsSelectors: ['*[data-no-transforms="true"], *[data-no-transforms="true"] *'],
110
136
  }),
111
137
  ```
112
138
 
package/index.d.ts CHANGED
@@ -48,6 +48,19 @@ export interface SvgLoaderOptions {
48
48
  * This also can be done in an import: `import imageSrc from "./path/to/image.svg?preserve-line-width"`.
49
49
  */
50
50
  preserveLineWidthList: (string | RegExp)[];
51
+ /**
52
+ * A list of files or directories to disable preserving line width of. Overrides {@link preserveLineWidthList}.
53
+ */
54
+ skipPreserveLineWidthList: (string | RegExp)[];
55
+ /**
56
+ * A list of CSS selectors to disable {@link preserveLineWidthList} for. Use it to leave specific elements stroke
57
+ * width as-is.
58
+ *
59
+ * Can be a list of selectors or selectors-per-files specifiers.
60
+ *
61
+ * Unlike {@link skipSetCurrentColorSelectors} and {@link skipTransformsSelectors}, doesn't impact build performance.
62
+ */
63
+ skipPreserveLineWidthSelectors: (string | SelectorsPerFiles)[];
51
64
  /**
52
65
  * A list of files or directories to replace fill, stroke and `<stop>` colors to `currentColor` of, i.e.:
53
66
  *
@@ -65,6 +78,39 @@ export interface SvgLoaderOptions {
65
78
  * This also can be done in an import: `import imageSrc from "./path/to/image.svg?set-current-color"`.
66
79
  */
67
80
  setCurrentColorList: (string | RegExp)[];
81
+ /**
82
+ * A list of files or directories to disable setting current color of. Overrides {@link setCurrentColorList}.
83
+ */
84
+ skipSetCurrentColorList: (string | RegExp)[];
85
+ /**
86
+ * A list of CSS selectors to disable {@link setCurrentColorList} for. Use it to leave specific elements colors as-is.
87
+ *
88
+ * Can be a list of selectors or selectors-per-files specifiers.
89
+ *
90
+ * **You probably don't need this option.**
91
+ *
92
+ * For example, if you're creating multi-colored icons, consider following:
93
+ *
94
+ * 1. Using SVG-symbols
95
+ * 1. Add data-attributes for different colors: `data-color-primary`, `data-color-secondary` or whatever fits your
96
+ * needs.
97
+ * 1. Colorize icon with CSS:
98
+ *
99
+ * ```css
100
+ * svg *[data-color-primary] {
101
+ * color: var(--icon-color-primary);
102
+ * }
103
+ *
104
+ * svg *[data-color-secondary] {
105
+ * color: var(--icon-color-secondary);
106
+ * }
107
+ * ```
108
+ *
109
+ * This way you'll get full extensibility.
110
+ *
111
+ * **Heavy usage may significantly slow down build time.** Limit selectors to specific files to improve performance.
112
+ */
113
+ skipSetCurrentColorSelectors: (string | SelectorsPerFiles)[];
68
114
  /**
69
115
  * A list of files to skip while transforming.
70
116
  *
@@ -74,6 +120,17 @@ export interface SvgLoaderOptions {
74
120
  * SVGO is still applied to the added files.
75
121
  */
76
122
  skipTransformsList: (string | RegExp)[];
123
+ /**
124
+ * A list of CSS selectors to disable all transforms for. Use it to leave specific elements as-is.
125
+ *
126
+ * Can be a list of selectors or selectors-per-files specifiers.
127
+ *
128
+ * You probably don't need this option. Try rethinking how you manage your assets (see example at
129
+ * {@link skipSetCurrentColorSelectors}). This option is for some very obscure edge cases only.
130
+ *
131
+ * **Heavy usage may significantly slow down build time.** Limit selectors to specific files to improve performance.
132
+ */
133
+ skipTransformsSelectors: (string | SelectorsPerFiles)[];
77
134
  /**
78
135
  * A list of files to skip loading of. Useful for passing original files to another loader.
79
136
  *
@@ -106,6 +163,19 @@ export interface SvgLoaderOptions {
106
163
  */
107
164
  defaultImport: ImportType;
108
165
  }
166
+ /**
167
+ * CSS selector per file or files
168
+ */
169
+ export interface SelectorsPerFiles {
170
+ /**
171
+ * List of filenames and/or paths matchers
172
+ */
173
+ files: (string | RegExp)[];
174
+ /**
175
+ * List of selectors
176
+ */
177
+ selectors: string[];
178
+ }
109
179
  /**
110
180
  * A Vite plugin that:
111
181
  *
@@ -115,8 +185,11 @@ export interface SvgLoaderOptions {
115
185
  * 1. Source code data URI: `import imageSrcDataUri from "./path/to/image.svg?source-data-uri"`.
116
186
  * 1. Source code Base64: `import imageBase64 from "./path/to/image.svg?base64"`.
117
187
  * 1. Source code Base64 data URI: `import imageBase64DataUri from "./path/to/image.svg?base64-data-uri"`.
118
- * 1. Can preserve line width (make icons and line art have same line width when scaling): `import imageSrc from "./path/to/image.svg?preserve-line-width"`. See also: {@link SvgLoaderOptions.preserveLineWidthList}.
119
- * 1. Can replace colors with `currentColor`: `import imageSrc from "./path/to/image.svg?set-current-color"`. See also: {@link SvgLoaderOptions.setCurrentColorList}.
188
+ * 1. Can preserve line width (make icons and line art have same line width when scaling):
189
+ * `import imageSrc from "./path/to/image.svg?preserve-line-width"`.
190
+ * See also: {@link SvgLoaderOptions.preserveLineWidthList}.
191
+ * 1. Can replace colors with `currentColor`: `import imageSrc from "./path/to/image.svg?set-current-color"`.
192
+ * See also: {@link SvgLoaderOptions.setCurrentColorList}.
120
193
  * 1. Will minimize your SVGs using [SVGO](https://github.com/svg/svgo).
121
194
  *
122
195
  * Parameters can be chained with an `&` symbol like in a normal URL:
@@ -147,7 +220,8 @@ export interface SvgLoaderOptions {
147
220
  * import image from "./path/to/image.svg?skip-awesome-svg-loader";
148
221
  * ```
149
222
  *
150
- * You can set filenames and regexes in {@link SvgLoaderOptions}, so you don't have to write such long urls for every import.
223
+ * You can set filenames and regexes in {@link SvgLoaderOptions}, so you don't have to write such long urls for every
224
+ * import.
151
225
  *
152
226
  * @param options Plugin options
153
227
  */
package/index.js CHANGED
@@ -42,22 +42,28 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
42
42
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
43
43
 
44
44
  // src/index.ts
45
- var src_exports = {};
46
- __export(src_exports, {
45
+ var index_exports = {};
46
+ __export(index_exports, {
47
47
  viteAwesomeSvgLoader: () => viteAwesomeSvgLoader
48
48
  });
49
- module.exports = __toCommonJS(src_exports);
49
+ module.exports = __toCommonJS(index_exports);
50
50
  var import_fs_extra = __toESM(require("fs-extra"));
51
51
  var import_path = __toESM(require("path"));
52
52
  var import_svgo = require("svgo");
53
+ var import_xast = require("svgo/lib/xast.js");
53
54
  var csstree = __toESM(require("css-tree"));
54
55
  var import_imurmurhash = __toESM(require("imurmurhash"));
55
56
  var IMPORT_TYPES = ["url", "source", "source-data-uri", "base64", "base64-data-uri"];
56
57
  var DEFAULT_OPTIONS = {
57
58
  tempDir: ".temp",
58
59
  preserveLineWidthList: [],
60
+ skipPreserveLineWidthList: [],
61
+ skipPreserveLineWidthSelectors: [],
59
62
  setCurrentColorList: [],
63
+ skipSetCurrentColorList: [],
64
+ skipSetCurrentColorSelectors: [],
60
65
  skipTransformsList: [],
66
+ skipTransformsSelectors: [],
61
67
  skipFilesList: [],
62
68
  defaultImport: "source"
63
69
  };
@@ -136,18 +142,34 @@ function viteAwesomeSvgLoader(options = {}) {
136
142
  const query = {};
137
143
  for (const pair of queryKVPairs) {
138
144
  const [key, value] = pair.split("=");
139
- query[key.toLocaleLowerCase()] = value || "1";
145
+ query[key.toLowerCase()] = value || "1";
140
146
  }
141
- if (shouldDoThing(relPathWithSlash, query["skip-awesome-svg-loader"], mergedOptions.skipFilesList)) {
147
+ if (matchesQueryOrList(relPathWithSlash, query["skip-awesome-svg-loader"], mergedOptions.skipFilesList)) {
142
148
  return null;
143
149
  }
144
- const shouldSkipTransforms = shouldDoThing(
150
+ const shouldSkipTransforms = matchesQueryOrList(
145
151
  relPathWithSlash,
146
152
  query["skip-transforms"],
147
153
  mergedOptions.skipTransformsList
148
154
  );
149
- const shouldPreserveLineWidth = !shouldSkipTransforms && shouldDoThing(relPathWithSlash, query["preserve-line-width"], mergedOptions.preserveLineWidthList);
150
- const shouldSetCurrentColor = !shouldSkipTransforms && shouldDoThing(relPathWithSlash, query["set-current-color"], mergedOptions.setCurrentColorList);
155
+ const shouldPreserveLineWidth = !shouldSkipTransforms && matchesQueryOrList(relPathWithSlash, query["preserve-line-width"], mergedOptions.preserveLineWidthList) && !matchesQueryOrList(relPathWithSlash, void 0, mergedOptions.skipPreserveLineWidthList);
156
+ const shouldSetCurrentColor = !shouldSkipTransforms && matchesQueryOrList(relPathWithSlash, query["set-current-color"], mergedOptions.setCurrentColorList) && !matchesQueryOrList(relPathWithSlash, void 0, mergedOptions.skipSetCurrentColorList);
157
+ const skipPreserveLineWidthSelectors = selectorsToList(
158
+ relPathWithSlash,
159
+ mergedOptions.skipPreserveLineWidthSelectors,
160
+ !shouldPreserveLineWidth
161
+ );
162
+ const skipSetCurrentColorSelectors = selectorsToList(
163
+ relPathWithSlash,
164
+ mergedOptions.skipSetCurrentColorSelectors,
165
+ !shouldSetCurrentColor
166
+ );
167
+ const skipTransformsSelectors = selectorsToList(
168
+ relPathWithSlash,
169
+ mergedOptions.skipTransformsSelectors,
170
+ shouldSkipTransforms
171
+ );
172
+ const nodesWithOrigColors = [];
151
173
  let joinedParamsStr = "";
152
174
  for (const param of [shouldSkipTransforms, shouldPreserveLineWidth, shouldSetCurrentColor]) {
153
175
  joinedParamsStr += param ? "1" : "0";
@@ -159,6 +181,7 @@ function viteAwesomeSvgLoader(options = {}) {
159
181
  const fullPath = root + relPathWithSlash;
160
182
  let code = import_fs_extra.default.readFileSync(fullPath).toString();
161
183
  let isFillSetOnRoot = false;
184
+ let didTransform = false;
162
185
  code = (0, import_svgo.optimize)(code, {
163
186
  multipass: true,
164
187
  plugins: [
@@ -173,14 +196,30 @@ function viteAwesomeSvgLoader(options = {}) {
173
196
  {
174
197
  name: "awesome-svg-loader",
175
198
  fn: () => {
199
+ if (didTransform) {
200
+ return null;
201
+ }
202
+ didTransform = true;
176
203
  return {
204
+ root: {
205
+ enter: (root2) => {
206
+ for (const selectors of [skipSetCurrentColorSelectors, skipTransformsSelectors]) {
207
+ for (const selector of selectors) {
208
+ nodesWithOrigColors.push(...(0, import_xast.querySelectorAll)(root2, selector));
209
+ }
210
+ }
211
+ }
212
+ },
177
213
  element: {
178
214
  enter: (node) => {
179
- if (shouldPreserveLineWidth) {
215
+ if (matchesSelectors(node, skipTransformsSelectors)) {
216
+ return;
217
+ }
218
+ if (shouldPreserveLineWidth && !matchesSelectors(node, skipPreserveLineWidthSelectors)) {
180
219
  preserveLineWidth(node, fullPath);
181
220
  }
182
- if (shouldSetCurrentColor) {
183
- isFillSetOnRoot = setCurrentColor(node, isFillSetOnRoot);
221
+ if (shouldSetCurrentColor && !matchesSelectors(node, skipSetCurrentColorSelectors)) {
222
+ isFillSetOnRoot = setCurrentColor(node, isFillSetOnRoot, nodesWithOrigColors);
184
223
  }
185
224
  }
186
225
  }
@@ -197,13 +236,13 @@ function viteAwesomeSvgLoader(options = {}) {
197
236
  }
198
237
  switch (importType) {
199
238
  case "source":
200
- return "export default `" + code.replaceAll("`", "\\`") + "`;";
239
+ return "export default `" + escapeBackticks(code) + "`;";
201
240
  case "source-data-uri":
202
- return "export default `data:image/svg+xml," + encodeURIComponent(code.replaceAll("`", "\\`")) + "`;";
241
+ return "export default `data:image/svg+xml," + encodeURIComponent(code) + "`;";
203
242
  case "base64":
204
- return "export default `" + toBase64(code).replaceAll("`", "\\`") + "`;";
243
+ return "export default `" + escapeBackticks(toBase64(code)) + "`;";
205
244
  case "base64-data-uri":
206
- return "export default `data:image/svg+xml;base64," + encodeURIComponent(toBase64(code)).replaceAll("`", "\\`") + "`;";
245
+ return "export default `data:image/svg+xml;base64," + encodeURIComponent(toBase64(code)) + "`;";
207
246
  }
208
247
  if (!isBuildMode) {
209
248
  const assetUrl = mergedOptions.tempDir + assetRelPath;
@@ -223,23 +262,54 @@ function toBase64(str) {
223
262
  const binString = String.fromCodePoint(...new TextEncoder().encode(str));
224
263
  return btoa(binString);
225
264
  }
226
- function shouldDoThing(relPathWithSlash, queryValue, list) {
227
- if ((queryValue == null ? void 0 : queryValue.toLocaleLowerCase()) === "false") {
265
+ function escapeBackticks(str) {
266
+ return str.replaceAll("`", "\\`");
267
+ }
268
+ function matchesQueryOrList(relPathWithSlash, queryValue, matchers) {
269
+ if ((queryValue == null ? void 0 : queryValue.toLowerCase()) === "false") {
228
270
  return false;
229
271
  }
230
272
  if (queryValue) {
231
273
  return true;
232
274
  }
275
+ return matchesPath(relPathWithSlash, matchers);
276
+ }
277
+ function matchesPath(relPathWithSlash, matchers) {
233
278
  const filename = import_path.default.basename(relPathWithSlash);
234
- for (const entry of list) {
235
- for (const name of [filename, relPathWithSlash]) {
236
- if (name === entry || entry instanceof RegExp && entry.exec(name)) {
279
+ const toMatch = [filename, relPathWithSlash];
280
+ for (const matcher of matchers) {
281
+ for (const entry of toMatch) {
282
+ if (entry === matcher || matcher instanceof RegExp && matcher.exec(entry)) {
237
283
  return true;
238
284
  }
239
285
  }
240
286
  }
241
287
  return false;
242
288
  }
289
+ function selectorsToList(relPathWithSlash, selectors, returnEmptyList) {
290
+ const resolvedSelectors = [];
291
+ if (returnEmptyList) {
292
+ return resolvedSelectors;
293
+ }
294
+ for (const selector of selectors) {
295
+ if (typeof selector === "string") {
296
+ resolvedSelectors.push(selector);
297
+ continue;
298
+ }
299
+ if (matchesPath(relPathWithSlash, selector.files)) {
300
+ resolvedSelectors.push(...selector.selectors);
301
+ }
302
+ }
303
+ return resolvedSelectors;
304
+ }
305
+ function matchesSelectors(node, selectors) {
306
+ for (const selector of selectors) {
307
+ if ((0, import_xast.matches)(node, selector)) {
308
+ return true;
309
+ }
310
+ }
311
+ return false;
312
+ }
243
313
  var TAGS_TO_PRESERVE_LINE_WIDTH_OF = {
244
314
  circle: true,
245
315
  ellipse: true,
@@ -290,15 +360,15 @@ var IGNORE_COLORS = {
290
360
  transparent: true,
291
361
  currentColor: true
292
362
  };
293
- function setCurrentColor(node, isFillSetOnRoot) {
294
- var _a;
363
+ function setCurrentColor(node, isFillSetOnRoot, nodesWithOrigColors) {
295
364
  if (node.name === "style") {
296
- const newCss = setCurrentColorCss((_a = node.children[0]) == null ? void 0 : _a.value, false);
365
+ const firstChild = node.children[0];
366
+ const newCss = setCurrentColorCss(firstChild == null ? void 0 : firstChild.value, nodesWithOrigColors, false);
297
367
  if (newCss) {
298
- node.children[0].value = newCss;
368
+ firstChild.value = newCss;
299
369
  }
300
370
  } else {
301
- const newCss = setCurrentColorCss(node.attributes.style, true);
371
+ const newCss = setCurrentColorCss(node.attributes.style, nodesWithOrigColors, true);
302
372
  if (newCss) {
303
373
  node.attributes.style = newCss;
304
374
  }
@@ -319,7 +389,7 @@ function setCurrentColor(node, isFillSetOnRoot) {
319
389
  }
320
390
  return isFillSetOnRoot;
321
391
  }
322
- function setCurrentColorCss(css, isInline = false) {
392
+ function setCurrentColorCss(css, nodesWithOrigColors, isInline = false) {
323
393
  if (!css || typeof css !== "string") {
324
394
  return "";
325
395
  }
@@ -328,19 +398,74 @@ function setCurrentColorCss(css, isInline = false) {
328
398
  css = `{${css}}`;
329
399
  context = "block";
330
400
  }
401
+ const shouldPreserveColors = !isInline && nodesWithOrigColors.length;
402
+ let origColorSelectors = [];
403
+ let currentColorSelectors = [];
404
+ let didSplitSelectors = false;
331
405
  const ast = csstree.parse(css, { context });
332
406
  csstree.walk(ast, {
333
- visit: "Declaration",
334
- enter: (node) => {
335
- var _a, _b;
336
- if (!COLOR_ATTRS_TO_REPLACE[node.property]) {
407
+ // Ignore because of broken types in csstree:
408
+ // @ts-ignore
409
+ visit: shouldPreserveColors ? void 0 : "Declaration",
410
+ enter: function(node) {
411
+ var _a, _b, _c, _d, _e, _f, _g;
412
+ if (node.__SKIP_SVG_LOADER__ || ((_a = this.rule) == null ? void 0 : _a.__SKIP_SVG_LOADER__)) {
337
413
  return;
338
414
  }
339
- const identifier = (_b = (_a = node.value) == null ? void 0 : _a.children) == null ? void 0 : _b.first;
415
+ if (shouldPreserveColors) {
416
+ if (node.type === "SelectorList") {
417
+ origColorSelectors = [];
418
+ currentColorSelectors = [];
419
+ didSplitSelectors = false;
420
+ return;
421
+ }
422
+ if (node.type === "Selector") {
423
+ const selector = csstree.generate(node);
424
+ let isOrigColor = false;
425
+ for (const svgNode of nodesWithOrigColors) {
426
+ if ((0, import_xast.matches)(svgNode, selector)) {
427
+ isOrigColor = true;
428
+ node.__ORIG_COLOR__ = true;
429
+ break;
430
+ }
431
+ }
432
+ (isOrigColor ? origColorSelectors : currentColorSelectors).push(selector);
433
+ return;
434
+ }
435
+ }
436
+ if (node.type !== "Declaration" || !COLOR_ATTRS_TO_REPLACE[node.property]) {
437
+ return;
438
+ }
439
+ const identifier = (_c = (_b = node.value) == null ? void 0 : _b.children) == null ? void 0 : _c.first;
340
440
  const color = (identifier == null ? void 0 : identifier.value) || (identifier == null ? void 0 : identifier.name);
341
- if (color && !IGNORE_COLORS[color]) {
342
- node.value = csstree.parse("currentColor", { context: "value" });
441
+ if (!color || IGNORE_COLORS[color]) {
442
+ return;
443
+ }
444
+ if (shouldPreserveColors && !didSplitSelectors && ((_d = this.rule) == null ? void 0 : _d.prelude.type) === "SelectorList") {
445
+ const origColorsRule = csstree.clone(this.rule);
446
+ origColorsRule.__SKIP_SVG_LOADER__ = true;
447
+ const origColorsSelectors = new csstree.List();
448
+ const selectors = this.rule.prelude.children;
449
+ selectors.forEach((node2, listItem) => {
450
+ if (node2.__ORIG_COLOR__) {
451
+ selectors.remove(listItem);
452
+ origColorsSelectors.push(node2);
453
+ }
454
+ });
455
+ origColorsRule.prelude.children = origColorsSelectors;
456
+ const parent = ((_f = (_e = this.atrule) == null ? void 0 : _e.block) == null ? void 0 : _f.children) || ((_g = this.stylesheet) == null ? void 0 : _g.children);
457
+ let insertBefore;
458
+ parent == null ? void 0 : parent.some((rule, listItem) => {
459
+ if (rule === this.rule) {
460
+ insertBefore = listItem;
461
+ return true;
462
+ }
463
+ return false;
464
+ });
465
+ insertBefore ? parent == null ? void 0 : parent.insertData(origColorsRule, insertBefore) : parent == null ? void 0 : parent.push(origColorsRule);
466
+ didSplitSelectors = true;
343
467
  }
468
+ node.value = csstree.parse("currentColor", { context: "value" });
344
469
  }
345
470
  });
346
471
  return csstree.generate(ast);