This is a quick bit of service journalism about one thing that seemed less than obvious about converting the code in Modern Front-End Development For Rails to Rails 7.0, namely how to integrate TypeScript with the new tools.
Specifically, the Rails 7.0 version of the code ditches Webpacker in favor of the new jsbundling-rails and cssbundling-rails gems and uses esbuild instead of webpack. See here for an only-slightly out-of-date description of the new tools.
Most of the logistical changes that fall from the new tools involve things like changing some file names and tweaking some configuration, updating how we create manifests and so on.
It was not, however, immediately clear to me how to get TypeScript fully working in this setup.
The way that jsbundling-rails works is that it uses the same
package.json file as before, but instead of using Webpacker and creating an entire webpack configuration in-house, it just passes the whole thing to your bundling tool of choice, and just asks that the bundling tool places the resulting bundled file in the output directory (by default it’s
app/builds) so that the asset handler (Sprockets, by default) can just pass the bundled file down to the browser.
In practice, this is significantly less complicated, especially if you use esbuild instead of webpack, because esbuild has less configuration in general. It’s also significantly faster.
The installation for jsbundling-rails adds a script to the
package.json file that calls esbuild, and also adds a
Procfile with a command that triggers that script if a relevant file changes.
scripts part of a potential
package.json file, with the build commands for both jsbundling and cssbundling — these have been tweaked from the defaults just slightly.
And here’s the associated
web: bin/rails server -p 3000 js: yarn build:js --watch css: yarn build:css --watch
This seems like a problem, since if you are using TypeScript, you presumably are doing so because you want the code to be type safe.
The esbuild docs say “ you will still need to run
tsc -noEmit in parallel with esbuild to check types”, but doesn’t really say how to do that, and random Google searches were of mixed benefit, because a lot of the suggestions on how to do this brought in more complication than seemed necessary.
Happily, I found the tsc-watch package, whose sole purpose in life is to run the TypeScript compiler in watch mode and allow you to specify what to do on success or on failure.
I installed it:
$ yarn add --dev tsc-watch
And then I updated my scripts as follows:
build.js command stays the same, but I’ve added a new
dev command that uses
tsc-watch with four arguments:
noClear, which prevents
tsc-watchfrom clearing the console window. I’d like to do that myself, thank you very much.
-p tsconfig.json, points to the TypeScript config file that should govern the compilation I want to do:
--onSuccess \"yarn build:js\", controls what you want to have happen if the TypeScript compilation succeeds. In our case, we want the regular esbuild
build:jsto happen, since we now know the code is type safe.
--onFailure \"yarn failure:js\", controls what you want to have happen in the TypeScript compilation fails. I guess I had options here, but what I chose to do is
"rm ./app/assets/builds/application.js && rm ./app/assets/builds/application.js.map", meaning removing the existing esbuild files from the build directory so that the development browser page will error rather than return the most recent successful compilations. I thought that allowing the most recent success to stick around would be confusing.
Then to get this to work, we need the
dev command to be in the
Procfile instead of
web: bin/rails server -p 3000 js: yarn dev css: yarn build:css --watch
And that works. The
yarn dev sets itself up as a watcher automatically, we don’t need to pass
--watch to it.
When a relevant file changes,
tsc-watch is triggered and runs the TypeScript compiler. If the compile is successful, and only if it is successful, esbuild is called upon to bundle the code into a browser friendly form. If the compile fails, then we delete the last successful compile. The error message goes to the console, and we presumably fix the error.
I’m not sure that this is the 100% best way to make this work, but it seems to be working fine as I mess with the code for Modern Front-End to bring it to Rails 7 features.