Skip to content
This repository has been archived by the owner on Dec 8, 2024. It is now read-only.

Implements an option to support the template tag imports proposal #336

Merged
merged 1 commit into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 226 additions & 8 deletions __tests__/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,28 @@ describe('htmlbars-inline-precompile', function () {
`);
});

it('works with templates assigned to class expressions', function () {
let transpiled = transform(
`
import { hbs } from 'ember-template-imports';

const Foo = class {
static template = hbs\`hello\`;
}
`
);

expect(transpiled).toMatchInlineSnapshot(`
"import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\";

const Foo = _setComponentTemplate(Ember.HTMLBars.template(
/*
hello
*/
\\"precompiled(hello)\\"), class {});"
`);
});

it('correctly handles scope', function () {
let source = 'hello';
transform(
Expand Down Expand Up @@ -825,14 +847,6 @@ describe('htmlbars-inline-precompile', function () {
}).toThrow(
/Attempted to use `hbs` to define a template in an unsupported way. Templates defined using this helper must be:/
);

expect(() => {
transform(
"import { hbs } from 'ember-template-imports';\n let Foo = class { static template = hbs`hello`; }"
);
}).toThrow(
/Attempted to use `hbs` to define a template in an unsupported way. Templates defined using this helper must be:/
);
});

it('errors if passed incorrect useTemplateLiteralProposalSemantics version', function () {
Expand All @@ -851,4 +865,208 @@ describe('htmlbars-inline-precompile', function () {
);
});
});

describe('with useTemplateTagProposalSemantics', function () {
beforeEach(() => {
plugins = [
[
HTMLBarsInlinePrecompile,
{
precompile() {
return precompile.apply(this, arguments);
},

modules: {
'ember-template-imports': {
export: 'GLIMMER_TEMPLATE',
debugName: '<template>',
useTemplateTagProposalSemantics: 1,
},
},
},
],
'@babel/plugin-proposal-class-properties',
];
});

it('works with templates assigned to variables', function () {
let transpiled = transform(
`
const Foo = [GLIMMER_TEMPLATE(\`hello\`)];
`
);

expect(transpiled).toMatchInlineSnapshot(`
"import { templateOnly as _templateOnly } from \\"@ember/component/template-only\\";
import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\";

const Foo = _templateOnly(\\"foo-bar\\", \\"Foo\\");

_setComponentTemplate(Ember.HTMLBars.template(
/*
hello
*/
\\"precompiled(hello)\\"), Foo);"
`);
});

it('works with templates exported as variables', function () {
let transpiled = transform(
`
export const Foo = [GLIMMER_TEMPLATE(\`hello\`)];
`
);

expect(transpiled).toMatchInlineSnapshot(`
"import { templateOnly as _templateOnly } from \\"@ember/component/template-only\\";
import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\";
export const Foo = _templateOnly(\\"foo-bar\\", \\"Foo\\");

_setComponentTemplate(Ember.HTMLBars.template(
/*
hello
*/
\\"precompiled(hello)\\"), Foo);"
`);
});

it('works with templates exported as the default', function () {
let transpiled = transform(
`
export default [GLIMMER_TEMPLATE(\`hello\`)];
`
);

expect(transpiled).toMatchInlineSnapshot(`
"import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\";
import { templateOnly as _templateOnly } from \\"@ember/component/template-only\\";

const _fooBar = _templateOnly(\\"foo-bar\\", \\"_fooBar\\");

_setComponentTemplate(Ember.HTMLBars.template(
/*
hello
*/
\\"precompiled(hello)\\"), _fooBar);

export default _fooBar;"
`);
});

it('works with templates defined at the top level', function () {
let transpiled = transform(
`
[GLIMMER_TEMPLATE(\`hello\`)];
`
);

expect(transpiled).toMatchInlineSnapshot(`
"import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\";
import { templateOnly as _templateOnly } from \\"@ember/component/template-only\\";

const _fooBar = _templateOnly(\\"foo-bar\\", \\"_fooBar\\");

_setComponentTemplate(Ember.HTMLBars.template(
/*
hello
*/
\\"precompiled(hello)\\"), _fooBar);

export default _fooBar;"
`);
});

it('works with templates assigned to classes', function () {
let transpiled = transform(
`
class Foo {
[GLIMMER_TEMPLATE(\`hello\`)];
}
`
);

expect(transpiled).toMatchInlineSnapshot(`
"import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\";

class Foo {}

_setComponentTemplate(Ember.HTMLBars.template(
/*
hello
*/
\\"precompiled(hello)\\"), Foo);"
`);
});

it('works with templates assigned to class expressions', function () {
let transpiled = transform(
`
const Foo = class {
[GLIMMER_TEMPLATE(\`hello\`)];
}
`
);

expect(transpiled).toMatchInlineSnapshot(`
"import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\";

const Foo = _setComponentTemplate(Ember.HTMLBars.template(
/*
hello
*/
\\"precompiled(hello)\\"), class {});"
`);
});

it('correctly handles scope', function () {
let source = 'hello';
transform(
`
import baz from 'qux';

let foo = 123;
const bar = 456;

export default [GLIMMER_TEMPLATE(\`${source}\`)];
`
);

expect(optionsReceived).toEqual({
contents: source,
isProduction: undefined,
scope: ['baz', 'foo', 'bar'],
strict: true,
});
});

it('errors if used in an incorrect positions', function () {
expect(() => {
transform("func([GLIMMER_TEMPLATE('hello')]);");
}).toThrow(
/Attempted to use `<template>` to define a template in an unsupported way. Templates defined using this syntax must be:/
);
});

it('errors if used with template literal syntax', function () {
plugins[0][1].modules['ember-template-imports'].useTemplateLiteralProposalSemantics = 1;

expect(() => {
transform("func([GLIMMER_TEMPLATE('hello')]);");
}).toThrow(/Cannot use both the template literal and template tag syntax proposals together/);
});

it('errors if passed incorrect useTemplateTagProposalSemantics version', function () {
plugins[0][1].modules['ember-template-imports'].useTemplateTagProposalSemantics = true;

expect(() => {
transform(
`
const Foo = [GLIMMER_TEMPLATE(\`hello\`)];
`
);
}).toThrow(
/Passed an invalid version for useTemplateTagProposalSemantics. This option must be assign a version number. The current valid version numbers are: 1/
);
});
});
});
49 changes: 46 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const { replaceTemplateLiteralProposal } = require('./src/template-literal-transform');
const { replaceTemplateTagProposal } = require('./src/template-tag-transform');

