VS Code + React + Typescript code quality setup 2020
So here’s the thing.
I started several projects combining React and Typescript lately and found myself repeating same setup over and over again.
Usually on project’s first day i would do only this chore and esentially waste one day. Dont get me wrong create-react-app offers nice start but gives you almost nothing in terms of code quality. As my teams usually consist of non trivial percentage of junior developers i want to make sure common mistakes are caught early, code is formatted well and commit messages make sense. If you are experiencing same problems keep reading
We will be using yarn as our package manager throughout this post.
If you dont have it installed yet do it via npm install -g yarn in your terminal
1. Lets start with creating our project
npx create-react-app dev-experience-boilerplate --template typescript
We are using npx which is npm package runner and executes create-react-app package without installing it globally Above code is equivalent to
npm install -g create-react-app create-react-app dev-experience-boilerplate --template typescript
3. Linting (Eslint)
Linting is by definition tool that analyzes source code to flag programming errors, bugs, stylistic errors, and suspicious constructs. We will be using eslint — linting tool for javascript. First lets integrate eslint in our IDE by installing VS Code extension from marketplace In Next step will be installling all needed dependencies
yarn add eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-header eslint-plugin-import eslint-config-prettier --dev
That is lot of dependencies.
What we did here? Well we installed bunch of plugins
Lets look at them one by one
- eslint — Tool itself
- eslint-config-airbnb — Good guys in airbnb made their eslint configuration public. Everyone can use extend and override defined rules
- eslint-plugin-react — React specific linting rules for ESLint
- eslint-plugin-jsx-a11y — Set of accessibility rules on JSX elements. We want to be proper developers and deliver best possible experience also for impaired visitors of our application. One of such rules can be that tags should have alt attribute so screen reader knows what is on image. If you forget to add alt wslint will yell at you
- eslint-plugin-react-hooks — Since react version 16.8.0 we are writing majority of our components in hooks. We want them write right.
- @typescript-eslint/parser — Sice our project uses typescript we need to tell eslint that our code is not vanilla javascript and needs to be parsed
- @typescript-eslint/eslint-plugin — Set of rules for typescript
- eslint-config-prettier — Eslint plugin that removes all rules that can possibly conflict with prettier which we will install in next step
- eslint-plugin-header — EsLint plugin to ensure that files begin with given comment. I personally like when every file starts with header with basic info like Author and Date. When you work in larger team its a nice way to see ownership of file and when something is not clear or right you know who you should bother with questions
- eslint-plugin-import — Linting of ES6 import/export syntax
Now when everything is installed lets define our rules
This is very opinionated but heres what works for me.
In root of your project create file named .eslintrc and paste following code snippet inside
< "parser": "@typescript-eslint/parser", "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "react-app", "prettier", "prettier/@typescript-eslint" ], "plugins": ["@typescript-eslint", "react-hooks", "header"], "settings": < "react": < "version": "detect" >>, "rules": < "header/header": [2, "block", [< "pattern": " \\* Author : .*" >]], "@typescript-eslint/consistent-type-definitions": ["warn", "type"], "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-member-accessibility": "off", "@typescript-eslint/no-angle-bracket-type-assertion": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-unused-vars": [ "error", < "argsIgnorePattern": "^_", "ignoreRestSiblings": true >], "@typescript-eslint/no-use-before-define": [ "warn", < "functions": false, "classes": false, "variables": true >], "import/no-extraneous-dependencies": "warn", "import/order": [ "warn", < "newlines-between": "always" >], "no-case-declarations": "warn", "no-console": "warn", "no-debugger": "warn", "no-else-return": "warn", "no-param-reassign": "warn", "no-undef": "off", "no-unused-vars": "off", "no-var": "warn", "object-shorthand": "warn", "padding-line-between-statements": [ "warn", < "blankLine": "always", "prev": "*", "next": "class" >, < "blankLine": "always", "prev": "*", "next": "for" >, < "blankLine": "always", "prev": "*", "next": "function" >, < "blankLine": "always", "prev": "*", "next": "if" >, < "blankLine": "always", "prev": "*", "next": "return" >, < "blankLine": "always", "prev": "*", "next": "switch" >, < "blankLine": "always", "prev": "*", "next": "try" >, < "blankLine": "always", "prev": "*", "next": "while" >, < "blankLine": "always", "prev": "block-like", "next": ["let", "const"] >], "prefer-const": "warn", "react/jsx-boolean-value": "warn", "react/jsx-curly-brace-presence": "warn", "react/jsx-key": "warn", "react/jsx-sort-props": [ "warn", < "callbacksLast": true, "reservedFirst": true, "shorthandLast": true >], "react/no-array-index-key": "warn", "react/prefer-stateless-function": "warn", "react/self-closing-comp": "warn", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "off", "yoda": "warn" > >
I dont want to go into much details here but i encourage you to sit with your team go through all of them, and discuss what works and what doesn’t for you. There is no single right answer to how .eslintrc should look like
Last thing we need to do is to set up linting command in package.json
Into section scripts add following snippet
"lint": "eslint \"src/**/*.\"", "lint:fix": "eslint --fix \"src/**/*.\""
Now when you run yarn lint in project root
You should see output similar to this
Ok so we have 14 warnings. Lets try to fix them by running yarn lint:fix in project root
Awesome down to 6 with no effort. Eslint sorted props added blank lines for better readability and more for us for free.
There are some console.log statements in serviceWorker.ts
For some reason i want to leave service worker as is and not to lint this partiular file.
Eslint comes with solution for that.
Lets create .eslintignore file in project root and add following content inside
Now after running yarn lint there should be no errors. Life is beautiful again 🦄
2. Prettier
Prettier is code formatter that supports number of languages and can be easily integrated into VS Code.
Similar to eslint we first need to install VS code extension
And create configuration file
Lets create file .prettierrc in project root and paste following content inside
Thats everything for prettier now your code will look nice and consistent across all files
If for some reason you dont want to prettify some of your files you can create .prettierignore file in your project root
3. Precommit hook
Now. You can run eslint and prettify every time you are about to commit your changes but lets be honest . We all forget.
You cannot forgot if husky barks at you though.
Husky is handy tool that prevents you from accidentaly pushing changes that are well. not ideal into repository.
First install dependencies
yarn add husky lint-staged --dev
Add following into your package.json ‘s script section
And following to the end of package.json
"husky": < "hooks": < "pre-commit": "lint-staged" >>, "lint-staged": < "src/**/*.": [ "prettier --config .prettierrc --write", "eslint --fix \"src/**/*.\"", "eslint \"src/**/*.\"", "git add" ] >
To see if our setup works lets create unused variable in App.tsx . And try to commit our changes via git add . and git commit -m «This shouldnt work»
And indeed husky barked and we have to fix our code in order to be able to push it into repository.
4. Commit message hook
Last thing i want to cover is consistent naming of commit messages. This is common mistake in lots of repositories. You can of course create your own git hook but i recently fell in love with git-cz which is tool for interactively commiting changes via your favourite terminal.
yarn add git-cz @commitlint/cli @commitlint/config-conventional --dev
Add following into your package.json ‘s script section
Add following to the end of package.json
And last thing is to tell husky to run our new commit-msg hook
We do this by changing husky section in package.json
We can test our new git hook by running yarn commit
You can see this awesome cli tool that lets you choose type of change you are about to commit and more. This can be all configured
In default config you will fill in folllwing:
- Type of change (test, feature, fix, chore, docs, refactor, style, ci/cd and performance)
- Commit message
- Longer description (optional)
- List of breaking changes (optional)
- Referenced issue (i.e JIRA task number)
And commit messages are now consistent across whole team
Plus you get neat commit message icons like this
You can find whole working solution on github
If you liked this article you can follow me on twitter
vscode-react-typescript
Installation
In order to install an extension you need to launch the Command Pallete (Ctrl + Shift + P or Cmd + Shift + P) and type Extensions. There you have either the option to show the already installed snippets or install new ones. Launch VS Code Quick Open (Ctrl + P or Cmd + P), paste the following command, and press enter. ext install vscode-react-typescript Alternatively you can open the extensions panel and search for ‘Typescript React code snippets’.
Supported languages (file extensions)
Snippets
Below is a list of all available snippets and the triggers of each one. The ⇥ means the TAB key.
Trigger | Content |
---|---|
tsrcc→ | class component skeleton |
tsrcfull→ | class component skeleton with Props, State, and constructor |
tsrcjc→ | class component skeleton without import and default export lines |
tsrpcc→ | class purecomponent skeleton |
tsrpcjc→ | class purecomponent without import and default export lines |
tsrpfc | pure function component skeleton |
tsdrpfc | stateless functional component with default export |
tsrsfc | stateless functional component |
conc→ | class default constructor with props and context |
cwm→ | componentWillMount method |
ren→ | render method |
cdm→ | componentDidMount method |
cwrp→ | componentWillReceiveProps method |
scu→ | shouldComponentUpdate method |
cwu→ | componentWillUpdate method |
cdu→ | componentDidUpdate method |
cwum→ | componentWillUnmount method |
gdsfp→ | getDerivedStateFromProps method |
gsbu | getSnapshotBeforeUpdate method |
sst→ | this.setState with object as parameter |
bnd→ | binds the this of method inside the constructor |
met→ | simple method |
tscntr→ | react redux container skeleton |
imt | create a import |