question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Typing targets with TypeScript and babel 7 with class properties proposal

See original GitHub issue

Setup:

There is a conflict in how TypeScript expects class properties to be typed, and how babel treats class properties, which is resulting in an error in Stimulus.

Consider the example from the handbook implemented with TypeScript:

<div data-controller="hello">
  <input data-target="hello.name" type="text">
  <button data-action="click->hello#greet">Greet</button>
</div>
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "name" ]
  readonly nameTarget!: Element

  greet() {
    const element = this.nameTarget
    const name = element.value
    console.log(`Hello, ${name}!`)
  }
}

When using TypeScript with babel 7 and the @babel/plugin-proposal-class-properties plugin, the resulting JavaScript ends up with the class property nameTarget being initialised with void 0, as per the spec. This results in the following error from Stimulus:

Cannot set property nameTarget of #<Controller> which has only a getter

This is perfectly reasonable behaviour from Stimulus: you wouldn’t want the nameTarget being overwritten by calling code, since it is managed by Stimulus.

Digging deeper into the issue, it appears that Stimulus defines the *Target properties when the controller is first loaded into the application, based on the static targets property. Babel’s assignment of this.nameTarget = void 0 occurs when the controller instance is created - by this time, the *Target properties have already been defined (without setters) and so the error is seen when the controller is instantiated.

Unfortunately I can’t see any way around this, other than

a) not typing the *Target properties, and ignoring/suppressing the TypeScript errors b) reverting back to using regular JavaScript

I’d really like to use TypeScript, especially for targets as the autocompletion tools for HTMLElements are such a timesaver. Can there be any way to solve this issue?

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:2
  • Comments:11 (2 by maintainers)

github_iconTop GitHub Comments

9reactions
fiznoolcommented, Sep 24, 2020

@Intrepidd I did find a workaround - you can use the declare keyword to define the class property. On seeing a declared property, Babel will strip this from the generated code, as it considers declare as type information only.

So the original example can be rewritten as follows:

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "name" ]
  declare readonly nameTarget: Element

  greet() {
    const element = this.nameTarget
    const name = element.value
    console.log(`Hello, ${name}!`)
  }
}

Note that you’ll need to opt-in to this behaviour with Babel 7 via the allowDeclareFields option to @babel/plugin-transform-typescript. I had to use the transform directly as the option didn’t seem to work using the preset. Babel 8 will make this the default behaviour.

9reactions
guzartcommented, Jan 24, 2019

Bumped into this issue myself. Most certainly a tricky one to solve. Maybe decorator? Too bad it’s still in the proposal stage.

It certainly makes sense that the spec tells that extending a class and declaring a property initializes such property, overriding whatever the base class has.

FWIW, I used the following technique to fool the TS compiler:

class HelloBaseController extends Controller {
  public nameTarget!: HTMLInputElement;
}

export class HelloController extends (Controller as typeof HelloBaseController) {
  public static targets = ['name'];
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Typing targets with TypeScript and babel 7 with class ... - GitHub
Babel configured to use @babel/plugin-proposal-class-properties. There is a conflict in how TypeScript expects class properties to be typed, and ...
Read more >
TypeScript and Babel 7 - Microsoft Developer Blogs
Today we're excited to announce something special for Babel users.Over a year ago, we set out to find what the biggest difficulties users ......
Read more >
babel/plugin-proposal-class-properties
Below is a class with four class properties which will be transformed. class Bork { //Property initializer syntax instanceProperty = "bork"; boundFunction =...
Read more >
TypeScript With Babel: A Beautiful Marriage - I Am Turns
TypeScript has never been easier thanks to the TypeScript plugin for Babel. Discover 4 reasons why TypeScript + Babel are a perfect pair, ......
Read more >
Using new Babel 7 and preset-typescript to compile Angular 6 ...
Recent launch of Babel 7 was accompanied by a pretty large fanfare, ... @babel/plugin-proposal-class-properties# Webpack-related packages
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found