TypeScript's motto is that it's JavaScript that scales, and one area where that's definitely true is in the extra work you have to do in order to spin up a project. Ba-dum! The time investment isn't so bad when you're setting up something you'll be working across for months on end, but when you're looking to just spin up a repo to learn, experiment or solve a few katas it can be a bit of a fiddle to get everything up and running over and over and over again. I just want that beautiful static typing 😭
If you'd like to just skip all the setup and get stuck in, you can find this starter template on GitHub.
Install Dependencies
Get ready to install this hearty wedge of dependencies. I'm using npm
:
$ npm init -y && npm i -D jest typescript ts-jest @types/jest eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-plugin-prettier eslint-config-prettier
TypeScript's own getting started requests a global installation, but I like to avoid these whenever possible. We'll throw in a script to get access to the TypeScript compiler, tsc
, elsewhere in our config.
Add Configuration
1. package.json
Outside of the standard npm init -y
template, nip into your package.json
and add a couple of updates to your scripts
to easily get access to our test runner and the TypeScript compiler.
"scripts": {
"test": "jest",
"tsc": "tsc"
},
2. jest.config.js
We're using ts-jest
with our project so we can feast on TypeScript's type checking directly from Jest. A quick little $ npx ts-jest config:init
will produce the below:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
3. tsconfig.json
By default, the TypeScript compiler can produce a hefty configuration file - it's got really informative comments so it helps the output not get too overwhelming. You can build one by running $ npm run tsc -- --init
, and the options below are how I like to have mine setup right now:
{
"compilerOptions": {
"target": "ES2015",
"module": "commonjs",
"outDir": "build",
"rootDir": "src",
"strict": true,
"esModuleInterop": true
},
"exclude": [
"node_modules",
"test"
]
}
4. .eslintrc.js
and .pretterrc
As of the start of this year, the TypeScript team formally adopted ESLint as the linter de rigueur of the TypeScript space.
If you're interested, the typescript-eslint
team talk about the inherent challenge of parsing TypeScript and ESLint together in their repo. It's a pretty fascinating topic!
Wedging ESLint together with Prettier - my favourite code formatter - and TypeScript is probably the most involved step. The brief overview is that we configure ESLint to disregard the rules that Prettier is concerned with, then roll Prettier in with our linter so they can run together as a single step.
We also need to point our ESLint config to our tsconfig.json
file and set up a few other options, such as the sourceType
and ecmaVersion
settings.
Here's how that looks:
.eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier/@typescript-eslint',
],
parserOptions: {
project: './tsconfig.json',
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
// Special ESLint rules or overrides go here.
},
}
Over in .prettierrc
I just like to pop in my preferred options:
.prettierrc
trailingComma: "es5"
tabWidth: 2
semi: false
singleQuote: true
And now we've got the one-two combo of ESLint and Prettier helping sure our code looks swell and operates properly. We're ready to actually write some code!
Hello World!
I like to separate out code in a src
directory, with tests in test
. So a quick $ mkdir src test
will set that up, and our previous steps will have TypeScript transpile everything in our src
directory to a JavaScript build
directory when we npm run tsc
.
But to really make check everything is working, it's time for a quintessential rite of passage:
test/sayHello.test.ts
import sayHello from '../src/sayHello'
test('sayHello can greet a user', (): void => {
const user = { name: 'TypeScript' }
expect(sayHello(user)).toBe('Hello TypeScript!')
})
And then to make that pass (and to check it's all working) we can throw in some of our fancy TypeScript features:
src/sayHello.ts
interface User {
name: string
}
const sayHello = (user: User): string => `Hello ${user.name}!`
export default sayHello
$ npm test
PASS test/sayHello.test.ts
✓ sayHello can greet a user (4ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.206s
Ran all test suites.
Fantastic! We've created a fine little environment for playing with TypeScript.
And, just to make sure we haven't set up any conflicting rules in our ESLint config, we can run a quick check:
$ ./node_modules/.bin/eslint --print-config src/sayHello.ts | ./node_modules/.bin/eslint-config-prettier-check
> No rules that are unnecessary or conflict with Prettier were found.
TypeScript is a really fun language to work in, but getting into a place where you can run the config quickly makes it far more pleasurable to get started. Feel free to share your own tips and tricks of configuring your TypeScript environment - I'm still learning the language myself, so would love to know how other people like to setup their repos!
Bonus 1: Add a Git Hook
One really neat part about the JavaScript ecosystem is that husky and lint-staged make it incredibly straightforward to run our linters and test suites as we're committing directly to git, wrapping us in another comfy blanket of consistent, tested code. While it's possible to get these up and running without introducing another pair of dependencies in our project, I think the time saved is absolutely worth it.
If you run $ npm i -D lint-staged husky
these features are just a quick addition to our package.json
away. Smoosh the below options somewhere within the file:
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,js,tsx,jsx}": ["eslint --fix", "jest --coverage --findRelatedTests", "git add"]
}
}
Now, when we git commit
our files we'll automatically run our linter and any tests that directly relate to what's in our staging area.
Bonus 2: VS Code Settings
I find people who use VS Code generally have excellent taste, but I tend to flick between it and Vim depending on whether I'm settling down for a while or just nipping in to a file to make a couple of tweaks. VS Code is prepared to do a lot of our heavy lifting, but as we've already configured so much elsewhere it's worth letting VS Code know it can ease off a bit.
First, grab the ES Lint extension for VS Code. Then add the below to your settings.json
:
"eslint.autoFixOnSave": true,
"eslint.validate": [
{
"language": "javascript",
"autoFix": true
},
{
"language": "typescript",
"autoFix": true
},
{
"language": "javascriptreact",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": true
}
],
"eslint.alwaysShowStatus": true,
"editor.formatOnSave": true,
"[typescript], [javascript]": {
"editor.formatOnSave": false
}
This tells both VS Code to take a break and ESLint to stop lounging around and do some work when it comes to JavaScript and TypeScript, and now our linting config should dutifully kick in and autoformat whenever we save. Neat!
Has this post been useful for you? I'd really appreciate any comments and feedback on whether there's anything that could be made clearer or explained better. I'd also massively appreciate any corrections!
Top comments (7)
Thanks Martin, nicely put together. Do you happen to know how to deal with errors from eslint when linting files in folders beginning/ending with double underscores? like __ mocks __ (spaces added to avoid markdown formatting) ? I see errors like these and can't see a way around it:
ESLint: 6.1.0.
No files matching the pattern "./src/mocks" were found.
Please check for typing mistakes in the pattern.
Im not martin but maybe your shell treats it like special characters. Try escape those.
Ah ha - changing from
"lint": "eslint --fix ./src/*"
to
"lint": "eslint --fix './src/*/'"
sorted it. Cheers
(I use zsh)
Hey Ross! Really sorry I missed your comment but glad you got it sorted out - I think escaping it is definitely the way to go, apologies for missing that out in my post.
I will update accordingly, I massively appreciate you letting me know!
I think you also need to install
@types/node
.Excellent post! I'm just getting started with Typescript and web components so this was a great first step.
Thanks Al! I'm so sorry it took me so long to reply to your comment but thanks for taking the time to say that. I hope you're having a great time with TypeScript 👍