React 17 with Typescript starter kit without create-react-app (incl. Webpack, ESLint & Prettier) ⚛

Source: https://imgur.com/gallery/J1Oioug

*Recently updated*

There are many advantages of using create-react-app. With just few clicks you are ready to go, but some doesn’t want all that magic and tons of boilerplate code (me included). With CRA approach you’ll end up with massive “blackbox” which you will have to eject in most of the times.

If you are looking for clean and simple React + Typescript + Webpack (including dev server) + ESLint and Prettier starter kit, you’ve just found the right one. Follow the steps below or jump straight to the repository at:

https://github.com/GR34SE/react-typescript-starter

Use this repo as a template: https://github.com/GR34SE/react-typescript-starter/generate

If you find that helpful you can star the repository.

yarn init --yesornpm init --yes
yarn add typescript -Dornpm i typescript --save-dev
{
"compilerOptions": {
"sourceMap": true,
"noImplicitAny": false,
"module": "commonjs",
"target": "es5",
"lib": [
"esnext",
"dom",
"dom.iterable"
],
"removeComments": true,
"allowSyntheticDefaultImports": true,
"jsx": "react",
"allowJs": true,
"baseUrl": "./",
"esModuleInterop": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"downlevelIteration": true,
"paths": {
"components/*": [
"src/components/*"
]
}
},
"include": [
"./src",
"./webpack.config.ts"
]
}
yarn add react react-dom
yarn add @types/react @types/react-dom -D
ornpm i react react-dom --save
npm i @types/react @types/react-dom --save-dev

./public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>React TypeScript App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

./src/index.tsx

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

./src/App.tsx

import React from "react";
import HelloWorld from "components/HelloWorld";

const App = () => <HelloWorld />;

export default App;

As you can see, i’ve used path bind to components directory — this also needs to be sorted out in webpack’s config in order to work.

./src/components/HelloWorld/index.tsx

import React from "react";

const HelloWorld = () => (
<>
<h1>Hello World</h1>

<hr />

<h3>Environmental variables:</h3>
<p>
process.env.PRODUCTION: <b>{process.env.PRODUCTION.toString()}</b>
</p>
<p>
process.env.NAME: <b>{process.env.NAME}</b>
</p>
<p>
process.env.VERSION: <b>{process.env.VERSION}</b>
</p>
</>
);

export default HelloWorld;

Adding Webpack

yarn add webpack webpack-cli webpack-dev-server ts-node @types/node @types/webpack @types/webpack-dev-server tsconfig-paths-webpack-plugin -Dornpm i webpack webpack-cli webpack-dev-server ts-node @types/node @types/webpack @types/webpack-dev-server tsconfig-paths-webpack-plugin --save-dev

We will run type checking through webpack (thanks to the great fork-ts-checker-webpack-plugin)

yarn add ts-loader fork-ts-checker-webpack-plugin html-webpack-plugin -Dornpm i ts-loader fork-ts-checker-webpack-plugin html-webpack-plugin --save-dev
import path from "path";
import webpack, {Configuration} from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import {TsconfigPathsPlugin} from "tsconfig-paths-webpack-plugin";

const webpackConfig = (env): Configuration => ({
entry: "./src/index.tsx",
...(env.production || !env.development ? {} : {devtool: "eval-source-map"}),
resolve: {
extensions: [".ts", ".tsx", ".js"],
//TODO waiting on https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/61
//@ts-ignore
plugins: [new TsconfigPathsPlugin()]
},
output: {
path: path.join(__dirname, "/dist"),
filename: "build.js"
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
transpileOnly: true
},
exclude: /dist/
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html"
}),
new webpack.DefinePlugin({
"process.env.PRODUCTION": env.production || !env.development,
"process.env.NAME": JSON.stringify(require("./package.json").name),
"process.env.VERSION": JSON.stringify(require("./package.json").version)
}),
new ForkTsCheckerWebpackPlugin({
eslint: {
files: "./src/**/*.{ts,tsx,js,jsx}" // required - same as command `eslint ./src/**/*.{ts,tsx,js,jsx} --ext .ts,.tsx,.js,.jsx`
}
})
]
});

export default webpackConfig;

HtmlWebpackPlugin is used in order to simply point out to our index.html template. With webpack.DefinePlugin we can set and access environment variables inside our app (i.e check whether we are in dev or production mode, print out the version tag)

"scripts": {
"start:dev": "webpack-cli serve --mode=development --env development --open --hot",
"build": "webpack --mode=production --env production --progress",
"lint": "eslint './src/**/*.{ts,tsx}'",
"lint:fix": "eslint './src/**/*.{ts,tsx}' --fix"
},

Adding Prettier

yarn add prettier -Dornpm i prettier --save-dev
{
"printWidth": 100,
"trailingComma": "none",
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"bracketSpacing": false,
"jsxBracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto",
"jsxSingleQuote": false,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"useTabs": false,
"htmlWhitespaceSensitivity": "css"
}

Also, I recommend setting up Prettier to run on each save. See: https://prettier.io/docs/en/editors.html

Adding ESLint

yarn add eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-prettier eslint-config-prettier eslint-plugin-import -Dornpm i eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-prettier eslint-config-prettier eslint-plugin-import --save-dev
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"react",
"react-hooks",
"eslint-plugin-import",
"prettier"
],
"env": {
"browser": true
},
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": [
"tsconfig.json"
],
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unused-vars": "off",
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".jsx",
".tsx"
]
}
],
"react/prop-types": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
},
"settings": {
"react": {
"version": "detect"
}
}
}

Create .eslintignore file and add webpack config file to it

webpack.config.ts

And voilà — we are ready to go. Start your app with start:dev script.

Fullstack Developer