React with Typescript

This is a seed project on React with Typescript that I use for the majority of my projects.

See code on github

As a veteran software engineer, I generally don't like using tools such as create-react-app. I prefer to create my project from scratch and want control over each of the tools and configuration files. As the web development industry changes, with cool things being created each day, I keep my seed projects up-to-date and stable so they can easily be cloned for rapid development.

At a glance

Markup (HTML)JSX (React 18)
ScriptTypescript/React
Styling (CSS)Tailwind
BuildWebpack 5/Terser
Unit TestJest/Testing library
Code quality/Linting:Eslint

Step 1: Create project and install basic packages

We will start by creating a directory and initialize npm.

npm init
This creates package.json. Let's add some packages.
// Basics yarn add @types/react @types/react-dom yarn add --dev typescript ts-loader ts-node // Add test yarn add --dev jest @types/jest @testing-library/react @testing-library/jest-dom // Add dev tools/build yarn add --dev webpack webpack-cli webpack-dev-server terser-webpack-plugin yarn add --dev copy-webpack-plugin react-dom style-loader css-loader postcss-loader

@types/* are type declaration pacakges. testing-library is one of the popular testing packages out there for React. ts-node is the typescript exection engine for Node.


Time to create typescript configuration file.

{ "compilerOptions": { "jsx": "react-jsx" /* This allows our JSX to be transpiled correctly */, "target": "es6" /* Set the JS language version for emitted JavaScript */, "esModuleInterop": true /* Emit additional JS to ease support for importing CommonJS modules.*/, "allowSyntheticDefaultImports": true /* Need for Webpack config file */, "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, "strict": true /* Enable all strict type-checking options. */, "skipLibCheck": true /* Skip type checking all .d.ts files. */, "outDir": "dist", "moduleResolution": "node", "types": ["@testing-library/jest-dom"] }, "include": ["**/*.ts", "**/*.tsx"] }

Step 2: Setup webpack and react

We'll start by creating webpack configuration file - webpack.config.ts

style-loader extracts css and injects them to head element in a html document. css-loader resolves the dependencies such as import and url. postcss-loader makes css cool with linting, vendor prefix etc.

import path from "path"; import { Configuration } from "webpack"; import CopyWebpackPlugin from "copy-webpack-plugin"; import TerserPlugin from 'terser-webpack-plugin'; const config: Configuration = { mode: (process.env.NODE_ENV as "production" | "development" | undefined) ?? "development", entry: "./src/index.tsx", module: { rules: [ { test: /.tsx?$/, use: [{ loader: 'ts-loader' }], exclude: /node_modules/, }, { test: /.css$/, use: ["style-loader", "css-loader","postcss-loader"], }, ], }, optimization: { minimizer: [new TerserPlugin({ extractComments: false, })], }, resolve: { extensions: [".tsx", ".ts", ".js"], }, output: { filename: "bundle.js", path: path.resolve(__dirname, "dist"), }, plugins: [ new CopyWebpackPlugin({ patterns: [{ from: "public" }], }) ], }; export default config; };

Create entry file - index.jsx

Since React 18, createRoot is used instead of render API.

import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "./index.css"; const rootElement = document.getElementById("root") as HTMLElement; const root = createRoot(rootElement); type AppProps = { title?: string; }; const App = ({ title }: AppProps) => <div className="elative bg-white px-6 pt-10 pb-8"> <h1 className="text-32l font-bold">{title}</h1> </div>; root.render( <StrictMode> <App title="Hello!!!"/> </StrictMode> );

Setup tailwind config file - tailwind.config.ts

Tailwind is kinda similar to bootstrap with its utility classes. But it doesn't provide classes for components like buttons and error messages.

module.exports = { content: [ "./public/**/*.html", "./src/**/*.tsx", "./src/**/*.css" ], theme: { extend: {}, }, plugins: [], }

Enable tailwind - tailwind.config.ts

@tailwind base; @tailwind components; @tailwind utilities;

Add HTML file - index.html

bundle.js is our bundled file (output).

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="root"></div> <script src="bundle.js"></script> </body> <html></html> </html>

Step 3: Add test

Add Test - jest.config.ts

Our jsx needs babel processing.

import type { Config } from 'jest'; const config: Config = { clearMocks: true, collectCoverage: true, coverageDirectory: "coverage", testEnvironment: "jsdom", transform: { '^.+\.tsx?$': [ 'ts-jest', { babelConfig: true, }, ], }, setupFilesAfterEnv: [ "<rootDir>/setupJest.ts" ], }; export default config;

Create file - setupTest.ts

jest-dom extends dom with methods such as toContainHTML, toHaveClass etc.

import "@testing-library/jest-dom";

Step 4: Setup ESLint and finishing touches.

Create eslint configuration - .eslintrc.json

npm init @eslint/config

We'll use the popular airbnb styleguide - eslint-config-airbnb-typescript for our application. Package:

Update scripts in package.json

"dev": "webpack serve", "build": "webpack", "server": "nodemon", "lint": "eslint src/**/*.tsx", "test": "jest", "coverage": "jest --coverage"

Step 5: Running the app.

npm run dev should start our dev server, npm run jest should start our unit test etc.

Posted on Jan 1, 2023