February 20, 2020

Rails, Typescript, and Stimulus

I had a bit of free time lately so I decided to try my hand at some TypeScript. Specifically, I wanted to see how feasible it is to use in a Rails project with Stimulus. Turns out it isn't too bad.

Installing TypeScript in Rails

The installation process is quite simple. Just run the following command in a Rails project (provided you have Webpacker installed, which is standard from Rails 6+):

bin/rails webpacker:install:typescript

If all goes well, you should see this wonderful message:

Webpacker now supports typescript πŸŽ‰

NOTE: You can also do this in rails new with the --webpacker=typescript flag, even though this little detail is not documented when running rails new --help)

Now that Rails knows how to TypeScript, you can rename all your .js files to .ts, and start writing some TypeScript!

TypeScript + Stimulus

Since Stimulus is itself written in TypeScript, it makes sense that it should work fairly well with TypeScript. However, there are a few caveats.

First of all, if you run rails webpacker:install:stimulus and you've renamed your application.js pack file to application.ts, the installation will fail since it will be looking specifically for application.js. Pack files generally should only contain imports, so it should be fine to leave them as .js.

Next, you'll probably start getting shouted at by TypeScript because require.context in app/javascript/controllers/index.ts is not immediately recognised:

Property 'context' does not exist on type 'NodeRequire'.

This is due to it being a non-standard function provided by Webpack, and you need to tell TypeScript about it. Luckily there's a package that does this for us:

bin/yarn add @types/webpack-env

That's all there is to that, since TypeScript will automatically pick up the new type definitions from the package.

At this point your controllers might not be loading because the require.context is still looking for .js files. You can just change that to .ts and it will fix that.

const context = require.context("controllers", true, /_controller\.ts$/)

Lastly, there is the small issue of TypeScript not being aware of your Stimulus targets since they are only generated by Stimulus at run-time:

Property 'outputTarget' does not exist on type 'default'.

Stimulus doesn't provide an elegant solution to this at the moment, so you're forced to explicitly declare all your target properties as well as listing them in static targets.

Here's an example:

JavaScript:

export default class extends Controller {
  static targets = [ "output" ]

  // ..
}

TypeScript:

export default class extends Controller {
  static targets = [ "output" ]

  outputTarget: Element
  outputTargets: Element[]

  // ..
}

It is important to note that you only have to declare the properties you plan on using, so if you're only using the singular [thing]Target, you don't have to declare the plural [thing]Targets, and vice-versa.

Notes

If at any point during these crucial setup phases, something doesn't work, don't panic. You probably just need to restart your Rails server and/or webpack-dev-server.

If you're using Visual Studio Code and your linter is busy yelling at you for no reason, the TypeScript plugin might be using a config which is out of date. Simply restarting the editor will fix that.

If things are really bad and after restarting everything (including your computer) it still isn't working, you might need to bust your cache. Simply rm -rf tmp/cache and rm -rf public/packs and restart your servers.

I've found that these slightly annoying situations only really occur while setting things up the first time. Once everything is set up properly you can happily TypeScript without any issues.

You can find an example app with commits for every step in this repo on GitHub: danini-the-panini/typescript_stimulus.