@llui/compiler 0.3.2 → 0.5.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/dist/transform.js CHANGED
@@ -718,6 +718,20 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
718
718
  ? generateDevCode(componentDecls, mcpPort)
719
719
  : { top: '', bottom: '' };
720
720
  let output = (_top ? _top + '\n' : '') + printer.printFile(transformed) + (_bottom ? '\n' + _bottom : '');
721
+ // Inject the `@llui/dom/internal` import on the fallback path too.
722
+ // The per-statement edit loop (where the normal injection lives)
723
+ // never ran to completion in this branch, so do it inline.
724
+ const internalEditFb = buildInternalImportEdit(lluiImport, usesBindUncertain, usesCloneStaticTemplate, usesApplyBinding, scopeRegistrationsInjected);
725
+ if (internalEditFb) {
726
+ // Place right after the public `@llui/dom` import in the
727
+ // printed output. The printer normalizes the import to a
728
+ // single line; locate it by string match.
729
+ const m = output.match(/import\s*\{[^}]*\}\s*from\s*['"]@llui\/dom['"];?\n/);
730
+ if (m && m.index !== undefined) {
731
+ const insertAt = m.index + m[0].length;
732
+ output = output.slice(0, insertAt) + internalEditFb.replacement + output.slice(insertAt);
733
+ }
734
+ }
721
735
  if (devMode || emitAgentMetadata) {
722
736
  output = appendCompilerCacheProps(output, componentDecls);
723
737
  }
@@ -738,6 +752,14 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
738
752
  finalEdits.push({ start: origStart, end: origEnd, replacement });
739
753
  }
740
754
  }
755
+ // Compiler-emitted internal helpers ride on `@llui/dom/internal`,
756
+ // not on the public `@llui/dom` barrel. Insert the import as a
757
+ // text-level edit (not an AST statement) so it doesn't disturb the
758
+ // origin↔transformed index pairing the per-statement diff relies on.
759
+ // See cleanupImports' NOTE and buildInternalImportEdit's docstring.
760
+ const internalEdit = buildInternalImportEdit(lluiImport, usesBindUncertain, usesCloneStaticTemplate, usesApplyBinding, scopeRegistrationsInjected);
761
+ if (internalEdit)
762
+ finalEdits.push(internalEdit);
741
763
  // Dev setup: enable* must run BEFORE user's mountApp (top of file),
742
764
  // but import.meta.hot.accept needs to reference user's component vars
743
765
  // (bottom of file). So split the injection.
@@ -1204,50 +1226,37 @@ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElT
1204
1226
  const clause = lluiImport.importClause;
1205
1227
  if (!clause?.namedBindings || !ts.isNamedImports(clause.namedBindings))
1206
1228
  return sf;
1207
- const remaining = clause.namedBindings.elements.filter((spec) => !compiled.has(spec.name.text));
1208
- const hasElSplit = clause.namedBindings.elements.some((s) => s.name.text === 'elSplit');
1209
- if (!hasElSplit && usesElSplit) {
1210
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('elSplit')));
1211
- }
1212
- const hasBindUncertain = clause.namedBindings.elements.some((s) => s.name.text === '__bindUncertain');
1213
- if (!hasBindUncertain && usesBindUncertain) {
1214
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__bindUncertain')));
1215
- }
1216
- const hasElTemplate = clause.namedBindings.elements.some((s) => s.name.text === 'elTemplate');
1217
- if (!hasElTemplate && usesElTemplate) {
1218
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('elTemplate')));
1219
- }
1220
- const hasCloneStaticTemplate = clause.namedBindings.elements.some((s) => s.name.text === '__cloneStaticTemplate');
1221
- if (!hasCloneStaticTemplate && usesCloneStaticTemplate) {
1222
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__cloneStaticTemplate')));
1223
- }
1224
- const hasMemo = clause.namedBindings.elements.some((s) => s.name.text === 'memo');
1225
- if (!hasMemo && usesMemo) {
1226
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('memo')));
1227
- }
1228
- if (usesApplyBinding) {
1229
- if (!clause.namedBindings.elements.some((s) => s.name.text === '__runPhase2')) {
1230
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__runPhase2')));
1231
- }
1232
- if (!clause.namedBindings.elements.some((s) => s.name.text === '__handleMsg')) {
1233
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__handleMsg')));
1229
+ // Public-surface imports stay on `from '@llui/dom'`. Compiler-emitted
1230
+ // runtime helpers go on a separate `from '@llui/dom/internal'`
1231
+ // declaration so the vite-plugin's post-bundle property-rename pass
1232
+ // never rewrites an import specifier against a public export name.
1233
+ // See emit-names.ts § COMPILER_DOM_INTERNAL_IMPORTS for the contract.
1234
+ const namedBindings = clause.namedBindings;
1235
+ const remaining = namedBindings.elements.filter((spec) => !compiled.has(spec.name.text));
1236
+ const publicHas = (name) => remaining.some((s) => s.name.text === name) ||
1237
+ namedBindings.elements.some((s) => s.name.text === name);
1238
+ const addPublic = (name) => {
1239
+ if (!publicHas(name)) {
1240
+ remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier(name)));
1234
1241
  }
1235
- }
1236
- // The connect-pattern injector (binding-descriptors.ts) emits
1237
- // `__registerScopeVariants([...])` calls; ensure the runtime
1238
- // helper is imported when at least one was inserted.
1239
- const hasRegisterScopeVariants = clause.namedBindings.elements.some((s) => s.name.text === '__registerScopeVariants');
1240
- if (!hasRegisterScopeVariants && usesRegisterScopeVariants) {
1241
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__registerScopeVariants')));
1242
- }
1243
- // v0.4 size-cut (Tier 1.2): add @llui/dom imports for each primitive
1244
- // referenced by synthesized __view factories. `ctx` is the destructured
1245
- // bag name; its primitive is `useContext` (the only rename pair).
1246
- for (const prim of viewBagPrimitivesNeeded) {
1247
- if (!clause.namedBindings.elements.some((s) => s.name.text === prim)) {
1248
- remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier(prim)));
1249
- }
1250
- }
1242
+ };
1243
+ if (usesElSplit)
1244
+ addPublic('elSplit');
1245
+ if (usesElTemplate)
1246
+ addPublic('elTemplate');
1247
+ if (usesMemo)
1248
+ addPublic('memo');
1249
+ for (const prim of viewBagPrimitivesNeeded)
1250
+ addPublic(prim);
1251
+ // NOTE: the compiler-emitted internal helpers (`__bindUncertain`,
1252
+ // `__cloneStaticTemplate`, `__runPhase2`, `__handleMsg`,
1253
+ // `__registerScopeVariants`) are NOT added here. They live on
1254
+ // `@llui/dom/internal`, and the outer transform pipeline inserts a
1255
+ // separate `from '@llui/dom/internal'` import via a text-level edit
1256
+ // (see `buildInternalImportEdit`). Inserting a new ImportDeclaration
1257
+ // here would break the caller's per-statement origin↔transformed
1258
+ // index pairing — the statement count would change and trailing
1259
+ // statements would silently drop out of the edit list.
1251
1260
  const newBindings = f.createNamedImports(remaining);
