also looking at this
fix(client-script): bind template names shadowed by inlined imports (#1767)
#1768
4 files
+97
-1
| @@ -338,6 +338,25 @@ export async function bundleClientScript( | ||
| 338 | 338 | break |
| 339 | 339 | } |
| 340 | 340 | |
| 341 | // Capture Bun's export-rename map BEFORE stripping (stacksjs/stx#1767). | |
| 342 | // When an inlined import shares a top-level name with a component-declared | |
| 343 | // const, Bun.build keeps the import under the bare name and RENAMES the | |
| 344 | // local (`foo` -> `foo2`); the catch-all export we added above then comes | |
| 345 | // back as `export { foo2 as foo }`. That `as` clause is the only record of | |
| 346 | // the rename. If we strip it blindly, the scope registrar re-harvests the | |
| 347 | // bare `foo` — now the raw import — and binds the template to the wrong | |
| 348 | // value. So collect [originalName, renamedBinding] pairs here and re-expose | |
| 349 | // them below, letting the registrar bind the original name to the correct | |
| 350 | // (renamed) identifier without touching the import the local closes over. | |
| 351 | const renamePairs: Array<[string, string]> = [] | |
| 352 | for (const exp of bundled.matchAll(/^\s*export\s*\{([^}]*)\}\s*;?\s*$/gm)) { | |
| 353 | for (const spec of exp[1].split(',')) { | |
| 354 | const asMatch = spec.trim().match(/^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/) | |
| 355 | if (asMatch && asMatch[1] !== asMatch[2]) | |
| 356 | renamePairs.push([asMatch[2], asMatch[1]]) // [exported/original name, renamed local binding] | |
| 357 | } | |
| 358 | } | |
| 359 | ||
| 341 | 360 | // Strip ESM export artifacts from the output |
| 342 | 361 | // Bun.build with format: 'esm' may add `export { ... }` at the end |
| 343 | 362 | bundled = bundled |
| @@ -345,6 +364,17 @@ export async function bundleClientScript( | ||
| 345 | 364 | .replace(/^\s*export\s+default\s+.*$/gm, '') |
| 346 | 365 | .trim() |
| 347 | 366 | |
| 367 | // Re-expose renamed component declarations under their ORIGINAL names as a | |
| 368 | // plain map (originalName -> renamed binding). The scope registrar merges | |
| 369 | // this so the template binds to the component's declaration, not the | |
| 370 | // shadowing import. We do NOT reassign the bare name (`var foo = foo2`): | |
| 371 | // the renamed local's closure still reads the import under `foo`, so | |
| 372 | // clobbering it would break the binding it depends on. See #1767. | |
| 373 | if (renamePairs.length > 0) { | |
| 374 | const entries = renamePairs.map(([orig, id]) => `${JSON.stringify(orig)}: ${id}`).join(', ') | |
| 375 | bundled += `\nvar __stxScopeRenames = { ${entries} };` | |
| 376 | } | |
| 377 | ||
| 348 | 378 | // Strip any remaining external import statements (stx, @stores etc.) |
| 349 | 379 | // These will be handled by transformAutoImports and transformStoreImports |
| 350 | 380 | // Keep them as-is — the existing transforms expect to find them |