what-compiler 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.
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "what-compiler",
3
+ "version": "0.1.0",
4
+ "description": "JSX compiler for What Framework - transforms JSX to optimized DOM operations",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./babel": "./src/babel-plugin.js",
10
+ "./vite": "./src/vite-plugin.js"
11
+ },
12
+ "keywords": [
13
+ "what",
14
+ "framework",
15
+ "compiler",
16
+ "jsx",
17
+ "babel",
18
+ "vite"
19
+ ],
20
+ "author": "",
21
+ "license": "MIT",
22
+ "peerDependencies": {
23
+ "@babel/core": "^7.0.0"
24
+ },
25
+ "files": [
26
+ "src"
27
+ ],
28
+ "devDependencies": {
29
+ "@babel/core": "^7.23.0",
30
+ "@babel/preset-env": "^7.23.0"
31
+ }
32
+ }
@@ -0,0 +1,535 @@
1
+ /**
2
+ * What Framework Babel Plugin
3
+ * Transforms JSX into optimized DOM operations
4
+ *
5
+ * Features:
6
+ * - Direct DOM creation (no virtual DOM)
7
+ * - Signal auto-unwrapping in expressions
8
+ * - Event modifiers (onClick|preventDefault)
9
+ * - Two-way binding (bind:value)
10
+ * - Static content hoisting
11
+ * - Control flow component optimization
12
+ */
13
+
14
+ const CONTROL_FLOW_COMPONENTS = new Set(['Show', 'For', 'Switch', 'Match', 'Suspense', 'ErrorBoundary', 'Portal']);
15
+ const EVENT_MODIFIERS = new Set(['preventDefault', 'stopPropagation', 'once', 'capture', 'passive', 'self']);
16
+ const SVG_ELEMENTS = new Set([
17
+ 'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse',
18
+ 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask', 'pattern', 'image',
19
+ 'text', 'tspan', 'textPath', 'foreignObject', 'linearGradient', 'radialGradient', 'stop'
20
+ ]);
21
+
22
+ export default function whatBabelPlugin({ types: t }) {
23
+ // Helper to check if an expression might be a signal
24
+ function mightBeSignal(node) {
25
+ // Identifiers that are likely signals
26
+ if (t.isIdentifier(node)) return true;
27
+ // Member expressions like store.count
28
+ if (t.isMemberExpression(node)) return true;
29
+ // Call expressions are definitely reactive
30
+ if (t.isCallExpression(node)) return true;
31
+ return false;
32
+ }
33
+
34
+ // Parse event modifiers from attribute name
35
+ function parseEventModifiers(name) {
36
+ const parts = name.split('|');
37
+ const eventName = parts[0];
38
+ const modifiers = parts.slice(1).filter(m => EVENT_MODIFIERS.has(m));
39
+ return { eventName, modifiers };
40
+ }
41
+
42
+ // Check if attribute is a binding
43
+ function isBindingAttribute(name) {
44
+ return name.startsWith('bind:');
45
+ }
46
+
47
+ // Get the binding property from bind:value -> value
48
+ function getBindingProperty(name) {
49
+ return name.slice(5); // Remove 'bind:'
50
+ }
51
+
52
+ // Check if element is a component (starts with uppercase)
53
+ function isComponent(name) {
54
+ return /^[A-Z]/.test(name);
55
+ }
56
+
57
+ // Check if element is SVG
58
+ function isSVGElement(name) {
59
+ return SVG_ELEMENTS.has(name);
60
+ }
61
+
62
+ // Transform JSX attribute value to runtime value
63
+ function transformAttributeValue(value, isDynamic = false) {
64
+ if (!value) {
65
+ // Boolean attribute: <input disabled />
66
+ return t.booleanLiteral(true);
67
+ }
68
+
69
+ if (t.isJSXExpressionContainer(value)) {
70
+ const expr = value.expression;
71
+
72
+ // Wrap potentially reactive expressions
73
+ if (isDynamic && mightBeSignal(expr)) {
74
+ // () => expr (auto-unwrap at runtime)
75
+ return t.arrowFunctionExpression([], expr);
76
+ }
77
+
78
+ return expr;
79
+ }
80
+
81
+ if (t.isStringLiteral(value)) {
82
+ return value;
83
+ }
84
+
85
+ return t.stringLiteral(value.value || '');
86
+ }
87
+
88
+ // Create event handler with modifiers
89
+ function createEventHandler(handler, modifiers) {
90
+ if (modifiers.length === 0) {
91
+ return handler;
92
+ }
93
+
94
+ // Build modifier chain
95
+ let wrappedHandler = handler;
96
+
97
+ for (const mod of modifiers) {
98
+ switch (mod) {
99
+ case 'preventDefault':
100
+ // (e) => { e.preventDefault(); handler(e); }
101
+ wrappedHandler = t.arrowFunctionExpression(
102
+ [t.identifier('e')],
103
+ t.blockStatement([
104
+ t.expressionStatement(
105
+ t.callExpression(
106
+ t.memberExpression(t.identifier('e'), t.identifier('preventDefault')),
107
+ []
108
+ )
109
+ ),
110
+ t.expressionStatement(
111
+ t.callExpression(wrappedHandler, [t.identifier('e')])
112
+ )
113
+ ])
114
+ );
115
+ break;
116
+
117
+ case 'stopPropagation':
118
+ wrappedHandler = t.arrowFunctionExpression(
119
+ [t.identifier('e')],
120
+ t.blockStatement([
121
+ t.expressionStatement(
122
+ t.callExpression(
123
+ t.memberExpression(t.identifier('e'), t.identifier('stopPropagation')),
124
+ []
125
+ )
126
+ ),
127
+ t.expressionStatement(
128
+ t.callExpression(wrappedHandler, [t.identifier('e')])
129
+ )
130
+ ])
131
+ );
132
+ break;
133
+
134
+ case 'self':
135
+ // (e) => { if (e.target === e.currentTarget) handler(e); }
136
+ wrappedHandler = t.arrowFunctionExpression(
137
+ [t.identifier('e')],
138
+ t.blockStatement([
139
+ t.ifStatement(
140
+ t.binaryExpression(
141
+ '===',
142
+ t.memberExpression(t.identifier('e'), t.identifier('target')),
143
+ t.memberExpression(t.identifier('e'), t.identifier('currentTarget'))
144
+ ),
145
+ t.expressionStatement(
146
+ t.callExpression(wrappedHandler, [t.identifier('e')])
147
+ )
148
+ )
149
+ ])
150
+ );
151
+ break;
152
+
153
+ case 'once':
154
+ case 'capture':
155
+ case 'passive':
156
+ // These are handled at addEventListener level, mark them
157
+ // Will be processed by the runtime
158
+ break;
159
+ }
160
+ }
161
+
162
+ return wrappedHandler;
163
+ }
164
+
165
+ // Transform JSX element to createElement call
166
+ function transformElement(path, state) {
167
+ const { node } = path;
168
+ const openingElement = node.openingElement;
169
+ const tagName = openingElement.name.name;
170
+ const attributes = openingElement.attributes;
171
+ const children = node.children;
172
+
173
+ // Check if it's a component
174
+ if (isComponent(tagName)) {
175
+ return transformComponent(path, state);
176
+ }
177
+
178
+ // Build props object
179
+ const props = [];
180
+ const eventProps = [];
181
+ const bindProps = [];
182
+ let hasSpread = false;
183
+ let spreadExpr = null;
184
+
185
+ for (const attr of attributes) {
186
+ if (t.isJSXSpreadAttribute(attr)) {
187
+ hasSpread = true;
188
+ spreadExpr = attr.argument;
189
+ continue;
190
+ }
191
+
192
+ const attrName = attr.name.name || attr.name;
193
+
194
+ // Handle event modifiers: onClick|preventDefault
195
+ if (attrName.startsWith('on') && attrName.includes('|')) {
196
+ const { eventName, modifiers } = parseEventModifiers(attrName);
197
+ const handler = transformAttributeValue(attr.value);
198
+ const wrappedHandler = createEventHandler(handler, modifiers);
199
+
200
+ eventProps.push(
201
+ t.objectProperty(
202
+ t.stringLiteral(eventName),
203
+ t.objectExpression([
204
+ t.objectProperty(t.identifier('handler'), wrappedHandler),
205
+ t.objectProperty(
206
+ t.identifier('modifiers'),
207
+ t.arrayExpression(modifiers.map(m => t.stringLiteral(m)))
208
+ )
209
+ ])
210
+ )
211
+ );
212
+ continue;
213
+ }
214
+
215
+ // Handle two-way binding: bind:value
216
+ if (isBindingAttribute(attrName)) {
217
+ const bindProp = getBindingProperty(attrName);
218
+ const signal = attr.value.expression;
219
+
220
+ bindProps.push(
221
+ t.objectProperty(
222
+ t.stringLiteral(bindProp),
223
+ signal
224
+ )
225
+ );
226
+ continue;
227
+ }
228
+
229
+ // Handle regular attributes
230
+ const isDynamic = t.isJSXExpressionContainer(attr.value) && mightBeSignal(attr.value?.expression);
231
+ const value = transformAttributeValue(attr.value, isDynamic);
232
+
233
+ // Use the correct attribute name (className -> class for DOM)
234
+ let domAttrName = attrName;
235
+ if (attrName === 'className') domAttrName = 'class';
236
+ if (attrName === 'htmlFor') domAttrName = 'for';
237
+
238
+ props.push(
239
+ t.objectProperty(
240
+ t.stringLiteral(domAttrName),
241
+ value
242
+ )
243
+ );
244
+ }
245
+
246
+ // Transform children
247
+ const transformedChildren = [];
248
+ for (const child of children) {
249
+ if (t.isJSXText(child)) {
250
+ const text = child.value.trim();
251
+ if (text) {
252
+ transformedChildren.push(t.stringLiteral(text));
253
+ }
254
+ } else if (t.isJSXExpressionContainer(child)) {
255
+ if (!t.isJSXEmptyExpression(child.expression)) {
256
+ const expr = child.expression;
257
+ // Wrap signal expressions for reactivity
258
+ if (mightBeSignal(expr)) {
259
+ transformedChildren.push(
260
+ t.arrowFunctionExpression([], expr)
261
+ );
262
+ } else {
263
+ transformedChildren.push(expr);
264
+ }
265
+ }
266
+ } else if (t.isJSXElement(child)) {
267
+ // Recursively transform child elements
268
+ transformedChildren.push(transformElement({ node: child }, state));
269
+ } else if (t.isJSXFragment(child)) {
270
+ // Handle fragments
271
+ for (const fragChild of child.children) {
272
+ if (t.isJSXElement(fragChild)) {
273
+ transformedChildren.push(transformElement({ node: fragChild }, state));
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ // Build the createElement call
280
+ const args = [
281
+ t.stringLiteral(tagName)
282
+ ];
283
+
284
+ // Props object
285
+ const propsObj = [];
286
+ if (props.length > 0) {
287
+ propsObj.push(...props);
288
+ }
289
+ if (eventProps.length > 0) {
290
+ propsObj.push(
291
+ t.objectProperty(
292
+ t.identifier('_events'),
293
+ t.objectExpression(eventProps)
294
+ )
295
+ );
296
+ }
297
+ if (bindProps.length > 0) {
298
+ propsObj.push(
299
+ t.objectProperty(
300
+ t.identifier('_bindings'),
301
+ t.objectExpression(bindProps)
302
+ )
303
+ );
304
+ }
305
+
306
+ if (hasSpread) {
307
+ // Merge spread with props
308
+ args.push(
309
+ t.callExpression(
310
+ t.memberExpression(t.identifier('Object'), t.identifier('assign')),
311
+ [t.objectExpression([]), spreadExpr, t.objectExpression(propsObj)]
312
+ )
313
+ );
314
+ } else {
315
+ args.push(t.objectExpression(propsObj));
316
+ }
317
+
318
+ // Children array
319
+ if (transformedChildren.length > 0) {
320
+ args.push(t.arrayExpression(transformedChildren));
321
+ }
322
+
323
+ // Determine if SVG context
324
+ const isSvg = isSVGElement(tagName);
325
+
326
+ // Call the appropriate createElement function
327
+ const createFn = isSvg ? '_createSVGElement' : '_createElement';
328
+
329
+ return t.callExpression(
330
+ t.identifier(createFn),
331
+ args
332
+ );
333
+ }
334
+
335
+ // Transform component JSX
336
+ function transformComponent(path, state) {
337
+ const { node } = path;
338
+ const openingElement = node.openingElement;
339
+ const componentName = openingElement.name.name;
340
+ const attributes = openingElement.attributes;
341
+ const children = node.children;
342
+
343
+ // Check for client directives (islands)
344
+ let clientDirective = null;
345
+ const filteredAttrs = [];
346
+
347
+ for (const attr of attributes) {
348
+ if (t.isJSXAttribute(attr)) {
349
+ const name = attr.name.name;
350
+ if (name && name.startsWith('client:')) {
351
+ clientDirective = name.slice(7); // 'load', 'idle', 'visible', etc.
352
+ if (attr.value) {
353
+ // client:media="(max-width: 768px)"
354
+ clientDirective = {
355
+ type: clientDirective,
356
+ value: attr.value.value
357
+ };
358
+ }
359
+ continue;
360
+ }
361
+ }
362
+ filteredAttrs.push(attr);
363
+ }
364
+
365
+ // Build props object
366
+ const props = [];
367
+ let hasSpread = false;
368
+ let spreadExpr = null;
369
+
370
+ for (const attr of filteredAttrs) {
371
+ if (t.isJSXSpreadAttribute(attr)) {
372
+ hasSpread = true;
373
+ spreadExpr = attr.argument;
374
+ continue;
375
+ }
376
+
377
+ const attrName = attr.name.name;
378
+ const value = transformAttributeValue(attr.value);
379
+
380
+ props.push(
381
+ t.objectProperty(
382
+ t.identifier(attrName),
383
+ value
384
+ )
385
+ );
386
+ }
387
+
388
+ // Handle children
389
+ const transformedChildren = [];
390
+ for (const child of children) {
391
+ if (t.isJSXText(child)) {
392
+ const text = child.value.trim();
393
+ if (text) {
394
+ transformedChildren.push(t.stringLiteral(text));
395
+ }
396
+ } else if (t.isJSXExpressionContainer(child)) {
397
+ if (!t.isJSXEmptyExpression(child.expression)) {
398
+ transformedChildren.push(child.expression);
399
+ }
400
+ } else if (t.isJSXElement(child)) {
401
+ transformedChildren.push(transformElement({ node: child }, state));
402
+ }
403
+ }
404
+
405
+ // Add children to props if present
406
+ if (transformedChildren.length > 0) {
407
+ if (transformedChildren.length === 1) {
408
+ props.push(
409
+ t.objectProperty(
410
+ t.identifier('children'),
411
+ transformedChildren[0]
412
+ )
413
+ );
414
+ } else {
415
+ props.push(
416
+ t.objectProperty(
417
+ t.identifier('children'),
418
+ t.arrayExpression(transformedChildren)
419
+ )
420
+ );
421
+ }
422
+ }
423
+
424
+ // Build props expression
425
+ let propsExpr;
426
+ if (hasSpread) {
427
+ propsExpr = t.callExpression(
428
+ t.memberExpression(t.identifier('Object'), t.identifier('assign')),
429
+ [t.objectExpression([]), spreadExpr, t.objectExpression(props)]
430
+ );
431
+ } else {
432
+ propsExpr = t.objectExpression(props);
433
+ }
434
+
435
+ // Handle control flow components specially
436
+ if (CONTROL_FLOW_COMPONENTS.has(componentName)) {
437
+ return t.callExpression(
438
+ t.identifier(`_${componentName}`),
439
+ [propsExpr]
440
+ );
441
+ }
442
+
443
+ // Handle islands
444
+ if (clientDirective) {
445
+ const directiveValue = typeof clientDirective === 'object'
446
+ ? t.objectExpression([
447
+ t.objectProperty(t.identifier('type'), t.stringLiteral(clientDirective.type)),
448
+ t.objectProperty(t.identifier('value'), t.stringLiteral(clientDirective.value))
449
+ ])
450
+ : t.stringLiteral(clientDirective);
451
+
452
+ return t.callExpression(
453
+ t.identifier('_createIsland'),
454
+ [
455
+ t.identifier(componentName),
456
+ propsExpr,
457
+ directiveValue
458
+ ]
459
+ );
460
+ }
461
+
462
+ // Regular component call
463
+ return t.callExpression(
464
+ t.identifier('_createComponent'),
465
+ [
466
+ t.identifier(componentName),
467
+ propsExpr
468
+ ]
469
+ );
470
+ }
471
+
472
+ return {
473
+ name: 'what-jsx-transform',
474
+
475
+ visitor: {
476
+ Program: {
477
+ enter(path, state) {
478
+ state.needsRuntime = false;
479
+ state.usedHelpers = new Set();
480
+ },
481
+
482
+ exit(path, state) {
483
+ if (state.needsRuntime) {
484
+ // Add runtime imports at the top
485
+ const helpers = Array.from(state.usedHelpers);
486
+
487
+ const importDecl = t.importDeclaration(
488
+ helpers.map(h => t.importSpecifier(t.identifier(h), t.identifier(h))),
489
+ t.stringLiteral('what-framework/runtime')
490
+ );
491
+
492
+ path.unshiftContainer('body', importDecl);
493
+ }
494
+ }
495
+ },
496
+
497
+ JSXElement(path, state) {
498
+ state.needsRuntime = true;
499
+ state.usedHelpers.add('_createElement');
500
+ state.usedHelpers.add('_createComponent');
501
+
502
+ const transformed = transformElement(path, state);
503
+ path.replaceWith(transformed);
504
+ },
505
+
506
+ JSXFragment(path, state) {
507
+ state.needsRuntime = true;
508
+ state.usedHelpers.add('_Fragment');
509
+
510
+ const children = [];
511
+ for (const child of path.node.children) {
512
+ if (t.isJSXText(child)) {
513
+ const text = child.value.trim();
514
+ if (text) {
515
+ children.push(t.stringLiteral(text));
516
+ }
517
+ } else if (t.isJSXExpressionContainer(child)) {
518
+ if (!t.isJSXEmptyExpression(child.expression)) {
519
+ children.push(child.expression);
520
+ }
521
+ } else if (t.isJSXElement(child)) {
522
+ children.push(transformElement({ node: child }, state));
523
+ }
524
+ }
525
+
526
+ path.replaceWith(
527
+ t.callExpression(
528
+ t.identifier('_Fragment'),
529
+ [t.arrayExpression(children)]
530
+ )
531
+ );
532
+ }
533
+ }
534
+ };
535
+ }
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What Compiler
3
+ * JSX transformation and build-time optimizations
4
+ */
5
+
6
+ export { default as babelPlugin } from './babel-plugin.js';
7
+ export { default as vitePlugin, what } from './vite-plugin.js';
8
+ export * from './runtime.js';
package/src/runtime.js ADDED
@@ -0,0 +1,483 @@
1
+ /**
2
+ * What Framework Runtime
3
+ * Optimized DOM creation and updates with signal reactivity
4
+ */
5
+
6
+ import { effect, untrack, batch } from 'what-framework';
7
+
8
+ // Check if value is a signal (callable with .set)
9
+ function isSignal(value) {
10
+ return typeof value === 'function' && typeof value.set === 'function';
11
+ }
12
+
13
+ // Unwrap a potentially reactive value
14
+ function unwrap(value) {
15
+ if (typeof value === 'function') {
16
+ return value();
17
+ }
18
+ return value;
19
+ }
20
+
21
+ // Set a DOM property with proper handling
22
+ function setProperty(el, key, value, isSVG = false) {
23
+ if (key === 'class') {
24
+ el.className = value ?? '';
25
+ } else if (key === 'style') {
26
+ if (typeof value === 'string') {
27
+ el.style.cssText = value;
28
+ } else if (typeof value === 'object') {
29
+ Object.assign(el.style, value);
30
+ }
31
+ } else if (key === 'ref') {
32
+ if (typeof value === 'function') {
33
+ value(el);
34
+ } else if (value && typeof value === 'object') {
35
+ value.current = el;
36
+ }
37
+ } else if (key.startsWith('on') && typeof value === 'function') {
38
+ const eventName = key.slice(2).toLowerCase();
39
+ el.addEventListener(eventName, value);
40
+ } else if (key === 'innerHTML' || key === 'textContent') {
41
+ el[key] = value ?? '';
42
+ } else if (key in el && !isSVG) {
43
+ try {
44
+ el[key] = value;
45
+ } catch {
46
+ el.setAttribute(key, value);
47
+ }
48
+ } else {
49
+ if (value === true) {
50
+ el.setAttribute(key, '');
51
+ } else if (value === false || value == null) {
52
+ el.removeAttribute(key);
53
+ } else {
54
+ el.setAttribute(key, value);
55
+ }
56
+ }
57
+ }
58
+
59
+ // Create a reactive property binding
60
+ function bindProperty(el, key, getValue, isSVG = false) {
61
+ let currentValue;
62
+
63
+ effect(() => {
64
+ const newValue = unwrap(getValue);
65
+ if (newValue !== currentValue) {
66
+ currentValue = newValue;
67
+ setProperty(el, key, newValue, isSVG);
68
+ }
69
+ });
70
+ }
71
+
72
+ // Create a text node that updates reactively
73
+ function createReactiveText(getValue) {
74
+ const node = document.createTextNode('');
75
+ let currentValue;
76
+
77
+ effect(() => {
78
+ const newValue = String(unwrap(getValue) ?? '');
79
+ if (newValue !== currentValue) {
80
+ currentValue = newValue;
81
+ node.textContent = newValue;
82
+ }
83
+ });
84
+
85
+ return node;
86
+ }
87
+
88
+ /**
89
+ * Create a DOM element with optimized property handling
90
+ */
91
+ export function _createElement(tag, props = {}, children = []) {
92
+ const el = document.createElement(tag);
93
+ const isSVG = false;
94
+
95
+ // Process props
96
+ for (const [key, value] of Object.entries(props)) {
97
+ if (key === '_events') {
98
+ // Handle events with modifiers
99
+ for (const [eventKey, eventConfig] of Object.entries(value)) {
100
+ const eventName = eventKey.slice(2).toLowerCase();
101
+ const { handler, modifiers } = eventConfig;
102
+
103
+ const options = {};
104
+ if (modifiers.includes('capture')) options.capture = true;
105
+ if (modifiers.includes('passive')) options.passive = true;
106
+ if (modifiers.includes('once')) options.once = true;
107
+
108
+ el.addEventListener(eventName, handler, options);
109
+ }
110
+ continue;
111
+ }
112
+
113
+ if (key === '_bindings') {
114
+ // Handle two-way bindings
115
+ for (const [bindKey, signal] of Object.entries(value)) {
116
+ if (bindKey === 'value') {
117
+ // Input value binding
118
+ el.value = unwrap(signal);
119
+
120
+ effect(() => {
121
+ const newValue = unwrap(signal);
122
+ if (el.value !== newValue) {
123
+ el.value = newValue;
124
+ }
125
+ });
126
+
127
+ el.addEventListener('input', (e) => {
128
+ signal.set(e.target.value);
129
+ });
130
+ } else if (bindKey === 'checked') {
131
+ // Checkbox binding
132
+ el.checked = unwrap(signal);
133
+
134
+ effect(() => {
135
+ el.checked = unwrap(signal);
136
+ });
137
+
138
+ el.addEventListener('change', (e) => {
139
+ signal.set(e.target.checked);
140
+ });
141
+ }
142
+ }
143
+ continue;
144
+ }
145
+
146
+ // Check if value is reactive (a function)
147
+ if (typeof value === 'function' && !key.startsWith('on')) {
148
+ bindProperty(el, key, value, isSVG);
149
+ } else {
150
+ setProperty(el, key, value, isSVG);
151
+ }
152
+ }
153
+
154
+ // Process children
155
+ for (const child of children) {
156
+ appendChild(el, child);
157
+ }
158
+
159
+ return el;
160
+ }
161
+
162
+ /**
163
+ * Create an SVG element
164
+ */
165
+ export function _createSVGElement(tag, props = {}, children = []) {
166
+ const el = document.createElementNS('http://www.w3.org/2000/svg', tag);
167
+ const isSVG = true;
168
+
169
+ // Process props
170
+ for (const [key, value] of Object.entries(props)) {
171
+ if (key === '_events' || key === '_bindings') continue;
172
+
173
+ if (typeof value === 'function' && !key.startsWith('on')) {
174
+ bindProperty(el, key, value, isSVG);
175
+ } else {
176
+ setProperty(el, key, value, isSVG);
177
+ }
178
+ }
179
+
180
+ // Process children
181
+ for (const child of children) {
182
+ appendChild(el, child);
183
+ }
184
+
185
+ return el;
186
+ }
187
+
188
+ /**
189
+ * Append a child to an element, handling different types
190
+ */
191
+ function appendChild(parent, child) {
192
+ if (child == null || child === false || child === true) {
193
+ return;
194
+ }
195
+
196
+ if (typeof child === 'string' || typeof child === 'number') {
197
+ parent.appendChild(document.createTextNode(String(child)));
198
+ } else if (typeof child === 'function') {
199
+ // Reactive child - could be a signal or computed
200
+ parent.appendChild(createReactiveText(child));
201
+ } else if (child instanceof Node) {
202
+ parent.appendChild(child);
203
+ } else if (Array.isArray(child)) {
204
+ for (const c of child) {
205
+ appendChild(parent, c);
206
+ }
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Create a component instance
212
+ */
213
+ export function _createComponent(Component, props) {
214
+ // Components are just functions that return DOM nodes
215
+ return untrack(() => Component(props));
216
+ }
217
+
218
+ /**
219
+ * Create a fragment (array of nodes)
220
+ */
221
+ export function _Fragment(children) {
222
+ const fragment = document.createDocumentFragment();
223
+ for (const child of children) {
224
+ appendChild(fragment, child);
225
+ }
226
+ return fragment;
227
+ }
228
+
229
+ /**
230
+ * Show component - conditional rendering
231
+ */
232
+ export function _Show(props) {
233
+ const { when, fallback, children } = props;
234
+ const marker = document.createComment('Show');
235
+ let currentNodes = [];
236
+ let showingMain = null;
237
+
238
+ const container = document.createDocumentFragment();
239
+ container.appendChild(marker);
240
+
241
+ effect(() => {
242
+ const condition = unwrap(when);
243
+ const shouldShowMain = Boolean(condition);
244
+
245
+ if (shouldShowMain === showingMain) return;
246
+ showingMain = shouldShowMain;
247
+
248
+ // Remove current nodes
249
+ for (const node of currentNodes) {
250
+ node.parentNode?.removeChild(node);
251
+ }
252
+ currentNodes = [];
253
+
254
+ // Insert new nodes
255
+ const content = shouldShowMain ? children : fallback;
256
+ if (content != null) {
257
+ const nodes = Array.isArray(content) ? content : [content];
258
+ for (const node of nodes) {
259
+ if (node instanceof Node) {
260
+ marker.parentNode?.insertBefore(node, marker.nextSibling);
261
+ currentNodes.push(node);
262
+ } else if (typeof node === 'string') {
263
+ const textNode = document.createTextNode(node);
264
+ marker.parentNode?.insertBefore(textNode, marker.nextSibling);
265
+ currentNodes.push(textNode);
266
+ }
267
+ }
268
+ }
269
+ });
270
+
271
+ return container;
272
+ }
273
+
274
+ /**
275
+ * For component - list rendering
276
+ */
277
+ export function _For(props) {
278
+ const { each, children: renderFn } = props;
279
+ const marker = document.createComment('For');
280
+ let currentNodes = [];
281
+ let currentItems = [];
282
+
283
+ const container = document.createDocumentFragment();
284
+ container.appendChild(marker);
285
+
286
+ effect(() => {
287
+ const items = unwrap(each) || [];
288
+
289
+ // Simple reconciliation - replace all for now
290
+ // TODO: Keyed reconciliation for better performance
291
+ for (const node of currentNodes) {
292
+ node.parentNode?.removeChild(node);
293
+ }
294
+ currentNodes = [];
295
+
296
+ const parent = marker.parentNode;
297
+ if (!parent) return;
298
+
299
+ items.forEach((item, index) => {
300
+ const rendered = renderFn(item, index);
301
+ if (rendered instanceof Node) {
302
+ parent.insertBefore(rendered, marker);
303
+ currentNodes.push(rendered);
304
+ }
305
+ });
306
+
307
+ currentItems = items;
308
+ });
309
+
310
+ return container;
311
+ }
312
+
313
+ /**
314
+ * Switch/Match components
315
+ */
316
+ export function _Switch(props) {
317
+ const { children } = props;
318
+ const matches = Array.isArray(children) ? children : [children];
319
+
320
+ const marker = document.createComment('Switch');
321
+ let currentNode = null;
322
+
323
+ const container = document.createDocumentFragment();
324
+ container.appendChild(marker);
325
+
326
+ effect(() => {
327
+ // Find first matching condition
328
+ let matched = null;
329
+ for (const match of matches) {
330
+ if (match && match._isMatch && unwrap(match.when)) {
331
+ matched = match.children;
332
+ break;
333
+ }
334
+ }
335
+
336
+ // Remove current
337
+ if (currentNode) {
338
+ currentNode.parentNode?.removeChild(currentNode);
339
+ currentNode = null;
340
+ }
341
+
342
+ // Insert matched
343
+ if (matched instanceof Node) {
344
+ marker.parentNode?.insertBefore(matched, marker.nextSibling);
345
+ currentNode = matched;
346
+ }
347
+ });
348
+
349
+ return container;
350
+ }
351
+
352
+ export function _Match(props) {
353
+ return {
354
+ _isMatch: true,
355
+ when: props.when,
356
+ children: props.children
357
+ };
358
+ }
359
+
360
+ /**
361
+ * Create an island (deferred hydration)
362
+ */
363
+ export function _createIsland(Component, props, directive) {
364
+ const type = typeof directive === 'string' ? directive : directive.type;
365
+ const value = typeof directive === 'object' ? directive.value : null;
366
+
367
+ // Create placeholder element
368
+ const el = document.createElement('div');
369
+ el.setAttribute('data-island', Component.name || 'Island');
370
+ el.setAttribute('data-hydrate', type);
371
+ if (value) {
372
+ el.setAttribute('data-hydrate-value', value);
373
+ }
374
+
375
+ // Store props for hydration
376
+ el._islandProps = props;
377
+ el._islandComponent = Component;
378
+
379
+ // Schedule hydration based on directive
380
+ switch (type) {
381
+ case 'load':
382
+ // Hydrate immediately
383
+ queueMicrotask(() => hydrateIsland(el));
384
+ break;
385
+
386
+ case 'idle':
387
+ // Hydrate when browser is idle
388
+ if ('requestIdleCallback' in window) {
389
+ requestIdleCallback(() => hydrateIsland(el));
390
+ } else {
391
+ setTimeout(() => hydrateIsland(el), 200);
392
+ }
393
+ break;
394
+
395
+ case 'visible':
396
+ // Hydrate when visible
397
+ const observer = new IntersectionObserver((entries) => {
398
+ if (entries[0].isIntersecting) {
399
+ observer.disconnect();
400
+ hydrateIsland(el);
401
+ }
402
+ });
403
+ queueMicrotask(() => observer.observe(el));
404
+ break;
405
+
406
+ case 'interaction':
407
+ // Hydrate on first interaction
408
+ const hydrate = () => {
409
+ el.removeEventListener('click', hydrate);
410
+ el.removeEventListener('focus', hydrate);
411
+ el.removeEventListener('mouseenter', hydrate);
412
+ hydrateIsland(el);
413
+ };
414
+ el.addEventListener('click', hydrate, { once: true });
415
+ el.addEventListener('focus', hydrate, { once: true });
416
+ el.addEventListener('mouseenter', hydrate, { once: true });
417
+ break;
418
+
419
+ case 'media':
420
+ // Hydrate when media query matches
421
+ const mq = window.matchMedia(value);
422
+ const checkMedia = () => {
423
+ if (mq.matches) {
424
+ mq.removeEventListener('change', checkMedia);
425
+ hydrateIsland(el);
426
+ }
427
+ };
428
+ if (mq.matches) {
429
+ queueMicrotask(() => hydrateIsland(el));
430
+ } else {
431
+ mq.addEventListener('change', checkMedia);
432
+ }
433
+ break;
434
+ }
435
+
436
+ return el;
437
+ }
438
+
439
+ function hydrateIsland(el) {
440
+ const Component = el._islandComponent;
441
+ const props = el._islandProps;
442
+
443
+ if (!Component) return;
444
+
445
+ const rendered = _createComponent(Component, props);
446
+ if (rendered instanceof Node) {
447
+ el.replaceWith(rendered);
448
+ }
449
+
450
+ // Dispatch event for tracking
451
+ document.dispatchEvent(new CustomEvent('island:hydrated', {
452
+ detail: { component: Component.name }
453
+ }));
454
+ }
455
+
456
+ /**
457
+ * Mount a component to the DOM
458
+ */
459
+ export function mount(component, container) {
460
+ const target = typeof container === 'string'
461
+ ? document.querySelector(container)
462
+ : container;
463
+
464
+ if (!target) {
465
+ throw new Error(`Mount target not found: ${container}`);
466
+ }
467
+
468
+ // Clear container
469
+ target.innerHTML = '';
470
+
471
+ // Render component
472
+ const rendered = typeof component === 'function'
473
+ ? component()
474
+ : component;
475
+
476
+ if (rendered instanceof Node) {
477
+ target.appendChild(rendered);
478
+ }
479
+
480
+ return () => {
481
+ target.innerHTML = '';
482
+ };
483
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * What Framework Vite Plugin
3
+ * Enables JSX transformation and other optimizations
4
+ */
5
+
6
+ import { transformSync } from '@babel/core';
7
+ import whatBabelPlugin from './babel-plugin.js';
8
+
9
+ export default function whatVitePlugin(options = {}) {
10
+ const {
11
+ // File extensions to process
12
+ include = /\.[jt]sx$/,
13
+ // Files to exclude
14
+ exclude = /node_modules/,
15
+ // Enable source maps
16
+ sourceMaps = true,
17
+ // Production optimizations
18
+ production = process.env.NODE_ENV === 'production'
19
+ } = options;
20
+
21
+ return {
22
+ name: 'vite-plugin-what',
23
+
24
+ // Transform JSX files
25
+ transform(code, id) {
26
+ // Check if we should process this file
27
+ if (!include.test(id)) return null;
28
+ if (exclude && exclude.test(id)) return null;
29
+
30
+ try {
31
+ const result = transformSync(code, {
32
+ filename: id,
33
+ sourceMaps,
34
+ plugins: [
35
+ [whatBabelPlugin, { production }]
36
+ ],
37
+ parserOpts: {
38
+ plugins: ['jsx', 'typescript']
39
+ }
40
+ });
41
+
42
+ if (!result || !result.code) {
43
+ return null;
44
+ }
45
+
46
+ return {
47
+ code: result.code,
48
+ map: result.map
49
+ };
50
+ } catch (error) {
51
+ console.error(`[what] Error transforming ${id}:`, error.message);
52
+ throw error;
53
+ }
54
+ },
55
+
56
+ // Resolve what-framework imports
57
+ resolveId(id) {
58
+ if (id === 'what-framework/runtime') {
59
+ return '\0what-runtime';
60
+ }
61
+ return null;
62
+ },
63
+
64
+ // Load virtual modules
65
+ load(id) {
66
+ if (id === '\0what-runtime') {
67
+ // Return the runtime module
68
+ return `export * from 'what-compiler/runtime';`;
69
+ }
70
+ return null;
71
+ },
72
+
73
+ // Configure for development
74
+ config(config, { mode }) {
75
+ return {
76
+ esbuild: {
77
+ // Let our plugin handle JSX, not esbuild
78
+ jsx: 'preserve',
79
+ jsxFactory: '_createElement',
80
+ jsxFragment: '_Fragment'
81
+ },
82
+ optimizeDeps: {
83
+ // Pre-bundle the framework
84
+ include: ['what-framework']
85
+ }
86
+ };
87
+ }
88
+ };
89
+ }
90
+
91
+ // Named export for compatibility
92
+ export { whatVitePlugin as what };