@epoch32/visage 1.1.5 → 1.2.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.
@@ -15,6 +15,8 @@ function parseNamedImports(importClause) {
15
15
  };
16
16
  });
17
17
  }
18
+ var VIRTUAL_PREFIX = "virtual:visage-icons";
19
+ var RESOLVED_PREFIX = `\x00${VIRTUAL_PREFIX}`;
18
20
  function visageLucide(options = {}) {
19
21
  const aliases = options.aliases ?? ["lucide-react"];
20
22
  return {
@@ -23,17 +25,33 @@ function visageLucide(options = {}) {
23
25
  config() {
24
26
  return {
25
27
  optimizeDeps: {
26
- exclude: aliases
28
+ exclude: [...aliases, VIRTUAL_PREFIX]
27
29
  }
28
30
  };
29
31
  },
30
32
  resolveId(id) {
33
+ if (id.startsWith(VIRTUAL_PREFIX)) {
34
+ return `\x00${id}`;
35
+ }
31
36
  if (aliases.includes(id)) {
32
37
  return `\x00visage-lucide:${id}`;
33
38
  }
34
39
  return null;
35
40
  },
36
41
  load(id) {
42
+ if (id.startsWith(RESOLVED_PREFIX)) {
43
+ const query = id.slice(RESOLVED_PREFIX.length);
44
+ const params = new URLSearchParams(query.startsWith("?") ? query.slice(1) : "");
45
+ const names = (params.get("icons") || "").split(",").map((s) => s.trim()).filter(Boolean);
46
+ const imports = names.map((n) => `${n} as __icon_node_${n}`).join(", ");
47
+ const exports = names.map((n) => `export const ${n} = /* @__PURE__ */ createIcon("${n}", __icon_node_${n});`).join(`
48
+ `);
49
+ return `import { createIcon } from "visage/ui";
50
+ import { ${imports} } from "lucide";
51
+
52
+ ${exports}
53
+ `;
54
+ }
37
55
  if (id.startsWith("\x00visage-lucide:")) {
38
56
  return `
39
57
  import * as lucideIcons from "lucide";
@@ -51,10 +69,6 @@ export const __getIcon = (name) => {
51
69
  export default new Proxy({}, {
52
70
  get: (_, prop) => typeof prop === "string" ? __getIcon(prop) : undefined
53
71
  });
54
-
55
- // Dynamic proxy export for all lucide icon names
56
- ${Object.keys({})};
57
- export * from "lucide";
58
72
  `;
59
73
  }
60
74
  return null;
@@ -66,20 +80,25 @@ export * from "lucide";
66
80
  let transformed = code;
67
81
  let hasMatches = false;
68
82
  for (const alias of aliases) {
69
- const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`, "g");
70
- transformed = transformed.replace(regex, (_match, importClause) => {
83
+ const importRegex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`, "g");
84
+ transformed = transformed.replace(importRegex, (_match, importClause) => {
71
85
  const imports = parseNamedImports(importClause);
72
86
  if (imports.length === 0)
73
87
  return "";
74
88
  hasMatches = true;
75
- const lucideNamedImports = imports.map((imp) => `${imp.importedName} as __icon_node_${imp.importedName}`).join(", ");
76
- const componentDeclarations = imports.map((imp) => {
77
- return `const ${imp.localName} = /* @__PURE__ */ __visageCreateIcon("${imp.importedName}", __icon_node_${imp.importedName});`;
78
- }).join(`
79
- `);
80
- return `import { createIcon as __visageCreateIcon } from "visage/ui";
81
- import { ${lucideNamedImports} } from "lucide";
82
- ${componentDeclarations}`;
89
+ const queryIcons = Array.from(new Set(imports.map((i) => i.importedName))).join(",");
90
+ const specifiers = imports.map((i) => i.importedName === i.localName ? i.importedName : `${i.importedName} as ${i.localName}`).join(", ");
91
+ return `import { ${specifiers} } from "${VIRTUAL_PREFIX}?icons=${queryIcons}";`;
92
+ });
93
+ const exportRegex = new RegExp(`export\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`, "g");
94
+ transformed = transformed.replace(exportRegex, (_match, exportClause) => {
95
+ const exports = parseNamedImports(exportClause);
96
+ if (exports.length === 0)
97
+ return "";
98
+ hasMatches = true;
99
+ const queryIcons = Array.from(new Set(exports.map((i) => i.importedName))).join(",");
100
+ const specifiers = exports.map((i) => i.importedName === i.localName ? i.importedName : `${i.importedName} as ${i.localName}`).join(", ");
101
+ return `export { ${specifiers} } from "${VIRTUAL_PREFIX}?icons=${queryIcons}";`;
83
102
  });
84
103
  }
85
104
  if (!hasMatches) {
@@ -6,22 +6,5 @@ export interface VisageLucideOptions {
6
6
  */
7
7
  aliases?: string[];
8
8
  }
9
- /**
10
- * Official Visage Lucide Vite Plugin
11
- *
12
- * Statically transforms `import { Icon } from "lucide-react"` at build time into
13
- * native, tree-shaken Visage SVG component instances using `createIcon` and `lucide`.
14
- *
15
- * Example:
16
- * ```ts
17
- * // vite.config.ts
18
- * import { defineConfig } from "vite";
19
- * import visage, { visageLucide } from "visage/vite";
20
- *
21
- * export default defineConfig({
22
- * plugins: [visage(), visageLucide()]
23
- * });
24
- * ```
25
- */
26
9
  export declare function visageLucide(options?: VisageLucideOptions): VisageVitePlugin;
27
10
  export default visageLucide;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epoch32/visage",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "Lightning-fast, zero-VDOM, fine-grained reactive UI library with full TypeScript support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,6 +48,9 @@ function parseNamedImports(importClause: string): ParsedImport[] {
48
48
  * });
49
49
  * ```
50
50
  */
51
+ const VIRTUAL_PREFIX = "virtual:visage-icons";
52
+ const RESOLVED_PREFIX = `\0${VIRTUAL_PREFIX}`;
53
+
51
54
  export function visageLucide(options: VisageLucideOptions = {}): VisageVitePlugin {
52
55
  const aliases = options.aliases ?? ["lucide-react"];
53
56
 
@@ -58,12 +61,15 @@ export function visageLucide(options: VisageLucideOptions = {}): VisageVitePlugi
58
61
  config() {
59
62
  return {
60
63
  optimizeDeps: {
61
- exclude: aliases
64
+ exclude: [...aliases, VIRTUAL_PREFIX]
62
65
  }
63
66
  };
64
67
  },
65
68
 
66
69
  resolveId(id: string) {
70
+ if (id.startsWith(VIRTUAL_PREFIX)) {
71
+ return `\0${id}`;
72
+ }
67
73
  if (aliases.includes(id)) {
68
74
  return `\0visage-lucide:${id}`;
69
75
  }
@@ -71,6 +77,22 @@ export function visageLucide(options: VisageLucideOptions = {}): VisageVitePlugi
71
77
  },
72
78
 
73
79
  load(id: string) {
80
+ if (id.startsWith(RESOLVED_PREFIX)) {
81
+ const query = id.slice(RESOLVED_PREFIX.length);
82
+ const params = new URLSearchParams(query.startsWith("?") ? query.slice(1) : "");
83
+ const names = (params.get("icons") || "")
84
+ .split(",")
85
+ .map((s) => s.trim())
86
+ .filter(Boolean);
87
+
88
+ const imports = names.map((n) => `${n} as __icon_node_${n}`).join(", ");
89
+ const exports = names
90
+ .map((n) => `export const ${n} = /* @__PURE__ */ createIcon("${n}", __icon_node_${n});`)
91
+ .join("\n");
92
+
93
+ return `import { createIcon } from "visage/ui";\nimport { ${imports} } from "lucide";\n\n${exports}\n`;
94
+ }
95
+
74
96
  if (id.startsWith("\0visage-lucide:")) {
75
97
  return `
76
98
  import * as lucideIcons from "lucide";
@@ -88,10 +110,6 @@ export const __getIcon = (name) => {
88
110
  export default new Proxy({}, {
89
111
  get: (_, prop) => typeof prop === "string" ? __getIcon(prop) : undefined
90
112
  });
91
-
92
- // Dynamic proxy export for all lucide icon names
93
- ${Object.keys({})};
94
- export * from "lucide";
95
113
  `;
96
114
  }
97
115
  return null;
@@ -106,26 +124,48 @@ export * from "lucide";
106
124
  let hasMatches = false;
107
125
 
108
126
  for (const alias of aliases) {
109
- // Regex to match: import { ... } from "lucide-react" or 'lucide-react'
110
- const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`, "g");
111
-
112
- transformed = transformed.replace(regex, (_match, importClause) => {
127
+ // Match: import { ... } from "alias"
128
+ const importRegex = new RegExp(
129
+ `import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`,
130
+ "g"
131
+ );
132
+ transformed = transformed.replace(importRegex, (_match, importClause) => {
113
133
  const imports = parseNamedImports(importClause);
114
134
  if (imports.length === 0) return "";
115
-
116
135
  hasMatches = true;
117
136
 
118
- const lucideNamedImports = imports
119
- .map((imp) => `${imp.importedName} as __icon_node_${imp.importedName}`)
137
+ const queryIcons = Array.from(new Set(imports.map((i) => i.importedName))).join(",");
138
+ const specifiers = imports
139
+ .map((i) =>
140
+ i.importedName === i.localName
141
+ ? i.importedName
142
+ : `${i.importedName} as ${i.localName}`
143
+ )
120
144
  .join(", ");
121
145
 
122
- const componentDeclarations = imports
123
- .map((imp) => {
124
- return `const ${imp.localName} = /* @__PURE__ */ __visageCreateIcon("${imp.importedName}", __icon_node_${imp.importedName});`;
125
- })
126
- .join("\n");
146
+ return `import { ${specifiers} } from "${VIRTUAL_PREFIX}?icons=${queryIcons}";`;
147
+ });
148
+
149
+ // Match: export { ... } from "alias"
150
+ const exportRegex = new RegExp(
151
+ `export\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`,
152
+ "g"
153
+ );
154
+ transformed = transformed.replace(exportRegex, (_match, exportClause) => {
155
+ const exports = parseNamedImports(exportClause);
156
+ if (exports.length === 0) return "";
157
+ hasMatches = true;
158
+
159
+ const queryIcons = Array.from(new Set(exports.map((i) => i.importedName))).join(",");
160
+ const specifiers = exports
161
+ .map((i) =>
162
+ i.importedName === i.localName
163
+ ? i.importedName
164
+ : `${i.importedName} as ${i.localName}`
165
+ )
166
+ .join(", ");
127
167
 
128
- return `import { createIcon as __visageCreateIcon } from "visage/ui";\nimport { ${lucideNamedImports} } from "lucide";\n${componentDeclarations}`;
168
+ return `export { ${specifiers} } from "${VIRTUAL_PREFIX}?icons=${queryIcons}";`;
129
169
  });
130
170
  }
131
171