Following the deprecation of TSLint in 2019 by Palantir, it's primary maintainer, many teams — including ours — moved to the de facto lint tool for Typescript: ESLint. This post will cover how we migrated our typescript codebase using a neat conversion tool, talk about some manual fixing that we had to do, and summarize our main learning points.
Let’s establish the background of the project before we get started. Our team is responsible for the backend of Unloc's platform, which consists of our API, data handling, periodic jobs etc. We maintain and develop an extensive Firebase backend consisting of over 80 000 lines of code written in Typescript.
As a growing team with engineers coming from different backgrounds, we see the value of aiming for a common code style for our codebase. We were happy campers using TSLint, but since its deprecation we had to look for shelter; another tool to protect us from ourselves. We eventually decided to follow Palantir's recommendation and migrate to ESLint.
Why use a linter in the first place?
First, let us define what a linter is. A linter is a tool used to analyse code, to flag programming errors, bugs, stylistic errors, and suspicious constructs. The usage brings many advantages to any software project. Instead of spending time and effort commenting on style issues in a pull request, you can focus on the important stuff: is this code doing what it is supposed to in a clean and efficient manner?
Linters and auto formatters are widely adopted by industry leading companies like Google, and some programming languages like Go and Elm even have them built-in as part of the language.
We can deprecate these types of comments with formatter and linting tools.
As developers, we know that a short cycle time is important for productivity. Therefore linting, formatting, and test errors should be discovered as early as possible in the development phase.
When working with linters, the best practice is to integrate them inside your favorite IDE. Most of them will have a broad range of extensions for this, so that you get alerted as you introduce an error. Finally, as the last check: if you use a build system, make sure to add a linting step that ensures only error-free code is admitted into your main branch.
ESLint has support for multiple editors.
Migrating with the tslint-to-eslint-config tool
Luckily for us, when we finally decided to do the migration we found out there's a great open-source tool called tslint-to-eslint-config. With one single command, this tool will convert your TSLint configuration to the closest possible ESLint equivalent. The best part is that you can run it using npx — so there's no need to install any packages. ⚡️Let’s try it out!
After finalizing its execution, tslint-to-eslint-config generates a new .eslintrc.js file with the corresponding ESLint rules based on your project's current TSLint rules. The tool will migrate most of TSLint rules but you should pay attention to its output in the terminal; some rules may need manual conversion.
This may also be a great opportunity to revisit your project's ruleset. Some rules may not be relevant anymore? Both ESLint and typescript-eslint have a set of recommended rules in case you need some defaults.
Output from running the tslint-to-eslint-config command
Installing required dependencies
Additional dependencies for various linting packages may be required to complete your migration. The exact packages will vary depending on the specific ruleset of your project. Refer to the console output you got and add them to your package.json.
Connecting ESLint to the development flow
Here at Unloc we run our linting process using npm scripts, so we had to update our lint script in our package.json to run ESLint instead of TSLint:
New lint script in our package.json file running "eslint src/ test/ -ext .ts,.tsx"This lints files with the .ts and .tsx extension found inside the src/ and test/ directories
One weird bug 🐛
Since not everything can be perfect in life — we ended up getting a strange bug after the conversion:
Turns out that due to a performance issue of ESLint, all linted files need to be referenced from tsconfig.json. And it was .eslintrc.js itself the one causing the issue! We fixed this by adding a reference it to tsconfig.json’s "include" array:
We're almost there! Now we only have to remove the old tslint.json and create a Pull Request, and we are good to go!
Migrating from TSLint to ESLint was something we kept delaying into the future, fearing it would be a buggy and lengthy process. Thanks to tslint-to-eslint-config, the migration was a breeze, and we only had to do one quick bug fix for a project with over 80 000 lines of TypeScript code.
If you decide to take on the task of migrating to ESLint — which you definitely should — these are the steps you should keep in mind:
- Run tslint-to-eslint-config inside your project's directory.
- Inspect the newly created rules and tune them up to meet the requirements of your project.
- Install any additional dependencies required for your specific linting process.
- Hook ESLint up to your development tools, CI, and/or build tools.
- Remove the now unused TSLint files and push your changes!
Learning points 👩🏫
Revisiting our linting setup reminded us of the importance of linting and formatting —and their role in a healthy development process.
Remember that linters are not meant to check the formatting of your code, you should use specialized tools for both: ESLint for linting and Prettier for example for formatting.
Over time we have learned that linting errors have a tendency to creep back into the codebase. To enforce our linting rules, we should add a step for them into our CI process.
Lastly, we have a lot of customized rules that can be hard to maintain, So sticking to the recommended ruleset from ESLint would be a good call.