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