@zenithbuild/cli 0.6.3 → 0.6.5

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/build.js CHANGED
@@ -16,13 +16,13 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
16
16
  import { mkdir, readdir, rm, stat } from 'node:fs/promises';
17
17
  import { createRequire } from 'node:module';
18
18
  import { basename, dirname, extname, join, relative, resolve } from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
19
  import { generateManifest } from './manifest.js';
21
20
  import { buildComponentRegistry, expandComponents, extractTemplate, isDocumentMode } from './resolve-components.js';
21
+ import { collectExpandedComponentOccurrences } from './component-occurrences.js';
22
+ import { applyOccurrenceRewritePlans, cloneComponentIrForInstance } from './component-instance-ir.js';
23
+ import { resolveBundlerBin, resolveCompilerBin } from './toolchain-paths.js';
24
+ import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
22
25
 
23
- const __filename = fileURLToPath(import.meta.url);
24
- const __dirname = dirname(__filename);
25
- const CLI_ROOT = resolve(__dirname, '..');
26
26
  const require = createRequire(import.meta.url);
27
27
  let cachedTypeScript = undefined;
28
28
 
@@ -40,40 +40,6 @@ function loadTypeScriptApi() {
40
40
  return cachedTypeScript;
41
41
  }
42
42
 
43
- /**
44
- * Resolve a binary path from deterministic candidates.
45
- *
46
- * Supports both repository layout (../zenith-*) and installed package layout
47
- * under node_modules/@zenithbuild (../compiler, ../bundler).
48
- *
49
- * @param {string[]} candidates
50
- * @returns {string}
51
- */
52
- function resolveBinary(candidates) {
53
- for (const candidate of candidates) {
54
- if (existsSync(candidate)) {
55
- return candidate;
56
- }
57
- }
58
- return candidates[0];
59
- }
60
-
61
- const COMPILER_BIN = resolveBinary([
62
- resolve(CLI_ROOT, '../compiler/target/release/zenith-compiler'),
63
- resolve(CLI_ROOT, '../zenith-compiler/target/release/zenith-compiler')
64
- ]);
65
-
66
- function getBundlerBin() {
67
- const envBin = process.env.ZENITH_BUNDLER_BIN;
68
- if (envBin && typeof envBin === 'string' && existsSync(envBin)) {
69
- return envBin;
70
- }
71
- return resolveBinary([
72
- resolve(CLI_ROOT, '../bundler/target/release/zenith-bundler'),
73
- resolve(CLI_ROOT, '../zenith-bundler/target/release/zenith-bundler')
74
- ]);
75
- }
76
-
77
43
  /**
78
44
  * Build a per-build warning emitter that deduplicates repeated compiler lines.
79
45
  *
@@ -133,9 +99,11 @@ function forwardStreamLines(stream, onLine) {
133
99
  * @param {object} compilerRunOptions
134
100
  * @param {(warning: string) => void} [compilerRunOptions.onWarning]
135
101
  * @param {boolean} [compilerRunOptions.suppressWarnings]
102
+ * @param {string} [compilerRunOptions.compilerBin]
136
103
  * @returns {object}
137
104
  */
