The @classic class decorator (provided by the ember-classic-decorator package) helps you keep track of code that still contains various classic Ember idioms and features that should be migrated away from. Whenever you see a class marked with @classic, you know that it needs a developer to review it to check for legacy code, and if that code exists, refactor it.

The @classic decorator workflow has two steps:

  1. Run the native-class-codemod to convert your project to native classes
  2. Progressively remove the @classic decorator from your app by refactoring classic code.

Why do I need it?

Native class syntax is one of the most anticipated new features in Ember Octane. Ember has always felt like a layer on top of JavaScript, and a huge part of the reason for that is the Ember Object Model, the class syntax that shipped as part of Ember from the very beginning.

Being able to use a mostly-vanilla JavaScript syntax for these classes, which represent a large portion of most app code, is exciting! Better yet, since native syntax is ultimately just a new syntax for doing exactly the same thing, there's a codemod that can automate most of the conversion for you!

However, native classes are only one of the new idioms introduced in Octane. Even after converting a class to native class syntax, there are a few additional changes you should make:

  1. Remove usage of EmberObject class methods. Even though you are using native class syntax, you still extend from the EmberObject base class, which has methods like get, set, incrementProperty, extend, and create. These methods are not necessary to use in Octane, and will likely be deprecated in the near future, so it's recommended that you remove any usage of them. The full list is:

    Note that you can still use the functional versions of these methods, just not the ones provided by the class itself:

    class MyComponent extends Component {
      updateValue(newValue) {
        // 🛑 this should be removed
        this.set('value', newValue);
        // 👍 This is ok
        set(this, 'value', newValue);
  2. Convert from init to constructor. init is in many ways similar to the standard JavaScript class constructor, but it is actually a lifecycle hook that runs after the object has been constructed. In most cases the hook can be renamed to constructor without any further changes, but there are sometimes subtle differences, so they generally require additional attention.

  3. Remove all mixins. Mixins are another classic class feature, and they will likely be deprecated in the post-Octane world. Removing them from your codebase will help to future-proof your app, and generally improve the maintainability of your app in the meantime. (See Converting Classes with Mixins for guides for migrating the many mixin-based patterns in Ember apps.)

  4. Rewrite Classic Components and Utility Classes entirely. Today, all classes extend from the EmberObject base class. Classic components rely on some intimate details of the EmberObject base class which cannot be changed, and this is part of the reason Glimmer components were introduced, so they should all be converted in time to Glimmer components. In addition, you may sometimes have written a class that wasn't a component, route, model or any other framework class. If you ever have directly extended EmberObject, then this is a utility class. This was done commonly before native classes were supported since there was no way to use Ember features with native JS syntax, but now that there is this is no longer needed. This classes should be rewritten so they don't extend from EmberObject at all anymore, and instead are plain native classes with no base class.

Keeping track of all of these additional changes is a large task, but we've tried to make it easier with the @classic decorator!

How it works?

Adding the @classic decorator to a class:

  1. Enforces that you are using init, not constructor, via lint
  2. Allows you to use classic class methods, such as this.get, via lint
  3. Allows you to use mixins in the class definition

Classes without the @classic decorator:

  1. Require you to use constructor, not init