svg-icon-baker 1.2.0 → 2.0.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/LICENSE CHANGED
@@ -1,7 +1,3 @@
1
- # svg-icon-baker license
2
-
3
- svg-icon-baker is released under the MIT license:
4
-
5
1
  MIT License
6
2
 
7
3
  Copyright (c) 2025-present, yangxu52
@@ -23,38 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
21
  SOFTWARE.
26
-
27
- # Licenses of bundled dependencies
28
-
29
- The published svg-icon-baker artifact additionally contains code with the following licenses:
30
- MIT
31
-
32
- # Bundled dependencies:
33
-
34
- ## fast-glob
35
-
36
- License: MIT
37
- by: Kir Belevich
38
- Repository: https://github.com/svg/svgo
39
-
40
- > MIT License
41
- >
42
- > Copyright (c) Kir Belevich
43
- >
44
- > Permission is hereby granted, free of charge, to any person obtaining a copy
45
- > of this software and associated documentation files (the "Software"), to deal
46
- > in the Software without restriction, including without limitation the rights
47
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
48
- > copies of the Software, and to permit persons to whom the Software is
49
- > furnished to do so, subject to the following conditions:
50
- >
51
- > The above copyright notice and this permission notice shall be included in all
52
- > copies or substantial portions of the Software.
53
- >
54
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
55
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
56
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
57
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
58
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
59
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
60
- > SOFTWARE.
package/README.md CHANGED
@@ -2,49 +2,196 @@
2
2
 
3
3
  > Bake the `svg` icon into `symbol` 🍪
4
4
 
5
- The core library for transforming SVG icons into optimized SVG symbol sprites.
5
+ `svg-icon-baker` is the core library for transforming SVG icons into optimized SVG symbol sprites.
6
+ It converts SVG content into `<symbol>` markup for SVG sprite usage while preserving structure and handling local id references safely across multiple icons.
6
7
 
