My setup for a typescript react project
A memo about my setup for a TypeScript React project: I copy and paste these commands whenever I need to start a new project and ensure that it has all linters, formatters, and pre-commit hooks configured.
Initialize proper version
I use nvm, and I automatically initialize the node environment with nvm if .nvmrc
is found.
# File ~/.zshrc
test -f ./.nvmrc && nvm use
Start a new project
I tried Vite for a few recent projects and liked it. That’s how I create a new empty project.
yarn create vite --template react-ts
Alternatively, here’s you can create a new project with create-react-app:
yarn create react-app my-app --template typescript
Define a node version
Use Node 18.x (aka Hydrogen) in a project.
echo lts/hydrogen > .nvmrc
Use Node 16.x (aka Gallium) in a project.
echo lts/gallium > .nvmrc
See Node codenames for more options.
Pinning versions
You can pin Node and NPM to a specific version.
Add to package.json
{
"engines": {
"node": "^18",
"npm": "^8"
}
}
Node is permissive, though, and by default it only raises a warning if engines don’t match. You can make node fail every time it tries to run the code in the environment that doesn’t match the pinned versions.
Create an .npmrc
file.
echo "engine-strict=true" > .npmrc
See also.
Linters
What do we install:
- ESLint for linting.
- Prettier for consistent autoformatting.
- TypeScript ESLint projects (parser and eslint-plugin) to help eslint understand TypeScript syntax have TypeScript-specific rules.
- ESLint config for the Google style guide.
- ESLint config for prettier. A config that turns off all rules that are unnecessary or might conflict with Prettier.
- React-specific linting rules for ESLint.
Also, a bunch of plugins for to help ESLint validate your imports.
- eslint-plugin-import
- eslint-import-resolver-typescript
- eslint-plugin-no-relative-import-paths
- eslint-plugin-unused-imports
yarn add -D \
eslint prettier \
@typescript-eslint/parser @typescript-eslint/eslint-plugin \
eslint-config-google eslint-config-prettier eslint-plugin-react \
eslint-plugin-import eslint-import-resolver-typescript \
eslint-plugin-no-relative-import-paths eslint-plugin-unused-imports
For the server-side minimal node-based setup without typescript and React.
yarn add -D eslint prettier eslint-config-google eslint-config-prettier
Add configuration file .eslintrc.cjs
. Here we extend the recommended ESLint configuration with the rules from the extensions that we’ve installed above.
In the rules
section, I overwrite some rules to my preferences. Usually, this sections grows with the project.
module.exports = {
extends: [
'google',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'prettier',
],
plugins: [
'@typescript-eslint',
'unused-imports',
'no-relative-import-paths',
'import',
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
env: {
es6: true,
node: true,
browser: true,
},
rules: {
'no-console': 'error',
'require-jsdoc': 'off',
'valid-jsdoc': 'off',
'react/boolean-prop-naming': 'error',
'react/sort-prop-types': 'error',
'import/newline-after-import': ['error'],
'unused-imports/no-unused-imports': 'error',
// We need to disable "no-unused-vars" when working with TypeScript,
// but plugin:@typescript-eslint/recommended is already doing that for us.
// See https://typescript-eslint.io/rules/no-unused-vars/
'@typescript-eslint/no-unused-vars': 'error',
'import/order': [
'error',
{
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
pathGroups: [
{
pattern: 'react',
group: 'builtin',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react'],
},
],
'no-relative-import-paths/no-relative-import-paths': [
'error',
{
allowSameFolder: true,
rootDir: 'src',
},
],
},
settings: {
react: {
version: 'detect',
},
'import/resolver': ['typescript'],
// See https://stackoverflow.com/a/61839578
'spaced-comment': ['error', 'always', { markers: ['/'] }],
},
};
The light version of the same file for a server-side node-based setup without typescript and React and import linters.
module.exports = {
extends: ['eslint:recommended', 'google', 'prettier'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
env: {
es6: true,
node: true,
},
rules: {
'require-jsdoc': 'off',
'valid-jsdoc': 'off',
'no-unused-vars': 'error',
},
};
I use single quotes in JavaScript strings, so I add a single-quote configuration for prettier: .prettierrc.json
:
echo '{ "singleQuote": true }' > .prettierrc.json
Install lint-staged and husky with mrm.
npx mrm@2 lint-staged
It installs dependencies and creates or updates necessary config files.
Double-check lint-staged configuration. You should have the following section in your package.json.
"lint-staged": {
"*.{js,jsx}": "eslint --cache --fix",
"*.{js,jsx,css,md}": "prettier --write",
"*.{ts,tsx}": "sh -c 'tsc --noEmit'"
}
Configuring absolute import paths
Configuring “no-relative-import-paths” Linter can be a bit tricky. I use the configuration that considers the “src” directory as the source root, and all imports from the “src” directory are considered absolute.
Update automatically generated tsconfig.json and include baseUrl.
{
"compilerOptions": {
"baseUrl": "src"
}
}
Teaching Vite understand absolute imports requires a separate plugin vite-tsconfig-paths. Install it with yarn.
yarn add -D vite-tsconfig-paths
Then update vite.config.js to use the plugin. I added tsconfigPaths to a currently existing react() plugin.
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths()],
});
Verify current project
npx eslint --cache --fix src
npx prettier --write src
VSCode
VSCode should catch the Linter configuration for the project. If it doesn’t, simply restarting VSCode often helps.
Automate this guideline
Note to self: you can turn this memo into an mrm preset.