@stephansama/vite-iconify-svgmap 0.1.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.
@@ -0,0 +1,23 @@
1
+ import { t as Options } from "../type-S4YO472i.mjs";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/svelte.d.ts
5
+ /**
6
+ * Vite plugins for sveltekit. add after `sveltekit()`:
7
+ *
8
+ * ```js
9
+ * plugins: [sveltekit(), iconifySvgmap()];
10
+ * ```
11
+ *
12
+ * Sveltekit prerenders in a worker thread during its server build's
13
+ * `writeBundle` and runs the adapter in `closeBundle`:
14
+ *
15
+ * - When the client build is written, an empty placeholder sprite is added for
16
+ * every installed icon pack so the prerender crawler does not fail on `<use
17
+ * href>` links to sprites that do not exist yet
18
+ * - After prerendering the real sprites replace the placeholders (unused ones are
19
+ * removed), before the adapter copies the client output
20
+ */
21
+ declare function iconifySvgmapSvelteKit(options?: Options): Plugin[];
22
+ //#endregion
23
+ export { iconifySvgmapSvelteKit as default };
@@ -0,0 +1,80 @@
1
+ import { a as getState, i as NAME_REGEX, n as iconifySvgmap, o as name, r as writeSprites } from "../src-CsllISxW.mjs";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ //#region src/svelte.ts
5
+ const EMPTY_SPRITE = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none"></svg>`;
6
+ const JSON_EXTENSION_REGEX = /\.json$/;
7
+ /**
8
+ * Vite plugins for sveltekit. add after `sveltekit()`:
9
+ *
10
+ * ```js
11
+ * plugins: [sveltekit(), iconifySvgmap()];
12
+ * ```
13
+ *
14
+ * Sveltekit prerenders in a worker thread during its server build's
15
+ * `writeBundle` and runs the adapter in `closeBundle`:
16
+ *
17
+ * - When the client build is written, an empty placeholder sprite is added for
18
+ * every installed icon pack so the prerender crawler does not fail on `<use
19
+ * href>` links to sprites that do not exist yet
20
+ * - After prerendering the real sprites replace the placeholders (unused ones are
21
+ * removed), before the adapter copies the client output
22
+ */
23
+ function iconifySvgmapSvelteKit(options = {}) {
24
+ const state = getState();
25
+ return [iconifySvgmap(options), {
26
+ apply: "build",
27
+ name: `${name}:sveltekit`,
28
+ writeBundle: {
29
+ async handler(outputOptions) {
30
+ if (!outputOptions.dir) return;
31
+ if (!Boolean(this.environment?.config.build.ssr)) {
32
+ await writePlaceholders(outputOptions.dir);
33
+ return;
34
+ }
35
+ const clientDirectory = path.resolve(outputOptions.dir, "..", "client");
36
+ const written = new Set(await writeSprites(clientDirectory));
37
+ for (const placeholder of state.placeholders) if (!written.has(placeholder)) await fs.promises.rm(placeholder, { force: true });
38
+ state.placeholders.clear();
39
+ if (written.size > 0) this.info(`wrote ${written.size} svg sprite(s)`);
40
+ },
41
+ order: "post",
42
+ sequential: true
43
+ }
44
+ }];
45
+ async function writePlaceholders(clientDirectory) {
46
+ const spriteDirectory = path.join(clientDirectory, state.spriteDir);
47
+ await fs.promises.mkdir(spriteDirectory, { recursive: true });
48
+ for (const pack of listInstalledPacks(state.root)) {
49
+ const filename = path.join(spriteDirectory, `${pack}.svg`);
50
+ if (fs.existsSync(filename)) continue;
51
+ await fs.promises.writeFile(filename, EMPTY_SPRITE);
52
+ state.placeholders.add(filename);
53
+ }
54
+ }
55
+ }
56
+ /** Icon packs resolvable from `root`, following node's module resolution */
57
+ function listInstalledPacks(root) {
58
+ const packs = /* @__PURE__ */ new Set();
59
+ let directory = path.resolve(root);
60
+ while (true) {
61
+ const modules = path.join(directory, "node_modules");
62
+ for (const pack of readDirectory(path.join(modules, "@iconify-json"))) if (NAME_REGEX.test(pack)) packs.add(pack);
63
+ for (const file of readDirectory(path.join(modules, "@iconify", "json", "json"))) {
64
+ const pack = file.replace(JSON_EXTENSION_REGEX, "");
65
+ if (NAME_REGEX.test(pack)) packs.add(pack);
66
+ }
67
+ const parent = path.dirname(directory);
68
+ if (parent === directory) return packs;
69
+ directory = parent;
70
+ }
71
+ }
72
+ function readDirectory(directory) {
73
+ try {
74
+ return fs.readdirSync(directory);
75
+ } catch {
76
+ return [];
77
+ }
78
+ }
79
+ //#endregion
80
+ export { iconifySvgmapSvelteKit as default };
@@ -0,0 +1,18 @@
1
+ //#region src/type.d.ts
2
+ interface Options {
3
+ /**
4
+ * Folder, relative to the output directory, that sprites for icons
5
+ * registered with `getIcon` are written to
6
+ *
7
+ * @default "_iconify"
8
+ */
9
+ dir?: string;
10
+ /**
11
+ * Directory used to resolve `@iconify-json/*` packages
12
+ *
13
+ * @default vite's `root`
14
+ */
15
+ root?: string | URL;
16
+ }
17
+ //#endregion
18
+ export { Options as t };
@@ -0,0 +1,5 @@
1
+ // lets plain tsc resolve `.astro` imports; editors use @astrojs/ts-plugin
2
+ declare module "*.astro" {
3
+ const component: (properties: Record<string, unknown>) => unknown;
4
+ export default component;
5
+ }
@@ -0,0 +1 @@
1
+ export { default as Icon } from "./icon.astro";
@@ -0,0 +1,31 @@
1
+ ---
2
+ /// <reference types="../../client.d.ts" />
3
+ import type { HTMLAttributes } from "astro/types";
4
+
5
+ import { getIcon } from "virtual:iconify-svgmap";
6
+
7
+ interface Props extends Omit<HTMLAttributes<"svg">, "height" | "width"> {
8
+ /** Icon name inside the pack, e.g. `astro` */
9
+ name: string;
10
+ /** Iconify pack, e.g. `logos` for `@iconify-json/logos` */
11
+ pack: string;
12
+ /** Width and height of the icon @default "1em" */
13
+ size?: number | string;
14
+ /** Accessible label; without it the icon is hidden from assistive tech */
15
+ title?: string;
16
+ }
17
+
18
+ const { name, pack, size = "1em", title, ...attributes } = Astro.props;
19
+ const href = getIcon(pack, name);
20
+ ---
21
+
22
+ <svg
23
+ aria-hidden={title ? undefined : "true"}
24
+ height={size}
25
+ role={title ? "img" : undefined}
26
+ width={size}
27
+ {...attributes}
28
+ >
29
+ {title && <title>{title}</title>}
30
+ <use href={href}></use>
31
+ </svg>
@@ -0,0 +1,3 @@
1
+ export { default as integration } from "../../dist/astro/integration.mjs";
2
+
3
+ export { Icon } from "./component";
@@ -0,0 +1,19 @@
1
+ import type { Component } from "svelte";
2
+ import type { SVGAttributes } from "svelte/elements";
3
+
4
+ export interface IconProps extends Omit<
5
+ SVGAttributes<SVGSVGElement>,
6
+ "height" | "width"
7
+ > {
8
+ /** Icon name inside the pack, e.g. `astro` */
9
+ name: string;
10
+ /** Iconify pack, e.g. `logos` for `@iconify-json/logos` */
11
+ pack: string;
12
+ /** Width and height of the icon @default "1em" */
13
+ size?: number | string;
14
+ /** Accessible label; without it the icon is hidden from assistive tech */
15
+ title?: string;
16
+ }
17
+
18
+ /** Svg sprite icon registered through `getIcon` */
19
+ export declare const Icon: Component<IconProps>;
@@ -0,0 +1 @@
1
+ export { default as Icon } from "./icon.svelte";
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { SVGAttributes } from "svelte/elements";
3
+
4
+ import { getIcon } from "virtual:iconify-svgmap";
5
+
6
+ interface Props extends Omit<
7
+ SVGAttributes<SVGSVGElement>,
8
+ "height" | "width"
9
+ > {
10
+ /** Icon name inside the pack, e.g. `astro` */
11
+ name: string;
12
+ /** Iconify pack, e.g. `logos` for `@iconify-json/logos` */
13
+ pack: string;
14
+ /** Width and height of the icon @default "1em" */
15
+ size?: number | string;
16
+ /** Accessible label; without it the icon is hidden from assistive tech */
17
+ title?: string;
18
+ }
19
+
20
+ const { name, pack, size = "1em", title, ...attributes }: Props = $props();
21
+ const href = $derived(getIcon(pack, name));
22
+ </script>
23
+
24
+ <svg
25
+ aria-hidden={title ? undefined : "true"}
26
+ height={size}
27
+ role={title ? "img" : undefined}
28
+ width={size}
29
+ {...attributes}
30
+ >
31
+ {#if title}<title>{title}</title>{/if}
32
+ <use {href}></use>
33
+ </svg>
@@ -0,0 +1,3 @@
1
+ export { default as integration } from "../../dist/svelte/integration.mjs";
2
+
3
+ export { Icon, type IconProps } from "./component";
@@ -0,0 +1,3 @@
1
+ export { default as integration } from "../../dist/svelte/integration.mjs";
2
+
3
+ export { Icon } from "./component.js";
package/package.json ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "name": "@stephansama/vite-iconify-svgmap",
3
+ "version": "0.1.0",
4
+ "description": "Vite plugin for generating iconify svg sprite maps in memory",
5
+ "keywords": [
6
+ "vite",
7
+ "vite-plugin",
8
+ "astro",
9
+ "astro-integration",
10
+ "svgmap",
11
+ "svg-sprite",
12
+ "iconify",
13
+ "tanstack-intent",
14
+ "astro-component",
15
+ "svelte"
16
+ ],
17
+ "homepage": "https://packages.stephansama.info/api/@stephansama/vite-iconify-svgmap",
18
+ "bugs": {
19
+ "url": "https://github.com/stephansama/packages/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/stephansama/packages.git",
24
+ "directory": "core/vite-iconify-svgmap"
25
+ },
26
+ "license": "MIT",
27
+ "author": {
28
+ "name": "Stephan Randle",
29
+ "email": "stephanrandle.dev@gmail.com",
30
+ "url": "https://stephansama.info"
31
+ },
32
+ "sideEffects": false,
33
+ "type": "module",
34
+ "exports": {
35
+ ".": "./dist/index.mjs",
36
+ "./astro": "./frameworks/astro/index.ts",
37
+ "./astro/component": "./frameworks/astro/component.ts",
38
+ "./astro/integration": "./dist/astro/integration.mjs",
39
+ "./client": {
40
+ "types": "./client.d.ts"
41
+ },
42
+ "./package.json": "./package.json",
43
+ "./svelte": {
44
+ "types": "./frameworks/svelte/index.d.ts",
45
+ "svelte": "./frameworks/svelte/component.js",
46
+ "default": "./frameworks/svelte/index.js"
47
+ },
48
+ "./svelte/component": {
49
+ "types": "./frameworks/svelte/component.d.ts",
50
+ "svelte": "./frameworks/svelte/component.js",
51
+ "default": "./frameworks/svelte/component.js"
52
+ },
53
+ "./svelte/integration": "./dist/svelte/integration.mjs"
54
+ },
55
+ "types": "./dist/index.d.mts",
56
+ "files": [
57
+ "client.d.ts",
58
+ "dist",
59
+ "frameworks",
60
+ "skills"
61
+ ],
62
+ "devDependencies": {
63
+ "@iconify-json/logos": "1.2.10",
64
+ "@iconify-json/octicon": "1.2.21",
65
+ "@iconify/types": "2.0.0",
66
+ "@iconify/utils": "2.3.0",
67
+ "@tanstack/intent": "0.0.41",
68
+ "astro": "6.3.1",
69
+ "svelte": "5.51.2",
70
+ "tsdown": "0.21.10",
71
+ "vite": "6.3.5"
72
+ },
73
+ "peerDependencies": {
74
+ "astro": ">=5",
75
+ "svelte": ">=5",
76
+ "vite": ">=6"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "astro": {
80
+ "optional": true
81
+ },
82
+ "svelte": {
83
+ "optional": true
84
+ }
85
+ },
86
+ "engines": {
87
+ "node": ">=24"
88
+ },
89
+ "publishConfig": {
90
+ "access": "public",
91
+ "provenance": true
92
+ },
93
+ "inlinedDependencies": {
94
+ "@antfu/install-pkg": "1.1.0",
95
+ "@antfu/utils": "8.1.1",
96
+ "@iconify/utils": "2.3.0",
97
+ "acorn": "8.16.0",
98
+ "kolorist": "1.8.0",
99
+ "local-pkg": "1.1.2",
100
+ "mlly": "1.8.2",
101
+ "package-manager-detector": "1.6.0",
102
+ "pathe": "2.0.3",
103
+ "quansync": "0.2.11",
104
+ "tinyexec": "1.1.2",
105
+ "ufo": "1.6.3"
106
+ },
107
+ "scripts": {
108
+ "build": "tsdown",
109
+ "build:snapshot": "tsdown -u",
110
+ "dev": "tsdown --watch",
111
+ "lint": "eslint ./",
112
+ "lint:fix": "eslint ./ --fix"
113
+ }
114
+ }
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: vite-iconify-svgmap
3
+ description: >
4
+ Vite plugin (with an Astro integration) that builds SVG sprite maps from
5
+ @iconify-json/* icon packs in memory. Import icons statically with
6
+ `import href from "virtual:iconify-svgmap/<pack>/<icon>"`, or call
7
+ `getIcon(pack, name)` from "virtual:iconify-svgmap" for icons only known while
8
+ rendering. Add `@stephansama/vite-iconify-svgmap/client` to tsconfig types.
9
+ type: framework
10
+ requires:
11
+ - vite
12
+ library: "@stephansama/vite-iconify-svgmap"
13
+ library_version: "0.0.0"
14
+ sources:
15
+ - stephansama/packages:core/vite-iconify-svgmap/src/index.ts
16
+ - stephansama/packages:core/vite-iconify-svgmap/src/astro.ts
17
+ - stephansama/packages:core/vite-iconify-svgmap/src/svelte.ts
18
+ - stephansama/packages:core/vite-iconify-svgmap/frameworks/astro/icon.astro
19
+ - stephansama/packages:core/vite-iconify-svgmap/frameworks/svelte/icon.svelte
20
+ - stephansama/packages:core/vite-iconify-svgmap/src/state.ts
21
+ - stephansama/packages:core/vite-iconify-svgmap/client.d.ts
22
+ ---
23
+
24
+ # vite-iconify-svgmap
25
+
26
+ Generates SVG sprite maps from Iconify icon packs. Icon usage is tracked in memory; the only files written are the final sprites.
27
+
28
+ ## Setup
29
+
30
+ 1. Install the plugin and at least one `@iconify-json/*` pack:
31
+
32
+ ```sh
33
+ pnpm add -D @stephansama/vite-iconify-svgmap @iconify-json/mdi
34
+ ```
35
+
36
+ 1. Astro: add the integration to `astro.config.mjs`:
37
+
38
+ ```js
39
+ import iconifySvgmap from "@stephansama/vite-iconify-svgmap/astro/integration";
40
+ import { defineConfig } from "astro/config";
41
+
42
+ export default defineConfig({
43
+ integrations: [iconifySvgmap()],
44
+ });
45
+ ```
46
+
47
+ SvelteKit: add the plugins from `@stephansama/vite-iconify-svgmap/svelte/integration` after `sveltekit()` in `vite.config.js`:
48
+
49
+ ```js
50
+ import iconifySvgmap from "@stephansama/vite-iconify-svgmap/svelte/integration";
51
+ import { sveltekit } from "@sveltejs/kit/vite";
52
+ import { defineConfig } from "vite";
53
+
54
+ export default defineConfig({ plugins: [sveltekit(), iconifySvgmap()] });
55
+ ```
56
+
57
+ Plain Vite: add `iconifySvgmap()` from `@stephansama/vite-iconify-svgmap` to `plugins`.
58
+
59
+ 1. Add the virtual module types to `tsconfig.json`:
60
+
61
+ ```json
62
+ { "compilerOptions": { "types": ["@stephansama/vite-iconify-svgmap/client"] } }
63
+ ```
64
+
65
+ ## Core Patterns
66
+
67
+ ### Static imports (preferred)
68
+
69
+ ```astro
70
+ ---
71
+ import home from "virtual:iconify-svgmap/mdi/home";
72
+ ---
73
+
74
+ <svg width="24" height="24" aria-hidden="true"><use href={home}></use></svg>
75
+ ```
76
+
77
+ The default export is the sprite href (`/_astro/mdi.<hash>.svg#home` in builds). Unknown packs or icons fail the build.
78
+
79
+ ### Icons only known while rendering
80
+
81
+ ```astro
82
+ ---
83
+ import { getIcon } from "virtual:iconify-svgmap";
84
+
85
+ const href = getIcon(entry.data.pack, entry.data.icon);
86
+ ---
87
+
88
+ <svg width="24" height="24" aria-hidden="true"><use href={href}></use></svg>
89
+ ```
90
+
91
+ `getIcon` is synchronous. It registers the icon in memory and returns `/_iconify/<pack>.svg?v=<build>#<icon>`. The Astro integration writes those sprites in `astro:build:done`; without Astro call `writeSprites(clientOutDir)` after rendering.
92
+
93
+ ### Icon components (astro, svelte)
94
+
95
+ ```astro
96
+ ---
97
+ import { Icon } from "@stephansama/vite-iconify-svgmap/astro/component";
98
+ ---
99
+
100
+ <Icon pack="mdi" name="home" size={24} title="Home" class="nav-icon" />
101
+ ```
102
+
103
+ `@stephansama/vite-iconify-svgmap/astro/component` and `@stephansama/vite-iconify-svgmap/svelte/component` (Svelte 5) both export `Icon`. It calls `getIcon` and renders `<svg><use href /></svg>`. Props: `pack`, `name`, `size` (default `"1em"`), `title` (adds `role="img"`, otherwise `aria-hidden="true"`); other attributes pass through to `<svg>`. Same rules as `getIcon`: needs the Astro integration (or `writeSprites`) and server rendering during the build; client-only Svelte components are not registered.
104
+
105
+ ## Common Mistakes
106
+
107
+ ### HIGH Computed virtual imports
108
+
109
+ Wrong:
110
+
111
+ ```js
112
+ const href = await import(`virtual:iconify-svgmap/${pack}/${name}`);
113
+ ```
114
+
115
+ Correct:
116
+
117
+ ```js
118
+ import { getIcon } from "virtual:iconify-svgmap";
119
+ const href = getIcon(pack, name);
120
+ ```
121
+
122
+ Vite cannot resolve template-literal virtual imports. Use `getIcon` for dynamic names.
123
+
124
+ ### HIGH Using the raw plugin with getIcon in SvelteKit
125
+
126
+ Wrong:
127
+
128
+ ```js
129
+ export default defineConfig({ plugins: [sveltekit(), iconifySvgmap()] }); // from "@stephansama/vite-iconify-svgmap"
130
+ ```
131
+
132
+ Correct:
133
+
134
+ ```js
135
+ import iconifySvgmap from "@stephansama/vite-iconify-svgmap/svelte/integration";
136
+ export default defineConfig({ plugins: [sveltekit(), iconifySvgmap()] });
137
+ ```
138
+
139
+ Without the `/svelte/integration` plugins the prerender crawler fails with `404 /_iconify/<pack>.svg` and sprites are never written.
140
+
141
+ ### HIGH Using the raw plugin with getIcon in Astro
142
+
143
+ Wrong:
144
+
145
+ ```js
146
+ export default defineConfig({ vite: { plugins: [iconifySvgmap()] } });
147
+ ```
148
+
149
+ Correct:
150
+
151
+ ```js
152
+ export default defineConfig({ integrations: [iconifySvgmap()] });
153
+ ```
154
+
155
+ Astro prerenders after Vite's build hooks, so only the integration (`/astro/integration`) can write `getIcon` sprites. Static imports work either way.
156
+
157
+ ### MEDIUM getIcon at request time
158
+
159
+ Icons first requested by on-demand rendered routes, only by client-side code, or by prerendering in another runtime (e.g. workerd) are not included in written sprites. Worker threads of the build process (SvelteKit's prerenderer) are supported. Use static imports, or make sure those icons are also rendered during the build.
160
+
161
+ Source: `core/vite-iconify-svgmap/src/index.ts`