Skip to content

Commit 81f5438

Browse files
authoredNov 1, 2023
Merge pull request #32 from emberjs/preserve-used-imports
Preserve used imports
2 parents fa775ca + 3d9f9d4 commit 81f5438

File tree

5 files changed

+815
-655
lines changed

5 files changed

+815
-655
lines changed
 

‎__tests__/tests.ts

+105
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import HTMLBarsInlinePrecompile, { Options } from '..';
44
import TransformTemplateLiterals from '@babel/plugin-transform-template-literals';
55
import TransformModules from '@babel/plugin-transform-modules-amd';
66
import TransformUnicodeEscapes from '@babel/plugin-transform-unicode-escapes';
7+
// @ts-expect-error no upstream types
8+
import TransformTypescript from '@babel/plugin-transform-typescript';
79
import { stripIndent } from 'common-tags';
810
import { EmberTemplateCompiler } from '../src/ember-template-compiler';
911
import sinon from 'sinon';
@@ -1780,6 +1782,109 @@ describe('htmlbars-inline-precompile', function () {
17801782
);
17811783
`);
17821784
});
1785+
1786+
it('interoperates correctly with @babel/plugin-transform-typescript when handling locals with hbs target', function () {
1787+
plugins = [
1788+
[
1789+
HTMLBarsInlinePrecompile,
1790+
{
1791+
compiler,
1792+
targetFormat: 'hbs',
1793+
},
1794+
],
1795+
TransformTypescript,
1796+
];
1797+
1798+
let transformed = transform(
1799+
`import { template } from '@ember/template-compiler';
1800+
import HelloWorld from 'somewhere';
1801+
export default template('<HelloWorld />', { eval: function() { return eval(arguments[0]) } })
1802+
`
1803+
);
1804+
1805+
expect(transformed).toEqualCode(`
1806+
import HelloWorld from "somewhere";
1807+
import { precompileTemplate } from "@ember/template-compilation";
1808+
import { setComponentTemplate } from "@ember/component";
1809+
import templateOnly from "@ember/component/template-only";
1810+
export default setComponentTemplate(precompileTemplate('<HelloWorld />', { scope: () => ({ HelloWorld }), strictMode: true }), templateOnly());
1811+
`);
1812+
});
1813+
1814+
it('respects local priority when inter-operating with @babel/plugin-transform-typescript', function () {
1815+
plugins = [
1816+
[
1817+
HTMLBarsInlinePrecompile,
1818+
{
1819+
compiler,
1820+
targetFormat: 'hbs',
1821+
},
1822+
],
1823+
TransformTypescript,
1824+
];
1825+
1826+
let transformed = transform(
1827+
`import { template } from '@ember/template-compiler';
1828+
import HelloWorld from 'somewhere';
1829+
export default function() {
1830+
let { HelloWorld } = globalThis;
1831+
return template('<HelloWorld />', { eval: function() { return eval(arguments[0]) } })
1832+
}
1833+
`
1834+
);
1835+
1836+
expect(transformed).toEqualCode(`
1837+
import { precompileTemplate } from "@ember/template-compilation";
1838+
import { setComponentTemplate } from "@ember/component";
1839+
import templateOnly from "@ember/component/template-only";
1840+
export default function() {
1841+
let { HelloWorld } = globalThis;
1842+
return setComponentTemplate(precompileTemplate('<HelloWorld />', { scope: () => ({ HelloWorld }), strictMode: true }), templateOnly());
1843+
}
1844+
`);
1845+
});
1846+
1847+
it('interoperates correctly with @babel/plugin-transform-typescript when handling locals with wire target', function () {
1848+
plugins = [
1849+
[
1850+
HTMLBarsInlinePrecompile,
1851+
{
1852+
compiler,
1853+
targetFormat: 'wire',
1854+
},
1855+
],
1856+
TransformTypescript,
1857+
];
1858+
1859+
let transformed = transform(
1860+
`import { template } from '@ember/template-compiler';
1861+
import HelloWorld from 'somewhere';
1862+
export default template('<HelloWorld />', { eval: function() { return eval(arguments[0]) } })
1863+
`
1864+
);
1865+
1866+
expect(normalizeWireFormat(transformed)).toEqualCode(`
1867+
import HelloWorld from 'somewhere';
1868+
import { createTemplateFactory } from "@ember/template-factory";
1869+
import { setComponentTemplate } from "@ember/component";
1870+
import templateOnly from "@ember/component/template-only";
1871+
export default setComponentTemplate(
1872+
createTemplateFactory(
1873+
/*
1874+
<HelloWorld />
1875+
*/
1876+
{
1877+
id: "<id>",
1878+
block: "[[[8,[32,0],null,null,null]],[],false,[]]",
1879+
moduleName: "<moduleName>",
1880+
scope: () => [HelloWorld],
1881+
isStrictMode: true,
1882+
}
1883+
),
1884+
templateOnly()
1885+
);
1886+
`);
1887+
});
17831888
});
17841889

17851890
describe('content-tag end-to-end', function () {

‎package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,19 @@
4242
"@babel/plugin-proposal-class-properties": "^7.14.5",
4343
"@babel/plugin-transform-modules-amd": "^7.14.5",
4444
"@babel/plugin-transform-template-literals": "^7.14.5",
45+
"@babel/plugin-transform-typescript": "^7.22.11",
4546
"@babel/plugin-transform-unicode-escapes": "^7.14.5",
4647
"@babel/traverse": "^7.14.5",
47-
"content-tag": "^0.1.0",
48+
"@types/babel__core": "^7.20.1",
4849
"@types/babel__traverse": "^7.11.1",
4950
"@types/jest": "^29.2.3",
51+
"@types/node": "^20.5.7",
5052
"@types/sinon": "^10.0.13",
5153
"@typescript-eslint/eslint-plugin": "^4.28.4",
5254
"@typescript-eslint/parser": "^4.28.4",
5355
"code-equality-assertions": "^0.7.0",
5456
"common-tags": "^1.8.0",
57+
"content-tag": "^0.1.0",
5558
"ember-source": "^3.28.9",
5659
"eslint": "^6.8.0",
5760
"eslint-config-prettier": "^6.15.0",

‎src/plugin.ts

+50
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ interface State<EnvSpecificOptions> {
147147
lastInsertedPath: NodePath<t.Statement> | undefined;
148148
filename: string;
149149
recursionGuard: Set<unknown>;
150+
originalImportedNames: Map<string, [string, string]>;
150151
}
151152

152153
export function makePlugin<EnvSpecificOptions>(loadOptions: (opts: EnvSpecificOptions) => Options) {
@@ -156,6 +157,25 @@ export function makePlugin<EnvSpecificOptions>(loadOptions: (opts: EnvSpecificOp
156157
let t = babel.types;
157158

158159
return {
160+
pre(this: State<EnvSpecificOptions>, file) {
161+
// Remember the available set of imported names very early here in <pre>
162+
// so that when other plugins (particularly
163+
// @babel/plugin-transform-typescript) drop "unused" imports in their
164+
// own Program.enter we still know about them. If we want to use them
165+
// from inside a template, they weren't really unused and we can ensure
166+
// they continue to exist.
167+
this.originalImportedNames = new Map();
168+
for (let statement of file.ast.program.body) {
169+
if (statement.type === 'ImportDeclaration') {
170+
for (let specifier of statement.specifiers) {
171+
this.originalImportedNames.set(specifier.local.name, [
172+
statement.source.value,
173+
importedName(specifier),
174+
]);
175+
}
176+
}
177+
}
178+
},
159179
visitor: {
160180
Program: {
161181
enter(path: NodePath<t.Program>, state: State<EnvSpecificOptions>) {
@@ -493,6 +513,7 @@ function insertCompiledTemplate<EnvSpecificOptions>(
493513
configFile: false,
494514
}) as t.File;
495515

516+
ensureImportedNames(target, scopeLocals, state.util, state.originalImportedNames);
496517
remapIdentifiers(precompileResultAST, babel, scopeLocals);
497518

498519
let templateExpression = (precompileResultAST.program.body[0] as t.VariableDeclaration)
@@ -558,6 +579,7 @@ function insertTransformedTemplate<EnvSpecificOptions>(
558579
maybePruneImport(state.util, target.get('callee'));
559580
target.set('callee', precompileTemplate(state.util, target));
560581
}
582+
ensureImportedNames(target, scopeLocals, state.util, state.originalImportedNames);
561583
updateScope(babel, target, scopeLocals);
562584
}
563585

@@ -592,6 +614,7 @@ function insertTransformedTemplate<EnvSpecificOptions>(
592614
let newCall = target.replaceWith(
593615
t.callExpression(precompileTemplate(state.util, target), [t.stringLiteral(transformed)])
594616
)[0];
617+
ensureImportedNames(newCall, scopeLocals, state.util, state.originalImportedNames);
595618
updateScope(babel, newCall, scopeLocals);
596619
} else {
597620
(target.get('quasi').get('quasis.0') as NodePath<t.TemplateElement>).replaceWith(
@@ -729,4 +752,31 @@ function name(node: t.StringLiteral | t.Identifier) {
729752
}
730753
}
731754

755+
function ensureImportedNames(
756+
target: NodePath<t.Node>,
757+
scopeLocals: ScopeLocals,
758+
util: ImportUtil,
759+
originalImportedNames: Map<string, [string, string]>
760+
) {
761+
for (let [nameInTemplate, identifier] of scopeLocals.entries()) {
762+
if (!target.scope.getBinding(identifier)) {
763+
let available = originalImportedNames.get(identifier);
764+
if (available) {
765+
let newIdent = util.import(target, available[0], available[1], identifier);
766+
scopeLocals.add(nameInTemplate, newIdent.name);
767+
}
768+
}
769+
}
770+
}
771+
772+
function importedName(node: t.ImportDeclaration['specifiers'][number]): string {
773+
if (node.type === 'ImportDefaultSpecifier') {
774+
return 'default';
775+
} else if (node.type === 'ImportNamespaceSpecifier') {
776+
return '*';
777+
} else {
778+
return name(node.imported);
779+
}
780+
}
781+
732782
export default makePlugin<Options>((options) => options);

‎src/scope-locals.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export class ScopeLocals {
3636

3737
add(key: string, value?: string) {
3838
this.#mapping[key] = value ?? key;
39-
this.#locals.push(key);
39+
if (!this.#locals.includes(key)) {
40+
this.#locals.push(key);
41+
}
4042
}
4143
}

0 commit comments

Comments
 (0)
Failed to load comments.