@zenithbuild/core 0.4.6 → 0.4.7

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/zen-build.js CHANGED
@@ -8441,7 +8441,12 @@ function generateExpressionId() {
8441
8441
  return `expr_${expressionIdCounter++}`;
8442
8442
  }
8443
8443
  function stripBlocks(html) {
8444
- let stripped = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
8444
+ let stripped = html.replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, (match, attrs, content) => {
8445
+ if (attrs.includes("src=")) {
8446
+ return match;
8447
+ }
8448
+ return "";
8449
+ });
8445
8450
  stripped = stripped.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
8446
8451
  return stripped;
8447
8452
  }
@@ -8942,6 +8947,8 @@ function parseComponentFile(filePath) {
8942
8947
  slots,
8943
8948
  props,
8944
8949
  styles,
8950
+ script: ir.script?.raw || null,
8951
+ scriptAttributes: ir.script?.attributes || null,
8945
8952
  hasScript: ir.script !== null,
8946
8953
  hasStyles: ir.styles.length > 0
8947
8954
  };
@@ -9133,13 +9140,20 @@ function resolveComponentsInIR(ir, components) {
9133
9140
  usedComponents.clear();
9134
9141
  const resolvedNodes = resolveComponentsInNodes(ir.template.nodes, components);
9135
9142
  const componentStyles = Array.from(usedComponents).map((name) => components.get(name)).filter((meta) => meta !== undefined && meta.styles.length > 0).flatMap((meta) => meta.styles.map((raw) => ({ raw })));
9143
+ const componentScripts = Array.from(usedComponents).map((name) => components.get(name)).filter((meta) => meta !== undefined && meta.script !== null).map((meta) => ({
9144
+ name: meta.name,
9145
+ script: meta.script,
9146
+ props: meta.props,
9147
+ scriptAttributes: meta.scriptAttributes || {}
9148
+ }));
9136
9149
  return {
9137
9150
  ...ir,
9138
9151
  template: {
9139
9152
  ...ir.template,
9140
9153
  nodes: resolvedNodes
9141
9154
  },
9142
- styles: [...ir.styles, ...componentStyles]
9155
+ styles: [...ir.styles, ...componentStyles],
9156
+ componentScripts: [...ir.componentScripts || [], ...componentScripts]
9143
9157
  };
9144
9158
  }
9145
9159
  function resolveComponentsInNodes(nodes, components, depth = 0) {
@@ -9199,20 +9213,24 @@ function resolveComponent(componentNode, components, depth = 0) {
9199
9213
  }
9200
9214
  const templateNodes = JSON.parse(JSON.stringify(componentMeta.nodes));
9201
9215
  const resolvedTemplate = resolveSlots(templateNodes, resolvedSlots);
9202
- const forwardedTemplate = forwardAttributesToRoot(resolvedTemplate, componentNode.attributes, componentNode.loopContext);
9216
+ const forwardedTemplate = forwardAttributesToRoot(resolvedTemplate, componentNode.attributes, componentNode.loopContext, componentMeta.hasScript ? componentName : undefined);
9203
9217
  const fullyResolved = resolveComponentsInNodes(forwardedTemplate, components, depth + 1);
9204
9218
  return fullyResolved;
9205
9219
  }
9206
- function forwardAttributesToRoot(nodes, attributes, loopContext) {
9207
- if (attributes.length === 0) {
9208
- return nodes;
9209
- }
9220
+ function forwardAttributesToRoot(nodes, attributes, loopContext, componentName) {
9210
9221
  const rootIndex = nodes.findIndex((n) => n.type === "element");
9211
9222
  if (rootIndex === -1) {
9212
9223
  return nodes;
9213
9224
  }
9214
9225
  const root = nodes[rootIndex];
9215
9226
  const mergedAttributes = [...root.attributes];
9227
+ if (componentName) {
9228
+ mergedAttributes.push({
9229
+ name: "data-zen-component",
9230
+ value: componentName,
9231
+ location: { line: 0, column: 0 }
9232
+ });
9233
+ }
9216
9234
  for (const attr of attributes) {
9217
9235
  const existingIndex = mergedAttributes.findIndex((a) => a.name === attr.name);
9218
9236
  const forwardedAttr = {
@@ -10877,10 +10895,73 @@ function transformStateDeclarations(script) {
10877
10895
  transformed = transformed.replace(/export\s+let\s+props(?:\s*:\s*([^;]+))?\s*;?[ \t]*/g, "");
10878
10896
  transformed = transformed.replace(/(?:type|interface)\s+Props\s*=?\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}[ \t]*;?/gs, "");
10879
10897
  transformed = transformed.replace(/import\s+{[^}]+}\s+from\s+['"]zenith\/runtime['"]\s*;?[ \t]*/g, "");
10898
+ transformed = transformed.replace(/import\s+\w+\s+from\s+['"][^'"]*\.zen['"];?\s*/g, "");
10899
+ transformed = transformed.replace(/import\s+{[^}]*}\s+from\s+['"][^'"]+\.zen['"];?\s*/g, "");
10880
10900
  transformed = transformed.replace(/import\s*{\s*([^}]+)\s*}\s*from\s*['"]zenith:content['"]\s*;?/g, (_, imports) => `const { ${imports.trim()} } = window.__zenith;`);
10881
10901
  return transformed.trim();
10882
10902
  }
10883
10903
 
10904
+ // compiler/transform/componentScriptTransformer.ts
10905
+ var NAMESPACE_BINDINGS = `const {
10906
+ signal, state, memo, effect, ref,
10907
+ batch, untrack, onMount, onUnmount
10908
+ } = __inst;`;
10909
+ var ZEN_PREFIX_MAPPINGS = {
10910
+ zenSignal: "signal",
10911
+ zenState: "state",
10912
+ zenMemo: "memo",
10913
+ zenEffect: "effect",
10914
+ zenRef: "ref",
10915
+ zenBatch: "batch",
10916
+ zenUntrack: "untrack",
10917
+ zenOnMount: "onMount",
10918
+ zenOnUnmount: "onUnmount"
10919
+ };
10920
+ function transformComponentScript(componentName, scriptContent, props) {
10921
+ let transformed = scriptContent;
10922
+ transformed = transformed.replace(/import\s+\w+\s+from\s+['"][^'"]*\.zen['"];?\s*/g, "");
10923
+ transformed = transformed.replace(/import\s+{[^}]*}\s+from\s+['"][^'"]+['"];?\s*/g, "");
10924
+ for (const [zenName, unprefixedName] of Object.entries(ZEN_PREFIX_MAPPINGS)) {
10925
+ const regex = new RegExp(`(?<!\\w)${zenName}\\s*\\(`, "g");
10926
+ transformed = transformed.replace(regex, `${unprefixedName}(`);
10927
+ }
10928
+ return transformed.trim();
10929
+ }
10930
+ function generateComponentFactory(componentName, transformedScript, propNames) {
10931
+ const propsDestructure = propNames.length > 0 ? `const { ${propNames.join(", ")} } = props || {};` : "";
10932
+ return `
10933
+ // Component Factory: ${componentName}
10934
+ // Instantiation is driven by hydrator, not by bundle load
10935
+ __zenith.defineComponent('${componentName}', function(props, rootElement) {
10936
+ const __inst = __zenith.createInstance('${componentName}', rootElement);
10937
+
10938
+ // Namespace bindings (instance-scoped primitives)
10939
+ ${NAMESPACE_BINDINGS}
10940
+
10941
+ ${propsDestructure}
10942
+
10943
+ // Component script (instance-scoped)
10944
+ ${transformedScript}
10945
+
10946
+ // Execute mount lifecycle (rootElement is already in DOM)
10947
+ __inst.mount();
10948
+
10949
+ return __inst;
10950
+ });
10951
+ `;
10952
+ }
10953
+ function transformAllComponentScripts(componentScripts) {
10954
+ if (!componentScripts || componentScripts.length === 0) {
10955
+ return "";
10956
+ }
10957
+ const factories = componentScripts.filter((comp) => comp.script && comp.script.trim().length > 0).map((comp) => {
10958
+ const transformed = transformComponentScript(comp.name, comp.script, comp.props);
10959
+ return generateComponentFactory(comp.name, transformed, comp.props);
10960
+ });
10961
+ return factories.join(`
10962
+ `);
10963
+ }
10964
+
10884
10965
  // compiler/runtime/transformIR.ts
10885
10966
  function transformIR(ir) {
10886
10967
  const expressionDependencies = analyzeAllExpressions(ir.template.expressions, ir.filePath, [], ir.script?.attributes["props"] ? ir.script.attributes["props"].split(",") : [], []);
@@ -10897,6 +10978,7 @@ function transformIR(ir) {
10897
10978
  const propDeclarations = extractProps(scriptContent);
10898
10979
  const stateInitCode = generateStateInitialization(stateDeclarations, [...propDeclarations, ...propKeys]);
10899
10980
  const scriptCode = transformStateDeclarations(scriptContent);
10981
+ const componentScriptCode = transformAllComponentScripts(ir.componentScripts || []);
10900
10982
  const bundle = generateRuntimeBundle({
10901
10983
  expressions,
10902
10984
  expressionRegistry,
@@ -10904,7 +10986,8 @@ function transformIR(ir) {
10904
10986
  navigationRuntime,
10905
10987
  stylesCode,
10906
10988
  scriptCode,
10907
- stateInitCode
10989
+ stateInitCode,
10990
+ componentScriptCode
10908
10991
  });
10909
10992
  return {
10910
10993
  expressions,
@@ -10940,6 +11023,9 @@ ${functionRegistrations}
10940
11023
  ${parts.stateInitCode ? `// State initialization
10941
11024
  ${parts.stateInitCode}` : ""}
10942
11025
 
11026
+ ${parts.componentScriptCode ? `// Component factories (instance-scoped)
11027
+ ${parts.componentScriptCode}` : ""}
11028
+
10943
11029
  // Export hydration functions
10944
11030
  if (typeof window !== 'undefined') {
10945
11031
  window.zenithHydrate = window.__zenith_hydrate || function(state, container) {
@@ -10998,10 +11084,21 @@ if (typeof window !== 'undefined') {
10998
11084
  // Get the router outlet or body
10999
11085
  const container = document.querySelector('#app') || document.body;
11000
11086
 
11001
- // Hydrate with state
11087
+ // Hydrate with state (expressions, bindings)
11002
11088
  if (window.__zenith_hydrate) {
11003
11089
  window.__zenith_hydrate(state, {}, {}, {}, container);
11004
11090
  }
11091
+
11092
+ // Hydrate components by discovering data-zen-component markers
11093
+ // This is the ONLY place component instantiation happens - driven by DOM markers
11094
+ if (window.__zenith && window.__zenith.hydrateComponents) {
11095
+ window.__zenith.hydrateComponents(container);
11096
+ }
11097
+
11098
+ // Trigger page-level mount lifecycle
11099
+ if (window.__zenith && window.__zenith.triggerMount) {
11100
+ window.__zenith.triggerMount();
11101
+ }
11005
11102
  }
11006
11103
 
11007
11104
  // Run on DOM ready
@@ -11333,7 +11430,12 @@ function discoverLayouts(layoutsDir) {
11333
11430
  if (match[1])
11334
11431
  styles.push(match[1].trim());
11335
11432
  }
11336
- let html = source.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
11433
+ let html = source.replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, (match2, attrs, content) => {
11434
+ if (attrs.includes("src=")) {
11435
+ return match2;
11436
+ }
11437
+ return "";
11438
+ });
11337
11439
  html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").trim();
11338
11440
  layouts.set(name, {
11339
11441
  name,
@@ -11835,6 +11937,144 @@ function generateBundleJS() {
11835
11937
  unmountCallbacks.length = 0;
11836
11938
  }
11837
11939
 
11940
+ // ============================================
11941
+ // Component Instance System
11942
+ // ============================================
11943
+ // Each component instance gets isolated state, effects, and lifecycles
11944
+ // Instances are tied to DOM elements via hydration markers
11945
+
11946
+ const componentRegistry = {};
11947
+
11948
+ function createComponentInstance(componentName, rootElement) {
11949
+ const instanceMountCallbacks = [];
11950
+ const instanceUnmountCallbacks = [];
11951
+ const instanceEffects = [];
11952
+ let instanceMounted = false;
11953
+
11954
+ return {
11955
+ // DOM reference
11956
+ root: rootElement,
11957
+
11958
+ // Lifecycle hooks (instance-scoped)
11959
+ onMount: function(fn) {
11960
+ if (instanceMounted) {
11961
+ const cleanup = fn();
11962
+ if (typeof cleanup === 'function') {
11963
+ instanceUnmountCallbacks.push(cleanup);
11964
+ }
11965
+ } else {
11966
+ instanceMountCallbacks.push(fn);
11967
+ }
11968
+ },
11969
+ onUnmount: function(fn) {
11970
+ instanceUnmountCallbacks.push(fn);
11971
+ },
11972
+
11973
+ // Reactivity (uses global primitives but tracks for cleanup)
11974
+ signal: function(initial) {
11975
+ return zenSignal(initial);
11976
+ },
11977
+ state: function(initial) {
11978
+ return zenState(initial);
11979
+ },
11980
+ ref: function(initial) {
11981
+ return zenRef(initial);
11982
+ },
11983
+ effect: function(fn) {
11984
+ const cleanup = zenEffect(fn);
11985
+ instanceEffects.push(cleanup);
11986
+ return cleanup;
11987
+ },
11988
+ memo: function(fn) {
11989
+ return zenMemo(fn);
11990
+ },
11991
+ batch: function(fn) {
11992
+ zenBatch(fn);
11993
+ },
11994
+ untrack: function(fn) {
11995
+ return zenUntrack(fn);
11996
+ },
11997
+
11998
+ // Lifecycle execution
11999
+ mount: function() {
12000
+ instanceMounted = true;
12001
+ for (let i = 0; i < instanceMountCallbacks.length; i++) {
12002
+ try {
12003
+ const cleanup = instanceMountCallbacks[i]();
12004
+ if (typeof cleanup === 'function') {
12005
+ instanceUnmountCallbacks.push(cleanup);
12006
+ }
12007
+ } catch(e) {
12008
+ console.error('[Zenith] Component mount error:', componentName, e);
12009
+ }
12010
+ }
12011
+ instanceMountCallbacks.length = 0;
12012
+ },
12013
+ unmount: function() {
12014
+ instanceMounted = false;
12015
+ // Cleanup effects
12016
+ for (let i = 0; i < instanceEffects.length; i++) {
12017
+ try {
12018
+ if (typeof instanceEffects[i] === 'function') instanceEffects[i]();
12019
+ } catch(e) {
12020
+ console.error('[Zenith] Effect cleanup error:', e);
12021
+ }
12022
+ }
12023
+ instanceEffects.length = 0;
12024
+ // Run unmount callbacks
12025
+ for (let i = 0; i < instanceUnmountCallbacks.length; i++) {
12026
+ try { instanceUnmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
12027
+ }
12028
+ instanceUnmountCallbacks.length = 0;
12029
+ }
12030
+ };
12031
+ }
12032
+
12033
+ function defineComponent(name, factory) {
12034
+ componentRegistry[name] = factory;
12035
+ }
12036
+
12037
+ function instantiateComponent(name, props, rootElement) {
12038
+ const factory = componentRegistry[name];
12039
+ if (!factory) {
12040
+ console.warn('[Zenith] Component not found:', name);
12041
+ return null;
12042
+ }
12043
+ return factory(props, rootElement);
12044
+ }
12045
+
12046
+ /**
12047
+ * Hydrate components by discovering data-zen-component markers
12048
+ * This is the ONLY place component instantiation should happen
12049
+ */
12050
+ function hydrateComponents(container) {
12051
+ const componentElements = container.querySelectorAll('[data-zen-component]');
12052
+
12053
+ for (let i = 0; i < componentElements.length; i++) {
12054
+ const el = componentElements[i];
12055
+ const componentName = el.getAttribute('data-zen-component');
12056
+
12057
+ // Skip if already hydrated
12058
+ if (el.__zenith_instance) continue;
12059
+
12060
+ // Parse props from data attribute if present
12061
+ const propsJson = el.getAttribute('data-zen-props') || '{}';
12062
+ let props = {};
12063
+ try {
12064
+ props = JSON.parse(propsJson);
12065
+ } catch(e) {
12066
+ console.warn('[Zenith] Invalid props JSON for', componentName);
12067
+ }
12068
+
12069
+ // Instantiate component and bind to DOM element
12070
+ const instance = instantiateComponent(componentName, props, el);
12071
+
12072
+ if (instance) {
12073
+ el.__zenith_instance = instance;
12074
+ }
12075
+ }
12076
+ }
12077
+
11838
12078
  // ============================================
11839
12079
  // Expression Registry & Hydration
11840
12080
  // ============================================
@@ -12254,8 +12494,13 @@ function generateBundleJS() {
12254
12494
  triggerUnmount: triggerUnmount,
12255
12495
  // Hydration
12256
12496
  hydrate: zenithHydrate,
12497
+ hydrateComponents: hydrateComponents, // Marker-driven component instantiation
12257
12498
  registerExpression: registerExpression,
12258
- getExpression: getExpression
12499
+ getExpression: getExpression,
12500
+ // Component instance system
12501
+ createInstance: createComponentInstance,
12502
+ defineComponent: defineComponent,
12503
+ instantiate: instantiateComponent
12259
12504
  };
12260
12505
 
12261
12506
  // Expose with zen* prefix for direct usage