@zenithbuild/compiler 1.0.11 → 1.0.12

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/dist/index.d.ts CHANGED
@@ -15,12 +15,14 @@ export declare function compileZen(filePath: string): Promise<{
15
15
  */
16
16
  export declare function compileZenSource(source: string, filePath: string, options?: {
17
17
  componentsDir?: string;
18
+ components?: Map<string, any>;
18
19
  }): Promise<{
19
20
  ir: ZenIR;
20
21
  compiled: CompiledTemplate;
21
22
  finalized?: FinalizedOutput;
22
23
  }>;
23
24
  export { parseZenFile };
25
+ export { discoverComponents } from './discovery/componentDiscovery';
24
26
  export { discoverLayouts } from './discovery/layouts';
25
27
  export { processLayout } from './transform/layoutProcessor';
26
28
  export { bundlePageScript } from './bundler';
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ import { transformTemplate } from './transform/transformTemplate';
3
3
  import { finalizeOutputOrThrow } from './finalize/finalizeOutput';
4
4
  import { validateIr } from './validate/invariants';
5
5
  import { parseZenFile } from './parseZenFile';
6
+ import { discoverComponents } from './discovery/componentDiscovery';
7
+ import { resolveComponentsInIR } from './transform/componentResolver';
6
8
  /**
7
9
  * Compile a .zen file into IR and CompiledTemplate
8
10
  */
@@ -16,12 +18,15 @@ export async function compileZen(filePath) {
16
18
  export async function compileZenSource(source, filePath, options) {
17
19
  // Parse with native bridge
18
20
  let ir = parseZenFile(filePath, source);
19
- // Resolve components if components directory is provided
20
- if (options?.componentsDir) {
21
- const { discoverComponents } = require('./discovery/componentDiscovery');
22
- const { resolveComponentsInIR } = require('./transform/componentResolver');
21
+ // Resolve components if explicitly provided OR if components directory is set
22
+ if (options?.components || options?.componentsDir) {
23
+ let components = options.components || new Map();
24
+ // If directory provided, discover and merge
25
+ if (options.componentsDir) {
26
+ const discovered = discoverComponents(options.componentsDir);
27
+ components = new Map([...components, ...discovered]);
28
+ }
23
29
  // Component resolution may throw InvariantError — let it propagate
24
- const components = discoverComponents(options.componentsDir);
25
30
  ir = resolveComponentsInIR(ir, components);
26
31
  }
27
32
  // Validate all compiler invariants after resolution
@@ -38,6 +43,7 @@ export async function compileZenSource(source, filePath, options) {
38
43
  }
39
44
  export { parseZenFile };
40
45
  // Feature exports
46
+ export { discoverComponents } from './discovery/componentDiscovery';
41
47
  export { discoverLayouts } from './discovery/layouts';
42
48
  export { processLayout } from './transform/layoutProcessor';
43
49
  export { bundlePageScript } from './bundler';
@@ -34,6 +34,10 @@ export function parseZenFile(filePath, sourceInput) {
34
34
  }
35
35
  }
36
36
  if (native && native.parseTemplateNative && native.parseScriptNative && native.extractStylesNative) {
37
+ console.log(`[ZenithDebug] parseZenFile: filePath=${filePath}, sourceLen=${source.length}`);
38
+ if (filePath.endsWith('index.zen')) {
39
+ console.log(`[ZenithDebug] source snippet: ${source.substring(0, 500)}`);
40
+ }
37
41
  try {
38
42
  const template = native.parseTemplateNative(source, filePath);
39
43
  const script = native.parseScriptNative(source);
@@ -25,19 +25,44 @@ export function generateBundleJS(pluginData) {
25
25
  : '{}';
26
26
  // Resolve core runtime paths - assumes sibling directory or relative in node_modules
27
27
  const rootDir = process.cwd();
28
- const coreDistPath = path.join(rootDir, '../zenith-core/dist/runtime');
28
+ let coreRuntimePath = path.join(rootDir, '../zenith-core/dist/runtime');
29
+ if (!existsSync(coreRuntimePath)) {
30
+ coreRuntimePath = path.join(rootDir, '../zenith-core/core');
31
+ }
29
32
  let reactivityJS = '';
30
33
  let lifecycleJS = '';
31
34
  try {
32
- const reactivityFile = path.join(coreDistPath, 'reactivity/index.js');
33
- const lifecycleFile = path.join(coreDistPath, 'lifecycle/index.js');
34
- if (existsSync(reactivityFile))
35
- reactivityJS = readFileSync(reactivityFile, 'utf-8');
36
- if (existsSync(lifecycleFile))
37
- lifecycleJS = readFileSync(lifecycleFile, 'utf-8');
35
+ let reactivityFile = path.join(coreRuntimePath, 'reactivity/index.js');
36
+ if (!existsSync(reactivityFile))
37
+ reactivityFile = path.join(coreRuntimePath, 'reactivity/index.ts');
38
+ let lifecycleFile = path.join(coreRuntimePath, 'lifecycle/index.js');
39
+ if (!existsSync(lifecycleFile))
40
+ lifecycleFile = path.join(coreRuntimePath, 'lifecycle/index.ts');
41
+ if (existsSync(reactivityFile) && reactivityFile.endsWith('.js')) {
42
+ reactivityJS = transformExportsToGlobal(readFileSync(reactivityFile, 'utf-8'));
43
+ }
44
+ if (existsSync(lifecycleFile) && lifecycleFile.endsWith('.js')) {
45
+ lifecycleJS = transformExportsToGlobal(readFileSync(lifecycleFile, 'utf-8'));
46
+ }
38
47
  }
39
48
  catch (e) {
40
- console.warn('[Zenith] Could not load runtime from core dist, falling back to basic support', e);
49
+ console.warn('[Zenith] Could not load runtime from core, falling back to internal', e);
50
+ }
51
+ // Fallback to internal hydration_runtime.js from native compiler source
52
+ // Use the compiler's own location to find the file, not process.cwd()
53
+ if (!reactivityJS || !lifecycleJS) {
54
+ // Resolve relative to this bundle-generator.ts file's location
55
+ // In compiled form, this will be in dist/runtime/, so we go up to find native/
56
+ const compilerRoot = path.resolve(path.dirname(import.meta.url.replace('file://', '')), '../../..');
57
+ const nativeRuntimePath = path.join(compilerRoot, 'native/compiler-native/src/hydration_runtime.js');
58
+ if (existsSync(nativeRuntimePath)) {
59
+ const nativeJS = readFileSync(nativeRuntimePath, 'utf-8');
60
+ // IMPORTANT: Include the FULL IIFE - do NOT strip the wrapper!
61
+ // The runtime has its own bootstrap guard and idempotency check.
62
+ // It will install primitives to window and create window.__ZENITH_RUNTIME__
63
+ reactivityJS = nativeJS;
64
+ lifecycleJS = ' '; // Ensure it doesn't trigger the "not found" message
65
+ }
41
66
  }
42
67
  return `/*!
43
68
  * Zenith Runtime v1.0.1
@@ -59,6 +84,73 @@ ${lifecycleJS ? ` // ============================================
59
84
  // ============================================
60
85
  ${lifecycleJS}` : ` // Fallback: Lifecycle not found`}
61
86
 
87
+ // ----------------------------------------------------------------------
88
+ // COMPAT: Expose internal exports as globals
89
+ // ----------------------------------------------------------------------
90
+ // The code above was stripped of "export { ... }" but assigned to internal variables.
91
+ // We need to map them back to global scope if they weren't attached by the code itself.
92
+
93
+ // Reactivity primitives map (internal name -> global alias)
94
+ // Based on zenith-core/core/reactivity/index.ts re-exports:
95
+ // export { zenSignal, zenState, ... }
96
+
97
+ // Since we stripped exports, we rely on the fact that the bundled code
98
+ // defines variables like "var zenSignal = ..." or "function zenSignal...".
99
+ // Note: Minified code variables might be renamed (e.g., "var P=...").
100
+ // Ideally, @zenithbuild/core should export an IIFE build for this purpose.
101
+ // For now, we assume the code above already does "global.zenSignal = ..."
102
+ // OR we rely on the Aliases section below to do the mapping if the names match.
103
+
104
+ // ============================================
105
+ // Lifecycle Hooks (Required for hydration)
106
+ // ============================================
107
+ // These functions are required by the runtime - define them if not injected from core
108
+
109
+ const mountCallbacks = [];
110
+ const unmountCallbacks = [];
111
+ let isMounted = false;
112
+
113
+ function zenOnMount(fn) {
114
+ if (isMounted) {
115
+ // Already mounted, run immediately
116
+ const cleanup = fn();
117
+ if (typeof cleanup === 'function') {
118
+ unmountCallbacks.push(cleanup);
119
+ }
120
+ } else {
121
+ mountCallbacks.push(fn);
122
+ }
123
+ }
124
+
125
+ function zenOnUnmount(fn) {
126
+ unmountCallbacks.push(fn);
127
+ }
128
+
129
+ // Called by hydration when page mounts
130
+ function triggerMount() {
131
+ isMounted = true;
132
+ for (let i = 0; i < mountCallbacks.length; i++) {
133
+ try {
134
+ const cleanup = mountCallbacks[i]();
135
+ if (typeof cleanup === 'function') {
136
+ unmountCallbacks.push(cleanup);
137
+ }
138
+ } catch(e) {
139
+ console.error('[Zenith] Mount callback error:', e);
140
+ }
141
+ }
142
+ mountCallbacks.length = 0;
143
+ }
144
+
145
+ // Called by router when page unmounts
146
+ function triggerUnmount() {
147
+ isMounted = false;
148
+ for (let i = 0; i < unmountCallbacks.length; i++) {
149
+ try { unmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
150
+ }
151
+ unmountCallbacks.length = 0;
152
+ }
153
+
62
154
  // ============================================
63
155
  // Component Instance System
64
156
  // ============================================
@@ -94,27 +186,27 @@ ${lifecycleJS ? ` // ============================================
94
186
 
95
187
  // Reactivity (uses global primitives but tracks for cleanup)
96
188
  signal: function(initial) {
97
- return zenSignal(initial);
189
+ return global.zenSignal(initial);
98
190
  },
99
191
  state: function(initial) {
100
- return zenState(initial);
192
+ return global.zenState(initial);
101
193
  },
102
194
  ref: function(initial) {
103
- return zenRef(initial);
195
+ return global.zenRef(initial);
104
196
  },
105
197
  effect: function(fn) {
106
- const cleanup = zenEffect(fn);
198
+ const cleanup = global.zenEffect(fn);
107
199
  instanceEffects.push(cleanup);
108
200
  return cleanup;
109
201
  },
110
202
  memo: function(fn) {
111
- return zenMemo(fn);
203
+ return global.zenMemo(fn);
112
204
  },
113
205
  batch: function(fn) {
114
- zenBatch(fn);
206
+ global.zenBatch(fn);
115
207
  },
116
208
  untrack: function(fn) {
117
- return zenUntrack(fn);
209
+ return global.zenUntrack(fn);
118
210
  },
119
211
 
120
212
  // Lifecycle execution
@@ -375,41 +467,59 @@ ${lifecycleJS ? ` // ============================================
375
467
  /**
376
468
  * Hydrate static HTML with dynamic expressions
377
469
  */
470
+ /**
471
+ * Hydrate static HTML with dynamic expressions (Comment-based)
472
+ */
378
473
  function zenithHydrate(pageState, container) {
379
474
  try {
380
475
  container = container || document;
381
476
 
382
- // Find all text expression placeholders
383
- const textNodes = container.querySelectorAll('[data-zen-text]');
384
- textNodes.forEach(el => updateNode(el, el.getAttribute('data-zen-text'), pageState));
477
+ // Walker to find comment nodes efficiently
478
+ const walker = document.createTreeWalker(
479
+ container,
480
+ NodeFilter.SHOW_COMMENT,
481
+ null,
482
+ false
483
+ );
385
484
 
386
- // Find all attribute expression placeholders
387
- const attrNodes = container.querySelectorAll('[data-zen-attr-class], [data-zen-attr-style], [data-zen-attr-src], [data-zen-attr-href]');
388
- attrNodes.forEach(el => {
389
- const attrMatch = Array.from(el.attributes).find(a => a.name.startsWith('data-zen-attr-'));
390
- if (attrMatch) updateNode(el, attrMatch.value, pageState);
391
- });
392
-
393
- // Find all loop placeholders
394
- const loopNodes = container.querySelectorAll('template[data-zen-loop]');
395
- loopNodes.forEach(el => updateLoopBinding(el, el.getAttribute('data-zen-loop'), pageState));
485
+ const exprLocationMap = new Map();
486
+ let node;
396
487
 
397
- // Wire up event handlers
488
+ while(node = walker.nextNode()) {
489
+ const content = node.nodeValue || '';
490
+ if (content.startsWith('zen:expr_')) {
491
+ const exprId = content.replace('zen:expr_', '');
492
+ exprLocationMap.set(node, exprId);
493
+ }
494
+ }
495
+
496
+ // Process expressions
497
+ for (const [commentNode, exprId] of exprLocationMap) {
498
+ updateNode(commentNode, exprId, pageState);
499
+ }
500
+
501
+ // Wire up event handlers (still attribute based, usually safe)
398
502
  const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
399
503
  eventTypes.forEach(eventType => {
400
504
  const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
401
505
  elements.forEach(el => {
402
506
  const handlerName = el.getAttribute('data-zen-' + eventType);
403
- if (handlerName && (global[handlerName] || getExpression(handlerName))) {
507
+ // Check global scope (window) or expression registry
508
+ if (handlerName) {
404
509
  el.addEventListener(eventType, function(e) {
405
- const handler = global[handlerName] || getExpression(handlerName);
406
- if (typeof handler === 'function') handler(e, el);
510
+ // Resolve handler at runtime to allow for late definition
511
+ const handler = global[handlerName] || getExpression(handlerName);
512
+ if (typeof handler === 'function') {
513
+ handler(e, el);
514
+ } else {
515
+ console.warn('[Zenith] Handler not found:', handlerName);
516
+ }
407
517
  });
408
518
  }
409
519
  });
410
520
  });
411
521
 
412
- // Trigger mount (only if container is root)
522
+ // Trigger mount
413
523
  if (container === document || container.id === 'app' || container.tagName === 'BODY') {
414
524
  triggerMount();
415
525
  }
@@ -417,6 +527,45 @@ ${lifecycleJS ? ` // ============================================
417
527
  renderErrorPage(e, { activity: 'zenithHydrate' });
418
528
  }
419
529
  }
530
+
531
+ // Update logic for comment placeholders
532
+ function updateNode(placeholder, exprId, pageState) {
533
+ const expr = getExpression(exprId);
534
+ if (!expr) return;
535
+
536
+ // Store reference to current nodes for cleanup
537
+ let currentNodes = [];
538
+
539
+ zenEffect(function() {
540
+ try {
541
+ const result = expr(pageState);
542
+
543
+ // Cleanup old nodes
544
+ currentNodes.forEach(n => n.remove());
545
+ currentNodes = [];
546
+
547
+ if (result == null || result === false) {
548
+ // Render nothing
549
+ } else if (result instanceof Node) {
550
+ placeholder.parentNode.insertBefore(result, placeholder);
551
+ currentNodes.push(result);
552
+ } else if (Array.isArray(result)) {
553
+ result.flat(Infinity).forEach(item => {
554
+ const n = item instanceof Node ? item : document.createTextNode(String(item));
555
+ placeholder.parentNode.insertBefore(n, placeholder);
556
+ currentNodes.push(n);
557
+ });
558
+ } else {
559
+ // Primitive
560
+ const n = document.createTextNode(String(result));
561
+ placeholder.parentNode.insertBefore(n, placeholder);
562
+ currentNodes.push(n);
563
+ }
564
+ } catch (e) {
565
+ renderErrorPage(e, { expressionId: exprId });
566
+ }
567
+ });
568
+ }
420
569
 
421
570
  // ============================================
422
571
  // zenith:content - Content Engine
@@ -800,15 +949,15 @@ ${lifecycleJS ? ` // ============================================
800
949
  // Export to window.__zenith
801
950
  // ============================================
802
951
 
803
- global.__zenith = {
804
- // Reactivity primitives
805
- signal: zenSignal,
806
- state: zenState,
807
- effect: zenEffect,
808
- memo: zenMemo,
809
- ref: zenRef,
810
- batch: zenBatch,
811
- untrack: zenUntrack,
952
+ global.__zenith = Object.assign(global.__zenith || {}, {
953
+ // Reactivity primitives (from hydration_runtime.js via global)
954
+ signal: global.zenSignal,
955
+ state: global.zenState,
956
+ effect: global.zenEffect,
957
+ memo: global.zenMemo,
958
+ ref: global.zenRef,
959
+ batch: global.zenBatch,
960
+ untrack: global.zenUntrack,
812
961
  // zenith:content
813
962
  defineSchema: defineSchema,
814
963
  zenCollection: zenCollection,
@@ -833,36 +982,31 @@ ${lifecycleJS ? ` // ============================================
833
982
  createInstance: createComponentInstance,
834
983
  defineComponent: defineComponent,
835
984
  instantiate: instantiateComponent
836
- };
985
+ });
837
986
 
838
- // Expose with zen* prefix for direct usage
839
- global.zenSignal = zenSignal;
840
- global.zenState = zenState;
841
- global.zenEffect = zenEffect;
842
- global.zenMemo = zenMemo;
843
- global.zenRef = zenRef;
844
- global.zenBatch = zenBatch;
845
- global.zenUntrack = zenUntrack;
846
- global.zenOnMount = zenOnMount;
847
- global.zenOnUnmount = zenOnUnmount;
848
- global.zenithHydrate = zenithHydrate;
987
+ // NOTE: zen* primitives (zenSignal, zenState, zenEffect, etc.) are already
988
+ // assigned to global by hydration_runtime.js IIFE - no need to reassign here.
849
989
 
850
- // Clean aliases
851
- global.signal = zenSignal;
852
- global.state = zenState;
853
- global.effect = zenEffect;
854
- global.memo = zenMemo;
855
- global.ref = zenRef;
856
- global.batch = zenBatch;
857
- global.untrack = zenUntrack;
858
- global.onMount = zenOnMount;
859
- global.onUnmount = zenOnUnmount;
990
+ // Clean aliases (point to already-assigned globals)
991
+ global.signal = global.zenSignal;
992
+ global.state = global.zenState;
993
+ global.effect = global.zenEffect;
994
+ global.memo = global.zenMemo;
995
+ global.ref = global.zenRef;
996
+ global.batch = global.zenBatch;
997
+ global.untrack = global.zenUntrack;
998
+ global.onMount = global.zenOnMount;
999
+ global.onUnmount = global.zenOnUnmount;
860
1000
 
861
1001
  // useZenOrder hook exports
862
1002
  global.createZenOrder = createZenOrder;
863
1003
  global.processRawSections = processRawSections;
864
1004
  global.slugify = slugify;
865
1005
 
1006
+ // zenith:content exports
1007
+ global.zenCollection = zenCollection;
1008
+ global.ZenCollection = ZenCollection;
1009
+
866
1010
  // ============================================
867
1011
  // SPA Router Runtime
868
1012
  // ============================================
@@ -1246,6 +1390,35 @@ ${lifecycleJS ? ` // ============================================
1246
1390
  })(typeof window !== 'undefined' ? window : this);
1247
1391
  `;
1248
1392
  }
1393
+ /**
1394
+ * Remove ESM export statements from bundled code
1395
+ * e.g. "export { a as b, c };" -> ""
1396
+ */
1397
+ /**
1398
+ * Transform ESM export statements to global assignments
1399
+ * e.g. "export { a as b, c };" -> "global.b = a; global.c = c;"
1400
+ */
1401
+ function transformExportsToGlobal(code) {
1402
+ // Regex to capture "export { ... };" content
1403
+ // Matches "export { a as b, c }"
1404
+ const exportPattern = /export\s*\{\s*([^}]+)\s*\};?/g;
1405
+ return code.replace(exportPattern, (match, content) => {
1406
+ const parts = content.split(',');
1407
+ const assignments = parts.map((part) => {
1408
+ const trimmed = part.trim();
1409
+ if (!trimmed)
1410
+ return '';
1411
+ // Check for "a as b"
1412
+ const asMatch = trimmed.match(/^(\S+)\s+as\s+(\S+)$/);
1413
+ if (asMatch) {
1414
+ return `global.${asMatch[2]} = ${asMatch[1]};`;
1415
+ }
1416
+ // Just "a"
1417
+ return `global.${trimmed} = ${trimmed};`;
1418
+ });
1419
+ return assignments.join('\n');
1420
+ });
1421
+ }
1249
1422
  /**
1250
1423
  * Generate a minified version of the bundle
1251
1424
  * For production builds
@@ -38,7 +38,7 @@ export function wrapExpression(expr, dependencies, loopContext // Phase 7: Loop
38
38
  try {
39
39
  // Expose zenith helpers for JSX and content
40
40
  const __zenith = window.__zenith || {};
41
- const zenCollection = __zenith.zenCollection || ((name) => ({ get: () => [] }));
41
+ const zenCollection = (window.__ZENITH_RUNTIME__ && window.__ZENITH_RUNTIME__.zenCollection) || __zenith.zenCollection || ((name) => ({ get: () => [] }));
42
42
 
43
43
  with (state) {
44
44
  return ${transformedCode};
package/dist/spa-build.js CHANGED
@@ -678,7 +678,7 @@ function generateZenPrimitivesRuntime() {
678
678
  // Export to window.__zenith
679
679
  // ============================================
680
680
 
681
- window.__zenith = {
681
+ window.__zenith = Object.assign(window.__zenith || {}, {
682
682
  // Reactivity primitives
683
683
  signal: zenSignal,
684
684
  state: zenState,
@@ -693,7 +693,7 @@ function generateZenPrimitivesRuntime() {
693
693
  // Internal hooks for router
694
694
  triggerMount,
695
695
  triggerUnmount
696
- };
696
+ });
697
697
 
698
698
  // Also expose with zen* prefix for direct usage
699
699
  window.zenSignal = zenSignal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenithbuild/compiler",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "The Iron Heart: Native Compiler for the Zenith Framework",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",