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

*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.

Initialize your project

yarn init --yesornpm init --yes

Install TypeScript

yarn add typescript -Dornpm i typescript --save-dev

Create tsconfig.json in root directory

{
"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"
]
}

Add React dependencies and types

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

Create initial App files in your project’s root directory

./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

Add dependencies

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

Create webpack.config.ts in your project’s root directory

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)

Add npm scripts to package.json

"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

Install dependencies

yarn add prettier -Dornpm i prettier --save-dev

Configure .prettierrc

{
"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

Install dependencies

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

Create .eslintrc.json file in your project’s root directory

{
"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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store