svgfusion 1.0.0 → 1.0.2

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,33 +1,33 @@
1
1
  <div align="center">
2
- <img src="./logo.png" alt="SVGFusion Logo" width="120" height="120">
3
-
4
- # SVGFusion
5
-
6
- **Transform SVG files into production-ready React and Vue 3 components**
7
-
8
- A powerful CLI tool and library that converts SVG files into optimized React and Vue components with built-in TypeScript support, smart naming conventions, and flexible configuration options.
9
-
10
- [![npm version](https://badge.fury.io/js/svgfusion.svg)](https://badge.fury.io/js/svgfusion)
11
- [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
12
- [![React](https://img.shields.io/badge/React-20232A?style=flat&logo=react&logoColor=61DAFB)](https://reactjs.org/)
13
- [![Vue.js](https://img.shields.io/badge/Vue.js-35495E?style=flat&logo=vue.js&logoColor=4FC08D)](https://vuejs.org/)
14
-
15
- [📚 Documentation](https://svgfusion.netlify.app) • [🎮 Playground](https://svgfusion.netlify.app/playground) • [📦 NPM](https://www.npmjs.com/package/svgfusion)
16
-
2
+ <img src="https://i.ibb.co/TZFfpFL/logo.png" alt="SVGFusion Logo" width="120" height="120">
3
+
4
+ # SVGFusion
5
+
6
+ **Transform SVG files into production-ready React and Vue 3 components**
7
+
8
+ A powerful Node.js CLI tool and library that converts SVG files into optimized React and Vue components with complex SVG support, TypeScript integration, and smart optimization for modern development workflows.
9
+
10
+ [![npm version](https://badge.fury.io/js/svgfusion.svg)](https://badge.fury.io/js/svgfusion)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
12
+ [![React](https://img.shields.io/badge/React-20232A?style=flat&logo=react&logoColor=61DAFB)](https://reactjs.org/)
13
+ [![Vue.js](https://img.shields.io/badge/Vue.js-35495E?style=flat&logo=vue.js&logoColor=4FC08D)](https://vuejs.org/)
14
+
15
+ [📚 Documentation](https://svgfusion.netlify.app) • [ CLI Reference](https://svgfusion.netlify.app/docs/cli-usage) • [📦 NPM](https://www.npmjs.com/package/svgfusion)
16
+
17
17
  </div>
18
18
 
19
19
  ## Features
20
20
 
21
21
  - **Dual Framework Support**: Generate both React and Vue 3 components from the same SVG
22
+ - **Complex SVG Support**: Handles gradients, masks, filters, patterns, and Figma exports
23
+ - **ID Collision Prevention**: Automatic unique ID generation for complex SVGs
22
24
  - **Optimized Output**: Built-in SVGO optimization with customizable settings
23
- - **Multi-Color Support**: Preserves gradients, patterns, and complex color schemes
24
- - **Class-Based Styling**: Supports CSS classes, Tailwind, and framework-specific styling
25
+ - **Icon Builder Ready**: Perfect for design systems and icon libraries
25
26
  - **TypeScript Ready**: Full TypeScript support with proper type definitions
26
27
  - **Flexible API**: Both CLI and programmatic usage
27
28
  - **Batch Processing**: Convert entire directories of SVG files
28
- - **Complex Filenames**: Handles design system metadata and special characters
29
+ - **Production Ready**: Robust output with proper error handling
29
30
  - **Zero Configuration**: Works out of the box with sensible defaults
30
- - **Production Ready**: Optimized output with proper TypeScript types
31
31
 
32
32
  ## Quick Start
33
33
 
@@ -343,7 +343,3 @@ Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for
343
343
  ## Changelog
344
344
 
345
345
  See [CHANGELOG.md](./CHANGELOG.md) for details.
346
-
347
- ---
348
-
349
- **Made with care for the developer community**
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
 
2
- export { }
2
+ export { }
package/dist/cli.js CHANGED
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
9
16
  var __copyProps = (to, from, except, desc) => {
10
17
  if (from && typeof from === "object" || typeof from === "function") {
11
18
  for (let key of __getOwnPropNames(from))
@@ -22,18 +29,29 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
29
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
30
  mod
24
31
  ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
33
 
26
- // node_modules/tsup/assets/cjs_shims.js
27
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
28
- var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
29
-
30
- // src/cli.ts
31
- var import_commander = require("commander");
34
+ // node_modules/.pnpm/tsup@8.5.0_typescript@5.8.3/node_modules/tsup/assets/cjs_shims.js
35
+ var getImportMetaUrl, importMetaUrl;
36
+ var init_cjs_shims = __esm({
37
+ "node_modules/.pnpm/tsup@8.5.0_typescript@5.8.3/node_modules/tsup/assets/cjs_shims.js"() {
38
+ "use strict";
39
+ getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
40
+ importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
+ }
42
+ });
32
43
 
33
44
  // src/utils/files.ts
34
- var import_promises = require("fs/promises");
35
- var import_path = require("path");
36
- var import_fs = require("fs");
45
+ var files_exports = {};
46
+ __export(files_exports, {
47
+ ensureDirectoryExists: () => ensureDirectoryExists,
48
+ getComponentFilename: () => getComponentFilename,
49
+ getFileExtension: () => getFileExtension,
50
+ readSvgDirectory: () => readSvgDirectory,
51
+ readSvgFile: () => readSvgFile,
52
+ writeComponentFile: () => writeComponentFile,
53
+ writeSvgFile: () => writeSvgFile
54
+ });
37
55
  async function readSvgFile(filePath) {
38
56
  try {
39
57
  const content = await (0, import_promises.readFile)(filePath, "utf-8");
@@ -42,6 +60,14 @@ async function readSvgFile(filePath) {
42
60
  throw new Error(`Failed to read SVG file: ${filePath}. ${error}`);
43
61
  }
44
62
  }
63
+ async function writeSvgFile(filePath, content) {
64
+ try {
65
+ await ensureDirectoryExists((0, import_path.dirname)(filePath));
66
+ await (0, import_promises.writeFile)(filePath, content, "utf-8");
67
+ } catch (error) {
68
+ throw new Error(`Failed to write SVG file: ${filePath}. ${error}`);
69
+ }
70
+ }
45
71
  async function writeComponentFile(filePath, content) {
46
72
  try {
47
73
  await ensureDirectoryExists((0, import_path.dirname)(filePath));
@@ -84,11 +110,31 @@ function getFileExtension(framework, typescript = true) {
84
110
  function getComponentFilename(_svgFilename, componentName, extension) {
85
111
  return `${componentName}${extension}`;
86
112
  }
113
+ var import_promises, import_path, import_fs;
114
+ var init_files = __esm({
115
+ "src/utils/files.ts"() {
116
+ "use strict";
117
+ init_cjs_shims();
118
+ import_promises = require("fs/promises");
119
+ import_path = require("path");
120
+ import_fs = require("fs");
121
+ }
122
+ });
123
+
124
+ // src/cli.ts
125
+ init_cjs_shims();
126
+ var import_commander = require("commander");
127
+ init_files();
87
128
 
88
- // src/converters/react.ts
129
+ // src/core/react-converter.ts
130
+ init_cjs_shims();
89
131
  var import_core = require("@svgr/core");
90
132
 
133
+ // src/core/converter.ts
134
+ init_cjs_shims();
135
+
91
136
  // src/utils/svgo.ts
137
+ init_cjs_shims();
92
138
  var import_svgo = require("svgo");
93
139
  var defaultConfig = {
94
140
  plugins: [
@@ -125,6 +171,7 @@ function optimizeSvg(svgContent, config = defaultConfig) {
125
171
  }
126
172
 
127
173
  // src/utils/name.ts
174
+ init_cjs_shims();
128
175
  function pascalCase(str) {
129
176
  return str.replace(/[^a-zA-Z0-9\s-_]/g, " ").split(/[\s-_]+/).filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
130
177
  }
@@ -135,186 +182,328 @@ function formatComponentName(name, prefix, suffix) {
135
182
  return `${prefixPart}${baseName}${suffixPart}`;
136
183
  }
137
184
 
138
- // src/converters/react.ts
139
- async function convertToReact(svgContent, options = {}) {
185
+ // src/core/converter.ts
186
+ var BaseConverter = class {
187
+ /**
188
+ * Process SVG content with optimization
189
+ */
190
+ async processSvg(svgContent, options) {
191
+ const { optimize: optimize2 = true } = options;
192
+ if (optimize2) {
193
+ return optimizeSvg(svgContent);
194
+ }
195
+ return svgContent;
196
+ }
197
+ /**
198
+ * Generate component name from options
199
+ */
200
+ generateComponentName(options) {
201
+ const { name, prefix, suffix } = options;
202
+ const baseName = name || "Icon";
203
+ return formatComponentName(baseName, prefix, suffix);
204
+ }
205
+ /**
206
+ * Generate filename for component
207
+ */
208
+ generateFilename(componentName, framework, typescript = true) {
209
+ try {
210
+ const {
211
+ getFileExtension: getFileExtension2,
212
+ getComponentFilename: getComponentFilename2
213
+ } = (init_files(), __toCommonJS(files_exports));
214
+ const extension = getFileExtension2(framework, typescript);
215
+ return getComponentFilename2("icon.svg", componentName, extension);
216
+ } catch {
217
+ const extensions = {
218
+ react: typescript ? ".tsx" : ".jsx",
219
+ vue: ".vue"
220
+ };
221
+ return `${componentName}${extensions[framework]}`;
222
+ }
223
+ }
224
+ };
225
+
226
+ // src/core/processors/svgr-processor.ts
227
+ init_cjs_shims();
228
+ function createSvgrConfig(options) {
140
229
  const {
141
- name,
142
- prefix,
143
- suffix,
144
- optimize: optimize2 = true,
145
230
  typescript = true,
146
231
  memo = true,
147
232
  ref = true,
148
233
  titleProp = true,
149
- descProp = true
234
+ descProp = true,
235
+ icon = true,
236
+ dimensions = false,
237
+ replaceAttrValues = { "#000": "currentColor", "#000000": "currentColor" },
238
+ svgProps = {},
239
+ expandProps = false,
240
+ nativeProps = true,
241
+ ariaLabelledBy = false,
242
+ ariaHidden = false,
243
+ role = "img"
150
244
  } = options;
151
- try {
152
- let processedSvg = svgContent;
153
- if (optimize2) {
154
- processedSvg = optimizeSvg(svgContent);
155
- }
156
- const baseName = name || "icon";
157
- const componentName = formatComponentName(baseName, prefix, suffix);
158
- const svgrOptions = {
159
- typescript,
160
- memo,
161
- ref,
162
- titleProp,
163
- descProp,
164
- svgProps: {
165
- className: "{className}"
166
- }
167
- };
168
- let result = await (0, import_core.transform)(processedSvg, svgrOptions, {
169
- componentName
170
- });
171
- const typeImports = typescript ? `import { SVGProps } from 'react';
172
- ` : "";
173
- const memoImport = memo ? `import { memo } from 'react';
174
- ` : "";
175
- const refImport = ref ? `import { forwardRef } from 'react';
176
- ` : "";
177
- const exports = `
178
- export default ${componentName};
179
- export { ${componentName} };`;
180
- const propsType = typescript ? `SVGProps<SVGSVGElement> & { className?: string; }` : "";
181
- const componentProps = typescript ? `props: ${propsType}` : "props";
182
- const componentFunc = memo ? `memo(` : "";
183
- const refWrapper = ref ? `forwardRef<SVGSVGElement, ${propsType}>(` : "";
184
- const closingWrappers = `${ref ? ")" : ""}${memo ? ")" : ""}`;
185
- result = `${typeImports}${memoImport}${refImport}
186
- const ${componentName} = ${componentFunc}${refWrapper}(${componentProps}) => {
187
- return ${result};
188
- }${closingWrappers};
245
+ const config = {
246
+ typescript,
247
+ memo,
248
+ ref,
249
+ titleProp,
250
+ descProp,
251
+ icon,
252
+ dimensions,
253
+ expandProps,
254
+ svgProps: {
255
+ className: "{className}",
256
+ ...ref && { ref: "{ref}" },
257
+ ...nativeProps && {
258
+ width: "{width}",
259
+ height: "{height}",
260
+ style: "{style}"
261
+ },
262
+ // Accessibility attributes
263
+ ...ariaLabelledBy && titleProp && descProp && {
264
+ "aria-labelledby": "{titleId} {descId}"
265
+ },
266
+ ...ariaHidden && { "aria-hidden": "true" },
267
+ role,
268
+ ...svgProps
269
+ },
270
+ replaceAttrValues,
271
+ plugins: ["@svgr/plugin-svgo", "@svgr/plugin-jsx", "@svgr/plugin-prettier"]
272
+ };
273
+ config.svgoConfig = {
274
+ plugins: [
275
+ {
276
+ name: "preset-default",
277
+ params: {
278
+ overrides: {
279
+ removeViewBox: false,
280
+ removeTitle: titleProp ? false : true,
281
+ removeDesc: descProp ? false : true,
282
+ removeUselessStrokeAndFill: false,
283
+ removeUnusedNS: false,
284
+ // Preserve complex features
285
+ removeUselessDefs: false,
286
+ convertShapeToPath: false,
287
+ mergePaths: false,
288
+ convertColors: false
289
+ // Disable color conversion to preserve gradients
290
+ }
291
+ }
292
+ },
293
+ ...icon && !dimensions ? [{ name: "removeAttrs", params: { attrs: ["width", "height"] } }] : [],
294
+ ...icon ? ["cleanupNumericValues"] : []
295
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
296
+ ]
297
+ // Type assertion to handle SVGO v3 plugin configuration complexity
298
+ };
299
+ return config;
300
+ }
301
+ function postProcessReactComponent(svgrOutput, componentName, _options) {
302
+ let processedCode = svgrOutput;
303
+ if (hasComplexFeatures(processedCode)) {
304
+ processedCode = addUniqueIds(processedCode, componentName);
305
+ }
306
+ return processedCode;
307
+ }
308
+ function hasComplexFeatures(code) {
309
+ const complexFeatures = [
310
+ "linearGradient",
311
+ "radialGradient",
312
+ "pattern",
313
+ "mask",
314
+ "filter",
315
+ "clipPath",
316
+ "marker",
317
+ "symbol",
318
+ "use"
319
+ ];
320
+ return complexFeatures.some(
321
+ (feature) => code.includes(`<${feature}`) || code.includes(`</${feature}`)
322
+ );
323
+ }
324
+ function addUniqueIds(code, componentName) {
325
+ const uniquePrefix = `${componentName.toLowerCase()}_`;
326
+ code = code.replace(/id="([^"]+)"/g, `id="${uniquePrefix}$1"`);
327
+ code = code.replace(/url\(#([^)]+)\)/g, `url(#${uniquePrefix}$1)`);
328
+ code = code.replace(/href="#([^"]+)"/g, `href="#${uniquePrefix}$1"`);
329
+ return code;
330
+ }
189
331
 
190
- ${componentName}.displayName = "${componentName}";
191
- ${exports}`;
192
- const extension = getFileExtension("react", typescript);
193
- const filename = getComponentFilename("icon.svg", componentName, extension);
194
- return {
195
- code: result,
196
- filename,
197
- componentName
198
- };
199
- } catch (error) {
200
- throw new Error(`Failed to convert SVG to React: ${error}`);
332
+ // src/core/react-converter.ts
333
+ var ReactConverter = class extends BaseConverter {
334
+ /**
335
+ * Convert SVG to React component using SVGR
336
+ */
337
+ async convert(svgContent, options = {}) {
338
+ try {
339
+ const componentName = this.generateComponentName(options);
340
+ let processedSvg = await this.processSvg(svgContent, options);
341
+ if (hasComplexFeatures(processedSvg)) {
342
+ processedSvg = addUniqueIds(processedSvg, componentName);
343
+ }
344
+ const svgrConfig = createSvgrConfig(options);
345
+ const svgrResult = await (0, import_core.transform)(processedSvg, svgrConfig, {
346
+ componentName
347
+ });
348
+ const code = postProcessReactComponent(
349
+ svgrResult,
350
+ componentName,
351
+ options
352
+ );
353
+ const filename = this.generateFilename(
354
+ componentName,
355
+ "react",
356
+ options.typescript ?? true
357
+ );
358
+ return {
359
+ code,
360
+ filename,
361
+ componentName
362
+ };
363
+ } catch (error) {
364
+ throw new Error(`Failed to convert SVG to React: ${error}`);
365
+ }
201
366
  }
367
+ };
368
+ async function convertToReact(svgContent, options = {}) {
369
+ const converter = new ReactConverter();
370
+ return converter.convert(svgContent, options);
202
371
  }
203
372
 
204
- // src/converters/vue.ts
205
- function convertToVue(svgContent, options = {}) {
373
+ // src/core/vue-converter.ts
374
+ init_cjs_shims();
375
+
376
+ // src/core/processors/vue-processor.ts
377
+ init_cjs_shims();
378
+ function generateVueComponent(svgContent, options) {
206
379
  const {
207
380
  name,
208
381
  prefix,
209
382
  suffix,
210
- optimize: optimize2 = true,
211
- typescript = true,
212
- compositionApi: _compositionApi = true,
213
- // eslint-disable-line @typescript-eslint/no-unused-vars
214
- scriptSetup = true
383
+ props = true,
384
+ replaceAttrValues = { "#000": "currentColor", "#000000": "currentColor" }
215
385
  } = options;
216
- try {
217
- let processedSvg = svgContent;
218
- if (optimize2) {
219
- processedSvg = optimizeSvg(svgContent);
220
- }
221
- const baseName = name || "icon";
222
- const componentName = formatComponentName(baseName, prefix, suffix);
223
- const cleanedSvg = processedSvg.replace(/<\?xml[^>]*\?>\s*/, "").replace(/xmlns="[^"]*"/g, "").replace(/width="[^"]*"/g, "").replace(/height="[^"]*"/g, "").replace(/<svg/, '<svg v-bind="$attrs"').replace(/class="([^"]*)"/g, 'class="$1"').replace(/currentColor/g, "currentColor");
224
- const scriptTag = scriptSetup ? generateScriptSetup(typescript, componentName) : generateCompositionScript(componentName, typescript);
225
- const template = `<template>
226
- ${cleanedSvg}
227
- </template>`;
228
- const style = `<style scoped>
229
- /* Add component-specific styles here */
230
- </style>`;
231
- const vueComponent = `${scriptTag}
232
-
233
- ${template}
234
-
235
- ${style}`;
236
- const extension = getFileExtension("vue", typescript);
237
- const filename = getComponentFilename("icon.svg", componentName, extension);
238
- return {
239
- code: vueComponent,
240
- filename,
241
- componentName
242
- };
243
- } catch (error) {
244
- throw new Error(`Failed to convert SVG to Vue: ${error}`);
386
+ const baseName = name || "Icon";
387
+ const componentName = formatComponentName(baseName, prefix, suffix);
388
+ let processedSvg = cleanSvgForVue(svgContent);
389
+ processedSvg = applyAttributeReplacements(processedSvg, replaceAttrValues);
390
+ if (hasComplexFeatures2(processedSvg)) {
391
+ processedSvg = addUniqueIds2(processedSvg, componentName);
245
392
  }
393
+ processedSvg = addVueAttributes(processedSvg, props);
394
+ const code = generateVueComponentCode(processedSvg, componentName, options);
395
+ return { code, componentName };
246
396
  }
247
- function generateScriptSetup(typescript, componentName) {
248
- const lang = typescript ? ' lang="ts"' : "";
249
- const propsType = typescript ? `
250
- interface Props {
251
- class?: string;
252
- style?: string | Record<string, any>;
253
- }` : "";
254
- const propsDefinition = typescript ? `
255
- const props = withDefaults(defineProps<Props>(), {
256
- class: '',
257
- style: undefined,
258
- });` : `
259
- const props = withDefaults(defineProps(), {
260
- class: '',
261
- style: undefined,
262
- });`;
263
- return `<script setup${lang}>${propsType}${propsDefinition}
264
-
265
- // Component name for debugging
266
- const __name = '${componentName}';
267
- </script>`;
397
+ function cleanSvgForVue(svgContent) {
398
+ return svgContent.replace(/<\?xml[^>]*\?>\s*/, "").replace(/<!--[\s\S]*?-->/g, "").replace(/xmlns="[^"]*"/g, "").trim();
399
+ }
400
+ function applyAttributeReplacements(svg, replacements) {
401
+ let processedSvg = svg;
402
+ for (const [from, to] of Object.entries(replacements)) {
403
+ const regex = new RegExp(from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
404
+ processedSvg = processedSvg.replace(regex, to);
405
+ }
406
+ return processedSvg;
407
+ }
408
+ function hasComplexFeatures2(svg) {
409
+ const complexFeatures = [
410
+ "linearGradient",
411
+ "radialGradient",
412
+ "pattern",
413
+ "mask",
414
+ "filter",
415
+ "clipPath",
416
+ "marker",
417
+ "symbol",
418
+ "use"
419
+ ];
420
+ return complexFeatures.some(
421
+ (feature) => svg.includes(`<${feature}`) || svg.includes(`</${feature}`)
422
+ );
268
423
  }
269
- function generateCompositionScript(componentName, typescript) {
270
- const lang = typescript ? ' lang="ts"' : "";
271
- const propsType = typescript ? `
424
+ function addUniqueIds2(svg, componentName) {
425
+ const uniquePrefix = `${componentName.toLowerCase()}_`;
426
+ svg = svg.replace(/id="([^"]+)"/g, `id="${uniquePrefix}$1"`);
427
+ svg = svg.replace(/url\(#([^)]+)\)/g, `url(#${uniquePrefix}$1)`);
428
+ svg = svg.replace(/href="#([^"]+)"/g, `href="#${uniquePrefix}$1"`);
429
+ return svg;
430
+ }
431
+ function addVueAttributes(svg, enableProps) {
432
+ if (!enableProps) return svg;
433
+ return svg.replace(
434
+ "<svg",
435
+ '<svg :class="className" :style="style" v-bind="$attrs"'
436
+ );
437
+ }
438
+ function generateVueComponentCode(svg, componentName, options) {
439
+ const { typescript, compositionApi, props } = options;
440
+ const scriptLang = typescript ? ' lang="ts"' : "";
441
+ const scriptSetup = compositionApi ? " setup" : "";
442
+ let script = `<script${scriptLang}${scriptSetup}>`;
443
+ if (compositionApi) {
444
+ if (props) {
445
+ script += `
272
446
  interface Props {
273
- class?: string;
274
- style?: string | Record<string, any>;
275
- }` : "";
276
- const exportStatement = typescript ? `
277
- const ${componentName} = defineComponent({
278
- name: '${componentName}',
279
- props: {
280
- class: {
281
- type: String,
282
- default: '',
283
- },
284
- style: {
285
- type: [String, Object],
286
- default: undefined,
287
- },
288
- },
289
- setup(props: Props) {
290
- return {};
291
- },
292
- });
447
+ className?: string;
448
+ style?: Record<string, any>;
449
+ }
293
450
 
294
- export default ${componentName};
295
- export { ${componentName} };` : `
296
- const ${componentName} = defineComponent({
451
+ defineProps<Props>();
452
+ `;
453
+ }
454
+ } else {
455
+ script += `
456
+ export default {
297
457
  name: '${componentName}',
298
458
  props: {
299
- class: {
300
- type: String,
301
- default: '',
302
- },
303
- style: {
304
- type: [String, Object],
305
- default: undefined,
306
- },
459
+ className: String,
460
+ style: Object,
307
461
  },
308
- setup(props) {
309
- return {};
310
- },
311
- });
462
+ };
463
+ `;
464
+ }
465
+ script += `</script>`;
466
+ const template = `
467
+ <template>
468
+ ${svg}
469
+ </template>`;
470
+ const style = `
471
+ <style scoped>
472
+ /* Component styles */
473
+ </style>`;
474
+ return [script, template, style].filter(Boolean).join("\n");
475
+ }
312
476
 
313
- export default ${componentName};
314
- export { ${componentName} };`;
315
- return `<script${lang}>
316
- import { defineComponent } from 'vue';${propsType}${exportStatement}
317
- </script>`;
477
+ // src/core/vue-converter.ts
478
+ var VueConverter = class extends BaseConverter {
479
+ /**
480
+ * Convert SVG to Vue component
481
+ */
482
+ async convert(svgContent, options = {}) {
483
+ try {
484
+ const processedSvg = await this.processSvg(svgContent, options);
485
+ const { code, componentName } = generateVueComponent(
486
+ processedSvg,
487
+ options
488
+ );
489
+ const filename = this.generateFilename(
490
+ componentName,
491
+ "vue",
492
+ options.typescript ?? true
493
+ );
494
+ return {
495
+ code,
496
+ filename,
497
+ componentName
498
+ };
499
+ } catch (error) {
500
+ throw new Error(`Failed to convert SVG to Vue: ${error}`);
501
+ }
502
+ }
503
+ };
504
+ async function convertToVue(svgContent, options = {}) {
505
+ const converter = new VueConverter();
506
+ return converter.convert(svgContent, options);
318
507
  }
319
508
 
320
509
  // src/cli.ts
@@ -382,7 +571,7 @@ program.command("convert").description("Convert SVG files to React or Vue compon
382
571
  for (const filePath of svgFiles) {
383
572
  const svgContent = await readSvgFile(filePath);
384
573
  const optimizedSvg = optimize2 ? optimizeSvg(svgContent) : svgContent;
385
- const result = framework === "react" ? await convertToReact(optimizedSvg, { typescript }) : convertToVue(optimizedSvg, { typescript });
574
+ const result = framework === "react" ? await convertToReact(optimizedSvg, { typescript }) : await convertToVue(optimizedSvg, { typescript });
386
575
  const outputPath = (0, import_path2.join)(output, result.filename);
387
576
  await writeComponentFile(outputPath, result.code);
388
577
  }
package/dist/index.d.mts CHANGED
@@ -13,10 +13,22 @@ interface ReactConversionOptions extends ConversionOptions {
13
13
  ref?: boolean;
14
14
  titleProp?: boolean;
15
15
  descProp?: boolean;
16
+ icon?: boolean;
17
+ dimensions?: boolean;
18
+ replaceAttrValues?: Record<string, string>;
19
+ svgProps?: Record<string, string>;
20
+ expandProps?: boolean | 'start' | 'end';
21
+ nativeProps?: boolean;
22
+ ariaLabelledBy?: boolean;
23
+ ariaHidden?: boolean;
24
+ role?: 'img' | 'graphics-document' | 'graphics-symbol' | 'presentation';
16
25
  }
17
26
  interface VueConversionOptions extends ConversionOptions {
18
27
  compositionApi?: boolean;
19
28
  scriptSetup?: boolean;
29
+ props?: boolean;
30
+ dimensions?: boolean;
31
+ replaceAttrValues?: Record<string, string>;
20
32
  }
21
33
  interface ConversionResult {
22
34
  code: string;
@@ -60,19 +72,13 @@ interface CliOptions {
60
72
 
61
73
  /**
62
74
  * Convert SVG to React component
63
- * @param svgContent - SVG content string
64
- * @param options - Conversion options
65
- * @returns Conversion result with React component code
66
75
  */
67
76
  declare function convertToReact(svgContent: string, options?: ReactConversionOptions): Promise<ConversionResult>;
68
77
 
69
78
  /**
70
- * Convert SVG to Vue 3 component
71
- * @param svgContent - SVG content string
72
- * @param options - Conversion options
73
- * @returns Conversion result with Vue component code
79
+ * Convert SVG to Vue component
74
80
  */
75
- declare function convertToVue(svgContent: string, options?: VueConversionOptions): ConversionResult;
81
+ declare function convertToVue(svgContent: string, options?: VueConversionOptions): Promise<ConversionResult>;
76
82
 
77
83
  /**
78
84
  * Optimize SVG content using SVGO
@@ -149,4 +155,4 @@ declare function pascalCase(str: string): string;
149
155
  */
150
156
  declare function formatComponentName(name: string, prefix?: string, suffix?: string): string;
151
157
 
152
- export { BatchConversionOptions, BatchConversionResult, CliOptions, ConversionError, ConversionOptions, ConversionResult, Framework, ReactConversionOptions, VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, pascalCase, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
158
+ export { type BatchConversionOptions, type BatchConversionResult, type CliOptions, type ConversionError, type ConversionOptions, type ConversionResult, type Framework, type ReactConversionOptions, type VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, pascalCase, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
package/dist/index.d.ts CHANGED
@@ -13,10 +13,22 @@ interface ReactConversionOptions extends ConversionOptions {
13
13
  ref?: boolean;
14
14
  titleProp?: boolean;
15
15
  descProp?: boolean;
16
+ icon?: boolean;
17
+ dimensions?: boolean;
18
+ replaceAttrValues?: Record<string, string>;
19
+ svgProps?: Record<string, string>;
20
+ expandProps?: boolean | 'start' | 'end';
21
+ nativeProps?: boolean;
22
+ ariaLabelledBy?: boolean;
23
+ ariaHidden?: boolean;
24
+ role?: 'img' | 'graphics-document' | 'graphics-symbol' | 'presentation';
16
25
  }
17
26
  interface VueConversionOptions extends ConversionOptions {
18
27
  compositionApi?: boolean;
19
28
  scriptSetup?: boolean;
29
+ props?: boolean;
30
+ dimensions?: boolean;
31
+ replaceAttrValues?: Record<string, string>;
20
32
  }
21
33
  interface ConversionResult {
22
34
  code: string;
@@ -60,19 +72,13 @@ interface CliOptions {
60
72
 
61
73
  /**
62
74
  * Convert SVG to React component
63
- * @param svgContent - SVG content string
64
- * @param options - Conversion options
65
- * @returns Conversion result with React component code
66
75
  */
67
76
  declare function convertToReact(svgContent: string, options?: ReactConversionOptions): Promise<ConversionResult>;
68
77
 
69
78
  /**
70
- * Convert SVG to Vue 3 component
71
- * @param svgContent - SVG content string
72
- * @param options - Conversion options
73
- * @returns Conversion result with Vue component code
79
+ * Convert SVG to Vue component
74
80
  */
75
- declare function convertToVue(svgContent: string, options?: VueConversionOptions): ConversionResult;
81
+ declare function convertToVue(svgContent: string, options?: VueConversionOptions): Promise<ConversionResult>;
76
82
 
77
83
  /**
78
84
  * Optimize SVG content using SVGO
@@ -149,4 +155,4 @@ declare function pascalCase(str: string): string;
149
155
  */
150
156
  declare function formatComponentName(name: string, prefix?: string, suffix?: string): string;
151
157
 
152
- export { BatchConversionOptions, BatchConversionResult, CliOptions, ConversionError, ConversionOptions, ConversionResult, Framework, ReactConversionOptions, VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, pascalCase, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
158
+ export { type BatchConversionOptions, type BatchConversionResult, type CliOptions, type ConversionError, type ConversionOptions, type ConversionResult, type Framework, type ReactConversionOptions, type VueConversionOptions, convertToReact, convertToVue, createSvgoConfig, formatComponentName, optimizeSvg, pascalCase, readSvgDirectory, readSvgFile, sanitizeComponentName, svgToComponentName, writeComponentFile, writeSvgFile };
package/dist/index.js CHANGED
@@ -1,101 +1,23 @@
1
- 'use strict';
2
-
3
- var core = require('@svgr/core');
4
- var svgo = require('svgo');
5
- var promises = require('fs/promises');
6
- var path = require('path');
7
- var fs = require('fs');
8
-
9
- var j={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:!1,removeTitle:!1,removeDesc:!1,removeUselessStrokeAndFill:!1,convertColors:{currentColor:!0,names2hex:!0,rgb2hex:!0,shorthex:!0,shortname:!0}}}},"removeDimensions","cleanupNumericValues"]};function g(e,t=j){try{return svgo.optimize(e,t).data}catch(r){throw new Error(`Failed to optimize SVG: ${r}`)}}function B(e){let t=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?!1:{currentColor:!0,names2hex:!0,rgb2hex:!0,shorthex:!0,shortname:!0}}}},"cleanupNumericValues"];return e.removeDimensions!==!1&&t.push("removeDimensions"),{plugins:t}}function _(e){let t=e.replace(/\.svg$/i,"");return t=k(t),u(t)}function k(e){let t=e,r=t.match(/^([^,]+),\s*Type=([^,]+)/i);if(r)return t=`${r[1]} ${r[2]}`,t;let n=t.match(/Size=(\w+)/i),s=t.match(/Color=(\w+)/i),i=t.match(/Type=(\w+)/i);if(n||s||i){let o=[];if(i&&o.push(i[1]),s&&o.push(s[1]),n&&o.push(n[1]),o.length>0)return t=o.join(" "),t}return t=t.replace(/\b\w+=\w+\b/g,"").replace(/,\s*/g," ").replace(/[=]/g," ").replace(/\s+/g," ").trim(),(!t||t.length<2)&&(t=e),t}function M(e){return u(e.replace(/[^a-zA-Z0-9]/g," "))}function u(e){return e.replace(/[^a-zA-Z0-9\s-_]/g," ").split(/[\s-_]+/).filter(t=>t.length>0).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}function v(e,t,r){let n=t?u(t):"",s=r?u(r):"",i=u(e);return `${n}${i}${s}`}async function J(e){try{return await promises.readFile(e,"utf-8")}catch(t){throw new Error(`Failed to read SVG file: ${e}. ${t}`)}}async function K(e,t){try{await D(path.dirname(e)),await promises.writeFile(e,t,"utf-8");}catch(r){throw new Error(`Failed to write SVG file: ${e}. ${r}`)}}async function Q(e,t){try{await D(path.dirname(e)),await promises.writeFile(e,t,"utf-8");}catch(r){throw new Error(`Failed to write component file: ${e}. ${r}`)}}async function T(e,t=!1){try{let r=await promises.readdir(e),n=[];for(let s of r){let i=path.join(e,s),o=await promises.stat(i);if(o.isDirectory()&&t){let c=await T(i,t);n.push(...c);}else o.isFile()&&path.extname(s).toLowerCase()===".svg"&&n.push(i);}return n}catch(r){throw new Error(`Failed to read directory: ${e}. ${r}`)}}async function D(e){fs.existsSync(e)||await promises.mkdir(e,{recursive:!0});}function C(e,t=!0){return e==="react"?t?".tsx":".jsx":".vue"}function $(e,t,r){return `${t}${r}`}async function Y(e,t={}){let{name:r,prefix:n,suffix:s,optimize:i=!0,typescript:o=!0,memo:c=!0,ref:l=!0,titleProp:m=!0,descProp:h=!0}=t;try{let p=e;i&&(p=g(e));let a=v(r||"icon",n,s),d=await core.transform(p,{typescript:o,memo:c,ref:l,titleProp:m,descProp:h,svgProps:{className:"{className}"}},{componentName:a}),x=o?`import { SVGProps } from 'react';
10
- `:"",w=c?`import { memo } from 'react';
11
- `:"",y=l?`import { forwardRef } from 'react';
12
- `:"",N=`
13
- export default ${a};
14
- export { ${a} };`,b=o?"SVGProps<SVGSVGElement> & { className?: string; }":"",z=o?`props: ${b}`:"props",E=c?"memo(":"",O=l?`forwardRef<SVGSVGElement, ${b}>(`:"";d=`${x}${w}${y}
15
- const ${a} = ${E}${O}(${z}) => {
16
- return ${d};
17
- }${`${l?")":""}${c?")":""}`};
18
-
19
- ${a}.displayName = "${a}";
20
- ${N}`;let P=C("react",o),G=$("icon.svg",a,P);return {code:d,filename:G,componentName:a}}catch(p){throw new Error(`Failed to convert SVG to React: ${p}`)}}function ee(e,t={}){let{name:r,prefix:n,suffix:s,optimize:i=!0,typescript:o=!0,compositionApi:c=!0,scriptSetup:l=!0}=t;try{let m=e;i&&(m=g(e));let p=v(r||"icon",n,s),S=m.replace(/<\?xml[^>]*\?>\s*/,"").replace(/xmlns="[^"]*"/g,"").replace(/width="[^"]*"/g,"").replace(/height="[^"]*"/g,"").replace(/<svg/,'<svg v-bind="$attrs"').replace(/class="([^"]*)"/g,'class="$1"').replace(/currentColor/g,"currentColor"),a=l?te(o,p):re(p,o),F=`<template>
21
- ${S}
22
- </template>`,x=`${a}
23
-
24
- ${F}
25
-
26
- <style scoped>
27
- /* Add component-specific styles here */
28
- </style>`,w=C("vue",o),y=$("icon.svg",p,w);return {code:x,filename:y,componentName:p}}catch(m){throw new Error(`Failed to convert SVG to Vue: ${m}`)}}function te(e,t){return `<script setup${e?' lang="ts"':""}>${e?`
29
- interface Props {
30
- class?: string;
31
- style?: string | Record<string, any>;
32
- }`:""}${e?`
33
- const props = withDefaults(defineProps<Props>(), {
34
- class: '',
35
- style: undefined,
36
- });`:`
37
- const props = withDefaults(defineProps(), {
38
- class: '',
39
- style: undefined,
40
- });`}
41
-
42
- // Component name for debugging
43
- const __name = '${t}';
44
- </script>`}function re(e,t){let r=t?' lang="ts"':"",n=t?`
1
+ 'use strict';var promises=require('fs/promises'),path=require('path'),fs=require('fs'),core=require('@svgr/core'),svgo=require('svgo');var d=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var R=(e,r)=>()=>(e&&(r=e(e=0)),r);var _=(e,r)=>{for(var t in r)d(e,t,{get:r[t],enumerable:true});},q=(e,r,t,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of L(r))!k.call(e,n)&&n!==t&&d(e,n,{get:()=>r[n],enumerable:!(o=I(r,n))||o.enumerable});return e};var M=e=>q(d({},"__esModule",{value:true}),e);var a=R(()=>{});var T={};_(T,{ensureDirectoryExists:()=>h,getComponentFilename:()=>ie,getFileExtension:()=>se,readSvgDirectory:()=>x,readSvgFile:()=>O,writeComponentFile:()=>D,writeSvgFile:()=>P});async function O(e){try{return await promises.readFile(e,"utf-8")}catch(r){throw new Error(`Failed to read SVG file: ${e}. ${r}`)}}async function P(e,r){try{await h(path.dirname(e)),await promises.writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write SVG file: ${e}. ${t}`)}}async function D(e,r){try{await h(path.dirname(e)),await promises.writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write component file: ${e}. ${t}`)}}async function x(e,r=false){try{let t=await promises.readdir(e),o=[];for(let n of t){let s=path.join(e,n),i=await promises.stat(s);if(i.isDirectory()&&r){let p=await x(s,r);o.push(...p);}else i.isFile()&&path.extname(n).toLowerCase()===".svg"&&o.push(s);}return o}catch(t){throw new Error(`Failed to read directory: ${e}. ${t}`)}}async function h(e){fs.existsSync(e)||await promises.mkdir(e,{recursive:true});}function se(e,r=true){return e==="react"?r?".tsx":".jsx":".vue"}function ie(e,r,t){return `${r}${t}`}var w=R(()=>{a();});a();a();a();a();var H={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:false,removeDesc:false,removeUselessStrokeAndFill:false,convertColors:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"removeDimensions","cleanupNumericValues"]};function C(e,r=H){try{return svgo.optimize(e,r).data}catch(t){throw new Error(`Failed to optimize SVG: ${t}`)}}function J(e){let r=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?false:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"cleanupNumericValues"];return e.removeDimensions!==false&&r.push("removeDimensions"),{plugins:r}}a();function K(e){let r=e.replace(/\.svg$/i,"");return r=Q(r),u(r)}function Q(e){let r=e,t=r.match(/^([^,]+),\s*Type=([^,]+)/i);if(t)return r=`${t[1]} ${t[2]}`,r;let o=r.match(/Size=(\w+)/i),n=r.match(/Color=(\w+)/i),s=r.match(/Type=(\w+)/i);if(o||n||s){let i=[];if(s&&i.push(s[1]),n&&i.push(n[1]),o&&i.push(o[1]),i.length>0)return r=i.join(" "),r}return r=r.replace(/\b\w+=\w+\b/g,"").replace(/,\s*/g," ").replace(/[=]/g," ").replace(/\s+/g," ").trim(),(!r||r.length<2)&&(r=e),r}function W(e){return u(e.replace(/[^a-zA-Z0-9]/g," "))}function u(e){return e.replace(/[^a-zA-Z0-9\s-_]/g," ").split(/[\s-_]+/).filter(r=>r.length>0).map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join("")}function f(e,r,t){let o=r?u(r):"",n=t?u(t):"",s=u(e);return `${o}${s}${n}`}var g=class{async processSvg(r,t){let{optimize:o=true}=t;return o?C(r):r}generateComponentName(r){let{name:t,prefix:o,suffix:n}=r;return f(t||"Icon",o,n)}generateFilename(r,t,o=true){try{let{getFileExtension:n,getComponentFilename:s}=(w(),M(T)),i=n(t,o);return s("icon.svg",r,i)}catch{return `${r}${{react:o?".tsx":".jsx",vue:".vue"}[t]}`}}};a();function U(e){let{typescript:r=true,memo:t=true,ref:o=true,titleProp:n=true,descProp:s=true,icon:i=true,dimensions:p=false,replaceAttrValues:c={"#000":"currentColor","#000000":"currentColor"},svgProps:l={},expandProps:v=false,nativeProps:E=true,ariaLabelledBy:j=false,ariaHidden:B=false,role:G="img"}=e,b={typescript:r,memo:t,ref:o,titleProp:n,descProp:s,icon:i,dimensions:p,expandProps:v,svgProps:{className:"{className}",...o&&{ref:"{ref}"},...E&&{width:"{width}",height:"{height}",style:"{style}"},...j&&n&&s&&{"aria-labelledby":"{titleId} {descId}"},...B&&{"aria-hidden":"true"},role:G,...l},replaceAttrValues:c,plugins:["@svgr/plugin-svgo","@svgr/plugin-jsx","@svgr/plugin-prettier"]};return b.svgoConfig={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:!n,removeDesc:!s,removeUselessStrokeAndFill:false,removeUnusedNS:false,removeUselessDefs:false,convertShapeToPath:false,mergePaths:false,convertColors:false}}},...i&&!p?[{name:"removeAttrs",params:{attrs:["width","height"]}}]:[],...i?["cleanupNumericValues"]:[]]},b}function A(e,r,t){let o=e;return y(o)&&(o=$(o,r)),o}function y(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function $(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}var S=class extends g{async convert(r,t={}){try{let o=this.generateComponentName(t),n=await this.processSvg(r,t);y(n)&&(n=$(n,o));let s=U(t),i=await core.transform(n,s,{componentName:o}),p=A(i,o,t),c=this.generateFilename(o,"react",t.typescript??!0);return {code:p,filename:c,componentName:o}}catch(o){throw new Error(`Failed to convert SVG to React: ${o}`)}}};async function ce(e,r={}){return new S().convert(e,r)}a();a();function z(e,r){let{name:t,prefix:o,suffix:n,props:s=true,replaceAttrValues:i={"#000":"currentColor","#000000":"currentColor"}}=r,c=f(t||"Icon",o,n),l=le(e);return l=pe(l,i),me(l)&&(l=ue(l,c)),l=ge(l,s),{code:fe(l,c,r),componentName:c}}function le(e){return e.replace(/<\?xml[^>]*\?>\s*/,"").replace(/<!--[\s\S]*?-->/g,"").replace(/xmlns="[^"]*"/g,"").trim()}function pe(e,r){let t=e;for(let[o,n]of Object.entries(r)){let s=new RegExp(o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");t=t.replace(s,n);}return t}function me(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function ue(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}function ge(e,r){return r?e.replace("<svg",'<svg :class="className" :style="style" v-bind="$attrs"'):e}function fe(e,r,t){let{typescript:o,compositionApi:n,props:s}=t,c=`<script${o?' lang="ts"':""}${n?" setup":""}>`;n?s&&(c+=`
45
2
  interface Props {
46
- class?: string;
47
- style?: string | Record<string, any>;
48
- }`:"",s=t?`
49
- const ${e} = defineComponent({
50
- name: '${e}',
3
+ className?: string;
4
+ style?: Record<string, any>;
5
+ }
6
+
7
+ defineProps<Props>();
8
+ `):c+=`
9
+ export default {
10
+ name: '${r}',
51
11
  props: {
52
- class: {
53
- type: String,
54
- default: '',
55
- },
56
- style: {
57
- type: [String, Object],
58
- default: undefined,
59
- },
12
+ className: String,
13
+ style: Object,
60
14
  },
61
- setup(props: Props) {
62
- return {};
63
- },
64
- });
65
-
66
- export default ${e};
67
- export { ${e} };`:`
68
- const ${e} = defineComponent({
69
- name: '${e}',
70
- props: {
71
- class: {
72
- type: String,
73
- default: '',
74
- },
75
- style: {
76
- type: [String, Object],
77
- default: undefined,
78
- },
79
- },
80
- setup(props) {
81
- return {};
82
- },
83
- });
84
-
85
- export default ${e};
86
- export { ${e} };`;return `<script${r}>
87
- import { defineComponent } from 'vue';${n}${s}
88
- </script>`}
89
-
90
- exports.convertToReact = Y;
91
- exports.convertToVue = ee;
92
- exports.createSvgoConfig = B;
93
- exports.formatComponentName = v;
94
- exports.optimizeSvg = g;
95
- exports.pascalCase = u;
96
- exports.readSvgDirectory = T;
97
- exports.readSvgFile = J;
98
- exports.sanitizeComponentName = M;
99
- exports.svgToComponentName = _;
100
- exports.writeComponentFile = Q;
101
- exports.writeSvgFile = K;
15
+ };
16
+ `,c+="</script>";let l=`
17
+ <template>
18
+ ${e}
19
+ </template>`;return [c,l,`
20
+ <style scoped>
21
+ /* Component styles */
22
+ </style>`].filter(Boolean).join(`
23
+ `)}var F=class extends g{async convert(r,t={}){try{let o=await this.processSvg(r,t),{code:n,componentName:s}=z(o,t),i=this.generateFilename(s,"vue",t.typescript??!0);return {code:n,filename:i,componentName:s}}catch(o){throw new Error(`Failed to convert SVG to Vue: ${o}`)}}};async function ve(e,r={}){return new F().convert(e,r)}w();exports.convertToReact=ce;exports.convertToVue=ve;exports.createSvgoConfig=J;exports.formatComponentName=f;exports.optimizeSvg=C;exports.pascalCase=u;exports.readSvgDirectory=x;exports.readSvgFile=O;exports.sanitizeComponentName=W;exports.svgToComponentName=K;exports.writeComponentFile=D;exports.writeSvgFile=P;
package/dist/index.mjs CHANGED
@@ -1,88 +1,23 @@
1
- import { transform } from '@svgr/core';
2
- import { optimize } from 'svgo';
3
- import { readFile, writeFile, readdir, stat, mkdir } from 'fs/promises';
4
- import { dirname, join, extname } from 'path';
5
- import { existsSync } from 'fs';
6
-
7
- var B={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:!1,removeTitle:!1,removeDesc:!1,removeUselessStrokeAndFill:!1,convertColors:{currentColor:!0,names2hex:!0,rgb2hex:!0,shorthex:!0,shortname:!0}}}},"removeDimensions","cleanupNumericValues"]};function v(e,t=B){try{return optimize(e,t).data}catch(r){throw new Error(`Failed to optimize SVG: ${r}`)}}function _(e){let t=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?!1:{currentColor:!0,names2hex:!0,rgb2hex:!0,shorthex:!0,shortname:!0}}}},"cleanupNumericValues"];return e.removeDimensions!==!1&&t.push("removeDimensions"),{plugins:t}}function k(e){let t=e.replace(/\.svg$/i,"");return t=M(t),u(t)}function M(e){let t=e,r=t.match(/^([^,]+),\s*Type=([^,]+)/i);if(r)return t=`${r[1]} ${r[2]}`,t;let n=t.match(/Size=(\w+)/i),s=t.match(/Color=(\w+)/i),i=t.match(/Type=(\w+)/i);if(n||s||i){let o=[];if(i&&o.push(i[1]),s&&o.push(s[1]),n&&o.push(n[1]),o.length>0)return t=o.join(" "),t}return t=t.replace(/\b\w+=\w+\b/g,"").replace(/,\s*/g," ").replace(/[=]/g," ").replace(/\s+/g," ").trim(),(!t||t.length<2)&&(t=e),t}function I(e){return u(e.replace(/[^a-zA-Z0-9]/g," "))}function u(e){return e.replace(/[^a-zA-Z0-9\s-_]/g," ").split(/[\s-_]+/).filter(t=>t.length>0).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}function d(e,t,r){let n=t?u(t):"",s=r?u(r):"",i=u(e);return `${n}${i}${s}`}async function K(e){try{return await readFile(e,"utf-8")}catch(t){throw new Error(`Failed to read SVG file: ${e}. ${t}`)}}async function Q(e,t){try{await N(dirname(e)),await writeFile(e,t,"utf-8");}catch(r){throw new Error(`Failed to write SVG file: ${e}. ${r}`)}}async function X(e,t){try{await N(dirname(e)),await writeFile(e,t,"utf-8");}catch(r){throw new Error(`Failed to write component file: ${e}. ${r}`)}}async function D(e,t=!1){try{let r=await readdir(e),n=[];for(let s of r){let i=join(e,s),o=await stat(i);if(o.isDirectory()&&t){let c=await D(i,t);n.push(...c);}else o.isFile()&&extname(s).toLowerCase()===".svg"&&n.push(i);}return n}catch(r){throw new Error(`Failed to read directory: ${e}. ${r}`)}}async function N(e){existsSync(e)||await mkdir(e,{recursive:!0});}function $(e,t=!0){return e==="react"?t?".tsx":".jsx":".vue"}function x(e,t,r){return `${t}${r}`}async function ee(e,t={}){let{name:r,prefix:n,suffix:s,optimize:i=!0,typescript:o=!0,memo:c=!0,ref:l=!0,titleProp:m=!0,descProp:S=!0}=t;try{let p=e;i&&(p=v(e));let a=d(r||"icon",n,s),C=await transform(p,{typescript:o,memo:c,ref:l,titleProp:m,descProp:S,svgProps:{className:"{className}"}},{componentName:a}),w=o?`import { SVGProps } from 'react';
8
- `:"",y=c?`import { memo } from 'react';
9
- `:"",h=l?`import { forwardRef } from 'react';
10
- `:"",z=`
11
- export default ${a};
12
- export { ${a} };`,V=o?"SVGProps<SVGSVGElement> & { className?: string; }":"",E=o?`props: ${V}`:"props",O=c?"memo(":"",P=l?`forwardRef<SVGSVGElement, ${V}>(`:"";C=`${w}${y}${h}
13
- const ${a} = ${O}${P}(${E}) => {
14
- return ${C};
15
- }${`${l?")":""}${c?")":""}`};
16
-
17
- ${a}.displayName = "${a}";
18
- ${z}`;let G=$("react",o),A=x("icon.svg",a,G);return {code:C,filename:A,componentName:a}}catch(p){throw new Error(`Failed to convert SVG to React: ${p}`)}}function te(e,t={}){let{name:r,prefix:n,suffix:s,optimize:i=!0,typescript:o=!0,compositionApi:c=!0,scriptSetup:l=!0}=t;try{let m=e;i&&(m=v(e));let p=d(r||"icon",n,s),F=m.replace(/<\?xml[^>]*\?>\s*/,"").replace(/xmlns="[^"]*"/g,"").replace(/width="[^"]*"/g,"").replace(/height="[^"]*"/g,"").replace(/<svg/,'<svg v-bind="$attrs"').replace(/class="([^"]*)"/g,'class="$1"').replace(/currentColor/g,"currentColor"),a=l?re(o,p):oe(p,o),b=`<template>
19
- ${F}
20
- </template>`,w=`${a}
21
-
22
- ${b}
23
-
24
- <style scoped>
25
- /* Add component-specific styles here */
26
- </style>`,y=$("vue",o),h=x("icon.svg",p,y);return {code:w,filename:h,componentName:p}}catch(m){throw new Error(`Failed to convert SVG to Vue: ${m}`)}}function re(e,t){return `<script setup${e?' lang="ts"':""}>${e?`
27
- interface Props {
28
- class?: string;
29
- style?: string | Record<string, any>;
30
- }`:""}${e?`
31
- const props = withDefaults(defineProps<Props>(), {
32
- class: '',
33
- style: undefined,
34
- });`:`
35
- const props = withDefaults(defineProps(), {
36
- class: '',
37
- style: undefined,
38
- });`}
39
-
40
- // Component name for debugging
41
- const __name = '${t}';
42
- </script>`}function oe(e,t){let r=t?' lang="ts"':"",n=t?`
1
+ import {dirname,join,extname}from'path';import'url';import {readFile,writeFile,readdir,stat,mkdir}from'fs/promises';import {existsSync}from'fs';import {transform}from'@svgr/core';import {optimize}from'svgo';var d=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var N=(e,r)=>()=>(e&&(r=e(e=0)),r);var q=(e,r)=>{for(var t in r)d(e,t,{get:r[t],enumerable:true});},M=(e,r,t,o)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of L(r))!I.call(e,n)&&n!==t&&d(e,n,{get:()=>r[n],enumerable:!(o=k(r,n))||o.enumerable});return e};var Z=e=>M(d({},"__esModule",{value:true}),e);var a=N(()=>{});var A={};q(A,{ensureDirectoryExists:()=>w,getComponentFilename:()=>ae,getFileExtension:()=>ie,readSvgDirectory:()=>h,readSvgFile:()=>O,writeComponentFile:()=>T,writeSvgFile:()=>D});async function O(e){try{return await readFile(e,"utf-8")}catch(r){throw new Error(`Failed to read SVG file: ${e}. ${r}`)}}async function D(e,r){try{await w(dirname(e)),await writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write SVG file: ${e}. ${t}`)}}async function T(e,r){try{await w(dirname(e)),await writeFile(e,r,"utf-8");}catch(t){throw new Error(`Failed to write component file: ${e}. ${t}`)}}async function h(e,r=false){try{let t=await readdir(e),o=[];for(let n of t){let s=join(e,n),i=await stat(s);if(i.isDirectory()&&r){let p=await h(s,r);o.push(...p);}else i.isFile()&&extname(n).toLowerCase()===".svg"&&o.push(s);}return o}catch(t){throw new Error(`Failed to read directory: ${e}. ${t}`)}}async function w(e){existsSync(e)||await mkdir(e,{recursive:true});}function ie(e,r=true){return e==="react"?r?".tsx":".jsx":".vue"}function ae(e,r,t){return `${r}${t}`}var y=N(()=>{a();});a();a();a();a();var J={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:false,removeDesc:false,removeUselessStrokeAndFill:false,convertColors:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"removeDimensions","cleanupNumericValues"]};function x(e,r=J){try{return optimize(e,r).data}catch(t){throw new Error(`Failed to optimize SVG: ${t}`)}}function K(e){let r=[{name:"preset-default",params:{overrides:{removeViewBox:!e.removeViewBox,removeTitle:!e.removeTitle,removeDesc:!e.removeDesc,removeUselessStrokeAndFill:!e.preserveClasses,convertColors:e.preserveColors?false:{currentColor:true,names2hex:true,rgb2hex:true,shorthex:true,shortname:true}}}},"cleanupNumericValues"];return e.removeDimensions!==false&&r.push("removeDimensions"),{plugins:r}}a();function Q(e){let r=e.replace(/\.svg$/i,"");return r=W(r),g(r)}function W(e){let r=e,t=r.match(/^([^,]+),\s*Type=([^,]+)/i);if(t)return r=`${t[1]} ${t[2]}`,r;let o=r.match(/Size=(\w+)/i),n=r.match(/Color=(\w+)/i),s=r.match(/Type=(\w+)/i);if(o||n||s){let i=[];if(s&&i.push(s[1]),n&&i.push(n[1]),o&&i.push(o[1]),i.length>0)return r=i.join(" "),r}return r=r.replace(/\b\w+=\w+\b/g,"").replace(/,\s*/g," ").replace(/[=]/g," ").replace(/\s+/g," ").trim(),(!r||r.length<2)&&(r=e),r}function X(e){return g(e.replace(/[^a-zA-Z0-9]/g," "))}function g(e){return e.replace(/[^a-zA-Z0-9\s-_]/g," ").split(/[\s-_]+/).filter(r=>r.length>0).map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join("")}function v(e,r,t){let o=r?g(r):"",n=t?g(t):"",s=g(e);return `${o}${s}${n}`}var f=class{async processSvg(r,t){let{optimize:o=true}=t;return o?x(r):r}generateComponentName(r){let{name:t,prefix:o,suffix:n}=r;return v(t||"Icon",o,n)}generateFilename(r,t,o=true){try{let{getFileExtension:n,getComponentFilename:s}=(y(),Z(A)),i=n(t,o);return s("icon.svg",r,i)}catch{return `${r}${{react:o?".tsx":".jsx",vue:".vue"}[t]}`}}};a();function z(e){let{typescript:r=true,memo:t=true,ref:o=true,titleProp:n=true,descProp:s=true,icon:i=true,dimensions:p=false,replaceAttrValues:c={"#000":"currentColor","#000000":"currentColor"},svgProps:l={},expandProps:C=false,nativeProps:B=true,ariaLabelledBy:U=false,ariaHidden:_=false,role:G="img"}=e,V={typescript:r,memo:t,ref:o,titleProp:n,descProp:s,icon:i,dimensions:p,expandProps:C,svgProps:{className:"{className}",...o&&{ref:"{ref}"},...B&&{width:"{width}",height:"{height}",style:"{style}"},...U&&n&&s&&{"aria-labelledby":"{titleId} {descId}"},..._&&{"aria-hidden":"true"},role:G,...l},replaceAttrValues:c,plugins:["@svgr/plugin-svgo","@svgr/plugin-jsx","@svgr/plugin-prettier"]};return V.svgoConfig={plugins:[{name:"preset-default",params:{overrides:{removeViewBox:false,removeTitle:!n,removeDesc:!s,removeUselessStrokeAndFill:false,removeUnusedNS:false,removeUselessDefs:false,convertShapeToPath:false,mergePaths:false,convertColors:false}}},...i&&!p?[{name:"removeAttrs",params:{attrs:["width","height"]}}]:[],...i?["cleanupNumericValues"]:[]]},V}function E(e,r,t){let o=e;return $(o)&&(o=F(o,r)),o}function $(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function F(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}var S=class extends f{async convert(r,t={}){try{let o=this.generateComponentName(t),n=await this.processSvg(r,t);$(n)&&(n=F(n,o));let s=z(t),i=await transform(n,s,{componentName:o}),p=E(i,o,t),c=this.generateFilename(o,"react",t.typescript??!0);return {code:p,filename:c,componentName:o}}catch(o){throw new Error(`Failed to convert SVG to React: ${o}`)}}};async function le(e,r={}){return new S().convert(e,r)}a();a();function j(e,r){let{name:t,prefix:o,suffix:n,props:s=true,replaceAttrValues:i={"#000":"currentColor","#000000":"currentColor"}}=r,c=v(t||"Icon",o,n),l=pe(e);return l=me(l,i),ue(l)&&(l=ge(l,c)),l=fe(l,s),{code:ve(l,c,r),componentName:c}}function pe(e){return e.replace(/<\?xml[^>]*\?>\s*/,"").replace(/<!--[\s\S]*?-->/g,"").replace(/xmlns="[^"]*"/g,"").trim()}function me(e,r){let t=e;for(let[o,n]of Object.entries(r)){let s=new RegExp(o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");t=t.replace(s,n);}return t}function ue(e){return ["linearGradient","radialGradient","pattern","mask","filter","clipPath","marker","symbol","use"].some(t=>e.includes(`<${t}`)||e.includes(`</${t}`))}function ge(e,r){let t=`${r.toLowerCase()}_`;return e=e.replace(/id="([^"]+)"/g,`id="${t}$1"`),e=e.replace(/url\(#([^)]+)\)/g,`url(#${t}$1)`),e=e.replace(/href="#([^"]+)"/g,`href="#${t}$1"`),e}function fe(e,r){return r?e.replace("<svg",'<svg :class="className" :style="style" v-bind="$attrs"'):e}function ve(e,r,t){let{typescript:o,compositionApi:n,props:s}=t,c=`<script${o?' lang="ts"':""}${n?" setup":""}>`;n?s&&(c+=`
43
2
  interface Props {
44
- class?: string;
45
- style?: string | Record<string, any>;
46
- }`:"",s=t?`
47
- const ${e} = defineComponent({
48
- name: '${e}',
3
+ className?: string;
4
+ style?: Record<string, any>;
5
+ }
6
+
7
+ defineProps<Props>();
8
+ `):c+=`
9
+ export default {
10
+ name: '${r}',
49
11
  props: {
50
- class: {
51
- type: String,
52
- default: '',
53
- },
54
- style: {
55
- type: [String, Object],
56
- default: undefined,
57
- },
58
- },
59
- setup(props: Props) {
60
- return {};
12
+ className: String,
13
+ style: Object,
61
14
  },
62
- });
63
-
64
- export default ${e};
65
- export { ${e} };`:`
66
- const ${e} = defineComponent({
67
- name: '${e}',
68
- props: {
69
- class: {
70
- type: String,
71
- default: '',
72
- },
73
- style: {
74
- type: [String, Object],
75
- default: undefined,
76
- },
77
- },
78
- setup(props) {
79
- return {};
80
- },
81
- });
82
-
83
- export default ${e};
84
- export { ${e} };`;return `<script${r}>
85
- import { defineComponent } from 'vue';${n}${s}
86
- </script>`}
87
-
88
- export { ee as convertToReact, te as convertToVue, _ as createSvgoConfig, d as formatComponentName, v as optimizeSvg, u as pascalCase, D as readSvgDirectory, K as readSvgFile, I as sanitizeComponentName, k as svgToComponentName, X as writeComponentFile, Q as writeSvgFile };
15
+ };
16
+ `,c+="</script>";let l=`
17
+ <template>
18
+ ${e}
19
+ </template>`;return [c,l,`
20
+ <style scoped>
21
+ /* Component styles */
22
+ </style>`].filter(Boolean).join(`
23
+ `)}var b=class extends f{async convert(r,t={}){try{let o=await this.processSvg(r,t),{code:n,componentName:s}=j(o,t),i=this.generateFilename(s,"vue",t.typescript??!0);return {code:n,filename:i,componentName:s}}catch(o){throw new Error(`Failed to convert SVG to Vue: ${o}`)}}};async function Ce(e,r={}){return new b().convert(e,r)}y();export{le as convertToReact,Ce as convertToVue,K as createSvgoConfig,v as formatComponentName,x as optimizeSvg,g as pascalCase,h as readSvgDirectory,O as readSvgFile,X as sanitizeComponentName,Q as svgToComponentName,T as writeComponentFile,D as writeSvgFile};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svgfusion",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A powerful CLI tool and library that converts SVG files into production-ready React and Vue 3 components with TypeScript support and automatic optimization.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -13,6 +13,10 @@
13
13
  "import": "./dist/index.mjs",
14
14
  "require": "./dist/index.js"
15
15
  },
16
+ "./browser": {
17
+ "import": "./dist/browser.mjs",
18
+ "types": "./dist/browser.d.ts"
19
+ },
16
20
  "./cli": {
17
21
  "import": "./dist/cli.mjs",
18
22
  "require": "./dist/cli.js"
@@ -48,16 +52,16 @@
48
52
  "type": "git",
49
53
  "url": "git+https://github.com/lolvOid/svgfusion.git"
50
54
  },
51
- "homepage": "https://github.com/lolvOid/svgfusion#readme",
55
+ "homepage": "https://svgfusion.netlify.app",
52
56
  "bugs": {
53
57
  "url": "https://github.com/lolvOid/svgfusion/issues"
54
58
  },
55
59
  "scripts": {
56
60
  "build": "tsup && chmod +x dist/cli.js",
57
61
  "dev": "tsup --watch",
58
- "test": "vitest",
59
- "test:coverage": "vitest --coverage",
60
- "test:watch": "vitest --watch",
62
+ "test": "jest",
63
+ "test:coverage": "jest --coverage",
64
+ "test:watch": "jest --watch",
61
65
  "lint": "eslint src/**/*.ts tests/**/*.ts",
62
66
  "lint:fix": "eslint src/**/*.ts tests/**/*.ts --fix",
63
67
  "format": "prettier --write src/**/*.ts tests/**/*.ts *.md",
@@ -65,15 +69,18 @@
65
69
  "type-check": "tsc --noEmit",
66
70
  "clean": "rm -rf dist",
67
71
  "prepare": "husky install",
68
- "prepublishOnly": "npm run clean && npm run build && npm run test",
72
+ "prepublishOnly": "pnpm run clean && pnpm run build && pnpm run test",
69
73
  "release": "semantic-release",
70
74
  "release:dry": "semantic-release --dry-run",
71
- "postinstall": "cd docs && npm ci && cd .. && echo 'Documentation dependencies installed.'",
72
- "dev:docs": "cd docs && npm run start",
73
- "build:docs": "cd docs && npm run build"
75
+ "dev:docs": "cd docs && pnpm run start",
76
+ "build:docs": "cd docs && pnpm run build",
77
+ "postinstall": "if [ -d docs ]; then cd docs && pnpm install; fi"
74
78
  },
75
79
  "dependencies": {
76
80
  "@svgr/core": "^8.0.0",
81
+ "@svgr/plugin-jsx": "^8.1.0",
82
+ "@svgr/plugin-prettier": "^8.1.0",
83
+ "@svgr/plugin-svgo": "^8.1.0",
77
84
  "@types/figlet": "^1.7.0",
78
85
  "chalk": "^5.0.0",
79
86
  "commander": "^11.0.0",
@@ -88,18 +95,19 @@
88
95
  "@semantic-release/git": "^10.0.1",
89
96
  "@semantic-release/github": "^11.0.3",
90
97
  "@semantic-release/npm": "^12.0.2",
98
+ "@types/jest": "^30.0.0",
91
99
  "@types/node": "^20.0.0",
92
100
  "@typescript-eslint/eslint-plugin": "^6.21.0",
93
101
  "@typescript-eslint/parser": "^6.21.0",
94
- "@vitest/coverage-v8": "^3.2.4",
95
102
  "eslint": "^8.57.0",
96
103
  "husky": "^8.0.3",
104
+ "jest": "^30.0.4",
97
105
  "lint-staged": "^15.2.2",
98
106
  "prettier": "^2.8.8",
99
107
  "semantic-release": "^24.2.6",
100
- "tsup": "^7.0.0",
101
- "typescript": "^5.0.0",
102
- "vitest": "^3.2.4"
108
+ "ts-jest": "^29.4.0",
109
+ "tsup": "^8.5.0",
110
+ "typescript": "^5.0.0"
103
111
  },
104
112
  "engines": {
105
113
  "node": ">=18.0.0"