babel-plugin-htmlbars-inline-precompile
Advanced tools
Comparing version 4.2.1 to 4.3.0
@@ -84,2 +84,3 @@ 'use strict'; | ||
isProduction: true, | ||
scope: null, | ||
}, | ||
@@ -144,2 +145,4 @@ ], | ||
isProduction: true, | ||
scope: null, | ||
strict: false, | ||
}); | ||
@@ -251,2 +254,5 @@ }); | ||
contents: source, | ||
isProduction: undefined, | ||
scope: null, | ||
strict: false, | ||
}); | ||
@@ -263,30 +269,2 @@ }); | ||
it('throws error when import statement is not using default specifier', function () { | ||
expect(() => transform("import { hbs } from 'htmlbars-inline-precompile'")).toThrow( | ||
/Only `import hbs from 'htmlbars-inline-precompile'` is supported/, | ||
'needed import syntax is present' | ||
); | ||
expect(() => transform("import { hbs } from 'htmlbars-inline-precompile'")).toThrow( | ||
/You used: `import { hbs } from 'htmlbars-inline-precompile'`/, | ||
'used import syntax is present' | ||
); | ||
}); | ||
it('throws error when import statement is not using custom specifier', function () { | ||
plugins[0][1].modules = { | ||
'foo-bar': 'baz', | ||
}; | ||
expect(() => transform("import hbs from 'foo-bar'")).toThrow( | ||
/Only `import { baz } from 'foo-bar'` is supported/, | ||
'needed import syntax is present' | ||
); | ||
expect(() => transform("import hbs from 'foo-bar'")).toThrow( | ||
/You used: `import hbs from 'foo-bar'`/, | ||
'used import syntax is present' | ||
); | ||
}); | ||
it('replaces tagged template expressions with precompiled version', function () { | ||
@@ -345,2 +323,129 @@ let transformed = transform( | ||
it('does not fully remove imports that have other imports', function () { | ||
plugins[0][1].modules = { | ||
precompile1: 'default', | ||
precompile2: 'hbs', | ||
precompile3: 'hbs', | ||
}; | ||
let transformed = transform(` | ||
import hbs, { foo } from 'precompile1'; | ||
import { hbs as otherHbs, bar } from 'precompile2'; | ||
import baz, { hbs as otherOtherHbs } from 'precompile3'; | ||
let a = hbs\`hello\`; | ||
let b = otherHbs\`hello\`; | ||
let c = otherOtherHbs\`hello\`; | ||
`); | ||
expect(transformed).toMatchInlineSnapshot(` | ||
"import { foo } from 'precompile1'; | ||
import { bar } from 'precompile2'; | ||
import baz from 'precompile3'; | ||
let a = Ember.HTMLBars.template( | ||
/* | ||
hello | ||
*/ | ||
\\"precompiled(hello)\\"); | ||
let b = Ember.HTMLBars.template( | ||
/* | ||
hello | ||
*/ | ||
\\"precompiled(hello)\\"); | ||
let c = Ember.HTMLBars.template( | ||
/* | ||
hello | ||
*/ | ||
\\"precompiled(hello)\\");" | ||
`); | ||
}); | ||
it('works with multiple imports from different modules', function () { | ||
plugins = [ | ||
[ | ||
HTMLBarsInlinePrecompile, | ||
{ | ||
precompile() { | ||
return precompile.apply(this, arguments); | ||
}, | ||
modules: { | ||
'ember-cli-htmlbars': 'hbs', | ||
'@ember/template-compilation': { | ||
export: 'precompileTemplate', | ||
}, | ||
}, | ||
}, | ||
], | ||
]; | ||
let transformed = transform(` | ||
import { hbs } from 'ember-cli-htmlbars'; | ||
import { precompileTemplate } from '@ember/template-compilation'; | ||
let a = hbs\`hello\`; | ||
let b = precompileTemplate('hello'); | ||
`); | ||
let expected = `let a = Ember.HTMLBars.template(\n/*\n hello\n*/\n"precompiled(hello)");\nlet b = Ember.HTMLBars.template(\n/*\n hello\n*/\n"precompiled(hello)");`; | ||
expect(transformed).toEqual(expected, 'tagged template is replaced'); | ||
}); | ||
it('can disable template literal usage', function () { | ||
plugins = [ | ||
[ | ||
HTMLBarsInlinePrecompile, | ||
{ | ||
precompile() { | ||
return precompile.apply(this, arguments); | ||
}, | ||
modules: { | ||
'@ember/template-compilation': { | ||
export: 'precompileTemplate', | ||
disableTemplateLiteral: true, | ||
}, | ||
}, | ||
}, | ||
], | ||
]; | ||
expect(() => { | ||
transform(` | ||
import { precompileTemplate } from '@ember/template-compilation'; | ||
let a = precompileTemplate\`hello\`; | ||
`); | ||
}).toThrow( | ||
/Attempted to use `precompileTemplate` as a template tag, but it can only be called as a function with a string passed to it:/ | ||
); | ||
}); | ||
it('can disable function call usage', function () { | ||
plugins = [ | ||
[ | ||
HTMLBarsInlinePrecompile, | ||
{ | ||
precompile() { | ||
return precompile.apply(this, arguments); | ||
}, | ||
modules: { | ||
'ember-template-imports': { | ||
export: 'hbs', | ||
disableFunctionCall: true, | ||
}, | ||
}, | ||
}, | ||
], | ||
]; | ||
expect(() => { | ||
transform(` | ||
import { hbs } from 'ember-template-imports'; | ||
let a = hbs(\`hello\`); | ||
`); | ||
}).toThrow( | ||
/Attempted to use `hbs` as a function call, but it can only be used as a template tag:/ | ||
); | ||
}); | ||
it('works properly when used along with modules transform', function () { | ||
@@ -476,2 +581,280 @@ plugins.push([TransformModules]); | ||
}); | ||
describe('with Ember imports', function () { | ||
it('adds an Ember import if useEmberModule is set to true', function () { | ||
plugins = [ | ||
[ | ||
HTMLBarsInlinePrecompile, | ||
{ | ||
precompile() { | ||
return precompile.apply(this, arguments); | ||
}, | ||
useEmberModule: true, | ||
}, | ||
], | ||
]; | ||
let transpiled = transform( | ||
"import hbs from 'htmlbars-inline-precompile';\nvar compiled = hbs`hello`;" | ||
); | ||
expect(transpiled).toMatchInlineSnapshot(` | ||
"import _ember from \\"ember\\"; | ||
var compiled = _ember.HTMLBars.template( | ||
/* | ||
hello | ||
*/ | ||
\\"precompiled(hello)\\");" | ||
`); | ||
}); | ||
it('Uses existing Ember import if one exists', function () { | ||
plugins = [ | ||
[ | ||
HTMLBarsInlinePrecompile, | ||
{ | ||
precompile() { | ||
return precompile.apply(this, arguments); | ||
}, | ||
useEmberModule: true, | ||
}, | ||
], | ||
]; | ||
let transpiled = transform( | ||
"import Foo from 'ember';\nimport hbs from 'htmlbars-inline-precompile';\nvar compiled = hbs`hello`;" | ||
); | ||
expect(transpiled).toMatchInlineSnapshot(` | ||
"import Foo from 'ember'; | ||
var compiled = Foo.HTMLBars.template( | ||
/* | ||
hello | ||
*/ | ||
\\"precompiled(hello)\\");" | ||
`); | ||
}); | ||
}); | ||
describe('with transformScope: true', function () { | ||
beforeEach(() => { | ||
plugins = [ | ||
[ | ||
HTMLBarsInlinePrecompile, | ||
{ | ||
precompile() { | ||
return precompile.apply(this, arguments); | ||
}, | ||
modules: { | ||
'@ember/template-compilation': { | ||
export: 'precompileTemplate', | ||
shouldParseScope: true, | ||
}, | ||
}, | ||
}, | ||
], | ||
]; | ||
}); | ||
it('correctly handles scope', function () { | ||
let source = 'hello'; | ||
transform( | ||
`import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('${source}', { scope: { foo, bar } });` | ||
); | ||
expect(optionsReceived).toEqual({ | ||
contents: source, | ||
scope: ['foo', 'bar'], | ||
}); | ||
}); | ||
it('errors if scope contains mismatched keys/values', function () { | ||
expect(() => { | ||
transform( | ||
"import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('hello', { scope: { foo: bar } });" | ||
); | ||
}).toThrow( | ||
/Scope objects for `precompileTemplate` may only contain direct references to in-scope values, e.g. { foo } or { foo: foo }/ | ||
); | ||
}); | ||
it('errors if scope is not an object', function () { | ||
expect(() => { | ||
transform( | ||
"import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('hello', { scope: ['foo', 'bar'] });" | ||
); | ||
}).toThrow( | ||
/Scope objects for `precompileTemplate` must be an object expression containing only references to in-scope values/ | ||
); | ||
}); | ||
it('errors if scope contains any non-reference values', function () { | ||
expect(() => { | ||
transform( | ||
"import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('hello', { scope: { foo, bar: 123 } });" | ||
); | ||
}).toThrow( | ||
/Scope objects for `precompileTemplate` may only contain direct references to in-scope values, e.g. { bar } or { bar: bar }/ | ||
); | ||
}); | ||
}); | ||
describe('with useTemplateLiteralProposalSemantics', function () { | ||
beforeEach(() => { | ||
plugins = [ | ||
[ | ||
HTMLBarsInlinePrecompile, | ||
{ | ||
precompile() { | ||
return precompile.apply(this, arguments); | ||
}, | ||
modules: { | ||
'ember-template-imports': { | ||
export: 'hbs', | ||
useTemplateLiteralProposalSemantics: 1, | ||
}, | ||
}, | ||
}, | ||
], | ||
'@babel/plugin-proposal-class-properties', | ||
]; | ||
}); | ||
it('works with templates assigned to variables', function () { | ||
let transpiled = transform( | ||
` | ||
import { hbs } from 'ember-template-imports'; | ||
const Foo = hbs\`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 the default', function () { | ||
let transpiled = transform( | ||
` | ||
import { hbs } from 'ember-template-imports'; | ||
export default hbs\`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( | ||
` | ||
import { hbs } from 'ember-template-imports'; | ||
class Foo { | ||
static template = hbs\`hello\`; | ||
} | ||
` | ||
); | ||
expect(transpiled).toMatchInlineSnapshot(` | ||
"import { setComponentTemplate as _setComponentTemplate } from \\"@ember/component\\"; | ||
class Foo {} | ||
_setComponentTemplate(Ember.HTMLBars.template( | ||
/* | ||
hello | ||
*/ | ||
\\"precompiled(hello)\\"), Foo);" | ||
`); | ||
}); | ||
it('correctly handles scope', function () { | ||
let source = 'hello'; | ||
transform( | ||
` | ||
import { hbs } from 'ember-template-imports'; | ||
import baz from 'qux'; | ||
let foo = 123; | ||
const bar = 456; | ||
export default hbs\`${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("import { hbs } from 'ember-template-imports';\nhbs`hello`;"); | ||
}).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';\nfunc(hbs`hello`);"); | ||
}).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 () { | ||
plugins[0][1].modules['ember-template-imports'].useTemplateLiteralProposalSemantics = true; | ||
expect(() => { | ||
transform( | ||
` | ||
import { hbs } from 'ember-template-imports'; | ||
const Foo = hbs\`hello\`; | ||
` | ||
); | ||
}).toThrow( | ||
/Passed an invalid version for useTemplateLiteralProposalSemantics. This option must be assign a version number. The current valid version numbers are: 1/ | ||
); | ||
}); | ||
}); | ||
}); |
@@ -0,1 +1,13 @@ | ||
## v4.3.0 (2021-02-22) | ||
#### :rocket: Enhancement | ||
* [#335](https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile/pull/335) Implements an option to support the template literal imports proposal ([@pzuraq](https://github.com/pzuraq)) | ||
* [#334](https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile/pull/334) Add support for multiple imports ([@pzuraq](https://github.com/pzuraq)) | ||
* [#333](https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile/pull/333) [FEAT] Add `shouldParseScope`, `disableTemplateTag`, `disableFunctionCall` options ([@pzuraq](https://github.com/pzuraq)) | ||
* [#332](https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile/pull/332) Adds `useEmberModule` option ([@pzuraq](https://github.com/pzuraq)) | ||
#### Committers: 1 | ||
- Chris Garrett ([@pzuraq](https://github.com/pzuraq)) | ||
## v4.2.1 (2020-11-09) | ||
@@ -2,0 +14,0 @@ |
394
index.js
'use strict'; | ||
const { replaceTemplateLiteralProposal } = require('./src/template-literal-transform'); | ||
@@ -10,8 +11,8 @@ module.exports = function (babel) { | ||
function parseExpression(buildError, node) { | ||
function parseExpression(buildError, name, node) { | ||
switch (node.type) { | ||
case 'ObjectExpression': | ||
return parseObjectExpression(buildError, node); | ||
return parseObjectExpression(buildError, name, node); | ||
case 'ArrayExpression': { | ||
return parseArrayExpression(buildError, node); | ||
return parseArrayExpression(buildError, name, node); | ||
} | ||
@@ -24,3 +25,3 @@ case 'StringLiteral': | ||
throw buildError( | ||
`hbs can only accept static options but you passed ${JSON.stringify(node)}` | ||
`${name} can only accept static options but you passed ${JSON.stringify(node)}` | ||
); | ||
@@ -30,4 +31,4 @@ } | ||
function parseArrayExpression(buildError, node) { | ||
let result = node.elements.map((element) => parseExpression(buildError, element)); | ||
function parseArrayExpression(buildError, name, node) { | ||
let result = node.elements.map((element) => parseExpression(buildError, name, element)); | ||
@@ -37,3 +38,23 @@ return result; | ||
function parseObjectExpression(buildError, node) { | ||
function parseScopeObject(buildError, name, node) { | ||
if (node.type !== 'ObjectExpression') { | ||
throw buildError( | ||
`Scope objects for \`${name}\` must be an object expression containing only references to in-scope values` | ||
); | ||
} | ||
return node.properties.map((prop) => { | ||
let { key, value } = prop; | ||
if (value.type !== 'Identifier' || value.name !== key.name) { | ||
throw buildError( | ||
`Scope objects for \`${name}\` may only contain direct references to in-scope values, e.g. { ${key.name} } or { ${key.name}: ${key.name} }` | ||
); | ||
} | ||
return key.name; | ||
}); | ||
} | ||
function parseObjectExpression(buildError, name, node, shouldParseScope = false) { | ||
let result = {}; | ||
@@ -43,3 +64,3 @@ | ||
if (property.computed || !['Identifier', 'StringLiteral'].includes(property.key.type)) { | ||
throw buildError('hbs can only accept static options'); | ||
throw buildError(`${name} can only accept static options`); | ||
} | ||
@@ -50,4 +71,10 @@ | ||
let value = parseExpression(buildError, property.value); | ||
let value; | ||
if (shouldParseScope && propertyName === 'scope') { | ||
value = parseScopeObject(buildError, name, property.value); | ||
} else { | ||
value = parseExpression(buildError, name, property.value); | ||
} | ||
result[propertyName] = value; | ||
@@ -59,3 +86,3 @@ }); | ||
function compileTemplate(precompile, template, _options) { | ||
function compileTemplate(precompile, template, emberIdentifier, _options) { | ||
let options = Object.assign({ contents: template }, _options); | ||
@@ -88,3 +115,3 @@ | ||
t.memberExpression( | ||
t.memberExpression(t.identifier('Ember'), t.identifier('HTMLBars')), | ||
t.memberExpression(emberIdentifier, t.identifier('HTMLBars')), | ||
t.identifier('template') | ||
@@ -96,143 +123,288 @@ ), | ||
return { | ||
visitor: { | ||
ImportDeclaration(path, state) { | ||
let node = path.node; | ||
function getScope(scope) { | ||
let names = []; | ||
let modules = state.opts.modules || { | ||
'htmlbars-inline-precompile': 'default', | ||
}; | ||
while (scope) { | ||
for (let binding in scope.bindings) { | ||
names.push(binding); | ||
} | ||
if (state.opts.modulePaths) { | ||
let modulePaths = state.opts.modulePaths; | ||
scope = scope.parent; | ||
} | ||
modulePaths.forEach((path) => (modules[path] = 'default')); | ||
} | ||
return names; | ||
} | ||
let modulePaths = Object.keys(modules); | ||
let matchingModulePath = modulePaths.find((value) => t.isLiteral(node.source, { value })); | ||
let modulePathExport = modules[matchingModulePath]; | ||
function replacePath(path, state, compiled, options) { | ||
if (options.useTemplateLiteralProposalSemantics) { | ||
replaceTemplateLiteralProposal(t, path, state, compiled, options); | ||
} else { | ||
path.replaceWith(compiled); | ||
} | ||
} | ||
if (matchingModulePath) { | ||
let first = node.specifiers && node.specifiers[0]; | ||
let localName = first.local.name; | ||
let visitor = { | ||
Program(path, state) { | ||
let options = state.opts || {}; | ||
if (modulePathExport === 'default') { | ||
if (!t.isImportDefaultSpecifier(first)) { | ||
let input = state.file.code; | ||
let usedImportStatement = input.slice(node.start, node.end); | ||
let msg = `Only \`import hbs from '${matchingModulePath}'\` is supported. You used: \`${usedImportStatement}\``; | ||
throw path.buildCodeFrameError(msg); | ||
} | ||
} else { | ||
if (!t.isImportSpecifier(first) || modulePathExport !== first.imported.name) { | ||
let input = state.file.code; | ||
let usedImportStatement = input.slice(node.start, node.end); | ||
let msg = `Only \`import { ${modulePathExport} } from '${matchingModulePath}'\` is supported. You used: \`${usedImportStatement}\``; | ||
// Find/setup Ember global identifier | ||
let useEmberModule = Boolean(options.useEmberModule); | ||
let allAddedImports = {}; | ||
throw path.buildCodeFrameError(msg); | ||
} | ||
} | ||
state.ensureImport = (exportName, moduleName) => { | ||
let addedImports = (allAddedImports[moduleName] = allAddedImports[moduleName] || {}); | ||
state.importId = | ||
state.importId || path.scope.generateUidIdentifierBasedOnNode(path.node.id); | ||
if (addedImports[exportName]) return addedImports[exportName]; | ||
path.scope.rename(localName, state.importId.name); | ||
path.remove(); | ||
if (exportName === 'default' && moduleName === 'ember' && !useEmberModule) { | ||
addedImports[exportName] = t.identifier('Ember'); | ||
return addedImports[exportName]; | ||
} | ||
}, | ||
TaggedTemplateExpression(path, state) { | ||
if (!state.importId) { | ||
return; | ||
} | ||
let importDeclarations = path.get('body').filter((n) => n.type === 'ImportDeclaration'); | ||
let tagPath = path.get('tag'); | ||
if (tagPath.node.name !== state.importId.name) { | ||
return; | ||
let preexistingImportDeclaration = importDeclarations.find( | ||
(n) => n.get('source').get('value').node === moduleName | ||
); | ||
if (preexistingImportDeclaration) { | ||
let importSpecifier = preexistingImportDeclaration.get('specifiers').find(({ node }) => { | ||
return exportName === 'default' | ||
? t.isImportDefaultSpecifier(node) | ||
: node.imported.name === exportName; | ||
}); | ||
if (importSpecifier) { | ||
addedImports[exportName] = importSpecifier.node.local; | ||
} | ||
} | ||
if (path.node.quasi.expressions.length) { | ||
throw path.buildCodeFrameError( | ||
'placeholders inside a tagged template string are not supported' | ||
if (!addedImports[exportName]) { | ||
let uid = path.scope.generateUidIdentifier( | ||
exportName === 'default' ? moduleName : exportName | ||
); | ||
addedImports[exportName] = uid; | ||
let newImportSpecifier = | ||
exportName === 'default' | ||
? t.importDefaultSpecifier(uid) | ||
: t.importSpecifier(uid, t.identifier(exportName)); | ||
let newImport = t.importDeclaration([newImportSpecifier], t.stringLiteral(moduleName)); | ||
path.unshiftContainer('body', newImport); | ||
} | ||
let template = path.node.quasi.quasis.map((quasi) => quasi.value.cooked).join(''); | ||
return addedImports[exportName]; | ||
}; | ||
let { precompile, isProduction } = state.opts; | ||
// Setup other module options and create cache for values | ||
let modules = state.opts.modules || { | ||
'htmlbars-inline-precompile': { export: 'default', shouldParseScope: false }, | ||
}; | ||
path.replaceWith(compileTemplate(precompile, template, { isProduction })); | ||
}, | ||
if (state.opts.modulePaths) { | ||
let modulePaths = state.opts.modulePaths; | ||
CallExpression(path, state) { | ||
if (!state.importId) { | ||
return; | ||
} | ||
modulePaths.forEach((path) => (modules[path] = { export: 'default' })); | ||
} | ||
let calleePath = path.get('callee'); | ||
if (calleePath.node.name !== state.importId.name) { | ||
return; | ||
} | ||
let presentModules = new Map(); | ||
let importDeclarations = path.get('body').filter((n) => n.type === 'ImportDeclaration'); | ||
let args = path.node.arguments; | ||
for (let module in modules) { | ||
let paths = importDeclarations.filter( | ||
(path) => !path.removed && path.get('source').get('value').node === module | ||
); | ||
let template; | ||
for (let path of paths) { | ||
let options = modules[module]; | ||
switch (args[0] && args[0].type) { | ||
case 'StringLiteral': | ||
template = args[0].value; | ||
break; | ||
case 'TemplateLiteral': | ||
if (args[0].expressions.length) { | ||
throw path.buildCodeFrameError( | ||
'placeholders inside a template string are not supported' | ||
); | ||
if (typeof options === 'string') { | ||
// Normalize 'moduleName': 'importSpecifier' | ||
options = { export: options }; | ||
} else { | ||
// else clone options so we don't mutate it | ||
options = Object.assign({}, options); | ||
} | ||
let modulePathExport = options.export; | ||
let importSpecifierPath = path | ||
.get('specifiers') | ||
.find(({ node }) => | ||
modulePathExport === 'default' | ||
? t.isImportDefaultSpecifier(node) | ||
: node.imported && node.imported.name === modulePathExport | ||
); | ||
if (importSpecifierPath) { | ||
let localName = importSpecifierPath.node.local.name; | ||
options.modulePath = module; | ||
options.originalName = localName; | ||
let localImportId = path.scope.generateUidIdentifierBasedOnNode(path.node.id); | ||
path.scope.rename(localName, localImportId); | ||
// If it was the only specifier, remove the whole import, else | ||
// remove the specifier | ||
if (path.node.specifiers.length === 1) { | ||
path.remove(); | ||
} else { | ||
template = args[0].quasis.map((quasi) => quasi.value.cooked).join(''); | ||
importSpecifierPath.remove(); | ||
} | ||
break; | ||
case 'TaggedTemplateExpression': | ||
throw path.buildCodeFrameError('tagged template strings inside hbs are not supported'); | ||
default: | ||
throw path.buildCodeFrameError( | ||
'hbs should be invoked with at least a single argument: the template string' | ||
); | ||
presentModules.set(localImportId, options); | ||
} | ||
} | ||
} | ||
let options; | ||
state.presentModules = presentModules; | ||
}, | ||
switch (args.length) { | ||
case 1: | ||
options = {}; | ||
break; | ||
case 2: { | ||
if (args[1].type !== 'ObjectExpression') { | ||
throw path.buildCodeFrameError( | ||
'hbs can only be invoked with 2 arguments: the template string, and any static options' | ||
); | ||
} | ||
ClassDeclaration(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) => { | ||
if (path.type !== 'ClassProperty') return; | ||
options = parseObjectExpression(path.buildCodeFrameError.bind(path), args[1]); | ||
let keyPath = path.get('key'); | ||
let valuePath = path.get('value'); | ||
break; | ||
if (keyPath && visitor[keyPath.type]) { | ||
visitor[keyPath.type](keyPath, state); | ||
} | ||
if (valuePath && visitor[valuePath.type]) { | ||
visitor[valuePath.type](valuePath, state); | ||
} | ||
}); | ||
}, | ||
TaggedTemplateExpression(path, state) { | ||
let tagPath = path.get('tag'); | ||
let options = state.presentModules.get(tagPath.node.name); | ||
if (!options) { | ||
return; | ||
} | ||
if (options.disableTemplateLiteral) { | ||
throw path.buildCodeFrameError( | ||
`Attempted to use \`${options.originalName}\` as a template tag, but it can only be called as a function with a string passed to it: ${options.originalName}('content here')` | ||
); | ||
} | ||
if (path.node.quasi.expressions.length) { | ||
throw path.buildCodeFrameError( | ||
'placeholders inside a tagged template string are not supported' | ||
); | ||
} | ||
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 emberIdentifier = state.ensureImport('default', 'ember'); | ||
replacePath( | ||
path, | ||
state, | ||
compileTemplate(precompile, template, emberIdentifier, { isProduction, scope, strict }), | ||
options | ||
); | ||
}, | ||
CallExpression(path, state) { | ||
let calleePath = path.get('callee'); | ||
let options = state.presentModules.get(calleePath.node.name); | ||
if (!options) { | ||
return; | ||
} | ||
if (options.disableFunctionCall) { | ||
throw path.buildCodeFrameError( | ||
`Attempted to use \`${options.originalName}\` as a function call, but it can only be used as a template tag: ${options.originalName}\`content here\`` | ||
); | ||
} | ||
let args = path.node.arguments; | ||
let template; | ||
switch (args[0] && args[0].type) { | ||
case 'StringLiteral': | ||
template = args[0].value; | ||
break; | ||
case 'TemplateLiteral': | ||
if (args[0].expressions.length) { | ||
throw path.buildCodeFrameError( | ||
'placeholders inside a template string are not supported' | ||
); | ||
} else { | ||
template = args[0].quasis.map((quasi) => quasi.value.cooked).join(''); | ||
} | ||
default: | ||
break; | ||
case 'TaggedTemplateExpression': | ||
throw path.buildCodeFrameError( | ||
`tagged template strings inside ${options.originalName} are not supported` | ||
); | ||
default: | ||
throw path.buildCodeFrameError( | ||
'hbs should be invoked with at least a single argument: the template string' | ||
); | ||
} | ||
let compilerOptions; | ||
switch (args.length) { | ||
case 1: | ||
compilerOptions = {}; | ||
break; | ||
case 2: { | ||
if (args[1].type !== 'ObjectExpression') { | ||
throw path.buildCodeFrameError( | ||
'hbs can only be invoked with 2 arguments: the template string, and any static options' | ||
); | ||
} | ||
} | ||
let { precompile, isProduction } = state.opts; | ||
compilerOptions = parseObjectExpression( | ||
path.buildCodeFrameError.bind(path), | ||
options.originalName, | ||
args[1], | ||
true | ||
); | ||
// allow the user specified value to "win" over ours | ||
if (!('isProduction' in options)) { | ||
options.isProduction = isProduction; | ||
break; | ||
} | ||
default: | ||
throw path.buildCodeFrameError( | ||
'hbs can only be invoked with 2 arguments: the template string, and any static options' | ||
); | ||
} | ||
path.replaceWith(compileTemplate(precompile, template, options)); | ||
}, | ||
let { precompile, isProduction } = state.opts; | ||
// allow the user specified value to "win" over ours | ||
if (!('isProduction' in compilerOptions)) { | ||
compilerOptions.isProduction = isProduction; | ||
} | ||
replacePath( | ||
path, | ||
state, | ||
compileTemplate( | ||
precompile, | ||
template, | ||
state.ensureImport('default', 'ember'), | ||
compilerOptions | ||
), | ||
options | ||
); | ||
}, | ||
}; | ||
return { visitor }; | ||
}; | ||
@@ -239,0 +411,0 @@ |
{ | ||
"name": "babel-plugin-htmlbars-inline-precompile", | ||
"version": "4.2.1", | ||
"version": "4.3.0", | ||
"description": "Babel plugin to replace tagged template strings with precompiled HTMLBars templates", | ||
@@ -13,15 +13,16 @@ "repository": "https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile", | ||
"devDependencies": { | ||
"@babel/core": "^7.12.3", | ||
"@babel/plugin-transform-modules-amd": "^7.12.1", | ||
"@babel/plugin-transform-template-literals": "^7.12.1", | ||
"@babel/plugin-transform-unicode-escapes": "^7.12.1", | ||
"@babel/core": "^7.12.16", | ||
"@babel/plugin-proposal-class-properties": "^7.12.13", | ||
"@babel/plugin-transform-modules-amd": "^7.12.13", | ||
"@babel/plugin-transform-template-literals": "^7.12.13", | ||
"@babel/plugin-transform-unicode-escapes": "^7.12.13", | ||
"common-tags": "^1.8.0", | ||
"ember-source": "^3.22.0", | ||
"ember-source": "^3.25.1", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.15.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^3.1.4", | ||
"jest": "^26.6.2", | ||
"prettier": "^2.1.2", | ||
"release-it": "^14.2.1", | ||
"eslint-plugin-prettier": "^3.3.1", | ||
"jest": "^26.6.3", | ||
"prettier": "^2.2.1", | ||
"release-it": "^14.4.0", | ||
"release-it-lerna-changelog": "^3.1.0" | ||
@@ -28,0 +29,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
64251
10
1156
15