loading...
Cover image for Just My Type: Rolling a fresh TypeScript project with Jest, ESLint and Prettier in 2019

Just My Type: Rolling a fresh TypeScript project with Jest, ESLint and Prettier in 2019

martingaston profile image Martin Gaston ・5 min read

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!

Discussion

pic
Editor guide
Collapse
rcoundon profile image
Ross Coundon

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.

Collapse
tkdmzq profile image
TKDMzq

Im not martin but maybe your shell treats it like special characters. Try escape those.

Collapse
rcoundon profile image
Ross Coundon

Ah ha - changing from

"lint": "eslint --fix ./src/*"

to

"lint": "eslint --fix './src/*/'"

sorted it. Cheers

(I use zsh)

Thread Thread
martingaston profile image
Martin Gaston Author

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!

Collapse
hotcher2 profile image
Al Hotchkiss

Excellent post! I'm just getting started with Typescript and web components so this was a great first step.

Collapse
martingaston profile image
Martin Gaston Author

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 👍

Collapse
zhuangya profile image
zhuangya

I think you also need to install @types/node.