138
105
  function runCompiler(filePath, stdinSource, compilerOpts = {}, compilerRunOptions = {}) {
106
+ const compilerBin = compilerRunOptions.compilerBin || resolveCompilerBin();
139
107
  const args = stdinSource !== undefined
140
108
  ? ['--stdin', filePath]
141
109
  : [filePath];
@@ -150,7 +118,7 @@ function runCompiler(filePath, stdinSource, compilerOpts = {}, compilerRunOption
150
118
  opts.input = stdinSource;
151
119
  }
152
120
 
153
- const result = spawnSync(COMPILER_BIN, args, opts);
121
+ const result = spawnSync(compilerBin, args, opts);
154
122
 
155
123
  if (result.error) {
156
124
  throw new Error(`Compiler spawn failed for ${filePath}: ${result.error.message}`);
@@ -200,6 +168,8 @@ function stripStyleBlocks(source) {
200
168
  * @param {string} compPath
201
169
  * @param {string} componentSource
202
170
  * @param {object} compIr
171
+ * @param {object} compilerOpts
172
+ * @param {string} compilerBin
203
173
  * @returns {{
204
174
  * map: Map<string, string>,
205
175
  * bindings: Map<string, {
@@ -215,13 +185,14 @@ function stripStyleBlocks(source) {
215
185
  * ambiguous: Set<string>
216
186
  * }}
217
187
  */
218
- function buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts) {
188
+ function buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts, compilerBin) {
219
189
  const out = {
220
190
  map: new Map(),
221
191
  bindings: new Map(),
222
192
  signals: Array.isArray(compIr?.signals) ? compIr.signals : [],
223
193
  stateBindings: Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [],
224
- ambiguous: new Set()
194
+ ambiguous: new Set(),
195
+ sequence: []
225
196
  };
226
197
  const rewrittenExpressions = Array.isArray(compIr?.expressions) ? compIr.expressions : [];
227
198
  const rewrittenBindings = Array.isArray(compIr?.expression_bindings) ? compIr.expression_bindings : [];
@@ -236,7 +207,10 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
236
207
 
237
208
  let templateIr;
238
209
  try {
239
- templateIr = runCompiler(compPath, templateOnly, compilerOpts, { suppressWarnings: true });
210
+ templateIr = runCompiler(compPath, templateOnly, compilerOpts, {
211
+ suppressWarnings: true,
212
+ compilerBin
213
+ });
240
214
  } catch {
241
215
  return out;
242
216
  }
@@ -264,6 +238,12 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
264
238
  }
265
239
  : null;
266
240
 
241
+ out.sequence.push({
242
+ raw,
243
+ rewritten,
244
+ binding: normalizedBinding
245
+ });
246
+
267
247
  if (!out.ambiguous.has(raw) && normalizedBinding) {
268
248
  const existingBinding = out.bindings.get(raw);
269
249
  if (existingBinding) {
@@ -327,7 +307,7 @@ function resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding) {
327
307
  }
328
308
 
329
309
  const pageStateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
330
- const pageSignals = Array.isArray(pageIr?.hoisted?.signals) ? pageIr.hoisted.signals : [];
310
+ const pageSignals = Array.isArray(pageIr?.signals) ? pageIr.signals : [];
331
311
  const pageStateIndexByKey = new Map();
332
312
  const pageSignalIndexByStateKey = new Map();
333
313
 
@@ -620,6 +600,107 @@ function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambiguous) {
620
600
  }
621
601
  }
622
602
 
603
+ function applyScopedIdentifierRewrites(pageIr, scopeRewrite) {
604
+ if (!Array.isArray(pageIr?.expressions) || pageIr.expressions.length === 0) {
605
+ return;
606
+ }
607
+ const bindings = Array.isArray(pageIr.expression_bindings) ? pageIr.expression_bindings : [];
608
+ const rewriteContext = {
609
+ scopeRewrite
610
+ };
611
+
612
+ for (let index = 0; index < pageIr.expressions.length; index++) {
613
+ const current = pageIr.expressions[index];
614
+ if (typeof current === 'string') {
615
+ pageIr.expressions[index] = rewritePropsExpression(current, rewriteContext);
616
+ }
617
+
618
+ if (!bindings[index] || typeof bindings[index] !== 'object') {
619
+ continue;
620
+ }
621
+
622
+ if (typeof bindings[index].literal === 'string') {
623
+ bindings[index].literal = rewritePropsExpression(bindings[index].literal, rewriteContext);
624
+ }
625
+ if (typeof bindings[index].compiled_expr === 'string') {
626
+ bindings[index].compiled_expr = rewritePropsExpression(bindings[index].compiled_expr, rewriteContext);
627
+ }
628
+ }
629
+ }
630
+
631
+ function synthesizeSignalBackedCompiledExpressions(pageIr) {
632
+ if (!Array.isArray(pageIr?.expression_bindings) || pageIr.expression_bindings.length === 0) {
633
+ return;
634
+ }
635
+
636
+ const stateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
637
+ const signals = Array.isArray(pageIr?.signals) ? pageIr.signals : [];
638
+ if (stateBindings.length === 0 || signals.length === 0) {
639
+ return;
640
+ }
641
+
642
+ const signalIndexByStateKey = new Map();
643
+ for (let index = 0; index < signals.length; index++) {
644
+ const stateIndex = signals[index]?.state_index;
645
+ const stateKey = Number.isInteger(stateIndex) ? stateBindings[stateIndex]?.key : null;
646
+ if (typeof stateKey === 'string' && stateKey.length > 0) {
647
+ signalIndexByStateKey.set(stateKey, index);
648
+ }
649
+ }
650
+ if (signalIndexByStateKey.size === 0) {
651
+ return;
652
+ }
653
+
654
+ for (let index = 0; index < pageIr.expression_bindings.length; index++) {
655
+ const binding = pageIr.expression_bindings[index];
656
+ if (!binding || typeof binding !== 'object') {
657
+ continue;
658
+ }
659
+ if (typeof binding.compiled_expr === 'string' && binding.compiled_expr.includes('signalMap.get(')) {
660
+ continue;
661
+ }
662
+
663
+ const candidate = typeof binding.literal === 'string' && binding.literal.trim().length > 0
664
+ ? binding.literal
665
+ : typeof pageIr.expressions?.[index] === 'string'
666
+ ? pageIr.expressions[index]
667
+ : null;
668
+ if (typeof candidate !== 'string' || candidate.trim().length === 0) {
669
+ continue;
670
+ }
671
+
672
+ let rewritten = candidate;
673
+ const signalIndices = [];
674
+ for (const [stateKey, signalIndex] of signalIndexByStateKey.entries()) {
675
+ if (!rewritten.includes(stateKey)) {
676
+ continue;
677
+ }
678
+ const escaped = stateKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
679
+ const pattern = new RegExp(`\\b${escaped}\\b`, 'g');
680
+ if (!pattern.test(rewritten)) {
681
+ continue;
682
+ }
683
+ rewritten = rewritten.replace(pattern, `signalMap.get(${signalIndex}).get()`);
684
+ signalIndices.push(signalIndex);
685
+ }
686
+
687
+ if (rewritten === candidate || signalIndices.length === 0) {
688
+ continue;
689
+ }
690
+
691
+ const uniqueSignalIndices = [...new Set(signalIndices)].sort((a, b) => a - b);
692
+ binding.compiled_expr = rewritten;
693
+ binding.signal_indices = uniqueSignalIndices;
694
+ if (uniqueSignalIndices.length === 1) {
695
+ binding.signal_index = uniqueSignalIndices[0];
696
+ const stateIndex = signals[uniqueSignalIndices[0]]?.state_index;
697
+ if (Number.isInteger(stateIndex)) {
698
+ binding.state_index = stateIndex;
699
+ }
700
+ }
701
+ }
702
+ }
703
+
623
704
  function normalizeExpressionBindingDependencies(pageIr) {
624
705
  if (!Array.isArray(pageIr?.expression_bindings) || pageIr.expression_bindings.length === 0) {
625
706
  return;
@@ -1653,6 +1734,62 @@ function deriveScopedIdentifierAlias(value) {
1653
1734
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate) ? candidate : ident;
1654
1735
  }
1655
1736
 
1737
+ /**
1738
+ * @param {string} source
1739
+ * @returns {string[]}
1740
+ */
1741
+ function extractDeclaredIdentifiers(source) {
1742
+ const text = String(source || '').trim();
1743
+ if (!text) {
1744
+ return [];
1745
+ }
1746
+
1747
+ const ts = loadTypeScriptApi();
1748
+ if (ts) {
1749
+ const sourceFile = ts.createSourceFile('zenith-hoisted-declaration.ts', text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1750
+ const identifiers = [];
1751
+ const collectBindingNames = (name) => {
1752
+ if (ts.isIdentifier(name)) {
1753
+ identifiers.push(name.text);
1754
+ return;
1755
+ }
1756
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
1757
+ for (const element of name.elements) {
1758
+ if (ts.isBindingElement(element)) {
1759
+ collectBindingNames(element.name);
1760
+ }
1761
+ }
1762
+ }
1763
+ };
1764
+
1765
+ for (const statement of sourceFile.statements) {
1766
+ if (!ts.isVariableStatement(statement)) {
1767
+ continue;
1768
+ }
1769
+ for (const declaration of statement.declarationList.declarations) {
1770
+ collectBindingNames(declaration.name);
1771
+ }
1772
+ }
1773
+
1774
+ if (identifiers.length > 0) {
1775
+ return identifiers;
1776
+ }
1777
+ }
1778
+
1779
+ const fallback = [];
1780
+ const match = text.match(/^\s*(?:const|let|var)\s+([\s\S]+?);?\s*$/);
1781
+ if (!match) {
1782
+ return fallback;
1783
+ }
1784
+ const declarationList = match[1];
1785
+ const identifierRe = /(?:^|,)\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*(?::[^=,]+)?=/g;
1786
+ let found;
1787
+ while ((found = identifierRe.exec(declarationList)) !== null) {
1788
+ fallback.push(found[1]);
1789
+ }
1790
+ return fallback;
1791
+ }
1792
+
1656
1793
  /**
1657
1794
  * @param {Map<string, string>} map
1658
1795
  * @param {Set<string>} ambiguous
@@ -1698,9 +1835,131 @@ function buildScopedIdentifierRewrite(ir) {
1698
1835
  recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(fnName), fnName);
1699
1836
  }
1700
1837
 
1838
+ const declarations = Array.isArray(ir?.hoisted?.declarations) ? ir.hoisted.declarations : [];
1839
+ for (const declaration of declarations) {
1840
+ if (typeof declaration !== 'string') {
1841
+ continue;
1842
+ }
1843
+ for (const identifier of extractDeclaredIdentifiers(declaration)) {
1844
+ recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(identifier), identifier);
1845
+ }
1846
+ }
1847
+
1701
1848
  return out;
1702
1849
  }
1703
1850
 
1851
+ function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
1852
+ const ts = loadTypeScriptApi();
1853
+ if (!(scopeMap instanceof Map) || !ts) {
1854
+ return expr;
1855
+ }
1856
+
1857
+ const wrapped = `const __zenith_expr__ = (${expr});`;
1858
+ let sourceFile;
1859
+ try {
1860
+ sourceFile = ts.createSourceFile('zenith-expression.ts', wrapped, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1861
+ } catch {
1862
+ return expr;
1863
+ }
1864
+
1865
+ const statement = sourceFile.statements[0];
1866
+ if (!statement || !ts.isVariableStatement(statement)) {
1867
+ return expr;
1868
+ }
1869
+ const initializer = statement.declarationList.declarations[0]?.initializer;
1870
+ const root = initializer && ts.isParenthesizedExpression(initializer) ? initializer.expression : initializer;
1871
+ if (!root) {
1872
+ return expr;
1873
+ }
1874
+
1875
+ const replacements = [];
1876
+ const collectBoundNames = (name, target) => {
1877
+ if (ts.isIdentifier(name)) {
1878
+ target.add(name.text);
1879
+ return;
1880
+ }
1881
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
1882
+ for (const element of name.elements) {
1883
+ if (ts.isBindingElement(element)) {
1884
+ collectBoundNames(element.name, target);
1885
+ }
1886
+ }
1887
+ }
1888
+ };
1889
+ const shouldSkipIdentifier = (node, localBindings) => {
1890
+ if (localBindings.has(node.text)) {
1891
+ return true;
1892
+ }
1893
+ const parent = node.parent;
1894
+ if (!parent) {
1895
+ return false;
1896
+ }
1897
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
1898
+ return true;
1899
+ }
1900
+ if (ts.isPropertyAssignment(parent) && parent.name === node) {
1901
+ return true;
1902
+ }
1903
+ if (ts.isShorthandPropertyAssignment(parent)) {
1904
+ return true;
1905
+ }
1906
+ if (ts.isBindingElement(parent) && parent.name === node) {
1907
+ return true;
1908
+ }
1909
+ if (ts.isParameter(parent) && parent.name === node) {
1910
+ return true;
1911
+ }
1912
+ return false;
1913
+ };
1914
+ const visit = (node, localBindings) => {
1915
+ let nextBindings = localBindings;
1916
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
1917
+ nextBindings = new Set(localBindings);
1918
+ if (node.name && ts.isIdentifier(node.name)) {
1919
+ nextBindings.add(node.name.text);
1920
+ }
1921
+ for (const param of node.parameters) {
1922
+ collectBoundNames(param.name, nextBindings);
1923
+ }
1924
+ }
1925
+
1926
+ if (ts.isIdentifier(node) && !shouldSkipIdentifier(node, nextBindings)) {
1927
+ const rewritten = scopeMap.get(node.text);
1928
+ if (
1929
+ typeof rewritten === 'string' &&
1930
+ rewritten.length > 0 &&
1931
+ rewritten !== node.text &&
1932
+ !(scopeAmbiguous instanceof Set && scopeAmbiguous.has(node.text))
1933
+ ) {
1934
+ replacements.push({
1935
+ start: node.getStart(sourceFile),
1936
+ end: node.getEnd(),
1937
+ text: rewritten
1938
+ });
1939
+ }
1940
+ }
1941
+
1942
+ ts.forEachChild(node, (child) => visit(child, nextBindings));
1943
+ };
1944
+
1945
+ visit(root, new Set());
1946
+ if (replacements.length === 0) {
1947
+ return expr;
1948
+ }
1949
+
1950
+ let rewritten = wrapped;
1951
+ for (const replacement of replacements.sort((a, b) => b.start - a.start)) {
1952
+ rewritten = `${rewritten.slice(0, replacement.start)}${replacement.text}${rewritten.slice(replacement.end)}`;
1953
+ }
1954
+
1955
+ const prefix = 'const __zenith_expr__ = (';
1956
+ const suffix = ');';
1957
+ if (!rewritten.startsWith(prefix) || !rewritten.endsWith(suffix)) {
1958
+ return expr;
1959
+ }
1960
+ return rewritten.slice(prefix.length, rewritten.length - suffix.length);
1961
+ }
1962
+
1704
1963
  /**
1705
1964
  * @param {string} expr
1706
1965
  * @param {{
@@ -1730,18 +1989,21 @@ function rewritePropsExpression(expr, rewriteContext = null) {
1730
1989
  const scopeMap = rewriteContext?.scopeRewrite?.map;
1731
1990
  const scopeAmbiguous = rewriteContext?.scopeRewrite?.ambiguous;
1732
1991
  const rootMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)([\s\S]*)$/);
1733
- if (!rootMatch || !(scopeMap instanceof Map)) {
1992
+ if (!(scopeMap instanceof Map)) {
1734
1993
  return trimmed;
1735
1994
  }
1995
+ if (!rootMatch) {
1996
+ return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
1997
+ }
1736
1998
 
1737
1999
  const root = rootMatch[1];
1738
2000
  if (scopeAmbiguous instanceof Set && scopeAmbiguous.has(root)) {
1739
- return trimmed;
2001
+ return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
1740
2002
  }
1741
2003
 
1742
2004
  const rewrittenRoot = scopeMap.get(root);
1743
2005
  if (typeof rewrittenRoot !== 'string' || rewrittenRoot.length === 0 || rewrittenRoot === root) {
1744
- return trimmed;
2006
+ return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
1745
2007
  }
1746
2008
 
1747
2009
  return `${rewrittenRoot}${rootMatch[2]}`;
@@ -1874,13 +2136,21 @@ function deferComponentRuntimeBlock(source) {
1874
2136
  * @param {string} projectRoot
1875
2137
  * @param {object | null} [logger]
1876
2138
  * @param {boolean} [showInfo]
2139
+ * @param {string} [bundlerBin]
1877
2140
  * @returns {Promise<void>}
1878
2141
  */
1879
- function runBundler(envelope, outDir, projectRoot, logger = null, showInfo = true) {
2142
+ function runBundler(
2143
+ envelope,
2144
+ outDir,
2145
+ projectRoot,
2146
+ logger = null,
2147
+ showInfo = true,
2148
+ bundlerBin = resolveBundlerBin(projectRoot)
2149
+ ) {
1880
2150
  return new Promise((resolvePromise, rejectPromise) => {
1881
2151
  const useStructuredLogger = Boolean(logger && typeof logger.childLine === 'function');
1882
2152
  const child = spawn(
1883
- getBundlerBin(),
2153
+ bundlerBin,
1884
2154
  ['--out-dir', outDir],
1885
2155
  {
1886
2156
  cwd: projectRoot,
@@ -1968,6 +2238,8 @@ async function collectAssets(rootDir) {
1968
2238
  export async function build(options) {
1969
2239
  const { pagesDir, outDir, config = {}, logger = null, showBundlerInfo = true } = options;
1970
2240
  const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
2241
+ const compilerBin = resolveCompilerBin(projectRoot);
2242
+ const bundlerBin = resolveBundlerBin(projectRoot);
1971
2243
  const softNavigationEnabled = config.softNavigation === true || config.router === true;
1972
2244
  const compilerOpts = {
1973
2245
  typescriptDefault: config.typescriptDefault === true,
@@ -1978,6 +2250,15 @@ export async function build(options) {
1978
2250
  await rm(outDir, { recursive: true, force: true });
1979
2251
  await mkdir(outDir, { recursive: true });
1980
2252
 
2253
+ if (logger) {
2254
+ await maybeWarnAboutZenithVersionMismatch({
2255
+ projectRoot,
2256
+ logger,
2257
+ command: 'build',
2258
+ bundlerBinPath: bundlerBin
2259
+ });
2260
+ }
2261
+
1981
2262
  // Derive src/ directory from pages/ directory
1982
2263
  const srcDir = resolve(pagesDir, '..');
1983
2264
 
@@ -2015,7 +2296,7 @@ export async function build(options) {
2015
2296
  for (const entry of manifest) {
2016
2297
  const sourceFile = join(pagesDir, entry.file);
2017
2298
  const rawSource = readFileSync(sourceFile, 'utf8');
2018
- const componentUsageAttrs = collectRecursiveComponentUsageAttrs(rawSource, registry, sourceFile);
2299
+ const componentOccurrences = collectExpandedComponentOccurrences(rawSource, registry, sourceFile);
2019
2300
 
2020
2301
  const baseName = sourceFile.slice(0, -extname(sourceFile).length);
2021
2302
  let adjacentGuard = null;
@@ -2026,7 +2307,7 @@ export async function build(options) {
2026
2307
  }
2027
2308
 
2028
2309
  // 2a. Expand PascalCase component tags
2029
- const { expandedSource, usedComponents } = expandComponents(
2310
+ const { expandedSource } = expandComponents(
2030
2311
  rawSource, registry, sourceFile
2031
2312
  );
2032
2313
  const extractedServer = extractServerScript(expandedSource, sourceFile, compilerOpts);
@@ -2037,7 +2318,10 @@ export async function build(options) {
2037
2318
  sourceFile,
2038
2319
  compileSource,
2039
2320
  compilerOpts,
2040
- { onWarning: emitCompilerWarning }
2321
+ {
2322
+ compilerBin,
2323
+ onWarning: emitCompilerWarning
2324
+ }
2041
2325
  );
2042
2326
 
2043
2327
  const hasGuard = (extractedServer.serverScript && extractedServer.serverScript.has_guard) || adjacentGuard !== null;
@@ -2077,19 +2361,42 @@ export async function build(options) {
2077
2361
  pageIr.hoisted.state = pageIr.hoisted.state || [];
2078
2362
  pageIr.hoisted.code = pageIr.hoisted.code || [];
2079
2363
  const seenStaticImports = new Set();
2364
+ const occurrenceCountByPath = new Map();
2365
+ for (const occurrence of componentOccurrences) {
2366
+ const key = occurrence.componentPath || occurrence.name;
2367
+ occurrenceCountByPath.set(key, (occurrenceCountByPath.get(key) || 0) + 1);
2368
+ }
2369
+
2080
2370
  const pageExpressionRewriteMap = new Map();
2081
2371
  const pageExpressionBindingMap = new Map();
2082
2372
  const pageAmbiguousExpressionMap = new Set();
2083
2373
  const knownRefKeys = new Set();
2374
+ const componentOccurrencePlans = [];
2084
2375
  const pageScopeRewrite = buildScopedIdentifierRewrite(pageIr);
2085
- const pageSelfExpressionRewrite = buildComponentExpressionRewrite(sourceFile, compileSource, pageIr, compilerOpts);
2376
+ const pageSelfExpressionRewrite = buildComponentExpressionRewrite(
2377
+ sourceFile,
2378
+ compileSource,
2379
+ pageIr,
2380
+ compilerOpts,
2381
+ compilerBin
2382
+ );
2383
+ mergeExpressionRewriteMaps(
2384
+ pageExpressionRewriteMap,
2385
+ pageExpressionBindingMap,
2386
+ pageAmbiguousExpressionMap,
2387
+ pageSelfExpressionRewrite,
2388
+ pageIr
2389
+ );
2086
2390
  const componentScopeRewriteCache = new Map();
2391
+ let componentInstanceCounter = 0;
2087
2392
 
2088
2393
  // 2c. Compile each used component separately for its script IR
2089
- for (const compName of usedComponents) {
2090
- const compPath = registry.get(compName);
2394
+ for (const occurrence of componentOccurrences) {
2395
+ const compName = occurrence.name;
2396
+ const compPath = occurrence.componentPath || registry.get(compName);
2091
2397
  if (!compPath) continue;
2092
2398
  const componentSource = readFileSync(compPath, 'utf8');
2399
+ const occurrenceCount = occurrenceCountByPath.get(compPath) || 0;
2093
2400
 
2094
2401
  let compIr;
2095
2402
  if (componentIrCache.has(compPath)) {
@@ -2100,7 +2407,10 @@ export async function build(options) {
2100
2407
  compPath,
2101
2408
  componentCompileSource,
2102
2409
  compilerOpts,
2103
- { onWarning: emitCompilerWarning }
2410
+ {
2411
+ compilerBin,
2412
+ onWarning: emitCompilerWarning
2413
+ }
2104
2414
  );
2105
2415
  componentIrCache.set(compPath, compIr);
2106
2416
  }
@@ -2113,19 +2423,20 @@ export async function build(options) {
2113
2423
 
2114
2424
  let expressionRewrite = componentExpressionRewriteCache.get(compPath);
2115
2425
  if (!expressionRewrite) {
2116
- expressionRewrite = buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts);
2426
+ expressionRewrite = buildComponentExpressionRewrite(
2427
+ compPath,
2428
+ componentSource,
2429
+ compIr,
2430
+ compilerOpts,
2431
+ compilerBin
2432
+ );
2117
2433
  componentExpressionRewriteCache.set(compPath, expressionRewrite);
2118
2434
  }
2119
2435
 
2120
- let usageEntry = (componentUsageAttrs.get(compName) || [])[0] || { attrs: '', ownerPath: sourceFile };
2121
- if (!usageEntry || typeof usageEntry !== 'object') {
2122
- usageEntry = { attrs: '', ownerPath: sourceFile };
2123
- }
2124
-
2125
2436
  let attrExpressionRewrite = pageSelfExpressionRewrite;
2126
2437
  let attrScopeRewrite = pageScopeRewrite;
2127
- const ownerPath = typeof usageEntry.ownerPath === 'string' && usageEntry.ownerPath.length > 0
2128
- ? usageEntry.ownerPath
2438
+ const ownerPath = typeof occurrence.ownerPath === 'string' && occurrence.ownerPath.length > 0
2439
+ ? occurrence.ownerPath
2129
2440
  : sourceFile;
2130
2441
 
2131
2442
  if (ownerPath !== sourceFile) {
@@ -2136,7 +2447,10 @@ export async function build(options) {
2136
2447
  ownerPath,
2137
2448
  stripStyleBlocks(ownerSource),
2138
2449
  compilerOpts,
2139
- { onWarning: emitCompilerWarning }
2450
+ {
2451
+ compilerBin,
2452
+ onWarning: emitCompilerWarning
2453
+ }
2140
2454
  );
2141
2455
  componentIrCache.set(ownerPath, ownerIr);
2142
2456
  }
@@ -2144,7 +2458,13 @@ export async function build(options) {
2144
2458
  attrExpressionRewrite = componentExpressionRewriteCache.get(ownerPath);
2145
2459
  if (!attrExpressionRewrite) {
2146
2460
  const ownerSource = readFileSync(ownerPath, 'utf8');
2147
- attrExpressionRewrite = buildComponentExpressionRewrite(ownerPath, ownerSource, ownerIr, compilerOpts);
2461
+ attrExpressionRewrite = buildComponentExpressionRewrite(
2462
+ ownerPath,
2463
+ ownerSource,
2464
+ ownerIr,
2465
+ compilerOpts,
2466
+ compilerBin
2467
+ );
2148
2468
  componentExpressionRewriteCache.set(ownerPath, attrExpressionRewrite);
2149
2469
  }
2150
2470
 
@@ -2155,17 +2475,36 @@ export async function build(options) {
2155
2475
  }
2156
2476
  }
2157
2477
 
2478
+ const useIsolatedInstance = occurrenceCount > 1;
2479
+ const { ir: instanceIr, refIdentifierPairs } = useIsolatedInstance
2480
+ ? cloneComponentIrForInstance(
2481
+ compIr,
2482
+ componentInstanceCounter++,
2483
+ extractDeclaredIdentifiers,
2484
+ resolveStateKeyFromBindings
2485
+ )
2486
+ : { ir: compIr, refIdentifierPairs: [] };
2487
+ const instanceRewrite = useIsolatedInstance
2488
+ ? buildComponentExpressionRewrite(
2489
+ compPath,
2490
+ componentSource,
2491
+ instanceIr,
2492
+ compilerOpts,
2493
+ compilerBin
2494
+ )
2495
+ : expressionRewrite;
2496
+
2158
2497
  // 2d. Merge component IR into page IR
2159
2498
  mergeComponentIr(
2160
2499
  pageIr,
2161
- compIr,
2500
+ instanceIr,
2162
2501
  compPath,
2163
2502
  sourceFile,
2164
2503
  {
2165
2504
  includeCode: true,
2166
2505
  cssImportsOnly: isDocMode,
2167
2506
  documentMode: isDocMode,
2168
- componentAttrs: typeof usageEntry.attrs === 'string' ? usageEntry.attrs : '',
2507
+ componentAttrs: typeof occurrence.attrs === 'string' ? occurrence.attrs : '',
2169
2508
  componentAttrsRewrite: {
2170
2509
  expressionRewrite: attrExpressionRewrite,
2171
2510
  scopeRewrite: attrScopeRewrite
@@ -2175,21 +2514,36 @@ export async function build(options) {
2175
2514
  knownRefKeys
2176
2515
  );
2177
2516
 
2178
- mergeExpressionRewriteMaps(
2179
- pageExpressionRewriteMap,
2180
- pageExpressionBindingMap,
2181
- pageAmbiguousExpressionMap,
2182
- expressionRewrite,
2183
- pageIr
2184
- );
2517
+ if (useIsolatedInstance) {
2518
+ componentOccurrencePlans.push({
2519
+ rewrite: instanceRewrite,
2520
+ expressionSequence: instanceRewrite.sequence,
2521
+ refSequence: refIdentifierPairs
2522
+ });
2523
+ } else {
2524
+ mergeExpressionRewriteMaps(
2525
+ pageExpressionRewriteMap,
2526
+ pageExpressionBindingMap,
2527
+ pageAmbiguousExpressionMap,
2528
+ expressionRewrite,
2529
+ pageIr
2530
+ );
2531
+ }
2185
2532
  }
2186
2533
 
2534
+ applyOccurrenceRewritePlans(
2535
+ pageIr,
2536
+ componentOccurrencePlans,
2537
+ (rewrite, binding) => resolveRewrittenBindingMetadata(pageIr, rewrite, binding)
2538
+ );
2187
2539
  applyExpressionRewrites(
2188
2540
  pageIr,
2189
2541
  pageExpressionRewriteMap,
2190
2542
  pageExpressionBindingMap,
2191
2543
  pageAmbiguousExpressionMap
2192
2544
  );
2545
+ applyScopedIdentifierRewrites(pageIr, buildScopedIdentifierRewrite(pageIr));
2546
+ synthesizeSignalBackedCompiledExpressions(pageIr);
2193
2547
  normalizeExpressionBindingDependencies(pageIr);
2194
2548
 
2195
2549
  rewriteLegacyMarkupIdentifiers(pageIr);
@@ -2204,7 +2558,7 @@ export async function build(options) {
2204
2558
  }
2205
2559
 
2206
2560
  if (envelopes.length > 0) {
2207
- await runBundler(envelopes, outDir, projectRoot, logger, showBundlerInfo);
2561
+ await runBundler(envelopes, outDir, projectRoot, logger, showBundlerInfo, bundlerBin);
2208
2562
  }
2209
2563
 
2210
2564
  const assets = await collectAssets(outDir);