diff --git a/__tests__/tests.ts b/__tests__/tests.ts index 6655ae1..3501d18 100644 --- a/__tests__/tests.ts +++ b/__tests__/tests.ts @@ -690,6 +690,37 @@ describe('htmlbars-inline-precompile', function () { expect(transformed).toContain(`import two from "my-library"`); }); + it('reuses existing imports when possible', () => { + precompile = runASTTransform(compiler, importTransform); + + let transformed = transform(stripIndent` + import { precompileTemplate } from '@ember/template-compilation'; + export default function() { + const template = precompileTemplate('{{onePlusOne}}{{onePlusOne}}'); + } + `); + + expect(transformed).toContain(`{{two}}{{two}}`); + expect(transformed).toContain(`locals: [two]`); + expect(transformed).toContain(`import two from "my-library"`); + }); + + it('rebinds existing imports when necessary', () => { + precompile = runASTTransform(compiler, importTransform); + + let transformed = transform(stripIndent` + import { precompileTemplate } from '@ember/template-compilation'; + export default function() { + const template = precompileTemplate('{{onePlusOne}}{{#let "twice" as |two|}}{{onePlusOne}}{{/let}}'); + } + `); + + expect(transformed).toContain(`{{two}}{{#let "twice" as |two|}}{{two0}}{{/let}}`); + expect(transformed).toContain(`locals: [two, two0]`); + expect(transformed).toContain(`import two from "my-library"`); + expect(transformed).toContain('let two0 = two'); + }); + it('does not smash existing js binding for expression', function () { precompile = runASTTransform(compiler, expressionTransform); @@ -751,6 +782,22 @@ describe('htmlbars-inline-precompile', function () { expect(transformed).toContain(`let two = 1 + 1`); }); + it('does not smash other previously-bound expressions with new ones', () => { + precompile = runASTTransform(compiler, expressionTransform); + + let transformed = transform(stripIndent` + import { precompileTemplate } from '@ember/template-compilation'; + export default function() { + const template = precompileTemplate('{{onePlusOne}}{{onePlusOne}}'); + } + `); + + expect(transformed).toContain(`{{two}}{{two0}}`); + expect(transformed).toContain(`locals: [two, two0]`); + expect(transformed).toContain(`let two = 1 + 1`); + expect(transformed).toContain(`let two0 = 1 + 1`); + }); + it('can bind expressions that need imports', function () { let nowTransform: ASTPluginBuilder> = (env) => { return { diff --git a/src/js-utils.ts b/src/js-utils.ts index 458b876..850d867 100644 --- a/src/js-utils.ts +++ b/src/js-utils.ts @@ -50,7 +50,9 @@ export class JSUtils { let name = unusedNameLike( opts?.nameHint ?? 'a', (candidate) => - this.#template.scope.hasBinding(candidate) || astNodeHasBinding(target, candidate) + this.#template.scope.hasBinding(candidate) || + this.#locals.includes(candidate) || + astNodeHasBinding(target, candidate) ); let t = this.#babel.types; this.#emitStatement( @@ -98,8 +100,19 @@ export class JSUtils { opts?.nameHint ); - let identifier = unusedNameLike(importedIdentifier.name, (candidate) => - astNodeHasBinding(target, candidate) + // If we're already referencing the imported name from the outer scope and + // it's not shadowed at our target location in the template, we can reuse + // the existing import. + if ( + this.#locals.includes(importedIdentifier.name) && + !astNodeHasBinding(target, importedIdentifier.name) + ) { + return importedIdentifier.name; + } + + let identifier = unusedNameLike( + importedIdentifier.name, + (candidate) => this.#locals.includes(candidate) || astNodeHasBinding(target, candidate) ); if (identifier !== importedIdentifier.name) { // The importedIdentifier that we have in Javascript is not usable within