module.exports = function (babel) {
let t = babel.types;
Expand Down Expand Up @@ -126,9 +127,22 @@ module.exports = function (babel) {
return names;
}

function shouldUseAutomaticScope(options) {
return options.useTemplateLiteralProposalSemantics || options.useTemplateTagProposalSemantics;
}

function shouldUseStrictMode(options) {
return (
Boolean(options.useTemplateLiteralProposalSemantics) ||
Boolean(options.useTemplateTagProposalSemantics)
);
}

function replacePath(path, state, compiled, options) {
if (options.useTemplateLiteralProposalSemantics) {
replaceTemplateLiteralProposal(t, path, state, compiled, options);
} else if (options.useTemplateTagProposalSemantics) {
replaceTemplateTagProposal(t, path, state, compiled, options);
} else {
path.replaceWith(compiled);
}
Expand Down Expand Up @@ -203,6 +217,27 @@ module.exports = function (babel) {
let importDeclarations = path.get('body').filter((n) => n.type === 'ImportDeclaration');

for (let module in modules) {
let options = modules[module];

if (options.useTemplateTagProposalSemantics) {
if (options.useTemplateLiteralProposalSemantics) {
throw path.buildCodeFrameError(
'Cannot use both the template literal and template tag syntax proposals together'
);
}

// template tags don't have an import
presentModules.set(
options.export,
Object.assign({}, options, {
modulePath: module,
originalName: options.export,
})
);

continue;
}

let paths = importDeclarations.filter(
(path) => !path.removed && path.get('source').get('value').node === module
);
Expand Down Expand Up @@ -252,7 +287,7 @@ module.exports = function (babel) {
state.presentModules = presentModules;
},

ClassDeclaration(path, state) {
Class(path, state) {
// Processing classes this way allows us to process ClassProperty nodes
// before other transforms, such as the class-properties transform
path.get('body.body').forEach((path) => {
Expand Down Expand Up @@ -294,8 +329,8 @@ module.exports = function (babel) {
let template = path.node.quasi.quasis.map((quasi) => quasi.value.cooked).join('');

let { precompile, isProduction } = state.opts;
let scope = options.useTemplateLiteralProposalSemantics ? getScope(path.scope) : null;
let strict = Boolean(options.useTemplateLiteralProposalSemantics);
let scope = shouldUseAutomaticScope(options) ? getScope(path.scope) : null;
let strict = shouldUseStrictMode(options);

let emberIdentifier = state.ensureImport('default', 'ember');

Expand Down Expand Up @@ -383,6 +418,14 @@ module.exports = function (babel) {
compilerOptions.isProduction = isProduction;
}

if (shouldUseAutomaticScope(options)) {
compilerOptions.scope = getScope(path.scope);
}

if (shouldUseStrictMode(options)) {
compilerOptions.strict = true;
}

replacePath(
path,
state,
Expand Down
31 changes: 18 additions & 13 deletions src/template-literal-transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,28 @@ module.exports.replaceTemplateLiteralProposal = function (t, path, state, compil
);
}

let classDeclaration = parentPath.parentPath.parentPath;
let classPath = parentPath.parentPath.parentPath;

if (classDeclaration.node.type !== 'ClassDeclaration') {
throw path.buildCodeFrameError(
`Attempted to use \`${options.originalName}\` to define a template for an anonymous class. Templates declared with this helper must be assigned to classes which have a name.`
if (classPath.node.type === 'ClassDeclaration') {
classPath.insertAfter(
t.expressionStatement(
t.callExpression(state.ensureImport('setComponentTemplate', '@ember/component'), [
compiled,
classPath.node.id,
])
)
);
} else {
classPath.replaceWith(
t.expressionStatement(
t.callExpression(state.ensureImport('setComponentTemplate', '@ember/component'), [
compiled,
classPath.node,
])
)
);
}

classDeclaration.insertAfter(
t.expressionStatement(
t.callExpression(state.ensureImport('setComponentTemplate', '@ember/component'), [
compiled,
classDeclaration.node.id,
])
)
);

parentPath.remove();
} else {
throw path.buildCodeFrameError(
Expand Down
Loading