svgfusion 1.0.0-beta.3 → 1.0.0-beta.5
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 +61 -16
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +295 -40
- package/package.json +3 -3
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -89
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# SVGFusion
|
|
2
2
|
|
|
3
|
-
**Transform SVG files into production-ready React and Vue 3 components with TypeScript support
|
|
3
|
+
**Transform SVG files into production-ready React and Vue 3 components with TypeScript support and automatic optimization.**
|
|
4
|
+
|
|
5
|
+
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.
|
|
4
6
|
|
|
5
7
|
[](https://badge.fury.io/js/svgfusion)
|
|
6
8
|
[](https://www.typescriptlang.org/)
|
|
@@ -17,12 +19,21 @@
|
|
|
17
19
|
- **Flexible API**: Both CLI and programmatic usage
|
|
18
20
|
- **Batch Processing**: Convert entire directories of SVG files
|
|
19
21
|
- **Complex Filenames**: Handles design system metadata and special characters
|
|
22
|
+
- **Zero Configuration**: Works out of the box with sensible defaults
|
|
23
|
+
- **Production Ready**: Optimized output with proper TypeScript types
|
|
20
24
|
|
|
21
25
|
## Quick Start
|
|
22
26
|
|
|
23
27
|
### Installation
|
|
24
28
|
|
|
25
29
|
```bash
|
|
30
|
+
# Install globally (recommended for CLI usage)
|
|
31
|
+
npm install -g svgfusion
|
|
32
|
+
|
|
33
|
+
# Or use npx (no installation needed)
|
|
34
|
+
npx svgfusion convert ./icons --output ./components
|
|
35
|
+
|
|
36
|
+
# Or install locally for programmatic usage
|
|
26
37
|
npm install svgfusion
|
|
27
38
|
# or
|
|
28
39
|
yarn add svgfusion
|
|
@@ -33,23 +44,57 @@ pnpm add svgfusion
|
|
|
33
44
|
### CLI Usage
|
|
34
45
|
|
|
35
46
|
```bash
|
|
36
|
-
# Convert to React components
|
|
37
|
-
svgfusion
|
|
47
|
+
# Convert to React components (default)
|
|
48
|
+
svgfusion convert ./icons --output ./components
|
|
38
49
|
|
|
39
50
|
# Convert to Vue 3 components
|
|
40
|
-
svgfusion
|
|
51
|
+
svgfusion convert ./icons --output ./components --framework vue
|
|
52
|
+
|
|
53
|
+
# Single file conversion with TypeScript
|
|
54
|
+
svgfusion convert ./star.svg --output ./components --typescript
|
|
55
|
+
|
|
56
|
+
# Skip optimization
|
|
57
|
+
svgfusion convert ./icons --output ./components --no-optimize
|
|
58
|
+
|
|
59
|
+
# Using npx (no global install needed)
|
|
60
|
+
npx svgfusion convert ./icons --output ./components --framework react
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### CLI Options
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
svgfusion
|
|
65
|
+
```bash
|
|
66
|
+
svgfusion convert <input> [options]
|
|
67
|
+
|
|
68
|
+
Options:
|
|
69
|
+
-o, --output <output> Output directory (default: "./components")
|
|
70
|
+
-f, --framework <framework> Target framework (react|vue) (default: "react")
|
|
71
|
+
-t, --typescript Generate TypeScript files
|
|
72
|
+
--no-optimize Skip SVG optimization
|
|
73
|
+
-h, --help Show help
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Using with npx (No Installation Required)
|
|
77
|
+
|
|
78
|
+
Perfect for trying out SVGFusion or one-time conversions:
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
|
|
80
|
+
```bash
|
|
81
|
+
# Convert React components
|
|
82
|
+
npx svgfusion convert ./assets/icons --output ./src/components/icons
|
|
83
|
+
|
|
84
|
+
# Convert Vue components with TypeScript
|
|
85
|
+
npx svgfusion convert ./assets/icons --output ./src/components --framework vue --typescript
|
|
86
|
+
|
|
87
|
+
# Convert single file
|
|
88
|
+
npx svgfusion convert ./logo.svg --output ./src/components --framework react
|
|
47
89
|
```
|
|
48
90
|
|
|
49
91
|
### Programmatic Usage
|
|
50
92
|
|
|
51
93
|
```typescript
|
|
52
|
-
import { convertToReact, convertToVue } from 'svgfusion';
|
|
94
|
+
import { convertToReact, convertToVue, readSvgFile } from 'svgfusion';
|
|
95
|
+
|
|
96
|
+
// Read SVG file
|
|
97
|
+
const svgContent = await readSvgFile('./icons/star.svg');
|
|
53
98
|
|
|
54
99
|
// React conversion
|
|
55
100
|
const reactResult = await convertToReact(svgContent, {
|
|
@@ -60,7 +105,7 @@ const reactResult = await convertToReact(svgContent, {
|
|
|
60
105
|
});
|
|
61
106
|
|
|
62
107
|
// Vue conversion
|
|
63
|
-
const vueResult =
|
|
108
|
+
const vueResult = convertToVue(svgContent, {
|
|
64
109
|
name: 'StarIcon',
|
|
65
110
|
typescript: true,
|
|
66
111
|
scriptSetup: true,
|
|
@@ -88,7 +133,7 @@ Convert SVG to React component.
|
|
|
88
133
|
|
|
89
134
|
### `convertToVue(svgContent, options)`
|
|
90
135
|
|
|
91
|
-
Convert SVG to Vue 3 component.
|
|
136
|
+
Convert SVG to Vue 3 component. **Note: This is a synchronous function.**
|
|
92
137
|
|
|
93
138
|
**Options:**
|
|
94
139
|
|
|
@@ -102,14 +147,14 @@ Convert SVG to Vue 3 component.
|
|
|
102
147
|
|
|
103
148
|
### `optimizeSvg(svgContent, config?)`
|
|
104
149
|
|
|
105
|
-
Optimize SVG content using SVGO.
|
|
150
|
+
Optimize SVG content using SVGO. **Note: This is a synchronous function.**
|
|
106
151
|
|
|
107
152
|
### File Utilities
|
|
108
153
|
|
|
109
|
-
- `readSvgFile(filePath)` - Read SVG file
|
|
110
|
-
- `writeSvgFile(filePath, content)` - Write SVG file
|
|
111
|
-
- `readSvgDirectory(dirPath, recursive?)` - Read SVG files from directory
|
|
112
|
-
- `writeComponentFile(filePath, content)` - Write component file
|
|
154
|
+
- `readSvgFile(filePath)` - Read SVG file (async)
|
|
155
|
+
- `writeSvgFile(filePath, content)` - Write SVG file (async)
|
|
156
|
+
- `readSvgDirectory(dirPath, recursive?)` - Read SVG files from directory (async)
|
|
157
|
+
- `writeComponentFile(filePath, content)` - Write component file (async)
|
|
113
158
|
|
|
114
159
|
## Examples
|
|
115
160
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
export { }
|
package/dist/cli.js
CHANGED
|
@@ -1,56 +1,259 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
"use strict";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var fs = require('fs');
|
|
8
|
-
var core = require('@svgr/core');
|
|
9
|
-
var svgo = require('svgo');
|
|
10
|
-
var url = require('url');
|
|
4
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
5
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
|
|
6
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`:"",S=m?`import { forwardRef } from 'react';
|
|
15
|
-
`:"",D=`
|
|
16
|
-
export default ${c};
|
|
17
|
-
export { ${c} };`,G=o?"SVGProps<SVGSVGElement> & { className?: string; }":"",N=o?`props: ${G}`:"props",k=p?"memo(":"",_=m?`forwardRef<SVGSVGElement, ${G}>(`:"";v=`${h}${y}${S}
|
|
18
|
-
const ${c} = ${k}${_}(${N}) => {
|
|
19
|
-
return ${v};
|
|
20
|
-
}${`${m?")":""}${p?")":""}`};
|
|
8
|
+
// src/cli.ts
|
|
9
|
+
var import_commander = require("commander");
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
// src/utils/files.ts
|
|
12
|
+
var import_promises = require("fs/promises");
|
|
13
|
+
var import_path = require("path");
|
|
14
|
+
var import_fs = require("fs");
|
|
15
|
+
async function readSvgFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
18
|
+
return content;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`Failed to read SVG file: ${filePath}. ${error}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function writeComponentFile(filePath, content) {
|
|
24
|
+
try {
|
|
25
|
+
await ensureDirectoryExists((0, import_path.dirname)(filePath));
|
|
26
|
+
await (0, import_promises.writeFile)(filePath, content, "utf-8");
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw new Error(`Failed to write component file: ${filePath}. ${error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function readSvgDirectory(dirPath, recursive = false) {
|
|
32
|
+
try {
|
|
33
|
+
const files = await (0, import_promises.readdir)(dirPath);
|
|
34
|
+
const svgFiles = [];
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
const filePath = (0, import_path.join)(dirPath, file);
|
|
37
|
+
const fileStat = await (0, import_promises.stat)(filePath);
|
|
38
|
+
if (fileStat.isDirectory() && recursive) {
|
|
39
|
+
const nestedFiles = await readSvgDirectory(filePath, recursive);
|
|
40
|
+
svgFiles.push(...nestedFiles);
|
|
41
|
+
} else if (fileStat.isFile() && (0, import_path.extname)(file).toLowerCase() === ".svg") {
|
|
42
|
+
svgFiles.push(filePath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return svgFiles;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Failed to read directory: ${dirPath}. ${error}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function ensureDirectoryExists(dirPath) {
|
|
51
|
+
if (!(0, import_fs.existsSync)(dirPath)) {
|
|
52
|
+
await (0, import_promises.mkdir)(dirPath, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function getFileExtension(framework, typescript = true) {
|
|
56
|
+
if (framework === "react") {
|
|
57
|
+
return typescript ? ".tsx" : ".jsx";
|
|
58
|
+
} else {
|
|
59
|
+
return typescript ? ".vue" : ".vue";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getComponentFilename(_svgFilename, componentName, extension) {
|
|
63
|
+
return `${componentName}${extension}`;
|
|
64
|
+
}
|
|
26
65
|
|
|
27
|
-
|
|
66
|
+
// src/converters/react.ts
|
|
67
|
+
var import_core = require("@svgr/core");
|
|
28
68
|
|
|
29
|
-
|
|
69
|
+
// src/utils/svgo.ts
|
|
70
|
+
var import_svgo = require("svgo");
|
|
71
|
+
var defaultConfig = {
|
|
72
|
+
plugins: [
|
|
73
|
+
{
|
|
74
|
+
name: "preset-default",
|
|
75
|
+
params: {
|
|
76
|
+
overrides: {
|
|
77
|
+
removeViewBox: false,
|
|
78
|
+
removeTitle: false,
|
|
79
|
+
removeDesc: false,
|
|
80
|
+
removeUselessStrokeAndFill: false,
|
|
81
|
+
convertColors: {
|
|
82
|
+
currentColor: true,
|
|
83
|
+
names2hex: true,
|
|
84
|
+
rgb2hex: true,
|
|
85
|
+
shorthex: true,
|
|
86
|
+
// cspell:disable-line
|
|
87
|
+
shortname: true
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"removeDimensions",
|
|
93
|
+
"cleanupNumericValues"
|
|
94
|
+
]
|
|
95
|
+
};
|
|
96
|
+
function optimizeSvg(svgContent, config = defaultConfig) {
|
|
97
|
+
try {
|
|
98
|
+
const result = (0, import_svgo.optimize)(svgContent, config);
|
|
99
|
+
return result.data;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw new Error(`Failed to optimize SVG: ${error}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/utils/name.ts
|
|
106
|
+
function pascalCase(str) {
|
|
107
|
+
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("");
|
|
108
|
+
}
|
|
109
|
+
function formatComponentName(name, prefix, suffix) {
|
|
110
|
+
const prefixPart = prefix ? pascalCase(prefix) : "";
|
|
111
|
+
const suffixPart = suffix ? pascalCase(suffix) : "";
|
|
112
|
+
const baseName = pascalCase(name);
|
|
113
|
+
return `${prefixPart}${baseName}${suffixPart}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/converters/react.ts
|
|
117
|
+
async function convertToReact(svgContent, options = {}) {
|
|
118
|
+
const {
|
|
119
|
+
name,
|
|
120
|
+
prefix,
|
|
121
|
+
suffix,
|
|
122
|
+
optimize: optimize2 = true,
|
|
123
|
+
typescript = true,
|
|
124
|
+
memo = true,
|
|
125
|
+
ref = true,
|
|
126
|
+
titleProp = true,
|
|
127
|
+
descProp = true
|
|
128
|
+
} = options;
|
|
129
|
+
try {
|
|
130
|
+
let processedSvg = svgContent;
|
|
131
|
+
if (optimize2) {
|
|
132
|
+
processedSvg = optimizeSvg(svgContent);
|
|
133
|
+
}
|
|
134
|
+
const baseName = name || "icon";
|
|
135
|
+
const componentName = formatComponentName(baseName, prefix, suffix);
|
|
136
|
+
const svgrOptions = {
|
|
137
|
+
typescript,
|
|
138
|
+
memo,
|
|
139
|
+
ref,
|
|
140
|
+
titleProp,
|
|
141
|
+
descProp,
|
|
142
|
+
svgProps: {
|
|
143
|
+
className: "{className}"
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
let result = await (0, import_core.transform)(processedSvg, svgrOptions, {
|
|
147
|
+
componentName
|
|
148
|
+
});
|
|
149
|
+
const typeImports = typescript ? `import { SVGProps } from 'react';
|
|
150
|
+
` : "";
|
|
151
|
+
const memoImport = memo ? `import { memo } from 'react';
|
|
152
|
+
` : "";
|
|
153
|
+
const refImport = ref ? `import { forwardRef } from 'react';
|
|
154
|
+
` : "";
|
|
155
|
+
const exports = `
|
|
156
|
+
export default ${componentName};
|
|
157
|
+
export { ${componentName} };`;
|
|
158
|
+
const propsType = typescript ? `SVGProps<SVGSVGElement> & { className?: string; }` : "";
|
|
159
|
+
const componentProps = typescript ? `props: ${propsType}` : "props";
|
|
160
|
+
const componentFunc = memo ? `memo(` : "";
|
|
161
|
+
const refWrapper = ref ? `forwardRef<SVGSVGElement, ${propsType}>(` : "";
|
|
162
|
+
const closingWrappers = `${ref ? ")" : ""}${memo ? ")" : ""}`;
|
|
163
|
+
result = `${typeImports}${memoImport}${refImport}
|
|
164
|
+
const ${componentName} = ${componentFunc}${refWrapper}(${componentProps}) => {
|
|
165
|
+
return ${result};
|
|
166
|
+
}${closingWrappers};
|
|
167
|
+
|
|
168
|
+
${componentName}.displayName = "${componentName}";
|
|
169
|
+
${exports}`;
|
|
170
|
+
const extension = getFileExtension("react", typescript);
|
|
171
|
+
const filename = getComponentFilename("icon.svg", componentName, extension);
|
|
172
|
+
return {
|
|
173
|
+
code: result,
|
|
174
|
+
filename,
|
|
175
|
+
componentName
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
throw new Error(`Failed to convert SVG to React: ${error}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/converters/vue.ts
|
|
183
|
+
function convertToVue(svgContent, options = {}) {
|
|
184
|
+
const {
|
|
185
|
+
name,
|
|
186
|
+
prefix,
|
|
187
|
+
suffix,
|
|
188
|
+
optimize: optimize2 = true,
|
|
189
|
+
typescript = true,
|
|
190
|
+
compositionApi: _compositionApi = true,
|
|
191
|
+
// eslint-disable-line @typescript-eslint/no-unused-vars
|
|
192
|
+
scriptSetup = true
|
|
193
|
+
} = options;
|
|
194
|
+
try {
|
|
195
|
+
let processedSvg = svgContent;
|
|
196
|
+
if (optimize2) {
|
|
197
|
+
processedSvg = optimizeSvg(svgContent);
|
|
198
|
+
}
|
|
199
|
+
const baseName = name || "icon";
|
|
200
|
+
const componentName = formatComponentName(baseName, prefix, suffix);
|
|
201
|
+
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");
|
|
202
|
+
const scriptTag = scriptSetup ? generateScriptSetup(typescript, componentName) : generateCompositionScript(componentName, typescript);
|
|
203
|
+
const template = `<template>
|
|
204
|
+
${cleanedSvg}
|
|
205
|
+
</template>`;
|
|
206
|
+
const style = `<style scoped>
|
|
30
207
|
/* Add component-specific styles here */
|
|
31
|
-
</style
|
|
208
|
+
</style>`;
|
|
209
|
+
const vueComponent = `${scriptTag}
|
|
210
|
+
|
|
211
|
+
${template}
|
|
212
|
+
|
|
213
|
+
${style}`;
|
|
214
|
+
const extension = getFileExtension("vue", typescript);
|
|
215
|
+
const filename = getComponentFilename("icon.svg", componentName, extension);
|
|
216
|
+
return {
|
|
217
|
+
code: vueComponent,
|
|
218
|
+
filename,
|
|
219
|
+
componentName
|
|
220
|
+
};
|
|
221
|
+
} catch (error) {
|
|
222
|
+
throw new Error(`Failed to convert SVG to Vue: ${error}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function generateScriptSetup(typescript, componentName) {
|
|
226
|
+
const lang = typescript ? ' lang="ts"' : "";
|
|
227
|
+
const propsType = typescript ? `
|
|
32
228
|
interface Props {
|
|
33
229
|
class?: string;
|
|
34
230
|
style?: string | Record<string, any>;
|
|
35
|
-
}
|
|
231
|
+
}` : "";
|
|
232
|
+
const propsDefinition = typescript ? `
|
|
36
233
|
const props = withDefaults(defineProps<Props>(), {
|
|
37
234
|
class: '',
|
|
38
235
|
style: undefined,
|
|
39
|
-
})
|
|
236
|
+
});` : `
|
|
40
237
|
const props = withDefaults(defineProps(), {
|
|
41
238
|
class: '',
|
|
42
239
|
style: undefined,
|
|
43
|
-
})
|
|
240
|
+
});`;
|
|
241
|
+
return `<script setup${lang}>${propsType}${propsDefinition}
|
|
44
242
|
|
|
45
243
|
// Component name for debugging
|
|
46
|
-
const __name = '${
|
|
47
|
-
</script
|
|
244
|
+
const __name = '${componentName}';
|
|
245
|
+
</script>`;
|
|
246
|
+
}
|
|
247
|
+
function generateCompositionScript(componentName, typescript) {
|
|
248
|
+
const lang = typescript ? ' lang="ts"' : "";
|
|
249
|
+
const propsType = typescript ? `
|
|
48
250
|
interface Props {
|
|
49
251
|
class?: string;
|
|
50
252
|
style?: string | Record<string, any>;
|
|
51
|
-
}
|
|
52
|
-
const
|
|
53
|
-
|
|
253
|
+
}` : "";
|
|
254
|
+
const exportStatement = typescript ? `
|
|
255
|
+
const ${componentName} = defineComponent({
|
|
256
|
+
name: '${componentName}',
|
|
54
257
|
props: {
|
|
55
258
|
class: {
|
|
56
259
|
type: String,
|
|
@@ -66,10 +269,10 @@ const ${e} = defineComponent({
|
|
|
66
269
|
},
|
|
67
270
|
});
|
|
68
271
|
|
|
69
|
-
export default ${
|
|
70
|
-
export { ${
|
|
71
|
-
const ${
|
|
72
|
-
name: '${
|
|
272
|
+
export default ${componentName};
|
|
273
|
+
export { ${componentName} };` : `
|
|
274
|
+
const ${componentName} = defineComponent({
|
|
275
|
+
name: '${componentName}',
|
|
73
276
|
props: {
|
|
74
277
|
class: {
|
|
75
278
|
type: String,
|
|
@@ -85,7 +288,59 @@ const ${e} = defineComponent({
|
|
|
85
288
|
},
|
|
86
289
|
});
|
|
87
290
|
|
|
88
|
-
export default ${
|
|
89
|
-
export { ${
|
|
90
|
-
|
|
91
|
-
|
|
291
|
+
export default ${componentName};
|
|
292
|
+
export { ${componentName} };`;
|
|
293
|
+
return `<script${lang}>
|
|
294
|
+
import { defineComponent } from 'vue';${propsType}${exportStatement}
|
|
295
|
+
</script>`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/cli.ts
|
|
299
|
+
var import_url = require("url");
|
|
300
|
+
var import_path2 = require("path");
|
|
301
|
+
var import_fs2 = require("fs");
|
|
302
|
+
var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
303
|
+
var __dirname = (0, import_path2.dirname)(__filename2);
|
|
304
|
+
var packageJson = JSON.parse(
|
|
305
|
+
(0, import_fs2.readFileSync)((0, import_path2.join)(__dirname, "..", "package.json"), "utf-8")
|
|
306
|
+
);
|
|
307
|
+
var program = new import_commander.Command();
|
|
308
|
+
program.name("svgfusion").description(
|
|
309
|
+
"Transform SVG files into production-ready React and Vue 3 components"
|
|
310
|
+
).version(packageJson.version);
|
|
311
|
+
program.command("convert").description("Convert SVG files to React or Vue components").argument("<input>", "Input SVG file or directory").option("-o, --output <output>", "Output directory", "./components").option(
|
|
312
|
+
"-f, --framework <framework>",
|
|
313
|
+
"Target framework (react|vue)",
|
|
314
|
+
"react"
|
|
315
|
+
).option("-t, --typescript", "Generate TypeScript files", false).option("--no-optimize", "Skip SVG optimization").action(
|
|
316
|
+
async (input, options) => {
|
|
317
|
+
console.log("\u{1F504} Processing SVG files...");
|
|
318
|
+
try {
|
|
319
|
+
const { framework, output, typescript, optimize: optimize2 } = options;
|
|
320
|
+
if (framework !== "react" && framework !== "vue") {
|
|
321
|
+
throw new Error('Framework must be either "react" or "vue"');
|
|
322
|
+
}
|
|
323
|
+
const svgFiles = await readSvgDirectory(input);
|
|
324
|
+
if (svgFiles.length === 0) {
|
|
325
|
+
throw new Error("No SVG files found in the input path");
|
|
326
|
+
}
|
|
327
|
+
console.log(`\u{1F504} Converting ${svgFiles.length} SVG file(s)...`);
|
|
328
|
+
for (const filePath of svgFiles) {
|
|
329
|
+
const svgContent = await readSvgFile(filePath);
|
|
330
|
+
const optimizedSvg = optimize2 ? optimizeSvg(svgContent) : svgContent;
|
|
331
|
+
const result = framework === "react" ? await convertToReact(optimizedSvg, { typescript }) : convertToVue(optimizedSvg, { typescript });
|
|
332
|
+
const outputPath = (0, import_path2.join)(output, result.filename);
|
|
333
|
+
await writeComponentFile(outputPath, result.code);
|
|
334
|
+
}
|
|
335
|
+
console.log(
|
|
336
|
+
`\u2705 Successfully converted ${svgFiles.length} SVG file(s) to ${framework} components`
|
|
337
|
+
);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error(
|
|
340
|
+
`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
341
|
+
);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svgfusion",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
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",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"url": "https://github.com/lolvoid/svgfusion/issues"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
|
-
"build": "tsup",
|
|
56
|
+
"build": "tsup && chmod +x dist/cli.js",
|
|
57
57
|
"dev": "tsup --watch",
|
|
58
58
|
"test": "vitest",
|
|
59
59
|
"test:coverage": "vitest --coverage",
|
package/dist/cli.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
package/dist/cli.mjs
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { readFile, writeFile, readdir, stat, mkdir } from 'fs/promises';
|
|
4
|
-
import { dirname, join, extname } from 'path';
|
|
5
|
-
import { readFileSync, existsSync } from 'fs';
|
|
6
|
-
import { transform } from '@svgr/core';
|
|
7
|
-
import { optimize } from 'svgo';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
|
|
10
|
-
async function z(e){try{return await readFile(e,"utf-8")}catch(t){throw new Error(`Failed to read SVG file: ${e}. ${t}`)}}async function E(e,t){try{await H(dirname(e)),await writeFile(e,t,"utf-8");}catch(r){throw new Error(`Failed to write component file: ${e}. ${r}`)}}async function C(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 p=await C(i,t);n.push(...p);}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 H(e){existsSync(e)||await mkdir(e,{recursive:!0});}function $(e,t=!0){return e==="react"?t?".tsx":".jsx":".vue"}function w(e,t,r){return `${t}${r}`}var Q={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 u(e,t=Q){try{return optimize(e,t).data}catch(r){throw new Error(`Failed to optimize SVG: ${r}`)}}function V(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 h(e,t,r){let n=t?V(t):"",s=r?V(r):"",i=V(e);return `${n}${i}${s}`}async function P(e,t={}){let{name:r,prefix:n,suffix:s,optimize:i=!0,typescript:o=!0,memo:p=!0,ref:m=!0,titleProp:l=!0,descProp:f=!0}=t;try{let a=e;i&&(a=u(e));let c=h(r||"icon",n,s),g=await transform(a,{typescript:o,memo:p,ref:m,titleProp:l,descProp:f,svgProps:{className:"{className}"}},{componentName:c}),y=o?`import { SVGProps } from 'react';
|
|
11
|
-
`:"",S=p?`import { memo } from 'react';
|
|
12
|
-
`:"",x=m?`import { forwardRef } from 'react';
|
|
13
|
-
`:"",N=`
|
|
14
|
-
export default ${c};
|
|
15
|
-
export { ${c} };`,T=o?"SVGProps<SVGSVGElement> & { className?: string; }":"",k=o?`props: ${T}`:"props",O=p?"memo(":"",_=m?`forwardRef<SVGSVGElement, ${T}>(`:"";g=`${y}${S}${x}
|
|
16
|
-
const ${c} = ${O}${_}(${k}) => {
|
|
17
|
-
return ${g};
|
|
18
|
-
}${`${m?")":""}${p?")":""}`};
|
|
19
|
-
|
|
20
|
-
${c}.displayName = "${c}";
|
|
21
|
-
${N}`;let A=$("react",o),j=w("icon.svg",c,A);return {code:g,filename:j,componentName:c}}catch(a){throw new Error(`Failed to convert SVG to React: ${a}`)}}function R(e,t={}){let{name:r,prefix:n,suffix:s,optimize:i=!0,typescript:o=!0,compositionApi:p=!0,scriptSetup:m=!0}=t;try{let l=e;i&&(l=u(e));let a=h(r||"icon",n,s),b=l.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"),c=m?Y(o,a):ee(a,o),G=`<template>
|
|
22
|
-
${b}
|
|
23
|
-
</template>`,y=`${c}
|
|
24
|
-
|
|
25
|
-
${G}
|
|
26
|
-
|
|
27
|
-
<style scoped>
|
|
28
|
-
/* Add component-specific styles here */
|
|
29
|
-
</style>`,S=$("vue",o),x=w("icon.svg",a,S);return {code:y,filename:x,componentName:a}}catch(l){throw new Error(`Failed to convert SVG to Vue: ${l}`)}}function Y(e,t){return `<script setup${e?' lang="ts"':""}>${e?`
|
|
30
|
-
interface Props {
|
|
31
|
-
class?: string;
|
|
32
|
-
style?: string | Record<string, any>;
|
|
33
|
-
}`:""}${e?`
|
|
34
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
35
|
-
class: '',
|
|
36
|
-
style: undefined,
|
|
37
|
-
});`:`
|
|
38
|
-
const props = withDefaults(defineProps(), {
|
|
39
|
-
class: '',
|
|
40
|
-
style: undefined,
|
|
41
|
-
});`}
|
|
42
|
-
|
|
43
|
-
// Component name for debugging
|
|
44
|
-
const __name = '${t}';
|
|
45
|
-
</script>`}function ee(e,t){let r=t?' lang="ts"':"",n=t?`
|
|
46
|
-
interface Props {
|
|
47
|
-
class?: string;
|
|
48
|
-
style?: string | Record<string, any>;
|
|
49
|
-
}`:"",s=t?`
|
|
50
|
-
const ${e} = defineComponent({
|
|
51
|
-
name: '${e}',
|
|
52
|
-
props: {
|
|
53
|
-
class: {
|
|
54
|
-
type: String,
|
|
55
|
-
default: '',
|
|
56
|
-
},
|
|
57
|
-
style: {
|
|
58
|
-
type: [String, Object],
|
|
59
|
-
default: undefined,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
setup(props: Props) {
|
|
63
|
-
return {};
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
export default ${e};
|
|
68
|
-
export { ${e} };`:`
|
|
69
|
-
const ${e} = defineComponent({
|
|
70
|
-
name: '${e}',
|
|
71
|
-
props: {
|
|
72
|
-
class: {
|
|
73
|
-
type: String,
|
|
74
|
-
default: '',
|
|
75
|
-
},
|
|
76
|
-
style: {
|
|
77
|
-
type: [String, Object],
|
|
78
|
-
default: undefined,
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
setup(props) {
|
|
82
|
-
return {};
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
export default ${e};
|
|
87
|
-
export { ${e} };`;return `<script${r}>
|
|
88
|
-
import { defineComponent } from 'vue';${n}${s}
|
|
89
|
-
</script>`}var se=fileURLToPath(import.meta.url),ie=dirname(se),ae=JSON.parse(readFileSync(join(ie,"..","package.json"),"utf-8")),F=new Command;F.name("svgfusion").description("Transform SVG files into production-ready React and Vue 3 components").version(ae.version);F.command("convert").description("Convert SVG files to React or Vue components").argument("<input>","Input SVG file or directory").option("-o, --output <output>","Output directory","./components").option("-f, --framework <framework>","Target framework (react|vue)","react").option("-t, --typescript","Generate TypeScript files",!1).option("--no-optimize","Skip SVG optimization").action(async(e,t)=>{console.log("\u{1F504} Processing SVG files...");try{let{framework:r,output:n,typescript:s,optimize:i}=t;if(r!=="react"&&r!=="vue")throw new Error('Framework must be either "react" or "vue"');let o=await C(e);if(o.length===0)throw new Error("No SVG files found in the input path");console.log(`\u{1F504} Converting ${o.length} SVG file(s)...`);for(let p of o){let m=await z(p),l=i?u(m):m,f=r==="react"?await P(l,{typescript:s}):R(l,{typescript:s}),a=join(n,f.filename);await E(a,f.code);}console.log(`\u2705 Successfully converted ${o.length} SVG file(s) to ${r} components`);}catch(r){console.error(`\u274C Error: ${r instanceof Error?r.message:"Unknown error"}`),process.exit(1);}});F.parse();
|