vite-plugin-for-svelte-svg 0.9.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/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # vite-plugin-for-svelte-svg
2
+
3
+ Vite plugin for SVG handling in Svelte 5 projects with dynamic color and size support.
4
+
5
+ ## Features
6
+
7
+ - 🎨 Dynamic color modification at runtime
8
+ - 📐 Flexible sizing with CSS units
9
+ - 🔄 Auto-generate TypeScript types for SVG names
10
+ - 🔥 HMR support - add/remove SVGs without restart
11
+ - 📦 Tree-shakeable
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add vite-plugin-for-svelte-svg
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### 1. Configure Vite
22
+
23
+ ```ts
24
+ // vite.config.ts
25
+ import { defineConfig } from 'vite'
26
+ import { svelte } from '@sveltejs/vite-plugin-svelte'
27
+ import svelteSvg from 'vite-plugin-for-svelte-svg'
28
+
29
+ export default defineConfig({
30
+ plugins: [
31
+ svelteSvg({
32
+ dir: 'src/svg',
33
+ typesOutput: 'src/type/svg.d.ts'
34
+ }),
35
+ svelte()
36
+ ]
37
+ })
38
+ ```
39
+
40
+ ### 2. Use the Component
41
+
42
+ ```svelte
43
+ <script lang="ts">
44
+ import { Svg } from '@/type/svg' // Auto-generated with typed name prop
45
+ </script>
46
+
47
+ <!-- Basic usage -->
48
+ <Svg name="icon" />
49
+
50
+ <!-- With size -->
51
+ <Svg name="logo" size="64px" />
52
+
53
+ <!-- With single color -->
54
+ <Svg name="arrow" color="#ff0000" />
55
+
56
+ <!-- With multiple colors (for multi-colored SVGs) -->
57
+ <Svg name="badge" color={['#fff', '#000', '#ccc']} />
58
+
59
+ <!-- With class and click handler -->
60
+ <Svg
61
+ name="button"
62
+ size="24px"
63
+ class="hover:opacity-80 cursor-pointer"
64
+ onclick={() => console.log('clicked')}
65
+ />
66
+ ```
67
+
68
+ ## API
69
+
70
+ ### Plugin Options
71
+
72
+ | Option | Type | Default | Description |
73
+ |--------|------|---------|-------------|
74
+ | `dir` | `string` | `'src/svg'` | SVG files directory |
75
+ | `typesOutput` | `string` | `'src/type/svg.d.ts'` | TypeScript types output path |
76
+ | `generateTypes` | `boolean` | `true` | Whether to generate types |
77
+
78
+ ### Svg Component Props
79
+
80
+ | Prop | Type | Default | Description |
81
+ |------|------|---------|-------------|
82
+ | `name` | `SvgName` | required | SVG file name (without extension), auto-completed |
83
+ | `size` | `string \| number` | `'40px'` | Width and height |
84
+ | `color` | `string \| string[]` | `undefined` | Fill color(s) for shapes |
85
+ | `class` | `string` | `''` | CSS classes |
86
+ | `...rest` | `HTMLAttributes` | - | All HTML attributes and events |
87
+
88
+ ### Generated File
89
+
90
+ The plugin generates a single `svg.ts` file containing:
91
+
92
+ ```ts
93
+ export type SvgName = 'icon1' | 'icon2' | ... // Union of all SVG names
94
+ export interface SvgProps { ... } // Typed props interface
95
+ export const Svg: Component<SvgProps> // Typed Svg component
96
+ ```
97
+
98
+ ## How It Works
99
+
100
+ 1. **Build/Dev start**: Plugin scans SVG directory and generates `svg.ts` with typed `Svg` component
101
+ 2. **Dev time**: Watches for SVG file changes and regenerates with HMR
102
+ 3. **Runtime**: SVGs are loaded as base64 data URLs via `import.meta.glob`, then parsed and modified in the browser
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,8 @@
1
+ import { Plugin } from 'vite';
2
+ import { P as PluginOptions } from './types-Bh508a5v.js';
3
+ export { N as NumberWithUnit, b as SvgBase64, a as SvgColor, S as SvgProps } from './types-Bh508a5v.js';
4
+ import 'svelte/elements';
5
+
6
+ declare function svelteSvgPlugin(options?: PluginOptions): Plugin;
7
+
8
+ export { PluginOptions, svelteSvgPlugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,87 @@
1
+ // src/index.ts
2
+ import { writeFileSync, existsSync, mkdirSync } from "fs";
3
+ import { resolve, dirname } from "path";
4
+ import { glob } from "glob";
5
+ function generateSvgTypes(svgDir, outputPath, root) {
6
+ const fullSvgDir = resolve(root, svgDir);
7
+ const fullOutputPath = resolve(root, outputPath);
8
+ const svgFiles = glob.sync(`${fullSvgDir}/**/*.svg`);
9
+ const svgNames = svgFiles.map((file) => {
10
+ const relativePath = file.replace(fullSvgDir, "");
11
+ const lastSlashIndex = Math.max(relativePath.lastIndexOf("/"), relativePath.lastIndexOf("\\"));
12
+ return relativePath.slice(lastSlashIndex + 1, -4)?.toLowerCase() ?? null;
13
+ }).filter(Boolean);
14
+ const svgNameType = svgNames.length > 0 ? svgNames.map((name) => `'${name}'`).join(" | ") : "string";
15
+ const outputDir = dirname(fullOutputPath);
16
+ const relativeSvgDir = resolve(root, svgDir).replace(/\\/g, "/");
17
+ const relativeOutputDir = outputDir.replace(/\\/g, "/");
18
+ let importPath = relativeSvgDir.replace(root.replace(/\\/g, "/"), "");
19
+ if (!importPath.startsWith("/")) importPath = "/" + importPath;
20
+ const moduleContent = `// Auto-generated by vite-plugin-for-svelte-svg
21
+ // Do not edit manually
22
+
23
+ import { Svg as BaseSvg, initSvg } from 'vite-plugin-for-svelte-svg/runtime'
24
+ import type { HTMLAttributes } from 'svelte/elements'
25
+ import type { Component } from 'svelte'
26
+
27
+ // Initialize SVG registry
28
+ initSvg(import.meta.glob('${importPath}/**/*.svg'))
29
+
30
+ export type SvgName = ${svgNameType}
31
+
32
+ export interface SvgProps extends Omit<HTMLAttributes<HTMLElement>, 'color'> {
33
+ name: SvgName
34
+ size?: string | number
35
+ color?: string | string[]
36
+ }
37
+
38
+ export const Svg = BaseSvg as Component<SvgProps>
39
+ `;
40
+ if (!existsSync(outputDir)) {
41
+ mkdirSync(outputDir, { recursive: true });
42
+ }
43
+ const tsOutputPath = fullOutputPath.replace(".d.ts", ".ts");
44
+ writeFileSync(tsOutputPath, moduleContent);
45
+ console.log(`[vite-plugin-for-svelte-svg] Generated: ${outputPath.replace(".d.ts", ".ts")}`);
46
+ }
47
+ function svelteSvgPlugin(options = {}) {
48
+ const {
49
+ dir = "src/svg",
50
+ typesOutput = "src/types/svg.d.ts",
51
+ generateTypes = true
52
+ } = options;
53
+ let root = process.cwd();
54
+ return {
55
+ name: "vite-plugin-for-svelte-svg",
56
+ configResolved(config) {
57
+ root = config.root;
58
+ },
59
+ buildStart() {
60
+ if (generateTypes) {
61
+ generateSvgTypes(dir, typesOutput, root);
62
+ }
63
+ },
64
+ configureServer(server) {
65
+ if (!generateTypes) return;
66
+ const fullSvgDir = resolve(root, dir);
67
+ server.watcher.add(fullSvgDir);
68
+ const handleChange = () => {
69
+ generateSvgTypes(dir, typesOutput, root);
70
+ server.ws.send({ type: "full-reload" });
71
+ };
72
+ server.watcher.on("add", (path) => {
73
+ if (path.startsWith(fullSvgDir) && path.endsWith(".svg")) {
74
+ handleChange();
75
+ }
76
+ });
77
+ server.watcher.on("unlink", (path) => {
78
+ if (path.startsWith(fullSvgDir) && path.endsWith(".svg")) {
79
+ handleChange();
80
+ }
81
+ });
82
+ }
83
+ };
84
+ }
85
+ export {
86
+ svelteSvgPlugin as default
87
+ };
@@ -0,0 +1,117 @@
1
+ <script lang="ts" module>
2
+ import type { SvgBase64, SvgColor, NumberWithUnit, SvgRegistry } from '../types'
3
+
4
+ let globalRegistry: SvgRegistry = {}
5
+
6
+ export function setSvgRegistry(registry: SvgRegistry) {
7
+ globalRegistry = registry
8
+ }
9
+
10
+ export function getSvgRegistry(): SvgRegistry {
11
+ return globalRegistry
12
+ }
13
+
14
+ function classValue2className(value: string | string[] | Record<string, boolean> | undefined | null): string {
15
+ if (!value) return ''
16
+ if (typeof value === 'string') return value
17
+ if (Array.isArray(value)) return value.filter(Boolean).join(' ')
18
+ if (typeof value === 'object') {
19
+ return Object.entries(value)
20
+ .filter(([_, condition]) => condition)
21
+ .map(([className]) => className)
22
+ .join(' ')
23
+ }
24
+ return ''
25
+ }
26
+
27
+ function processSvgHtml(
28
+ svgBase64: SvgBase64,
29
+ color: SvgColor,
30
+ size: NumberWithUnit,
31
+ className: string | undefined
32
+ ): string {
33
+ const svgHtml = decodeURIComponent(svgBase64).replace(/^data:image\/svg\+xml,/, '')
34
+ const parser = new DOMParser()
35
+ const doc = parser.parseFromString(svgHtml, 'image/svg+xml')
36
+ const svg = doc.querySelector('svg')
37
+
38
+ if (!svg) return ''
39
+
40
+ const classNameString = classValue2className(className)
41
+ if (classNameString.trim() !== '') {
42
+ classNameString.split(/\s+/).forEach((name) => {
43
+ svg.classList.add(name)
44
+ })
45
+ }
46
+
47
+ svg.style.width = String(size)
48
+ svg.style.height = String(size)
49
+ svg.classList.add('shrink-0')
50
+
51
+ // Handle color for shape elements
52
+ if (color !== undefined) {
53
+ const shapes = doc.querySelectorAll('rect,circle,path,ellipse,line,polyline,polygon,text')
54
+ if (typeof color === 'string') {
55
+ shapes.forEach((el) => {
56
+ el.removeAttribute('mask')
57
+ el.setAttribute('fill', color)
58
+ })
59
+ } else if (Array.isArray(color)) {
60
+ shapes.forEach((el, index) => {
61
+ if (color[index]) {
62
+ el.removeAttribute('mask')
63
+ el.setAttribute('fill', color[index])
64
+ }
65
+ })
66
+ }
67
+ }
68
+
69
+ return doc.documentElement.outerHTML
70
+ }
71
+ </script>
72
+
73
+ <script lang="ts">
74
+ import type { HTMLAttributes } from 'svelte/elements'
75
+
76
+ interface Props extends HTMLAttributes<HTMLElement> {
77
+ name: string
78
+ size?: NumberWithUnit
79
+ color?: SvgColor
80
+ }
81
+
82
+ const {
83
+ name,
84
+ size = '40px',
85
+ color,
86
+ class: className = '',
87
+ ...restProps
88
+ }: Props = $props()
89
+
90
+ const promise = $derived.by(() => {
91
+ const loader = globalRegistry[name.toLowerCase()]
92
+ if (loader) {
93
+ return loader()
94
+ }
95
+ return Promise.reject(new Error(`SVG "${name}" not found`))
96
+ })
97
+ </script>
98
+
99
+ {#await promise}
100
+ <span
101
+ data-role="svg-placeholder"
102
+ class={['inline-flex', className].filter(Boolean).join(' ')}
103
+ style="width:{size};height:{size}"
104
+ ></span>
105
+ {:then module}
106
+ <i {...restProps}>
107
+ {@html processSvgHtml(module.default as SvgBase64, color, size, className)}
108
+ </i>
109
+ {:catch}
110
+ <span data-role="svg-error" hidden></span>
111
+ {/await}
112
+
113
+ <style>
114
+ :global(svg.shrink-0) {
115
+ flex-shrink: 0;
116
+ }
117
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { SvgRegistry, SvgColor, NumberWithUnit } from '../types'
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+
4
+ export function setSvgRegistry(registry: SvgRegistry): void
5
+ export function getSvgRegistry(): SvgRegistry
6
+
7
+ interface Props extends Omit<HTMLAttributes<HTMLElement>, 'color'> {
8
+ name: string
9
+ size?: NumberWithUnit
10
+ color?: SvgColor
11
+ }
12
+
13
+ declare const Svg: import('svelte').Component<Props>
14
+ export default Svg
@@ -0,0 +1,14 @@
1
+ export { default as Svg } from './Svg.svelte';
2
+ import { c as SvgModule, d as SvgRegistry } from '../types-Bh508a5v.js';
3
+ export { N as NumberWithUnit, b as SvgBase64, a as SvgColor, S as SvgProps } from '../types-Bh508a5v.js';
4
+ import 'svelte/elements';
5
+
6
+ /**
7
+ * Initialize SVG registry from glob import result
8
+ * @example
9
+ * import { initSvg } from 'vite-plugin-for-svelte-svg/runtime'
10
+ * initSvg(import.meta.glob('./your-svg-dir/*.svg'))
11
+ */
12
+ declare function initSvg(globResult: Record<string, SvgModule>): SvgRegistry;
13
+
14
+ export { SvgModule, SvgRegistry, initSvg };
@@ -0,0 +1,21 @@
1
+ // src/runtime/index.ts
2
+ import { default as default2 } from "./Svg.svelte";
3
+
4
+ // src/runtime/loader.ts
5
+ import { setSvgRegistry } from "./Svg.svelte";
6
+ function initSvg(globResult) {
7
+ const registry = Object.entries(globResult).reduce((acc, [path, loader]) => {
8
+ const lastSlashIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
9
+ const name = path.slice(lastSlashIndex + 1, -4)?.toLowerCase() ?? null;
10
+ if (name !== null) {
11
+ acc[name] = loader;
12
+ }
13
+ return acc;
14
+ }, {});
15
+ setSvgRegistry(registry);
16
+ return registry;
17
+ }
18
+ export {
19
+ default2 as Svg,
20
+ initSvg
21
+ };
@@ -0,0 +1,33 @@
1
+ import { HTMLAttributes } from 'svelte/elements';
2
+
3
+ interface PluginOptions {
4
+ /**
5
+ * SVG files directory (relative to project root)
6
+ * @default 'src/svg'
7
+ */
8
+ dir?: string;
9
+ /**
10
+ * Output path for generated TypeScript types
11
+ * @default 'src/types/svg.d.ts'
12
+ */
13
+ typesOutput?: string;
14
+ /**
15
+ * Whether to generate types on startup
16
+ * @default true
17
+ */
18
+ generateTypes?: boolean;
19
+ }
20
+ type SvgBase64 = `data:image/svg+xml,${string}`;
21
+ type SvgColor = string | string[] | undefined;
22
+ type NumberWithUnit = `${number}${'px' | 'em' | 'rem' | '%' | 'vw' | 'vh'}` | number | string;
23
+ interface SvgProps extends Omit<HTMLAttributes<HTMLElement>, 'color'> {
24
+ name: string;
25
+ size?: NumberWithUnit;
26
+ color?: SvgColor;
27
+ }
28
+ type SvgModule = () => Promise<{
29
+ default: string;
30
+ }>;
31
+ type SvgRegistry = Record<string, SvgModule>;
32
+
33
+ export type { NumberWithUnit as N, PluginOptions as P, SvgProps as S, SvgColor as a, SvgBase64 as b, SvgModule as c, SvgRegistry as d };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "vite-plugin-for-svelte-svg",
3
+ "version": "0.9.0",
4
+ "description": "Vite plugin for SVG handling in Svelte projects with dynamic color and size support",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./runtime": {
14
+ "types": "./dist/runtime/index.d.ts",
15
+ "import": "./dist/runtime/index.js"
16
+ },
17
+ "./runtime/Svg.svelte": {
18
+ "types": "./dist/runtime/Svg.svelte.d.ts",
19
+ "svelte": "./dist/runtime/Svg.svelte"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup && node -e \"require('fs').cpSync('src/runtime/Svg.svelte', 'dist/runtime/Svg.svelte'); require('fs').cpSync('src/runtime/Svg.svelte.d.ts', 'dist/runtime/Svg.svelte.d.ts')\"",
27
+ "prepublishOnly": "pnpm build"
28
+ },
29
+ "peerDependencies": {
30
+ "svelte": "^5.0.0",
31
+ "vite": "^5.0.0 || ^6.0.0"
32
+ },
33
+ "dependencies": {
34
+ "glob": "^11.0.2"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "svelte": "^5.0.0",
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.0.0",
41
+ "vite": "^6.0.0"
42
+ },
43
+ "keywords": [
44
+ "vite",
45
+ "vite-plugin",
46
+ "svelte",
47
+ "svg"
48
+ ],
49
+ "license": "MIT",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/meiseayoung/vite-plugin-svelte-svg.git"
53
+ },
54
+ "homepage": "https://github.com/meiseayoung/vite-plugin-svelte-svg#readme",
55
+ "bugs": {
56
+ "url": "https://github.com/meiseayoung/vite-plugin-svelte-svg/issues"
57
+ },
58
+ "pnpm": {
59
+ "ignoredBuiltDependencies": [
60
+ "esbuild"
61
+ ]
62
+ }
63
+ }