Setting up a typescript project in 2021
Tags: typescript, beginners
Published: 2021-04-28 07:48:50

This post will describe how to create a project in typescript from scratch. The final project include some basic code, tests, commit hooks to enforce code formating, automatic tests on push and more ! I hope you'll enjoy it :)

# TL; DR
find the code [here](https://github.com/xiorcal/ts-demo)

# Basics

## Before you start

* you should have node/npm (latest lts should do the trick)
* `npm init` was run. So you have a `package.json`

## Setting up typescript
### Typescript itself
Pretty straight-forward : `npm install typescript --save-dev`

Now check if that worked ; create a file `src/demo.ts` and fill it with 
```typescript
export function greeter(person: string): string {
 return "Hello, " + person + "!";
}

console.log(greeter("World"));
```

And run... Wait... what should I run ? Meh that JS, a `node src/demo.ts` will do it !
...
Oh no... Why does it complains about some unexpected ':' ?
:-/

### Configuring typescript itself
I like to use a recommended default setting for tsc (typescript compiler) : `npm install --save-dev @tsconfig/recommended` then create a file named `tsconfig.json` and fill it with :
```json 
{
 "extends": "@tsconfig/recommended/tsconfig.json",
 "compilerOptions": {
 "outDir": "./dist",
 },
 "include": ["src/**/*"],
 "exclude": ["node_modules", "dist"]
}
```
in your package.json add a script for build : `"build": "tsc",`
you should be able to run `npm run build` then `node dist/demo.js`
Nice !

### Speeding things up
well I'm the kind of person that does not like to have to type 2 commands everytime my code changes. So lets spend some time automating it a bit

first add tsc-watch to the project: `npm install tsc-watch --save-dev`
then add a watch script to the package:
`"watch": "tsc-watch --onSuccess \"node ./dist/demo.js\"",`
now you can run `npm watch` and every change you made will imediately be complied and run ! 

## Enforce the "2 spaces or GTFO" law !
formatting will be done with eslint and prettier, enforcing formatting will be done with husky and its pre-commit hooks 

### configuring style tools
```bash
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-import prettier
```

in `.eslintrc.js` file :
```js
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.json',
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint/eslint-plugin'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
  ],
  root: true,
  env: {
    node: true,
  }
};

```

in `.eslintignore` : 
```text
node_modules
dist
```
in `.prettierrc.js` : 
```js
module.exports = {
  trailingComma: "all",
  tabWidth: 2,
  singleQuote: true,
};
```

add a `lint` script to package.json :
`    "lint": "eslint src --ext .js,.jsx,.ts,.tsx"`
### Automate it
Once again, I'm lazy, so I need those tools to run automatically for me. That's where husky commes to play!
```bash
npm install --save-dev husky pretty-quick
```
add a prepare script : `"prepare": "husky install"`
run it : `npm run prepare`
you should see a .husky directory now.
create a file nammed `pre-commit` in this directory and fill it with :
```bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install pretty-quick --staged
npm run lint
```
now your formatting will be fixed on commit \o/
## enforce the "meaningful commit or GTFO" law !
this will be done with commit-lint 
`npm install --save-dev @commitlint/config-conventional @commitlint/cli`
then create a `commit-msg` file in .husky directory with :
```bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install commitlint --edit "$1"

```
and a `commitlint.config.js` with `module.exports = {extends: ['@commitlint/config-conventional']}` in it.
Now your commit messages are force to obey the law !
## Prevent your friends to break the law
husky take care of that for you :)
## These are not the tests you are looking for
### Set-up
I use jest so we need to install it first: `npm install -D jest ts-jest @types/jest`
then config, `jest.config.js` : 
```js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
```
add a test script : `"test": "jest"`
### Your very first test
in `demo.spec.ts` we are going to ensure that our greeter is really polite:

```js
import {greeter} from './demo'

describe('greeter function', () => {
    test('a greet should start with Hello', () => {
      expect(greeter("Bob").startsWith('Hello')).toBe(true)
    });
});
```
try it with `npm run test`
### Guess what...? AUTOMATION
This time no external deps nor manual tweaking ! jest took care of that for us. simply run `jest --watch` ! Your tests should run on source edit. Alternatively you can use an extension in your ide to do it.

## Setting up your dev env
I'm using vscode so this is the one that will be covered by this section.
### Extensions 
* [Jest (orta.vscode-jest)](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest)
* [ESLint (dbaeumer.vscode-eslint)](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
* [Prettier - Code formatter (esbenp.prettier-vscode)](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)

I suggest enabling code coverage overlay for jest (io.orta.jest.coverage.toggle).
Using prettier as formatter directly in editor will instantanely format your code correctly rather than on commit.

## Time for release ?
Ok so your greeter is now ready for its first release ! Lets take advantage of our strict commit message policy to have a nice changelog ! standard-version will help us do so 
`npm i --save-dev standard-version`
and a release script : `"release": "standard-version"`
finally run `npm run release -- --first-release` to do your... first release !
# automate it, noob !
using github actions because those are pretty neat. create a `.github/workflows/` directory to setup those actions.
## auto test on commit
This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
[For more information](https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions)
`node.js.yml` : 
```yaml

name: Node.js CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x, 14.x]
    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test
```

## auto publish on release
`npmPublish.yml`: 
```yaml
name: Publish package on npm

on:
  release:
    types: [created]

jobs:
  publish-npm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
        with:
          node-version: 12
          registry-url: https://registry.npmjs.org/
      - name: Cache node modules
        uses: actions/cache@v1
        with:
          path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-
      - run: npm ci
      - run: |
          if [ ${{ github.event.release.action }} = "prereleased" ]; then
            npm publish --tag beta
          else
            npm publish
          fi
        env:
          NODE_AUTH_TOKEN: ${{secrets.npm_token}}
```

`NPM_TOKEN` is a **repository secret** holding an access token with publish rights from npm. get your own at https://www.npmjs.com/settings/{YOUR_USER}/tokens
# fun stuff
haha you whish