Typing targets with TypeScript and babel 7 with class properties proposal
See original GitHub issueSetup:
- using Stimulus with TypeScript and babel 7
- TypeScript is providing type checking and compiling to esnext-level JS
- Resulting JS is then transpired by babel down to targets based on @babel/preset-env
- Babel configured to use @babel/plugin-proposal-class-properties
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:
- Created 5 years ago
- Reactions:2
- Comments:11 (2 by maintainers)
Top GitHub Comments
@Intrepidd I did find a workaround - you can use the
declare
keyword to define the class property. On seeing adeclare
d property, Babel will strip this from the generated code, as it considersdeclare
as type information only.So the original example can be rewritten as follows:
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.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: