Skip to content

Commit f78b546

Browse files
committed
allow reuse of bindings
1 parent cdbabfd commit f78b546

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

__tests__/tests.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe('htmlbars-inline-precompile', function () {
3333
});
3434

3535
afterEach(function () {
36+
sharedStateToBabel = null;
3637
sinon.restore();
3738
});
3839

@@ -654,13 +655,23 @@ describe('htmlbars-inline-precompile', function () {
654655
);
655656
});
656657

658+
let sharedStateToBabel = null as any;
659+
657660
let expressionTransform: ExtendedPluginBuilder = (env) => {
658661
return {
659662
name: 'expression-transform',
660663
visitor: {
661664
PathExpression(node, path) {
662665
if (node.original === 'onePlusOne') {
666+
let boundName = sharedStateToBabel?.boundName;
667+
if (boundName) {
668+
env.meta.jsutils.bindVariable(boundName);
669+
return env.syntax.builders.path(boundName);
670+
}
663671
let name = env.meta.jsutils.bindExpression('1+1', path, { nameHint: 'two' });
672+
if (sharedStateToBabel) {
673+
sharedStateToBabel.boundName = name;
674+
}
664675
return env.syntax.builders.path(name);
665676
}
666677
return undefined;
@@ -1314,6 +1325,47 @@ describe('htmlbars-inline-precompile', function () {
13141325
`);
13151326
});
13161327

1328+
it('can reuse bindings', function () {
1329+
plugins = [
1330+
[HTMLBarsInlinePrecompile, { targetFormat: 'hbs', transforms: [expressionTransform] }],
1331+
];
1332+
1333+
sharedStateToBabel = {};
1334+
1335+
let transformed = transform(stripIndent`
1336+
import { precompileTemplate } from '@ember/template-compilation';
1337+
import Message from 'message';
1338+
const template = precompileTemplate('<Message @text={{onePlusOne}} />', {
1339+
scope: () => ({
1340+
Message
1341+
})
1342+
});
1343+
const template2 = precompileTemplate('<Message @text={{onePlusOne}} />', {
1344+
scope: () => ({
1345+
Message
1346+
})
1347+
});
1348+
`);
1349+
1350+
expect(transformed).toEqualCode(`
1351+
import { precompileTemplate } from '@ember/template-compilation';
1352+
import Message from 'message';
1353+
let two = 1 + 1;
1354+
const template = precompileTemplate("<Message @text={{two}} />", {
1355+
scope: () => ({
1356+
Message,
1357+
two
1358+
})
1359+
});
1360+
const template2 = precompileTemplate("<Message @text={{two}} />", {
1361+
scope: () => ({
1362+
Message,
1363+
two
1364+
})
1365+
});
1366+
`);
1367+
});
1368+
13171369
it('adds new locals to preexisting renamed scope', function () {
13181370
plugins = [
13191371
[HTMLBarsInlinePrecompile, { targetFormat: 'hbs', transforms: [expressionTransform] }],
@@ -1323,6 +1375,12 @@ describe('htmlbars-inline-precompile', function () {
13231375
import { precompileTemplate } from '@ember/template-compilation';
13241376
import Message$ from 'message';
13251377
import Label from 'label';
1378+
const template2 = precompileTemplate('<Label /><Message />', {
1379+
scope: () => ({
1380+
Label,
1381+
Message: Message$
1382+
})
1383+
});
13261384
const template = precompileTemplate('<Label /><Message @text={{onePlusOne}} />', {
13271385
scope: () => ({
13281386
Label,
@@ -1336,6 +1394,12 @@ describe('htmlbars-inline-precompile', function () {
13361394
import Message$ from 'message';
13371395
import Label from 'label';
13381396
let two = 1 + 1;
1397+
const template2 = precompileTemplate("<Label /><Message />", {
1398+
scope: () => ({
1399+
Label,
1400+
Message: Message$,
1401+
}),
1402+
});
13391403
const template = precompileTemplate("<Label /><Message @text={{two}} />", {
13401404
scope: () => ({
13411405
Label,
@@ -1862,6 +1926,16 @@ describe('htmlbars-inline-precompile', function () {
18621926
expect(spy.firstCall.lastArg).toHaveProperty('locals', ['foo', 'bar']);
18631927
});
18641928

1929+
it('correctly removes not used scope', function () {
1930+
let spy = sinon.spy(compiler, 'precompile');
1931+
transform(`
1932+
import { precompileTemplate } from '@ember/template-compilation';
1933+
let foo, bar;
1934+
var compiled = precompileTemplate('<foo /><bar/>', { scope: () => ({ foo, bar, baz }) });
1935+
`);
1936+
expect(spy.firstCall.lastArg).toHaveProperty('locals', ['foo', 'bar']);
1937+
});
1938+
18651939
it('does not automagically add to scope when not using implicit-scope-form', function () {
18661940
let spy = sinon.spy(compiler, 'precompile');
18671941
transform(`
@@ -2205,6 +2279,63 @@ describe('htmlbars-inline-precompile', function () {
22052279
`);
22062280
});
22072281

2282+
it('works with two components', function () {
2283+
plugins = [
2284+
[
2285+
HTMLBarsInlinePrecompile,
2286+
{
2287+
targetFormat: 'hbs',
2288+
},
2289+
],
2290+
];
2291+
2292+
let p = new Preprocessor();
2293+
let processed = p.process(
2294+
`
2295+
import Component from '@glimmer/component';
2296+
2297+
export default class Test extends Component {
2298+
foo = 1;
2299+
2300+
<template>
2301+
<Icon />
2302+
</template>
2303+
}
2304+
2305+
const Icon = <template>Icon</template>;
2306+
`
2307+
);
2308+
2309+
let transformed = transform(processed);
2310+
2311+
expect(transformed).toEqualCode(`
2312+
import Component from "@glimmer/component";
2313+
import { precompileTemplate } from "@ember/template-compilation";
2314+
import { setComponentTemplate } from "@ember/component";
2315+
import templateOnly from "@ember/component/template-only";
2316+
export default class Test extends Component {
2317+
foo = 1;
2318+
static {
2319+
setComponentTemplate(
2320+
precompileTemplate("\\n <Icon />\\n ", {
2321+
strictMode: true,
2322+
scope: () => ({
2323+
Icon,
2324+
}),
2325+
}),
2326+
this
2327+
);
2328+
}
2329+
}
2330+
const Icon = setComponentTemplate(
2331+
precompileTemplate("Icon", {
2332+
strictMode: true,
2333+
}),
2334+
templateOnly()
2335+
);
2336+
`);
2337+
});
2338+
22082339
it('works for class member form with `this` references', function () {
22092340
plugins = [
22102341
[

src/js-utils.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class JSUtils {
1616
#babel: typeof Babel;
1717
#state: State;
1818
#template: NodePath<t.Expression>;
19-
#addedBinding: (name: string) => void;
19+
#addedBinding: (name: string, jsName?: string) => void;
2020
#importer: ImportUtil;
2121

2222
constructor(
@@ -84,6 +84,20 @@ export class JSUtils {
8484
return name;
8585
}
8686

87+
/**
88+
* Allows (re)use of binding that you already have in the js code or created with bindExpression
89+
* Especially useful if you have a custom transform that creates a binding that you want to reuse
90+
*
91+
* @param hbsName the name you are using in your template to access the binding
92+
* @param jsName the name of a js variable
93+
*
94+
* @return The name you can use in your template to access the binding.
95+
*/
96+
bindVariable(hbsName: string, jsName?: string) {
97+
this.#addedBinding(hbsName, jsName);
98+
return hbsName;
99+
}
100+
87101
#emitStatement<T extends t.Statement>(statement: T): NodePath<T> {
88102
if (this.#state.lastInsertedPath) {
89103
this.#state.lastInsertedPath = this.#state.lastInsertedPath.insertAfter(statement)[0];

0 commit comments

Comments
 (0)