My battle with noUnusedLocals and create-react-app
You know the drill. You’re debugging a problem and need to comment out some code. If you have typescript installed and noUnusedLocals set to ‘true’, you start commenting out one line, which may lead to commenting out all the declarations of the variables in the line, which leads to commenting out the import statements, and possibly more lines that were consuming the variable(s) you just commented out, and on and on and on. An hour later, you’ve finally gotten your app to compile again. Suddenly, a setting that was supposed to be helpful has eaten up a good portion of your time. Multiply this by days/engineers in your company and it’s a decent time sink.
What does noUnusedLocals do?
It alerts you to variables in your codebase that are unused. This is a _good_ thing, and the feedback should be welcome, at some point in your development pipeline. Arguably the best time would be at commit.
The quick and dirty fix is to find your tsconfig.json file and set the noUnusedLocals flag to ‘false’. In practice, you don’t bother to change it right away because you think it’ll be easy enough to just comment out. Five changes in and you’re in too deep to go back and just switch the flag (“I’ve invested all this time, I’m not turning back now!) I can’t tell you how many times a day this was me (yes, sometimes I just left the flag turned off 😱).
I brought it up to my team and the natural response was to have different versions of the config; one for development and one for prod (or better yet, whatever is used to run your builds upon PR).
Yes! Perfect!
Except…we’re using Facebook’s CRA (create-react-app) and, although you can specify different tsconfig files to the Typescript compiler, CRA does not pass any of the Typescript CLI flags along to the compiler. Bummer. That means that, not only can you not have two different versions of the config, but you also cannot pass the noUnusedLocals flag along to the compiler (thereby allowing us to specify it in one of our package.json CRA scripts). CRA looks for the tsconfig.json file (named just so) in the current directory and that’s the one it uses.
Bummer.
Facebook’s suggestion to work around this is to either (1) eject from CRA or (2) fork the repo. Neither of which were particularly appealing to our team.
My next step was to write a script that copied a dev version of the tsconfig over the one that was being consumed by CRA. This was workable for now, but felt very hacky and could easily get out of sync with changes to the REAL tsconfig.json.
I brought this up to the team and different ideas were batted around:
- Similar to the copy script: create symlinks that get redirected before build time. Argument against: Same as copying the file; and that possibly the commands wouldn’t be compatible across shells
- Have a job that runs on our CI pipeline that modifies the setting to ‘true’. Argument against: you wouldn’t get the feedback until the job runs on CI, unless you remembered to run it locally (and wanted to bother)
- Have a script that runs as a pre-commit hook that modifies the setting to ‘true’. Argument against: pre-commit hooks can be disabled so can’t be relied upon (so this wouldn’t be an adequate solution by itself but could be used in conjunction with a job on CI)
- Disable this setting altogether and use a TypeScript ESLint rule instead to enforce the check. Argument against: it’s currently experimental and is very slow due to using the types support in typescript-eslint
- Make noUnusedLocals a warning — this would have been huge win, but: Argument against: it’s not possible :)
Ultimately what I chose to do is write a script (feel free to suggest improvements) that toggled it for me, and created an alias ‘tog’ for that script.
To create your own alias, you can add the following to your .bash_profile if you’re using bash, .zshrc if you’re using zsh, or your corresponding config file if you’re using a different shell (syntax may vary in other shells):
alias tog='tools/toggleAllowUnusedVars.sh'
Note that my script was under the ‘tools’ folder — you’ll need to update your alias to point to the script file’s location.
It still requires that I make sure not to check in the change to our repo (a step which could be eliminated by the pre-commit hook) but it’s no different than manually changing the setting (except that perhaps I’m changing it more often now).
This didn’t seem to be a huge pain point for everyone but it sure was for me. Pick your battles!