Ember Octane is coming soon


(This article was originally published in www.pzuraq.com)

Hello again, and welcome back!This is the fifth and last entry in the multipart Ember Octane is coming soon Series in which we will preview some of the features in Ember’s upcoming Octane version, including:


These are not All The new features that will be part of Octane are just the ones I am personally most familiar with. This series is aimed at existing Ember users, but if you are new to Ember or tried Ember not long ago and want to know how the situation has changed, I will provide background information on existing features as we proceed. These posts will not delve into all the edge cases of the feature, they are more like an overview of what is about to happen.If you are curious about something Version Exactly, you can view the quick breakdown The first article in the series.

Now, let’s move to the Glimmer component!

Better component API

At the end of the Ember@v1 cycle, the community began to notice some pain points of the Ember component API. Although components are popular in general and quickly surpassed views as the default rendering primitive in Ember, there are some paper cuts here and there that make them feel harder to use than they should. in particular:

  1. syntax: The fact that the components need to be the same {{double-curly}} Grammars as helpers and bindings in templates can sometimes make them difficult to parse. There is a lot of visual confusion, and it may be difficult to figure out where and what is called:
{{#modal-dialog}}
  {{#power-select
    options=names
    onchange=(action "foo")
    as |name|}}
    {{capitalize name}}
  {{/power-select}}
{{/modal-dialog}}
  1. Packaging elements: The component has an implicit wrapper element, it always wraps the template, and you need to create a class to customize:
import Component from '@ember/component';

export default Component.extend({
  tagName: 'button',
  classNames: ['btn'],
});

This means that the template is not the only source of truth for the final output of the component-the user must read the component class to know whether it has customized the template in some way. This also means that users usually have to create a class to customize this element, otherwise it will become a template-only component.

  1. parameter: The parameter of the component is directly assigned as the attribute of the component instance. This usually leads to conflicts between parameters and properties or methods on the component, and it is difficult to distinguish between the two:
import Component from '@ember/component';

export default Component.extend({
  init() {
    this._super(...arguments);

    // You may wonder where this magic `filter`
    // value came from. Is it a method on the
    // superclass? Actually, it's an argument
    // that was passed to the component, a callback.
    this.filter('').then((results) => {
      return this.set('results', results);
    });
  },
});
  1. Two-way binding: At the beginning of Ember, two-way data binding was the standard for front-end frameworks, but over time it became clear that one-way data flow made the most sense, both from a performance point of view and from a code organization point of view. Ember components can still modify the values ​​bound in the parent template, but this behavior is often problematic and error-prone.

These, and many other small paper-cuts along the way, prompted the core team to rethink component APIs.In the process, the part of the rethinking is broken down into the parts we have covered in this series, such as <AngleBracket> The syntax and infrastructure are in place to rationalize Ember’s component API internally so that a brand new API can be added side-by-side to the original API. Components are the foundation of Ember applications and usually contain most of the code in the application, so it is important to be able to upgrade one component at a time rather than through a large-scale rewrite.

Glimmer Components is the end result of all hard work. They are lighter, simpler, and more ergonomic, and can solve all these problems and more.

less is more

Most importantly, the Glimmer component is a significant simplification of the Ember component API and is now known as a classic component in the community. Over the years, Classic Components has accumulated a lot of things, including:

In contrast, Glimmer Components just 2 Lifecycle hooks and 3 characteristic. They do not have any element or DOM-based attributes, hooks, or event handlers, and their responsibilities have been passed to element modifiers.This Dramatic Simplify what you need to learn in order to start using Ember’s bread and butter, allowing you to focus on productivity out of the box.

Other major differences include:

Last but not least, the same name of Glimmer Components-and Shimmer.js, To supplement Ember’s minimal component framework.

Lifecycle hooks and modifiers

As mentioned above, the Glimmer component has only two lifecycle hooks- constructor Functions for setting up components, and willDestroy The function to remove it. It also has only 3 attributes: isDestroying, isDestroyed, with args (We will talk about it later).

interface GlimmerComponent<T = object> {
  args: T;

  isDestroying: boolean;
  isDestroyed: boolean;

  constructor(owner: Opaque, args: T): void;
  willDestroy(): void;
}

You may want to know how to replace hooks, for example didInsertElement with didUpdateAttrs, Or similar properties this.element. After all 13 Hooks, that must cover a lot of functions, right?In fact, our case study shows that many of these hooks have obvious overlaps with each other, and most of their functions can be replaced with Getter And derived state, or pass Modifier. I discussed modifiers in depth in the previous blog post, but the point is that they are new primitives for DOM manipulation, and using Glimmer components, they will become as long as Ways to access the DOM.

Reducing the number of lifecycle hooks makes designing components easier.No more arguing about which hook to use, benefits and trade-offs, and time differences didRender with didReceiveAttrs, When to use willDestroyElement with didDestroyElementInstead, push as much business logic as possible into the getter and tracking properties, and use modifiers for any advanced side-effect DOM logic.

External HTML

In Glimmer Components, what you see in the template is what you get in the output. There are no surrounding elements around the template-the template represents the “outer edge” of the component, not just the “inside”.This means you don’t have to use something like tagName, classNames, Or attributeBindings Customize your template, forever. The component:

// app/templates/hello-button.js
import Component from '@ember/component';

export default Component.extend({
  tagName: 'button',
  classNames: ['btn'],
  attributeBindings: ['role'],
  role: 'button',
});
<!-- app/templates/components/hello-button.hbs -->
Hello, world!

Can be rewritten as:

<!-- app/templates/components/hello-button.hbs -->
<button class="btn" role="button">
  Hello, world!
</button>

This makes the template easier to reason about, because the complete definition is no longer split between two different files.You no longer need to remember that there is some kind of external element, and it May or may not, but usually A kind div.

However, this does bring about the problem of attribute reflection.As we learned in the post Angle bracket syntax, When used with angle brackets, the attributes added to the component will be reflected on the main element:

<MyButton class="custom-btn" aria-labelledby="my-label"/>

Use classic components, main components Yes Packaging elements.In Glimmer Components, there is no clear main element-there may be multiple top-level elements, or there may be Do not The element is just text.What is special ...attributes The syntax is used:

<!-- app/templates/components/hello-button.hbs -->
<button ...attributes class="btn" role="button">
  Hello, world!
</button>

This syntax allows you to choose which elements to apply the attribute to. It can be applied multiple times or not at all, in which case using properties on the component will cause an error. This allows us to clearly see the location of the applied element attributes and easier to control it.For example, you can Nested Elements instead of top-level elements.

Another cool feature of this syntax is command Its application can be used to determine how it overrides attributes.Coming attribute before ...attributes Will be overwritten, but the attributes appearing Rear will not. For example, given these two possibilities:

<div data-foo="inner" ...attributes></div>
<div ...attributes data-foo="inner"></div>

Through this call:

<Foo data-foo="outer" />

We will get this result:

<div data-foo="outer"></div>
<div data-foo="inner"></div>

The system is generally more flexible, which means we can write easier-to-understand components using clearer, more readable and self-explanatory templates!

Namespace parameters

The parameters in the Glimmer component are placed in args Properties, not directly on component properties.This makes it clearer what the value is debate The attributes passed to the component, and the attributes used internally by the component. Looking back at our example in the introduction, this:

import Component from '@ember/component';

export default Component.extend({
  init() {
    this._super(...arguments);

    this.filter('').then((results) => {
      return this.set('results', results);
    });
  },
});

Becomes like this:

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

export default class FilterComponent extends Component {
  @tracked results;

  constructor() {
    super(...arguments);

    this.args.filter('').then((results) => {
      this.results = results;
    });
  }
}

We can clearly see now filter It is a parameter, not some ubiquitous API function.

This args Objects are also immutable (although the parameters they themselves Not).This forces One-way data flow, From the parent component to the child component, and prevent the general two-way data binding.It also means that when you see an argument, you know It is a value passed by the parent, not a value managed internally by the component. This distinction also helps to reason about the component code.

Stateless template-only component

Only template components can be used to quickly extract bits and pieces of functionality from other components without having to bring business logic. They only own one file and focus on the presentation, which makes things easier. However, for classic components, they have two main problems:

  1. There is no way to control the packaging elements, and usually a class must be created for this. The Glimmer component uses external HTML semantics to solve this problem, as we discussed above.
  2. Although only the template component has no logic, the classic component is used Have done Stateful.They need an instance to save their parameter value, which is possible, Although it is somewhat difficult, assign values ​​to the instance and use the built-in helper to make them stateful.

Use only template Glimmer component, the component is completely Stateless. They have no support instances at all, which makes them faster, and they cannot set or access any state other than their parameters, which makes them generally easier to reason about.

Glimmer.js compatibility

In the past few years, Glimmer.js has been a testing ground for many of Ember’s ideas. It is a thin wrapper on top of Glimmer VM, Glimmer.js and Ember share the rendering engine. Although Ember is an all-round solution for ambitious applications, Glimmer.js seeks to be a minimal, component-only framework that allows users to build functionality as needed. Ultimately, the idea is that we will be able to install Ember the way we do, one package at a time.

The cross-compatibility of Glimmer components means that Ember users can share code using a smaller framework to better serve the target use case. Over time, this will mean that the ecosystem can use both at the same time, and we will be able to unify the two because we separate the monomers at once.

Put it together

As usual, I want to end with a high profile.This is a popular example Embers switch Plug-in, it provides a nice switching component, x-toggle-label ingredient:

import { readOnly } from '@ember/object/computed';
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from './template';

export default Component.extend({
  layout,
  tagName: 'label',
  attributeBindings: ['for'],
  classNames: ['toggle-text', 'toggle-prefix'],
  classNameBindings: ['labelType'],
  for: readOnly('switchId'),
  isVisible: readOnly('show'),

  labelType: computed('type', function() {
    let type = this.get('type');

    return `${type}-label`;
  }),

  type: computed('value', {
    get() {
      return this.get('value') ? 'on' : 'off';
    }
  }),

  click(e) {
    e.stopPropagation();
    e.preventDefault();
    this.sendToggle(this.get('value'));
  }
});
{{#if hasBlock}}
  {{yield label}}
{{else}}
  {{label}}
{{/if}}

As you can see, the component code is very heavy, most of which are element customizations. Converting it to a Glimmer component, and all the other improvements of Octane, we have:

import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class XToggleLabel extends Component {
  get type() {
    return this.args.value ? 'on' : 'off';
  }

  @action
  handleClick(e) {
    e.stopPropagation();
    e.preventDefault();
    this.args.sendToggle(this.args.value);
  }
}
<label
  for="{{@switchId}}"
  onclick={{this.handleClick}}

  class="
    toggle-text
    toggle-prefix
    {{this.type}}-label
    {{if @show 'is-visible' 'is-hidden'}}
  "
>
  {{#if hasBlock}}
    {{yield label}}
  {{else}}
    {{label}}
  {{/if}}
</label>

The class definition here is generally much smaller, because we can remove all the settings used template, We can put it where it really belongs: templates! This is generally easier to read because we don’t have to jump back and forth between templates and class definitions to understand what the final HTML will look like. It’s all in one place.

in conclusion

Glimmer Components is a long-overdue improvement to the Ember component system, and I am very happy to see how they clean up our code. The design process of this API took a long time, but in the end I think we came up with the best component API for Ember, and I think it will serve us well in the next few years. I am also very happy to see how Glimmer.js develops, because users will be able to write components for both!

This concludes this blog series! I hope you like these posts and look forward to launching a preview version in the coming weeks! Ember in 2019 will be a very different framework😄

Leave a Reply

Your email address will not be published. Required fields are marked *