diff --git a/packages/@ember/-internals/glimmer/lib/resolver.ts b/packages/@ember/-internals/glimmer/lib/resolver.ts index e1ae8935595..ed9b7e2b3c0 100644 --- a/packages/@ember/-internals/glimmer/lib/resolver.ts +++ b/packages/@ember/-internals/glimmer/lib/resolver.ts @@ -8,6 +8,7 @@ import { EMBER_GLIMMER_SET_COMPONENT_TEMPLATE, EMBER_MODULE_UNIFICATION, } from '@ember/canary-features'; +import { isTemplateOnlyComponent } from '@ember/component/template-only'; import { assert } from '@ember/debug'; import { _instrumentStart } from '@ember/instrumentation'; import { @@ -449,7 +450,14 @@ export default class RuntimeResolver implements IRuntimeResolver = null; - if (pair.component === null && ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS) { + if (pair.component === null) { + if (ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS) { + definition = new TemplateOnlyComponentDefinition(layout!); + } + } else if ( + EMBER_GLIMMER_SET_COMPONENT_TEMPLATE && + isTemplateOnlyComponent(pair.component.class) + ) { definition = new TemplateOnlyComponentDefinition(layout!); } diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/template-only-components-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/template-only-components-test.js index f35044b16d2..049b4ba37cf 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/template-only-components-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/template-only-components-test.js @@ -1,6 +1,10 @@ import { moduleFor, RenderingTestCase, classes, runTask } from 'internal-test-helpers'; +import { EMBER_GLIMMER_SET_COMPONENT_TEMPLATE } from '@ember/canary-features'; import { ENV } from '@ember/-internals/environment'; +import { setComponentTemplate } from '@ember/-internals/glimmer'; +import templateOnly from '@ember/component/template-only'; +import { compile } from 'ember-template-compiler'; class TemplateOnlyComponentsTest extends RenderingTestCase { registerComponent(name, template) { @@ -247,3 +251,49 @@ moduleFor( } } ); + +if (EMBER_GLIMMER_SET_COMPONENT_TEMPLATE) { + moduleFor( + 'Components test: template-only components (using `templateOnlyComponent()`)', + class extends RenderingTestCase { + ['@test it can render a component']() { + this.registerComponent('foo-bar', { ComponentClass: templateOnly(), template: 'hello' }); + + this.render('{{foo-bar}}'); + + this.assertInnerHTML('hello'); + + this.assertStableRerender(); + } + + ['@test it can render a component when template was not registered']() { + let ComponentClass = templateOnly(); + setComponentTemplate(compile('hello'), ComponentClass); + + this.registerComponent('foo-bar', { ComponentClass }); + + this.render('{{foo-bar}}'); + + this.assertInnerHTML('hello'); + + this.assertStableRerender(); + } + + ['@test setComponentTemplate takes precedence over registered layout']() { + let ComponentClass = templateOnly(); + setComponentTemplate(compile('hello'), ComponentClass); + + this.registerComponent('foo-bar', { + ComponentClass, + template: 'this should not be rendered', + }); + + this.render('{{foo-bar}}'); + + this.assertInnerHTML('hello'); + + this.assertStableRerender(); + } + } + ); +} diff --git a/packages/@ember/component/index.ts b/packages/@ember/component/index.ts new file mode 100644 index 00000000000..4a85c81bf0c --- /dev/null +++ b/packages/@ember/component/index.ts @@ -0,0 +1 @@ +export { Component } from '@ember/-internals/glimmer'; diff --git a/packages/@ember/component/template-only.ts b/packages/@ember/component/template-only.ts new file mode 100644 index 00000000000..1700d5687c7 --- /dev/null +++ b/packages/@ember/component/template-only.ts @@ -0,0 +1,16 @@ +// This is only exported for types, don't use this class directly +export class TemplateOnlyComponent { + constructor(public moduleName: string) {} + + toString(): string { + return '@ember/component/template-only'; + } +} + +export default function templateOnlyComponent(moduleName: string): TemplateOnlyComponent { + return new TemplateOnlyComponent(moduleName); +} + +export function isTemplateOnlyComponent(component: unknown): component is TemplateOnlyComponent { + return component instanceof TemplateOnlyComponent; +} diff --git a/packages/ember/index.js b/packages/ember/index.js index c42f51f7c2e..e92318ed02e 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -137,7 +137,7 @@ import Engine from '@ember/engine'; import EngineInstance from '@ember/engine/instance'; import { assign, merge } from '@ember/polyfills'; import { LOGGER, EMBER_EXTEND_PROTOTYPES, JQUERY_INTEGRATION } from '@ember/deprecated-features'; - +import templateOnlyComponent from '@ember/component/template-only'; // ****@ember/-internals/environment**** const Ember = (typeof context.imports.Ember === 'object' && context.imports.Ember) || {}; @@ -540,6 +540,7 @@ Ember._modifierManagerCapabilties = modifierCapabilties; if (EMBER_GLIMMER_SET_COMPONENT_TEMPLATE) { Ember._getComponentTemplate = getComponentTemplate; Ember._setComponentTemplate = setComponentTemplate; + Ember._templateOnlyComponent = templateOnlyComponent; } Ember.Handlebars = { template, diff --git a/packages/ember/tests/reexports_test.js b/packages/ember/tests/reexports_test.js index 54b5dac153e..d2873650453 100644 --- a/packages/ember/tests/reexports_test.js +++ b/packages/ember/tests/reexports_test.js @@ -232,6 +232,9 @@ let allExports = [ EMBER_GLIMMER_SET_COMPONENT_TEMPLATE ? ['_getComponentTemplate', '@ember/-internals/glimmer', 'getComponentTemplate'] : null, + EMBER_GLIMMER_SET_COMPONENT_TEMPLATE + ? ['_templateOnlyComponent', '@ember/component/template-only', 'default'] + : null, // @ember/-internals/runtime ['A', '@ember/-internals/runtime'],