Skip to content

Commit

Permalink
[FEATURE set-component-template] Add @ember/component/template-only
Browse files Browse the repository at this point in the history
As part of RFC 481 this adds the `@ember/component/template-only` module, and
its associated implementation.

Co-authored-by: Robert Jackson <me@rwjblue.com>
  • Loading branch information
chancancode and rwjblue committed Jul 11, 2019
1 parent b31998b commit b53699e
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 2 deletions.
10 changes: 9 additions & 1 deletion packages/@ember/-internals/glimmer/lib/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -449,7 +450,14 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe

let definition: Option<ComponentDefinition> = 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!);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -247,3 +251,57 @@ 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();
}

['@test templateOnly accepts a moduleName to be used for debugging / toString purposes'](
assert
) {
let ComponentClass = templateOnly('my-app/components/foo');

assert.equal(`${ComponentClass}`, 'my-app/components/foo');
}
}
);
}
1 change: 1 addition & 0 deletions packages/@ember/component/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Component } from '@ember/-internals/glimmer';
45 changes: 45 additions & 0 deletions packages/@ember/component/template-only.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// This is only exported for types, don't use this class directly
export class TemplateOnlyComponent {
constructor(public moduleName = '@ember/component/template-only') {}

toString(): string {
return this.moduleName;
}
}

/**
@module @ember/component/template-only
@public
*/

/**
This utility function is used to declare a given component has no backing class. When the rendering engine detects this it
is able to perform a number of optimizations. Templates that are associated with `templateOnly()` will be rendered _as is_
without adding a wrapping `<div>` (or any of the other element customization behaviors of [@ember/component](/ember/release/classes/Component)).
Specifically, this means that the template will be rendered as "outer HTML".
In general, this method will be used by build time tooling and would not be directly written in an application. However,
at times it may be useful to use directly to leverage the "outer HTML" semantics mentioned above. For example, if an addon would like
to use these semantics for its templates but cannot be certain it will only be consumed by applications that have enabled the
`template-only-glimmer-components` optional feature.
@example
```js
import templateOnly from '@ember/component/template-only';
export default templateOnly();
```
@public
@method templateOnly
@param {String} moduleName the module name that the template only component represents, this will be used for debugging purposes
@category EMBER_GLIMMER_SET_COMPONENT_TEMPLATE
*/
export default function templateOnlyComponent(moduleName: string): TemplateOnlyComponent {
return new TemplateOnlyComponent(moduleName);
}

export function isTemplateOnlyComponent(component: unknown): component is TemplateOnlyComponent {
return component instanceof TemplateOnlyComponent;
}
3 changes: 2 additions & 1 deletion packages/ember/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) || {};
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions packages/ember/tests/reexports_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
1 change: 1 addition & 0 deletions tests/docs/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ module.exports = {
'target',
'teardownViews',
'templateName',
'templateOnly',
'testCheckboxClick',
'testHelpers',
'testing',
Expand Down

0 comments on commit b53699e

Please sign in to comment.