gonia 0.0.3 → 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.
@@ -4,3 +4,4 @@
4
4
  * @packageDocumentation
5
5
  */
6
6
  export { gonia, gonia as default } from './plugin.js';
7
+ export type { GoniaPluginOptions } from './plugin.js';
@@ -2,18 +2,41 @@
2
2
  * Vite plugin for gonia.js.
3
3
  *
4
4
  * @remarks
5
- * Transforms directive functions to add $inject arrays at build time,
6
- * making the code minification-safe without manual annotations.
5
+ * - Auto-detects directive usage in templates and injects imports
6
+ * - Transforms directive functions to add $inject arrays at build time
7
+ * - Configures Vite for SSR with gonia.js
7
8
  *
8
9
  * @packageDocumentation
9
10
  */
10
11
  import type { Plugin } from 'vite';
11
12
  /**
12
- * Cubist.js Vite plugin.
13
+ * Plugin options.
14
+ */
15
+ export interface GoniaPluginOptions {
16
+ /**
17
+ * Automatically detect and import directives from source code.
18
+ * @defaultValue true
19
+ */
20
+ autoDirectives?: boolean;
21
+ /**
22
+ * Additional directives to include (for runtime/dynamic cases).
23
+ * Use directive names without the 'g-' prefix.
24
+ * @example ['text', 'for', 'if']
25
+ */
26
+ includeDirectives?: string[];
27
+ /**
28
+ * Directives to exclude from auto-detection.
29
+ * Use directive names without the 'g-' prefix.
30
+ */
31
+ excludeDirectives?: string[];
32
+ }
33
+ /**
34
+ * Gonia Vite plugin.
13
35
  *
14
36
  * @remarks
15
- * Adds $inject arrays to directive functions for minification safety.
16
- * Also configures Vite for SSR with gonia.js.
37
+ * - Auto-detects directive usage and injects imports
38
+ * - Adds $inject arrays to directive functions for minification safety
39
+ * - Configures Vite for SSR with gonia.js
17
40
  *
18
41
  * @example
19
42
  * ```ts
@@ -25,6 +48,18 @@ import type { Plugin } from 'vite';
25
48
  * plugins: [gonia()]
26
49
  * });
27
50
  * ```
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // With options
55
+ * export default defineConfig({
56
+ * plugins: [gonia({
57
+ * autoDirectives: true,
58
+ * includeDirectives: ['text', 'for'], // For dynamic/runtime HTML
59
+ * excludeDirectives: ['model'], // Never include these
60
+ * })]
61
+ * });
62
+ * ```
28
63
  */
29
- export declare function gonia(): Plugin;
64
+ export declare function gonia(options?: GoniaPluginOptions): Plugin;
30
65
  export default gonia;
@@ -2,60 +2,108 @@
2
2
  * Vite plugin for gonia.js.
3
3
  *
4
4
  * @remarks
5
- * Transforms directive functions to add $inject arrays at build time,
6
- * making the code minification-safe without manual annotations.
5
+ * - Auto-detects directive usage in templates and injects imports
6
+ * - Transforms directive functions to add $inject arrays at build time
7
+ * - Configures Vite for SSR with gonia.js
7
8
  *
8
9
  * @packageDocumentation
9
10
  */
10
11
  /**
11
- * Parse function parameters from a function string.
12
+ * Map of directive names to their export names from gonia.
12
13
  */