7
- If you like this project, please give it a [Star](https://github.com/yangxu52/svg-icon-baker).
8
+ ## Installation
9
+
10
+ ```bash
11
+ pnpm add svg-icon-baker
12
+ ```
8
13
 
9
14
  ## Usage
10
15
 
11
16
  ```ts
12
- import { bakeIcon, bakeIcons } from 'svg-icon-baker'
17
+ import { bakeIcon, bakeIcons, createBaker } from 'svg-icon-baker'
18
+
19
+ const source = {
20
+ name: 'icon-home',
21
+ content: '<svg viewBox="0 0 16 16"><path d="..."/></svg>',
22
+ }
13
23
 
14
- const source = { name: 'home', content: '<svg viewBox="0 0 16 16">...</svg>' }
15
24
  const result = bakeIcon(source)
16
- // result: { name: 'home', content: '<symbol id="home" viewBox="0 0 16 16">...</symbol>' }
17
25
 
18
- const results = bakeIcons([source])
26
+ console.log(result)
27
+ // {
28
+ // name: 'icon-home',
29
+ // content: '<symbol id="icon-home" viewBox="0 0 16 16">...</symbol>',
30
+ // issues: []
31
+ // }
32
+
33
+ const batch = bakeIcons([source])
34
+
35
+ const baker = createBaker({
36
+ idPolicy: { idStyle: 'named', delim: '_' },
37
+ })
38
+
39
+ const reused = baker.bakeIcon(source)
19
40
  ```
20
41
 
21
- `bakeIcon` and `bakeIcons` are synchronous.
42
+ `bakeIcon`, `bakeIcons`, and `createBaker(...).bakeIcon()` are synchronous.
22
43
 
23
44
  ## API
24
45
 
25
46
  ### `bakeIcon(source: BakeSource, options?: Options): BakeResult`
26
47
 
27
- Convert one SVG into one symbol result.
48
+ Convert one SVG input into one symbol result.
28
49
 
29
50
  ### `bakeIcons(sources: BakeSource[], options?: Options): BakeResult[]`
30
51
 
31
- Convert multiple SVG inputs with one inferred option set.
52
+ Convert multiple SVG inputs with one resolved option set.
53
+
54
+ ### `createBaker(options?: Options): Baker`
55
+
56
+ Create a reusable baker instance with one resolved option set.
57
+ This is the preferred API when the same options are applied to many icons.
58
+
59
+ ```ts
60
+ type Baker = {
61
+ bakeIcon(source: BakeSource): BakeResult
62
+ bakeIcons(sources: BakeSource[]): BakeResult[]
63
+ }
64
+ ```
65
+
66
+ ## Return Value
67
+
68
+ ```ts
69
+ type BakeResult = {
70
+ name: string
71
+ content: string
72
+ issues: BakeIssue[]
73
+ }
74
+ ```
75
+
76
+ - `name` is the original source name.
77
+ - `content` is the generated `<symbol>...</symbol>` markup.
78
+ - `issues` contains non-fatal diagnostics. Conversion still succeeds when issues are reported.
32
79
 
33
80
  ## Options
34
81
 
35
- | name | type | default | description |
36
- | ------------- | ------------- | ------- | ------------------------------------------------ |
37
- | `optimize` | `boolean` | `true` | Enable default safe SVGO optimization preset. |
38
- | `svgoOptions` | `SvgoOptions` | `{}` | Custom SVGO options into the optimization layer. |
82
+ | name | type | default | description |
83
+ | ------------- | ----------------- | ----------------------------------------------------------------------- | -------------------------------------------- |
84
+ | `optimize` | `boolean` | `true` | Enable the built-in safe SVGO optimization. |
85
+ | `svgoOptions` | `SvgoOptions` | `{}` | Merge custom SVGO options into optimization. |
86
+ | `idPolicy` | `IdPolicyOptions` | `{ rewrite: true, unresolved: 'prefix', idStyle: 'named', delim: '_' }` | Control local id rewriting for sprite use. |
87
+
88
+ ### `idPolicy`
89
+
90
+ ```ts
91
+ type IdPolicyOptions = {
92
+ rewrite?: boolean
93
+ unresolved?: 'prefix' | 'preserve'
94
+ idStyle?: 'named' | 'minified' | 'hashed'
95
+ delim?: '-' | '_'
96
+ }
97
+ ```
98
+
99
+ | name | type | default | description |
100
+ | ------------ | ----------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
101
+ | `rewrite` | `boolean` | `true` | Rewrite local ids and local references for sprite safety. |
102
+ | `unresolved` | `'prefix' \| 'preserve'` | `'prefix'` | For unresolved local references, either rewrite them into the icon namespace or keep them unchanged. Both behaviors report an issue. |
103
+ | `idStyle` | `'named' \| 'minified' \| 'hashed'` | `'named'` | Choose readable ids such as `icon_home`, short ids such as `icon_a`, or hashed ids such as `icon_k9x2m`. |
104
+ | `delim` | `'-' \| '_'` | `'_'` | Separator between the icon name and the generated id part. |
105
+
106
+ ## Diagnostics
107
+
108
+ ### Issues
109
+
110
+ ```ts
111
+ type BakeIssue = {
112
+ code: 'ResolveReferenceFailed' | 'DetectDefinitionDuplicate' | 'DetectReferenceCarrierUnsupported' | 'ParseStyleFailed'
113
+ message: string
114
+ targetId?: string
115
+ }
116
+ ```
117
+
118
+ Issues are returned in `BakeResult.issues` instead of throwing.
119
+ Currently emitted issue codes include:
120
+
121
+ - unresolved local references
122
+ - duplicate local definitions
123
+ - style content that cannot be safely parsed
124
+
125
+ `ParseStyleFailed` means one or more `<style>` blocks could not be safely parsed.
126
+ In that case, the original style content is preserved and conversion still succeeds.
127
+
128
+ `DetectReferenceCarrierUnsupported` is reserved for unsupported local reference carriers.
129
+ It is part of the public type contract, but it is not currently emitted by the implementation.
130
+
131
+ Example:
39
132
 
40
- Notes:
133
+ ```ts
134
+ const result = bakeIcon(
135
+ {
136
+ name: 'icon-alert',
137
+ content: '<svg viewBox="0 0 16 16"><use href="#ghost"/></svg>',
138
+ },
139
+ {
140
+ idPolicy: { unresolved: 'preserve' },
141
+ }
142
+ )
143
+
144
+ console.log(result.issues)
145
+ // [
146
+ // {
147
+ // code: 'ResolveReferenceFailed',
148
+ // message: 'Resolve reference failed for local target "ghost"; reference was preserved.',
149
+ // targetId: 'ghost'
150
+ // }
151
+ // ]
152
+ ```
153
+
154
+ ### Errors
155
+
156
+ Fatal failures throw `BakeError`.
157
+
158
+ ```ts
159
+ class BakeError extends Error {
160
+ code: 'ValidateSourceInvalid' | 'ValidateNameInvalid' | 'ValidateSvgRootInvalid' | 'ParseSvgFailed' | 'OptimizeSvgFailed' | 'ResolveViewBoxFailed'
161
+ cause?: unknown
162
+ }
163
+ ```
41
164
 
42
- - Behavior matrix:
43
- - `optimize: true` + no `svgoOptions`: run default safe optimization.
44
- - `optimize: true` + `svgoOptions`: run default safe optimization, then merge custom options.
45
- - `optimize: false` + no `svgoOptions`: skip optimization layer.
46
- - `optimize: false` + `svgoOptions`: run custom optimization settings only.
47
- - When `svgoOptions.plugins` is provided, custom plugins run after default safe optimization plugins.
165
+ Typical fatal cases:
166
+
167
+ - missing `name` or `content`
168
+ - invalid icon names
169
+ - input that does not start with an `<svg>` root
170
+ - malformed SVG that cannot be parsed for rewriting
171
+ - SVGO plugin failures
172
+ - output where `viewBox` cannot be determined
173
+
174
+ ## SVGO Behavior
175
+
176
+ `svg-icon-baker` uses SVGO as an optimization layer after conversion preparation.
177
+
178
+ - When `optimize` is `true`, the package applies a built-in safe preset.
179
+ - User `svgoOptions` are merged into that optimization step.
180
+ - `removeDimensions` is always applied so the output keeps `viewBox` as the sizing contract.
181
+ - User-supplied `prefixIds` and `cleanupIds` plugins are ignored to keep id rewriting behavior stable.
182
+
183
+ This means local id handling is controlled through `idPolicy`, not through external SVGO id plugins.
184
+
185
+ ## Output Guarantees
186
+
187
+ On success, the output keeps these behaviors:
188
+
189
+ - the root is always `<symbol>...</symbol>`
190
+ - the symbol root `id` equals `source.name`
191
+ - `viewBox` is required in the final output
192
+ - root `width` and `height` are removed
193
+ - non-structural root attributes such as `fill` are preserved
194
+ - XML declarations, BOM, comments, and doctype preamble are stripped
48
195
 
49
196
  ## Type Definitions
50
197
 
@@ -59,22 +206,39 @@ type BakeSource = {
59
206
  type BakeResult = {
60
207
  name: string
61
208
  content: string
209
+ issues: BakeIssue[]
62
210
  }
63
211
 
212
+ type BakeIssue = {
213
+ code: 'ResolveReferenceFailed' | 'DetectDefinitionDuplicate' | 'DetectReferenceCarrierUnsupported' | 'ParseStyleFailed'
214
+ message: string
215
+ targetId?: string
216
+ }
217
+
218
+ type BakeErrorCode =
219
+ | 'ValidateSourceInvalid'
220
+ | 'ValidateNameInvalid'
221
+ | 'ValidateSvgRootInvalid'
222
+ | 'ParseSvgFailed'
223
+ | 'OptimizeSvgFailed'
224
+ | 'ResolveViewBoxFailed'
225
+
64
226
  type SvgoOptions = Pick<Config, 'multipass' | 'floatPrecision' | 'js2svg' | 'plugins'>
65
227
 
228
+ type IdPolicyOptions = {
229
+ rewrite?: boolean
230
+ unresolved?: 'prefix' | 'preserve'
231
+ idStyle?: 'named' | 'minified' | 'hashed'
232
+ delim?: '-' | '_'
233
+ }
234
+
66
235
  type Options = {
67
236
  optimize?: boolean
68
237
  svgoOptions?: SvgoOptions
238
+ idPolicy?: IdPolicyOptions
69
239
  }
70
240
  ```
71
241
 
72
- ## Features
73
-
74
- - 🎯 Optimization: Reduce file size, and improve efficiency through `SVGO`
75
- - 🔗 Reference Handling: ID and reference prefixing for sprite safety
76
- - 🎨 Size Unify: `viewBox` preservation or inference from root dimensions
77
-
78
242
  ## License
79
243
 
80
244
  MIT © [yangxu52](https://github.com/yangxu52/svg-icon-baker/blob/main/LICENSE)
package/dist/index.d.mts CHANGED
@@ -4,9 +4,34 @@ type BakeSource = {
4
4
  name: string;
5
5
  content: string;
6
6
  };
7
+ type BakeIssueCode = 'ResolveReferenceFailed' | 'DetectDefinitionDuplicate' | 'DetectReferenceCarrierUnsupported' | 'ParseStyleFailed';
8
+ type BakeIssue = {
9
+ code: BakeIssueCode;
10
+ message: string;
11
+ targetId?: string;
12
+ };
13
+ type BakeErrorCode = 'ValidateSourceInvalid' | 'ValidateNameInvalid' | 'ValidateSvgRootInvalid' | 'ParseSvgFailed' | 'OptimizeSvgFailed' | 'ResolveViewBoxFailed';
14
+ declare class BakeError extends Error {
15
+ readonly code: BakeErrorCode;
16
+ readonly cause?: unknown;
17
+ constructor(code: BakeErrorCode, message: string, options?: {
18
+ cause?: unknown;
19
+ });
20
+ }
21
+ type IdPolicyOptions = {
22
+ rewrite?: boolean;
23
+ unresolved?: 'prefix' | 'preserve';
24
+ idStyle?: 'named' | 'minified' | 'hashed';
25
+ delim?: '-' | '_';
26
+ };
7
27
  type BakeResult = {
8
28
  name: string;
9
29
  content: string;
30
+ issues: BakeIssue[];
31
+ };
32
+ type Baker = {
33
+ bakeIcon(source: BakeSource): BakeResult;
34
+ bakeIcons(sources: BakeSource[]): BakeResult[];
10
35
  };
11
36
  type SvgoOptions = Pick<Config, 'multipass' | 'floatPrecision' | 'js2svg' | 'plugins'>;
12
37
  type Options = {
@@ -19,10 +44,16 @@ type Options = {
19
44
  * custom svgo options merged into optimizer
20
45
  */
21
46
  svgoOptions?: SvgoOptions;
47
+ /**
48
+ * sprite-safe local id rewriting
49
+ * @default { rewrite: true, unresolved: 'prefix', idStyle: 'named', delim: '_' }
50
+ */
51
+ idPolicy?: IdPolicyOptions;
22
52
  };
23
53
 
24
54
  declare function bakeIcon(source: BakeSource, options?: Options): BakeResult;
25
55
  declare function bakeIcons(sources: BakeSource[], options?: Options): BakeResult[];
56
+ declare function createBaker(options?: Options): Baker;
26
57
 
27
- export { bakeIcon, bakeIcons };
28
- export type { BakeResult, BakeSource, Options, SvgoOptions };
58
+ export { BakeError, bakeIcon, bakeIcons, createBaker };
59
+ export type { BakeErrorCode, BakeIssue, BakeIssueCode, BakeResult, BakeSource, Baker, IdPolicyOptions, Options, SvgoOptions };
package/dist/index.d.ts CHANGED
@@ -4,9 +4,34 @@ type BakeSource = {
4
4
  name: string;
5
5
  content: string;
6
6
  };
7
+ type BakeIssueCode = 'ResolveReferenceFailed' | 'DetectDefinitionDuplicate' | 'DetectReferenceCarrierUnsupported' | 'ParseStyleFailed';
8
+ type BakeIssue = {
9
+ code: BakeIssueCode;
10
+ message: string;
11
+ targetId?: string;
12
+ };
13
+ type BakeErrorCode = 'ValidateSourceInvalid' | 'ValidateNameInvalid' | 'ValidateSvgRootInvalid' | 'ParseSvgFailed' | 'OptimizeSvgFailed' | 'ResolveViewBoxFailed';
14
+ declare class BakeError extends Error {
15
+ readonly code: BakeErrorCode;
16
+ readonly cause?: unknown;
17
+ constructor(code: BakeErrorCode, message: string, options?: {
18
+ cause?: unknown;
19
+ });
20
+ }
21
+ type IdPolicyOptions = {
22
+ rewrite?: boolean;
23
+ unresolved?: 'prefix' | 'preserve';
24
+ idStyle?: 'named' | 'minified' | 'hashed';
25
+ delim?: '-' | '_';
26
+ };
7
27
  type BakeResult = {
8
28
  name: string;
9
29
  content: string;
30
+ issues: BakeIssue[];
31
+ };
32
+ type Baker = {
33
+ bakeIcon(source: BakeSource): BakeResult;
34
+ bakeIcons(sources: BakeSource[]): BakeResult[];
10
35
  };
11
36
  type SvgoOptions = Pick<Config, 'multipass' | 'floatPrecision' | 'js2svg' | 'plugins'>;
12
37
  type Options = {
@@ -19,10 +44,16 @@ type Options = {
19
44
  * custom svgo options merged into optimizer
20
45
  */
21
46
  svgoOptions?: SvgoOptions;
47
+ /**
48
+ * sprite-safe local id rewriting
49
+ * @default { rewrite: true, unresolved: 'prefix', idStyle: 'named', delim: '_' }
50
+ */
51
+ idPolicy?: IdPolicyOptions;
22
52
  };
23
53
 
24
54
  declare function bakeIcon(source: BakeSource, options?: Options): BakeResult;
25
55
  declare function bakeIcons(sources: BakeSource[], options?: Options): BakeResult[];
56
+ declare function createBaker(options?: Options): Baker;
26
57
 
27
- export { bakeIcon, bakeIcons };
28
- export type { BakeResult, BakeSource, Options, SvgoOptions };
58
+ export { BakeError, bakeIcon, bakeIcons, createBaker };
59
+ export type { BakeErrorCode, BakeIssue, BakeIssueCode, BakeResult, BakeSource, Baker, IdPolicyOptions, Options, SvgoOptions };
package/dist/index.mjs CHANGED
@@ -1 +1,697 @@
1
- import{optimize as m}from"svgo";const p={name:"preset-default",params:{overrides:{removeUselessDefs:!1,removeHiddenElems:!1,removeUnknownsAndDefaults:!1,collapseGroups:!1,mergePaths:!1,convertShapeToPath:!1}}},l=[{name:"removeTitle"},{name:"removeXMLNS"},{name:"removeXlink"}],c=new Set(["prefixIds"]);function i(e){const n=e??{};return{optimize:n.optimize??!0,svgoOptions:n.svgoOptions??{}}}function u(e,n){const t=[];return n.optimize&&(t.push(p),t.push(...l)),n.svgoOptions.plugins!=null&&t.push(...f(n.svgoOptions.plugins)),t.push(...g(e)),{multipass:n.svgoOptions.multipass,floatPrecision:n.svgoOptions.floatPrecision,js2svg:n.svgoOptions.js2svg,plugins:t}}function f(e){return e.filter(n=>{const t=v(n);return t==null?!0:!c.has(t)})}function v(e){return typeof e=="string"?e:e&&typeof e=="object"&&"name"in e&&typeof e.name=="string"?e.name:null}function g(e){return[{name:"removeDimensions"},{name:"prefixIds",params:{prefix:`${e}-`,delim:""}}]}function h(e,n){const t=i(n);return{name:e.name,content:a(e,t)}}function d(e,n){const t=i(n);return e.map(r=>({name:r.name,content:a(r,t)}))}function a(e,n){if(!e||!e.name||!e.content)throw new TypeError("Property name and content are required.");if(!/^[A-Za-z][A-Za-z0-9_-]*$/.test(e.name))throw new TypeError("Invalid name. Use letters, numbers, dash, or underscore, starting with a letter.");let t;try{t=m(e.content,u(e.name,n))}catch(s){throw new Error(`Parsing failed. ${String(s)}`)}const r=t.data.match(/viewBox="([^"]+)"/)?.[1];if(!r)throw new Error("Cannot determine viewBox. Provide an SVG with viewBox or width/height attributes.");const o=t.data.replace(/^\s*<\?xml[^>]*\?>\s*/i,"");return w(o,e.name,r)}function w(e,n,t){const r=e.match(/^\s*<svg\b[^>]*>/i)[0].replace(/^\s*<svg\b/i,"").replace(/>\s*$/i,"").replace(/\s+id=(['"])[^'"]*\1/gi,"").replace(/\s+viewBox=(['"])[^'"]*\1/gi,"").replace(/\s+width=(['"])[^'"]*\1/gi,"").replace(/\s+height=(['"])[^'"]*\1/gi,"").trim(),o=r?` ${r}`:"",s=`<symbol id="${n}" viewBox="${t}"${o}>`;return e.replace(/^\s*<svg\b[^>]*>/i,s).replace(/<\/svg>\s*$/i,"</symbol>").trim()}export{h as bakeIcon,d as bakeIcons};
1
+ import { optimize } from 'svgo';
2
+ import XMLBuilder from 'fast-xml-builder';
3
+ import { XMLParser } from 'fast-xml-parser';
4
+ import * as csstree from 'css-tree';
5
+
6
+ const parser = new XMLParser({
7
+ preserveOrder: true,
8
+ ignoreAttributes: false,
9
+ attributeNamePrefix: "",
10
+ trimValues: false,
11
+ parseTagValue: false,
12
+ parseAttributeValue: false,
13
+ commentPropName: "#comment",
14
+ cdataPropName: "#cdata"
15
+ });
16
+ const builder = new XMLBuilder({
17
+ preserveOrder: true,
18
+ ignoreAttributes: false,
19
+ attributeNamePrefix: "",
20
+ suppressEmptyNode: true,
21
+ commentPropName: "#comment",
22
+ cdataPropName: "#cdata"
23
+ });
24
+ function parseSvg(code) {
25
+ return parser.parse(code);
26
+ }
27
+ function buildSvg(document) {
28
+ return builder.build(document);
29
+ }
30
+ function visitElements(document, visit) {
31
+ for (const node of document) {
32
+ visitNode(node, visit);
33
+ }
34
+ }
35
+ function visitNode(node, visit) {
36
+ const entry = getElementEntry(node);
37
+ if (!entry) {
38
+ return;
39
+ }
40
+ visit(entry.name, entry.node, entry.attrs);
41
+ for (const child of entry.children) {
42
+ visitNode(child, visit);
43
+ }
44
+ }
45
+ function getElementEntry(node) {
46
+ if (isMetaNode(node)) {
47
+ return null;
48
+ }
49
+ const elementNode = node;
50
+ for (const [key, value] of Object.entries(elementNode)) {
51
+ if (key === ":@" || key.startsWith("#")) {
52
+ continue;
53
+ }
54
+ if (!Array.isArray(value)) {
55
+ continue;
56
+ }
57
+ const attrs = elementNode[":@"] ?? {};
58
+ return {
59
+ name: key,
60
+ node: elementNode,
61
+ attrs,
62
+ children: value
63
+ };
64
+ }
65
+ return null;
66
+ }
67
+ function getRootElementEntry(document) {
68
+ for (const node of document) {
69
+ const entry = getElementEntry(node);
70
+ if (entry) {
71
+ return entry;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ function isMetaNode(node) {
77
+ return "#text" in node || "#cdata" in node || "#comment" in node;
78
+ }
79
+
80
+ function rewriteRoot(document, symbolId) {
81
+ const rootEntry = getRootElementEntry(document);
82
+ if (!rootEntry || rootEntry.name !== "svg") {
83
+ throw new Error("Input must start with an <svg> root element.");
84
+ }
85
+ const viewBox = resolveViewBox(rootEntry.attrs);
86
+ if (!viewBox) {
87
+ throw new Error("Cannot determine viewBox. Provide an SVG with viewBox or width/height attributes.");
88
+ }
89
+ const children = rootEntry.node[rootEntry.name];
90
+ delete rootEntry.node[rootEntry.name];
91
+ rootEntry.node.symbol = children;
92
+ rootEntry.attrs.id = symbolId;
93
+ rootEntry.attrs.viewBox = viewBox;
94
+ delete rootEntry.attrs.width;
95
+ delete rootEntry.attrs.height;
96
+ }
97
+ function resolveViewBox(attrs) {
98
+ const explicitViewBox = attrs.viewBox?.trim();
99
+ if (explicitViewBox) {
100
+ return explicitViewBox;
101
+ }
102
+ const width = resolveDimension(attrs.width);
103
+ const height = resolveDimension(attrs.height);
104
+ if (width == null || height == null) {
105
+ return null;
106
+ }
107
+ return `0 0 ${width} ${height}`;
108
+ }
109
+ function resolveDimension(value) {
110
+ if (!value) {
111
+ return null;
112
+ }
113
+ const normalized = value.trim();
114
+ if (/^-?\d+(?:\.\d+)?$/.test(normalized)) {
115
+ return String(Number(normalized));
116
+ }
117
+ if (/^-?\d+(?:\.\d+)?px$/i.test(normalized)) {
118
+ return String(Number(normalized.slice(0, -2)));
119
+ }
120
+ return null;
121
+ }
122
+
123
+ const URL_REFERENCE_RE$1 = /\burl\((["'])?#(.+?)\1\)/gi;
124
+ const HREF_REFERENCE_RE$1 = /^#(.+)$/;
125
+ const SMIL_REFERENCE_RE$1 = /^([A-Za-z0-9_-]+)\.(begin|end)([+-].+)?$/;
126
+ function collectIds(document) {
127
+ const elements = [];
128
+ const definedIds = /* @__PURE__ */ new Map();
129
+ const referenceIds = [];
130
+ let styleParseFailureCount = 0;
131
+ let order = 0;
132
+ visitElements(document, (name, node, attrs) => {
133
+ const element = { name, node, attrs };
134
+ elements.push(element);
135
+ for (const attrName of ["id", "xml:id"]) {
136
+ const id = attrs[attrName];
137
+ if (!id) {
138
+ continue;
139
+ }
140
+ const records = definedIds.get(id) ?? [];
141
+ records.push({
142
+ id,
143
+ attrName,
144
+ element,
145
+ order
146
+ });
147
+ definedIds.set(id, records);
148
+ order += 1;
149
+ }
150
+ const attributeRefs = collectAttributeReferences(attrs);
151
+ const styleResult = collectStyleReferences(element);
152
+ const styleRefs = styleResult.references;
153
+ styleParseFailureCount += styleResult.parseFailureCount;
154
+ if (attributeRefs.length === 0 && styleRefs.length === 0) {
155
+ return;
156
+ }
157
+ referenceIds.push({ element, attributeRefs, styleRefs });
158
+ });
159
+ return {
160
+ document,
161
+ elements,
162
+ definedIds,
163
+ referenceIds,
164
+ styleParseFailureCount
165
+ };
166
+ }
167
+ function collectAttributeReferences(attrs) {
168
+ const refs = [];
169
+ for (const [attrName, value] of Object.entries(attrs)) {
170
+ if (!value) {
171
+ continue;
172
+ }
173
+ if (attrName === "href" || attrName === "xlink:href") {
174
+ const match = HREF_REFERENCE_RE$1.exec(value);
175
+ if (match) {
176
+ refs.push({
177
+ kind: "href",
178
+ attrName,
179
+ targetIds: [decodeURI(match[1])]
180
+ });
181
+ }
182
+ continue;
183
+ }
184
+ if (isUrlReferenceAttribute(attrName)) {
185
+ const targetIds = collectUrlReferences(value);
186
+ if (targetIds.length > 0) {
187
+ refs.push({
188
+ kind: attrName === "style" ? "style-attr" : "url",
189
+ attrName,
190
+ targetIds
191
+ });
192
+ }
193
+ continue;
194
+ }
195
+ if (attrName === "begin" || attrName === "end") {
196
+ const targetIds = collectSmilReferences(value);
197
+ if (targetIds.length > 0) {
198
+ refs.push({
199
+ kind: attrName,
200
+ attrName,
201
+ targetIds
202
+ });
203
+ }
204
+ continue;
205
+ }
206
+ if (attrName === "aria-labelledby" || attrName === "aria-describedby") {
207
+ const targetIds = value.split(/\s+/).filter(Boolean);
208
+ if (targetIds.length > 0) {
209
+ refs.push({
210
+ kind: attrName,
211
+ attrName,
212
+ targetIds
213
+ });
214
+ }
215
+ }
216
+ }
217
+ return refs;
218
+ }
219
+ function collectStyleReferences(element) {
220
+ if (element.name !== "style") {
221
+ return { references: [], parseFailureCount: 0 };
222
+ }
223
+ const entry = getElementEntry(element.node);
224
+ if (!entry) {
225
+ return { references: [], parseFailureCount: 0 };
226
+ }
227
+ const refs = [];
228
+ let parseFailureCount = 0;
229
+ for (const child of entry.children) {
230
+ if (!("#text" in child) || typeof child["#text"] !== "string") {
231
+ continue;
232
+ }
233
+ const result = collectStyleTextReferences(child["#text"]);
234
+ parseFailureCount += result.parseFailureCount;
235
+ const targetIds = result.targetIds;
236
+ if (targetIds.length === 0) {
237
+ continue;
238
+ }
239
+ refs.push({
240
+ kind: "style-selector",
241
+ textNode: child,
242
+ targetIds
243
+ });
244
+ }
245
+ return {
246
+ references: refs,
247
+ parseFailureCount
248
+ };
249
+ }
250
+ function collectUrlReferences(value) {
251
+ const ids = [];
252
+ for (const match of value.matchAll(URL_REFERENCE_RE$1)) {
253
+ ids.push(decodeURI(match[2]));
254
+ }
255
+ return ids;
256
+ }
257
+ function collectSmilReferences(value) {
258
+ return value.split(/\s*;\s*/).map((part) => SMIL_REFERENCE_RE$1.exec(part)?.[1] ?? null).filter((id) => id != null);
259
+ }
260
+ function collectStyleTextReferences(value) {
261
+ const ids = /* @__PURE__ */ new Set();
262
+ let cssAst;
263
+ try {
264
+ cssAst = csstree.parse(value, {
265
+ parseValue: true,
266
+ parseCustomProperty: false
267
+ });
268
+ } catch {
269
+ return {
270
+ targetIds: [],
271
+ parseFailureCount: 1
272
+ };
273
+ }
274
+ csstree.walk(cssAst, (node) => {
275
+ if (node.type === "IdSelector" && typeof node.name === "string") {
276
+ ids.add(node.name);
277
+ return;
278
+ }
279
+ if (node.type === "Url" && typeof node.value === "string" && node.value.length > 0) {
280
+ const raw = unquote$1(node.value);
281
+ if (raw.startsWith("#")) {
282
+ ids.add(decodeURI(raw.slice(1)));
283
+ }
284
+ }
285
+ });
286
+ return {
287
+ targetIds: [...ids],
288
+ parseFailureCount: 0
289
+ };
290
+ }
291
+ function isUrlReferenceAttribute(attrName) {
292
+ return attrName === "clip-path" || attrName === "color-profile" || attrName === "fill" || attrName === "filter" || attrName === "marker-end" || attrName === "marker-mid" || attrName === "marker-start" || attrName === "mask" || attrName === "stroke" || attrName === "style";
293
+ }
294
+ function unquote$1(value) {
295
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
296
+ return value.slice(1, -1);
297
+ }
298
+ return value;
299
+ }
300
+
301
+ const URL_REFERENCE_RE = /\burl\((["'])?#(.+?)\1\)/gi;
302
+ const HREF_REFERENCE_RE = /^#(.+)$/;
303
+ const SMIL_REFERENCE_RE = /^([A-Za-z0-9_-]+)\.(begin|end)([+-].+)?$/;
304
+ function rewriteIds(document, prefix, options, issues) {
305
+ const state = collectIds(document);
306
+ collectIssues(state, issues);
307
+ const idMap = createIdMap(state, prefix, options);
308
+ const unresolvedIdMap = /* @__PURE__ */ new Map();
309
+ let nextGeneratedIndex = idMap.size;
310
+ rewriteElementIds(state, idMap);
311
+ rewriteReferences(state, idMap, prefix, unresolvedIdMap, () => nextGeneratedIndex++, options, issues);
312
+ return idMap;
313
+ }
314
+ function createIdMap(state, prefix, options) {
315
+ const idMap = /* @__PURE__ */ new Map();
316
+ const records = [...state.definedIds.entries()].map(([id, definitions]) => ({
317
+ id,
318
+ order: Math.min(...definitions.map((definition) => definition.order))
319
+ })).sort((a, b) => a.order - b.order);
320
+ for (let counter = 0; counter < records.length; counter += 1) {
321
+ const { id } = records[counter];
322
+ idMap.set(id, createMappedId(id, prefix, counter, options));
323
+ }
324
+ return idMap;
325
+ }
326
+ function rewriteElementIds(state, idMap) {
327
+ for (const [id, definitions] of state.definedIds) {
328
+ const canonical = definitions[0];
329
+ canonical.element.attrs[canonical.attrName] = idMap.get(id);
330
+ for (const duplicate of definitions.slice(1)) {
331
+ delete duplicate.element.attrs[duplicate.attrName];
332
+ }
333
+ }
334
+ }
335
+ function rewriteReferences(state, idMap, prefix, unresolvedIdMap, nextIndex, options, issues) {
336
+ for (const reference of state.referenceIds) {
337
+ for (const attrRef of reference.attributeRefs) {
338
+ const value = reference.element.attrs[attrRef.attrName];
339
+ if (!value) {
340
+ continue;
341
+ }
342
+ if (attrRef.kind === "href") {
343
+ reference.element.attrs[attrRef.attrName] = rewriteHrefValue(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues);
344
+ continue;
345
+ }
346
+ if (attrRef.kind === "url" || attrRef.kind === "style-attr" && attrRef.attrName === "style") {
347
+ reference.element.attrs[attrRef.attrName] = rewriteUrlValue(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues);
348
+ continue;
349
+ }
350
+ if (attrRef.kind === "begin" || attrRef.kind === "end") {
351
+ reference.element.attrs[attrRef.attrName] = rewriteSmilValue(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues);
352
+ continue;
353
+ }
354
+ if (attrRef.kind === "aria-labelledby" || attrRef.kind === "aria-describedby") {
355
+ reference.element.attrs[attrRef.attrName] = rewriteTokenList(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues);
356
+ }
357
+ }
358
+ for (const styleRef of reference.styleRefs) {
359
+ styleRef.textNode["#text"] = rewriteStyleText(styleRef.textNode, idMap, prefix, unresolvedIdMap, nextIndex, options, issues);
360
+ }
361
+ }
362
+ }
363
+ function rewriteHrefValue(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues) {
364
+ const match = HREF_REFERENCE_RE.exec(value);
365
+ if (!match) {
366
+ return value;
367
+ }
368
+ const source = decodeURI(match[1]);
369
+ const next = idMap.get(source);
370
+ if (next) {
371
+ return `#${next}`;
372
+ }
373
+ return rewriteUnresolvedReference(source, value, prefix, unresolvedIdMap, nextIndex, options, issues, (resolved) => `#${resolved}`);
374
+ }
375
+ function rewriteUrlValue(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues) {
376
+ return value.replace(URL_REFERENCE_RE, (match, quote, body) => {
377
+ const source = decodeURI(body);
378
+ const next = idMap.get(source);
379
+ if (!next) {
380
+ return rewriteUnresolvedReference(source, match, prefix, unresolvedIdMap, nextIndex, options, issues, (resolved) => {
381
+ const wrappedQuote2 = quote ?? "";
382
+ return `url(${wrappedQuote2}#${resolved}${wrappedQuote2})`;
383
+ });
384
+ }
385
+ const wrappedQuote = quote ?? "";
386
+ return `url(${wrappedQuote}#${next}${wrappedQuote})`;
387
+ });
388
+ }
389
+ function rewriteSmilValue(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues) {
390
+ const parts = value.split(/\s*;\s*/);
391
+ return parts.map((part) => {
392
+ const match = SMIL_REFERENCE_RE.exec(part);
393
+ if (!match) {
394
+ return part;
395
+ }
396
+ const next = idMap.get(match[1]);
397
+ if (!next) {
398
+ return rewriteUnresolvedReference(match[1], part, prefix, unresolvedIdMap, nextIndex, options, issues, (resolved) => {
399
+ const offset2 = match[3] ?? "";
400
+ return `${resolved}.${match[2]}${offset2}`;
401
+ });
402
+ }
403
+ const offset = match[3] ?? "";
404
+ return `${next}.${match[2]}${offset}`;
405
+ }).join("; ");
406
+ }
407
+ function rewriteTokenList(value, idMap, prefix, unresolvedIdMap, nextIndex, options, issues) {
408
+ return value.split(/\s+/).filter(Boolean).map((token) => {
409
+ const next = idMap.get(token);
410
+ if (next) {
411
+ return next;
412
+ }
413
+ return rewriteUnresolvedReference(token, token, prefix, unresolvedIdMap, nextIndex, options, issues, (resolved) => resolved);
414
+ }).join(" ");
415
+ }
416
+ function rewriteStyleText(textNode, idMap, prefix, unresolvedIdMap, nextIndex, options, issues) {
417
+ let cssAst;
418
+ try {
419
+ cssAst = csstree.parse(textNode["#text"], {
420
+ parseValue: true,
421
+ parseCustomProperty: false
422
+ });
423
+ } catch {
424
+ return textNode["#text"];
425
+ }
426
+ csstree.walk(cssAst, (node) => {
427
+ if (node.type === "IdSelector" && typeof node.name === "string") {
428
+ const next = idMap.get(node.name);
429
+ if (next) {
430
+ node.name = next;
431
+ return;
432
+ }
433
+ const rewritten = rewriteUnresolvedReference(node.name, node.name, prefix, unresolvedIdMap, nextIndex, options, issues, (resolved) => resolved);
434
+ if (rewritten !== node.name) {
435
+ node.name = rewritten;
436
+ }
437
+ return;
438
+ }
439
+ if (node.type === "Url" && typeof node.value === "string" && node.value.length > 0) {
440
+ const raw = unquote(node.value);
441
+ if (!raw.startsWith("#")) {
442
+ return;
443
+ }
444
+ const source = decodeURI(raw.slice(1));
445
+ const next = idMap.get(source);
446
+ if (!next) {
447
+ const rewritten = rewriteUnresolvedReference(source, `#${source}`, prefix, unresolvedIdMap, nextIndex, options, issues, (resolved) => `#${resolved}`);
448
+ if (rewritten !== `#${source}`) {
449
+ node.value = rewritten;
450
+ }
451
+ return;
452
+ }
453
+ node.value = `#${next}`;
454
+ }
455
+ });
456
+ return csstree.generate(cssAst);
457
+ }
458
+ function encodeIndex(index) {
459
+ const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
460
+ let current = index;
461
+ let result = "";
462
+ do {
463
+ result = alphabet[current % alphabet.length] + result;
464
+ current = Math.floor(current / alphabet.length) - 1;
465
+ } while (current >= 0);
466
+ return result;
467
+ }
468
+ function createMappedId(sourceId, prefix, index, options) {
469
+ const delim = resolveDelim(options);
470
+ const style = options.idStyle;
471
+ if (style === "named") {
472
+ return `${prefix}${delim}${sourceId}`;
473
+ }
474
+ if (style === "hashed") {
475
+ return `${prefix}${delim}${hashId(sourceId)}`;
476
+ }
477
+ return `${prefix}${delim}${encodeIndex(index)}`;
478
+ }
479
+ function hashId(value) {
480
+ let hash = 5381;
481
+ for (const char of value) {
482
+ hash = (hash << 5) + hash ^ char.charCodeAt(0);
483
+ }
484
+ return Math.abs(hash >>> 0).toString(36);
485
+ }
486
+ function createUnresolvedMappedId(sourceId, prefix, unresolvedIdMap, nextIndex, options) {
487
+ const existing = unresolvedIdMap.get(sourceId);
488
+ if (existing) {
489
+ return existing;
490
+ }
491
+ const generated = createMappedId(sourceId, prefix, nextIndex(), options);
492
+ unresolvedIdMap.set(sourceId, generated);
493
+ return generated;
494
+ }
495
+ function rewriteUnresolvedReference(targetId, originalValue, prefix, unresolvedIdMap, nextIndex, options, issues, format) {
496
+ pushIssue(issues, {
497
+ code: "ResolveReferenceFailed",
498
+ message: `Resolve reference failed for local target "${targetId}"; reference was ${options.unresolved === "prefix" ? "prefixed" : "preserved"}.`,
499
+ targetId
500
+ });
501
+ if (options.unresolved === "preserve") {
502
+ return originalValue;
503
+ }
504
+ return format(createUnresolvedMappedId(targetId, prefix, unresolvedIdMap, nextIndex, options));
505
+ }
506
+ function resolveDelim(options) {
507
+ if (options.delim != null) {
508
+ return options.delim;
509
+ }
510
+ return "_";
511
+ }
512
+ function collectIssues(state, issues) {
513
+ for (const [id, definitions] of state.definedIds) {
514
+ if (definitions.length > 1) {
515
+ pushIssue(issues, {
516
+ code: "DetectDefinitionDuplicate",
517
+ message: `Detect definition duplicate for local target "${id}"; first occurrence was kept.`,
518
+ targetId: id
519
+ });
520
+ }
521
+ }
522
+ if (state.styleParseFailureCount > 0) {
523
+ pushIssue(issues, {
524
+ code: "ParseStyleFailed",
525
+ message: "Parse style failed for one or more <style> blocks; original content was preserved."
526
+ });
527
+ }
528
+ }
529
+ function pushIssue(issues, issue) {
530
+ const exists = issues.some((current) => current.code === issue.code && current.targetId === issue.targetId && current.message === issue.message);
531
+ if (!exists) {
532
+ issues.push(issue);
533
+ }
534
+ }
535
+ function unquote(value) {
536
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
537
+ return value.slice(1, -1);
538
+ }
539
+ return value;
540
+ }
541
+
542
+ function bakeSymbol(content, name, options) {
543
+ const document = parseSvg(content);
544
+ const issues = [];
545
+ if (options.rewrite) {
546
+ rewriteIds(document, name, options, issues);
547
+ }
548
+ rewriteRoot(document, name);
549
+ return {
550
+ name,
551
+ content: buildSvg(document).trim(),
552
+ issues
553
+ };
554
+ }
555
+
556
+ const PRESET_OVERRIDE_BLOCKED_PLUGINS = [
557
+ "cleanupIds",
558
+ "removeUselessDefs",
559
+ "removeHiddenElems",
560
+ "removeUnknownsAndDefaults",
561
+ "collapseGroups",
562
+ "mergePaths",
563
+ "convertShapeToPath",
564
+ "removeEmptyContainers"
565
+ ];
566
+ const FILTER_ONLY_BLOCKED_PLUGINS = ["prefixIds", "reusePaths", "convertOneStopGradients"];
567
+ const USER_FILTER_BLOCKED_PLUGINS = ["preset-default", ...PRESET_OVERRIDE_BLOCKED_PLUGINS, ...FILTER_ONLY_BLOCKED_PLUGINS];
568
+ const DEFAULT_SAFE_PRESET = {
569
+ name: "preset-default",
570
+ params: {
571
+ overrides: Object.fromEntries(PRESET_OVERRIDE_BLOCKED_PLUGINS.map((name) => [name, false]))
572
+ }
573
+ };
574
+ const DEFAULT_SAFE_PLUGINS = [{ name: "removeTitle" }, { name: "removeXMLNS" }, { name: "removeXlink" }];
575
+ function resolveOptions(userOption) {
576
+ const userObject = userOption ?? {};
577
+ return {
578
+ optimize: userObject.optimize ?? true,
579
+ svgoOptions: userObject.svgoOptions ?? {},
580
+ idPolicy: resolveIdPolicyOptions(userObject.idPolicy)
581
+ };
582
+ }
583
+ function createSvgoConfig(options) {
584
+ const plugins = [];
585
+ if (options.optimize) {
586
+ plugins.push(DEFAULT_SAFE_PRESET);
587
+ plugins.push(...DEFAULT_SAFE_PLUGINS);
588
+ }
589
+ if (options.svgoOptions.plugins != null) {
590
+ plugins.push(...filterPlugins(options.svgoOptions.plugins));
591
+ }
592
+ return {
593
+ multipass: options.svgoOptions.multipass,
594
+ floatPrecision: options.svgoOptions.floatPrecision,
595
+ js2svg: options.svgoOptions.js2svg,
596
+ plugins
597
+ };
598
+ }
599
+ function filterPlugins(plugins) {
600
+ return plugins.filter((plugin) => {
601
+ const name = resolvePluginName(plugin);
602
+ if (name == null) {
603
+ return true;
604
+ }
605
+ return !USER_FILTER_BLOCKED_PLUGINS.includes(name);
606
+ });
607
+ }
608
+ function resolvePluginName(plugin) {
609
+ if (typeof plugin === "string") {
610
+ return plugin;
611
+ }
612
+ if (plugin && typeof plugin === "object" && "name" in plugin && typeof plugin.name === "string") {
613
+ return plugin.name;
614
+ }
615
+ return null;
616
+ }
617
+ function resolveIdPolicyOptions(idPolicy) {
618
+ return {
619
+ rewrite: idPolicy?.rewrite ?? true,
620
+ unresolved: idPolicy?.unresolved ?? "prefix",
621
+ idStyle: idPolicy?.idStyle ?? "named",
622
+ delim: idPolicy?.delim ?? "_"
623
+ };
624
+ }
625
+
626
+ class BakeError extends Error {
627
+ code;
628
+ cause;
629
+ constructor(code, message, options) {
630
+ super(message, options);
631
+ this.name = "BakeError";
632
+ this.code = code;
633
+ this.cause = options?.cause;
634
+ }
635
+ }
636
+
637
+ function bakeIcon(source, options) {
638
+ return createBaker(options).bakeIcon(source);
639
+ }
640
+ function bakeIcons(sources, options) {
641
+ return createBaker(options).bakeIcons(sources);
642
+ }
643
+ function createBaker(options) {
644
+ const resolvedOptions = resolveOptions(options);
645
+ const svgoConfig = createSvgoConfig(resolvedOptions);
646
+ return {
647
+ bakeIcon(source) {
648
+ return convertSource(source, resolvedOptions, svgoConfig);
649
+ },
650
+ bakeIcons(sources) {
651
+ return sources.map((source) => {
652
+ return convertSource(source, resolvedOptions, svgoConfig);
653
+ });
654
+ }
655
+ };
656
+ }
657
+ function convertSource(source, options, svgoConfig) {
658
+ if (!source || !source.name || !source.content) {
659
+ throw new BakeError("ValidateSourceInvalid", "Property name and content are required.");
660
+ }
661
+ if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(source.name)) {
662
+ throw new BakeError("ValidateNameInvalid", "Invalid name. Use letters, numbers, dash, or underscore, starting with a letter.");
663
+ }
664
+ const normalizedSource = trimSvgPreamble(source.content);
665
+ if (!/^\s*<svg\b/i.test(normalizedSource)) {
666
+ throw new BakeError("ValidateSvgRootInvalid", "Input must start with an <svg> root element.");
667
+ }
668
+ const optimized = optimizeSvg(normalizedSource, svgoConfig);
669
+ try {
670
+ return bakeSymbol(optimized, source.name, options.idPolicy);
671
+ } catch (cause) {
672
+ if (cause instanceof BakeError) {
673
+ throw cause;
674
+ }
675
+ if (cause instanceof Error && cause.message === "Cannot determine viewBox. Provide an SVG with viewBox or width/height attributes.") {
676
+ throw new BakeError("ResolveViewBoxFailed", cause.message);
677
+ }
678
+ if (cause instanceof Error && cause.message === "Input must start with an <svg> root element.") {
679
+ throw new BakeError("ValidateSvgRootInvalid", cause.message);
680
+ }
681
+ throw new BakeError("ParseSvgFailed", "SVG parsing failed during id rewrite.", { cause });
682
+ }
683
+ }
684
+ function optimizeSvg(content, svgoConfig) {
685
+ let result;
686
+ try {
687
+ result = optimize(content, svgoConfig);
688
+ } catch (cause) {
689
+ throw new BakeError("OptimizeSvgFailed", "SVGO optimization failed.", { cause });
690
+ }
691
+ return result.data;
692
+ }
693
+ function trimSvgPreamble(content) {
694
+ return content.replace(/^(?:\uFEFF|\s|<\?xml[\s\S]*?\?>|<!--[\s\S]*?-->|<!DOCTYPE[\s\S]*?>)+/i, "");
695
+ }
696
+
697
+ export { BakeError, bakeIcon, bakeIcons, createBaker };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svg-icon-baker",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "A delightful toolkit for baking raw SVG icons into delicious SVG symbol sprites",
5
5
  "keywords": [
6
6
  "svg",
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/yangxu52/svg-icon-baker.git"
20
+ "url": "git+https://github.com/yangxu52/vite-plugin-svg-icons-ng.git",
21
+ "directory": "packages/svg-icon-baker"
21
22
  },
