Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include decorator and class field plugins after TypeScript, if present #311

Merged
merged 1 commit into from
Dec 12, 2019

Conversation

dfreeman
Copy link
Contributor

@dfreeman dfreeman commented Dec 12, 2019

Babel now has an official stance on the order the TS plugin should be added relative to any others that touch certain class features, and an assertion is tripped when attempting to use TS's new declare field syntax:

stack: SyntaxError: /...file.ts: TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.

If you have already enabled that plugin (or '@babel/preset-typescript'), make sure that it runs before any plugin related to additional class features:

 - @babel/plugin-proposal-class-properties
 - @babel/plugin-proposal-private-methods
 - @babel/plugin-proposal-decorators

What breaks

Before, both ember-cli-typescript and ember-cli-babel ensured that @babel/plugin-transform-typescript would appear after anything that touched class fields, because it maintained the edge-case semantics of tsc that initializes 'constructor fields' before regular class fields:

class MySketchyClass {
  constructor(private constructorField: string) {}
  public regularField = this.constructorField;
}

// transpiles via `tsc` to:

class MySketchyClass {
  constructor(constructorField) {
    this.constructorField = constructorField;
    this.regularField = this.constructorField;
  }
}

These semantics aren't actually possible with native class fields, but tsc doesn't yet support that as a compilation target and hasn't had to deal with the inconsistency.

With the plugins in the order Babel now asserts they should be in, Babel transpilation now gives you:

class MySketchyClass {
  constructor(constructorField) {
    // `regularField` will be undefined
    _defineProperty(this, 'regularField', this.constructorField);
    this.constructorField = constructorField;
  }
}

There should hopefully be little to no actual usage of this pattern in Ember TypeScript code in the wild, but it's not an easy condition to search for.

Non-TypeScript code wouldn't be impacted, since constructor fields aren't an ES feature.

What's fixed

The declare field modifier is the answer to a stumbling block that a lot of devs hit with the class fields spec, which dictates that foo; in a class body initializes that field to undefined during construction. This leaves TS authors without a clean way to declare the type of a class field without implicitly adding an undefined initializer for it, which is why the declare modifier was introduced.

While declare is the piece of syntax that currently triggers the plugin ordering assertion, given the way the TS plugin interacts with other class features, it's likely others will be introduced in the future as both languages evolve.

Why ember-cli-babel needs a change

In ember-cli-typescript we can ensure that the ordering is correct if the decorator or class-property plugins are already in the list as user-supplied configuration, but when ember-cli-babel adds them, it happens after we've had our chance to set configuration, so we can't control the ordering ourselves.

While this is technically a breaking change for anyone whose code relies on the constructor/class field initialization order, the impact will hopefully be minimal. I'd guess it will hit fewer folks than #307, so I'm hopeful we can get away with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants