@epoch32/visage 1.1.4 → 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,43 +25,80 @@ 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
- return "export default {};";
56
+ return `
57
+ import * as lucideIcons from "lucide";
58
+ import { createIcon } from "visage/ui";
59
+
60
+ const cache = new Map();
61
+ export const __getIcon = (name) => {
62
+ if (cache.has(name)) return cache.get(name);
63
+ const node = lucideIcons[name];
64
+ const comp = node ? createIcon(name, node) : () => null;
65
+ cache.set(name, comp);
66
+ return comp;
67
+ };
68
+
69
+ export default new Proxy({}, {
70
+ get: (_, prop) => typeof prop === "string" ? __getIcon(prop) : undefined
71
+ });
72
+ `;
39
73
  }
40
74
  return null;
41
75
  },
42
76
  transform(code, id) {
43
- if (!id.match(/\.[jt]sx?$/) || id.includes("node_modules")) {
77
+ if (!id.match(/\.([jt]sx?|mdx?)$/) || id.includes("node_modules")) {
44
78
  return null;
45
79
  }
46
80
  let transformed = code;
47
81
  let hasMatches = false;
48
82
  for (const alias of aliases) {
49
- const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`, "g");
50
- 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) => {
51
85
  const imports = parseNamedImports(importClause);
52
86
  if (imports.length === 0)
53
87
  return "";
54
88
  hasMatches = true;
55
- const lucideNamedImports = imports.map((imp) => `${imp.importedName} as __icon_node_${imp.importedName}`).join(", ");
56
- const componentDeclarations = imports.map((imp) => {
57
- return `const ${imp.localName} = /* @__PURE__ */ __visageCreateIcon("${imp.importedName}", __icon_node_${imp.importedName});`;
58
- }).join(`
59
- `);
60
- return `import { createIcon as __visageCreateIcon } from "visage/ui";
61
- import { ${lucideNamedImports} } from "lucide";
62
- ${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}";`;
63
102
  });
64
103
  }
65
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.4",
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,14 +77,46 @@ 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
- return "export default {};";
97
+ return `
98
+ import * as lucideIcons from "lucide";
99
+ import { createIcon } from "visage/ui";
100
+
101
+ const cache = new Map();
102
+ export const __getIcon = (name) => {
103
+ if (cache.has(name)) return cache.get(name);
104
+ const node = lucideIcons[name];
105
+ const comp = node ? createIcon(name, node) : () => null;
106
+ cache.set(name, comp);
107
+ return comp;
108
+ };
109
+
110
+ export default new Proxy({}, {
111
+ get: (_, prop) => typeof prop === "string" ? __getIcon(prop) : undefined
112
+ });
113
+ `;
76
114
  }
77
115
  return null;
78
116
  },
79
117
 
80
118
  transform(code: string, id: string) {
81
- if (!id.match(/\.[jt]sx?$/) || id.includes("node_modules")) {
119
+ if (!id.match(/\.([jt]sx?|mdx?)$/) || id.includes("node_modules")) {
82
120
  return null;
83
121
  }
84
122
 
@@ -86,26 +124,48 @@ export function visageLucide(options: VisageLucideOptions = {}): VisageVitePlugi
86
124
  let hasMatches = false;
87
125
 
88
126
  for (const alias of aliases) {
89
- // Regex to match: import { ... } from "lucide-react" or 'lucide-react'
90
- const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${alias}['"];?`, "g");
91
-
92
- 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) => {
93
133
  const imports = parseNamedImports(importClause);
94
134
  if (imports.length === 0) return "";
95
-
96
135
  hasMatches = true;
97
136
 
98
- const lucideNamedImports = imports
99
- .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
+ )
100
144
  .join(", ");
101
145
 
102
- const componentDeclarations = imports
103
- .map((imp) => {
104
- return `const ${imp.localName} = /* @__PURE__ */ __visageCreateIcon("${imp.importedName}", __icon_node_${imp.importedName});`;
105
- })
106
- .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(", ");
107
167
 
108
- return `import { createIcon as __visageCreateIcon } from "visage/ui";\nimport { ${lucideNamedImports} } from "lucide";\n${componentDeclarations}`;
168
+ return `export { ${specifiers} } from "${VIRTUAL_PREFIX}?icons=${queryIcons}";`;
109
169
  });
110
170
  }
111
171