1252
1261
  // New TS 6 signature: first arg is `phaseModifier` (undefined =
1253
1262
  // regular import; `ts.SyntaxKind.TypeKeyword` = `import type`).
@@ -1261,8 +1270,7 @@ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElT
1261
1270
  ts.isStringLiteral(stmt.moduleSpecifier) &&
1262
1271
  stmt.moduleSpecifier.text === '@llui/dom' &&
1263
1272
  // `phaseModifier === ts.SyntaxKind.TypeKeyword` is `import type
1264
- // …`; we only want to rewrite value imports. Replaces deprecated
1265
- // `isTypeOnly` from the TS<6 API.
1273
+ // …`; we only want to rewrite value imports.
1266
1274
  stmt.importClause?.phaseModifier !== ts.SyntaxKind.TypeKeyword) {
1267
1275
  replaced = true;
1268
1276
  return newImportDecl;
@@ -1271,6 +1279,40 @@ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElT
1271
1279
  });
1272
1280
  return f.updateSourceFile(sf, statements);
1273
1281
  }
1282
+ /**
1283
+ * Build a single text-insert edit that places
1284
+ * `import { ... } from '@llui/dom/internal'` immediately after the
1285
+ * existing `import { ... } from '@llui/dom'` statement (or appends it
1286
+ * at the start of the file if no @llui/dom import is found, though
1287
+ * the caller has already short-circuited in that case).
1288
+ *
1289
+ * Returns null when no internal helpers are needed. The edit is text-
1290
+ * level (not AST) so it does NOT alter `transformed.statements.length`,
1291
+ * keeping the per-statement origin↔transformed pairing intact.
1292
+ */
1293
+ function buildInternalImportEdit(lluiImport, usesBindUncertain, usesCloneStaticTemplate, usesApplyBinding, usesRegisterScopeVariants) {
1294
+ const names = new Set();
1295
+ if (usesBindUncertain)
1296
+ names.add('__bindUncertain');
1297
+ if (usesCloneStaticTemplate)
1298
+ names.add('__cloneStaticTemplate');
1299
+ if (usesApplyBinding) {
1300
+ names.add('__runPhase2');
1301
+ names.add('__handleMsg');
1302
+ }
1303
+ if (usesRegisterScopeVariants)
1304
+ names.add('__registerScopeVariants');
1305
+ if (names.size === 0)
1306
+ return null;
1307
+ const sortedNames = [...names].sort();
1308
+ const importLine = `import { ${sortedNames.join(', ')} } from '@llui/dom/internal'\n`;
1309
+ const insertAt = lluiImport.getEnd();
1310
+ // The lluiImport's getEnd() points to the position right after the
1311
+ // statement's trailing `;` or `'\n'`. Emit the new import on a fresh
1312
+ // line — `\n` prefix guarantees that even if the original import had
1313
+ // no trailing newline.
1314
+ return { start: insertAt, end: insertAt, replacement: '\n' + importLine };
1315
+ }
1274
1316
  // ── __msgSchema injection ────────────────────────────────────────
1275
1317
  // `injectStateSchema` was the inline emitter for `__stateSchema`.
1276
1318
  // Migrated to `stateSchemaModule` + the registry bridge (v2c/decomp-3).