Skip to content

Commit

Permalink
Merge pull request #32 from emberjs/preserve-used-imports
Browse files Browse the repository at this point in the history
Preserve used imports
  • Loading branch information
ef4 authored Nov 1, 2023
2 parents fa775ca + 3d9f9d4 commit 81f5438
Show file tree
Hide file tree
Showing 5 changed files with 815 additions and 655 deletions.
105 changes: 105 additions & 0 deletions __tests__/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import HTMLBarsInlinePrecompile, { Options } from '..';
import TransformTemplateLiterals from '@babel/plugin-transform-template-literals';
import TransformModules from '@babel/plugin-transform-modules-amd';
import TransformUnicodeEscapes from '@babel/plugin-transform-unicode-escapes';
// @ts-expect-error no upstream types
import TransformTypescript from '@babel/plugin-transform-typescript';
import { stripIndent } from 'common-tags';
import { EmberTemplateCompiler } from '../src/ember-template-compiler';
import sinon from 'sinon';
Expand Down Expand Up @@ -1780,6 +1782,109 @@ describe('htmlbars-inline-precompile', function () {
);
`);
});

it('interoperates correctly with @babel/plugin-transform-typescript when handling locals with hbs target', function () {
plugins = [
[
HTMLBarsInlinePrecompile,
{
compiler,
targetFormat: 'hbs',
},
],
TransformTypescript,
];

let transformed = transform(
`import { template } from '@ember/template-compiler';
import HelloWorld from 'somewhere';
export default template('<HelloWorld />', { eval: function() { return eval(arguments[0]) } })
`
);

expect(transformed).toEqualCode(`
import HelloWorld from "somewhere";
import { precompileTemplate } from "@ember/template-compilation";
import { setComponentTemplate } from "@ember/component";
import templateOnly from "@ember/component/template-only";
export default setComponentTemplate(precompileTemplate('<HelloWorld />', { scope: () => ({ HelloWorld }), strictMode: true }), templateOnly());
`);
});

it('respects local priority when inter-operating with @babel/plugin-transform-typescript', function () {
plugins = [
[
HTMLBarsInlinePrecompile,
{
compiler,
targetFormat: 'hbs',
},
],
TransformTypescript,
];

let transformed = transform(
`import { template } from '@ember/template-compiler';
import HelloWorld from 'somewhere';
export default function() {
let { HelloWorld } = globalThis;
return template('<HelloWorld />', { eval: function() { return eval(arguments[0]) } })
}
`
);

expect(transformed).toEqualCode(`
import { precompileTemplate } from "@ember/template-compilation";
import { setComponentTemplate } from "@ember/component";
import templateOnly from "@ember/component/template-only";
export default function() {
let { HelloWorld } = globalThis;
return setComponentTemplate(precompileTemplate('<HelloWorld />', { scope: () => ({ HelloWorld }), strictMode: true }), templateOnly());
}
`);
});

it('interoperates correctly with @babel/plugin-transform-typescript when handling locals with wire target', function () {
plugins = [
[
HTMLBarsInlinePrecompile,
{
compiler,
targetFormat: 'wire',
},
],
TransformTypescript,
];

let transformed = transform(
`import { template } from '@ember/template-compiler';
import HelloWorld from 'somewhere';
export default template('<HelloWorld />', { eval: function() { return eval(arguments[0]) } })
`
);

expect(normalizeWireFormat(transformed)).toEqualCode(`
import HelloWorld from 'somewhere';
import { createTemplateFactory } from "@ember/template-factory";
import { setComponentTemplate } from "@ember/component";
import templateOnly from "@ember/component/template-only";
export default setComponentTemplate(
createTemplateFactory(
/*
<HelloWorld />
*/
{
id: "<id>",
block: "[[[8,[32,0],null,null,null]],[],false,[]]",
moduleName: "<moduleName>",
scope: () => [HelloWorld],
isStrictMode: true,
}
),
templateOnly()
);
`);
});
});

describe('content-tag end-to-end', function () {
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,19 @@
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-transform-modules-amd": "^7.14.5",
"@babel/plugin-transform-template-literals": "^7.14.5",
"@babel/plugin-transform-typescript": "^7.22.11",
"@babel/plugin-transform-unicode-escapes": "^7.14.5",
"@babel/traverse": "^7.14.5",
"content-tag": "^0.1.0",
"@types/babel__core": "^7.20.1",
"@types/babel__traverse": "^7.11.1",
"@types/jest": "^29.2.3",
"@types/node": "^20.5.7",
"@types/sinon": "^10.0.13",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"code-equality-assertions": "^0.7.0",
"common-tags": "^1.8.0",
"content-tag": "^0.1.0",
"ember-source": "^3.28.9",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.15.0",
Expand Down
50 changes: 50 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ interface State<EnvSpecificOptions> {
lastInsertedPath: NodePath<t.Statement> | undefined;
filename: string;
recursionGuard: Set<unknown>;
originalImportedNames: Map<string, [string, string]>;
}

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

return {
pre(this: State<EnvSpecificOptions>, file) {
// Remember the available set of imported names very early here in <pre>
// so that when other plugins (particularly
// @babel/plugin-transform-typescript) drop "unused" imports in their
// own Program.enter we still know about them. If we want to use them
// from inside a template, they weren't really unused and we can ensure
// they continue to exist.
this.originalImportedNames = new Map();
for (let statement of file.ast.program.body) {
if (statement.type === 'ImportDeclaration') {
for (let specifier of statement.specifiers) {
this.originalImportedNames.set(specifier.local.name, [
statement.source.value,
importedName(specifier),
]);
}
}
}
},
visitor: {
Program: {
enter(path: NodePath<t.Program>, state: State<EnvSpecificOptions>) {
Expand Down Expand Up @@ -493,6 +513,7 @@ function insertCompiledTemplate<EnvSpecificOptions>(
configFile: false,
}) as t.File;

ensureImportedNames(target, scopeLocals, state.util, state.originalImportedNames);
remapIdentifiers(precompileResultAST, babel, scopeLocals);

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

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

function ensureImportedNames(
target: NodePath<t.Node>,
scopeLocals: ScopeLocals,
util: ImportUtil,
originalImportedNames: Map<string, [string, string]>
) {
for (let [nameInTemplate, identifier] of scopeLocals.entries()) {
if (!target.scope.getBinding(identifier)) {
let available = originalImportedNames.get(identifier);
if (available) {
let newIdent = util.import(target, available[0], available[1], identifier);
scopeLocals.add(nameInTemplate, newIdent.name);
}
}
}
}

function importedName(node: t.ImportDeclaration['specifiers'][number]): string {
if (node.type === 'ImportDefaultSpecifier') {
return 'default';
} else if (node.type === 'ImportNamespaceSpecifier') {
return '*';
} else {
return name(node.imported);
}
}

export default makePlugin<Options>((options) => options);
4 changes: 3 additions & 1 deletion src/scope-locals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export class ScopeLocals {

add(key: string, value?: string) {
this.#mapping[key] = value ?? key;
this.#locals.push(key);
if (!this.#locals.includes(key)) {
this.#locals.push(key);
}
}
}
Loading

0 comments on commit 81f5438

Please sign in to comment.