22
23
  "license": "MIT",
23
24
  "author": "yangxu52",
@@ -36,21 +37,26 @@
36
37
  "dist"
37
38
  ],
38
39
  "dependencies": {
40
+ "css-tree": "^3.2.1",
41
+ "fast-xml-builder": "^1.1.5",
42
+ "fast-xml-parser": "^5.7.2",
39
43
  "svgo": "^4.0.1"
40
44
  },
41
45
  "devDependencies": {
42
46
  "@vitest/coverage-v8": "^4.1.0",
47
+ "rimraf": "^6.1.3",
43
48
  "unbuild": "^3.6.1",
44
49
  "vitest": "^4.1.0"
45
50
  },
46
51
  "engines": {
47
- "node": "^20.0.0 || ^22.0.0 || >=24.0.0",
48
- "pnpm": ">=10.0.0"
52
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
49
53
  },
50
54
  "scripts": {
51
- "build": "unbuild",
52
55
  "dev": "unbuild --stub",
53
- "test": "vitest run --coverage",
54
- "test:watch": "vitest"
56
+ "build": "unbuild",
57
+ "test": "vitest run",
58
+ "coverage": "vitest run --coverage",
59
+ "clean": "rimraf dist",
60
+ "release": "commit-and-tag-version --path . -t svg-icon-baker@"
55
61
  }
56
62
  }