13
- function parseParams(fnStr) {
14
- // Match function parameters: handles regular, arrow, and async functions
15
- const match = fnStr.match(/^[^(]*\(([^)]*)\)/);
16
- if (!match)
17
- return null;
18
- const params = match[1];
19
- if (!params.trim())
20
- return [];
21
- return params
22
- .split(',')
23
- .map(p => p.trim())
24
- // Remove type annotations (: Type)
25
- .map(p => p.replace(/\s*:.*$/, ''))
26
- // Remove default values (= value)
27
- .map(p => p.replace(/\s*=.*$/, ''))
28
- .filter(Boolean);
14
+ const DIRECTIVE_MAP = {
15
+ text: 'text',
16
+ html: 'html',
17
+ show: 'show',
18
+ template: 'template',
19
+ slot: 'slot',
20
+ class: 'cclass',
21
+ model: 'model',
22
+ on: 'on',
23
+ for: 'cfor',
24
+ if: 'cif',
25
+ };
26
+ /**
27
+ * Detect directive names used in source code.
28
+ */
29
+ function detectDirectives(code, id, isDev) {
30
+ const found = new Set();
31
+ // Pattern 1: g-name as attribute in template literals or strings
32
+ // Matches: g-text, g-for, g-if, etc.
33
+ const attrPattern = /g-([a-z]+)/g;
34
+ let match;
35
+ while ((match = attrPattern.exec(code)) !== null) {
36
+ const name = match[1];
37
+ if (DIRECTIVE_MAP[name]) {
38
+ found.add(name);
39
+ }
40
+ }
41
+ // Pattern 2: Dynamic directive names we can resolve
42
+ // Matches: `g-${expr}` where expr is a string literal or simple variable
43
+ const dynamicPattern = /`g-\$\{([^}]+)\}`/g;
44
+ while ((match = dynamicPattern.exec(code)) !== null) {
45
+ const expr = match[1].trim();
46
+ // Try to resolve simple string literals
47
+ if (/^['"]([a-z]+)['"]$/.test(expr)) {
48
+ const name = expr.slice(1, -1);
49
+ if (DIRECTIVE_MAP[name]) {
50
+ found.add(name);
51
+ }
52
+ }
53
+ else if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(expr)) {
54
+ // It's a variable - try to find its value
55
+ const varPattern = new RegExp(`(?:const|let|var)\\s+${expr}\\s*=\\s*['"]([a-z]+)['"]`);
56
+ const varMatch = code.match(varPattern);
57
+ if (varMatch && DIRECTIVE_MAP[varMatch[1]]) {
58
+ found.add(varMatch[1]);
59
+ }
60
+ else if (isDev) {
61
+ console.warn(`[gonia] Could not resolve directive name in \`g-\${${expr}}\` at ${id}\n` +
62
+ ` Add to vite config: includeDirectives: ['expected-directive']`);
63
+ }
64
+ }
65
+ }
66
+ return found;
67
+ }
68
+ /**
69
+ * Generate import statement for detected directives.
70
+ */
71
+ function generateDirectiveImports(directives) {
72
+ if (directives.size === 0)
73
+ return '';
74
+ const imports = [];
75
+ for (const name of directives) {
76
+ const exportName = DIRECTIVE_MAP[name];
77
+ if (exportName) {
78
+ imports.push(exportName);
79
+ }
80
+ }
81
+ if (imports.length === 0)
82
+ return '';
83
+ // Import from gonia/directives which auto-registers
84
+ return `import { ${imports.join(', ')} } from 'gonia/directives';\n`;
29
85
  }
30
86
  /**
31
87
  * Transform source code to add $inject arrays to directive functions.
32
88
  */
33
- function transformCode(code, id) {
34
- // Skip node_modules and non-JS/TS files
35
- if (id.includes('node_modules'))
36
- return null;
37
- if (!/\.(ts|js|tsx|jsx)$/.test(id))
38
- return null;
39
- // Skip if no directive calls
40
- if (!code.includes('directive('))
41
- return null;
89
+ function transformInject(code, id) {
90
+ if (!code.includes('directive(')) {
91
+ return { code, modified: false };
92
+ }
42
93
  let result = code;
43
94
  let modified = false;
44
- // Pattern: directive('name', functionName, ...)
45
- // We need to find the function and add $inject to it
46
95
  const directiveCallPattern = /directive\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
47
96
  const functionNames = new Set();
48
97
  let match;
49
98
  while ((match = directiveCallPattern.exec(code)) !== null) {
50
99
  functionNames.add(match[2]);
51
100
  }
52
- if (functionNames.size === 0)
53
- return null;
101
+ if (functionNames.size === 0) {
102
+ return { code, modified: false };
103
+ }
54
104
  for (const fnName of functionNames) {
55
- // Skip if already has $inject
56
105
  if (code.includes(`${fnName}.$inject`))
57
106
  continue;
58
- // Find function declaration: function fnName(params) or const fnName = (params) =>
59
107
  const fnDeclPattern = new RegExp(`(?:function\\s+${fnName}\\s*\\(([^)]*)\\)|(?:const|let|var)\\s+${fnName}\\s*=\\s*(?:async\\s*)?(?:function\\s*)?\\(([^)]*)\\))`, 'g');
60
108
  const fnMatch = fnDeclPattern.exec(code);
61
109
  if (!fnMatch)
@@ -71,20 +119,20 @@ function transformCode(code, id) {
71
119
  .filter(Boolean);
72
120
  if (params.length === 0)
73
121
  continue;
74
- // Find the directive() call and insert $inject before it
75
122
  const directivePattern = new RegExp(`(directive\\s*\\(\\s*['"\`][^'"\`]+['"\`]\\s*,\\s*${fnName})`, 'g');
76
123
  const injectStatement = `${fnName}.$inject = ${JSON.stringify(params)};\n`;
77
124
  result = result.replace(directivePattern, `${injectStatement}$1`);
78
125
  modified = true;
79
126
  }
80
- return modified ? result : null;
127
+ return { code: result, modified };
81
128
  }
82
129
  /**
83
- * Cubist.js Vite plugin.
130
+ * Gonia Vite plugin.
84
131
  *
85
132
  * @remarks
86
- * Adds $inject arrays to directive functions for minification safety.
87
- * Also configures Vite for SSR with gonia.js.
133
+ * - Auto-detects directive usage and injects imports
134
+ * - Adds $inject arrays to directive functions for minification safety
135
+ * - Configures Vite for SSR with gonia.js
88
136
  *
89
137
  * @example
90
138
  * ```ts
@@ -96,14 +144,76 @@ function transformCode(code, id) {
96
144
  * plugins: [gonia()]
97
145
  * });
98
146
  * ```
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * // With options
151
+ * export default defineConfig({
152
+ * plugins: [gonia({
153
+ * autoDirectives: true,
154
+ * includeDirectives: ['text', 'for'], // For dynamic/runtime HTML
155
+ * excludeDirectives: ['model'], // Never include these
156
+ * })]
157
+ * });
158
+ * ```
99
159
  */
100
- export function gonia() {
160
+ export function gonia(options = {}) {
161
+ const { autoDirectives = true, includeDirectives = [], excludeDirectives = [], } = options;
162
+ let isDev = false;
163
+ // Track which directives have been injected per chunk
164
+ const injectedModules = new Set();
101
165
  return {
102
166
  name: 'gonia',
103
167
  enforce: 'pre',
168
+ configResolved(config) {
169
+ isDev = config.command === 'serve';
170
+ },
104
171
  transform(code, id) {
105
- const result = transformCode(code, id);
106
- if (result) {
172
+ // Skip node_modules (except for $inject transform in gonia itself)
173
+ const isGoniaInternal = id.includes('gonia') && id.includes('node_modules');
174
+ if (id.includes('node_modules') && !isGoniaInternal)
175
+ return null;
176
+ if (!/\.(ts|js|tsx|jsx|html)$/.test(id))
177
+ return null;
178
+ let result = code;
179
+ let modified = false;
180
+ // Collect directives to import
181
+ if (!isGoniaInternal) {
182
+ const detected = new Set();
183
+ // Auto-detect directives if enabled
184
+ if (autoDirectives) {
185
+ for (const name of detectDirectives(code, id, isDev)) {
186
+ detected.add(name);
187
+ }
188
+ }
189
+ // Add explicitly included directives (always, regardless of autoDirectives)
190
+ for (const name of includeDirectives) {
191
+ detected.add(name);
192
+ }
193
+ // Remove excluded directives
194
+ for (const name of excludeDirectives) {
195
+ detected.delete(name);
196
+ }
197
+ // Generate imports if we found directives and haven't already
198
+ if (detected.size > 0 && !injectedModules.has(id)) {
199
+ // Check if this file already imports from gonia/directives
200
+ if (!code.includes("from 'gonia/directives'") && !code.includes('from "gonia/directives"')) {
201
+ const importStatement = generateDirectiveImports(detected);
202
+ if (importStatement) {
203
+ result = importStatement + result;
204
+ modified = true;
205
+ injectedModules.add(id);
206
+ }
207
+ }
208
+ }
209
+ }
210
+ // Transform $inject arrays
211
+ const injectResult = transformInject(result, id);
212
+ if (injectResult.modified) {
213
+ result = injectResult.code;
214
+ modified = true;
215
+ }
216
+ if (modified) {
107
217
  return {
108
218
  code: result,
109
219
  map: null // TODO: proper source map support
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gonia",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "description": "A lightweight, SSR-first reactive UI library with declarative directives",
5
5
  "type": "module",
6
6
  "license": "MIT",