Bloğ ? > Monorepo React + React Native > Ch 2 - Project Setup

02 Apr 2021

So the first thing we’ll do, quite counterintuitively, is to deploy our application. Well, “Why deploy an empty application which doesn’t exist yet?“ you might ask. Why the heck not?

We will need three things to get started.

A source code repository hosting service. → Bitbucket
A URL endpoint from any CDN provider. → AWS S3
And the CI/CD solution of your choosing. → CircleCI

Repo setup

Let’s create a repository, my glorious project shall be called “monorepo-client”. Boring and nerdy, yet hits the spot. We can spice it up later when the project becomes a hit. Renaming repos is always fun.

Now that we have our remote repo setup, let’s clone it to our local, we will later connect this repo to CircleCI for CI/CD.

$ git clone git@bitbucket.org:monorepo-template/monorepo-client-root.git
$ cd monorepo-client-root
$ npm init

After we navigate to our cloned folder and run “npm init” we will be guided in creating a minimal package.json file that will look like this.

<root>/package.json

{
    "name": "monorepo-client-root",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "repository": {
    "type": "git",
    "url": "git+ssh://git@bitbucket.org/monorepo-template/monorepo-client-root.git"
    },
    "author": "",
    "license": "ISC",
    "homepage": "https://bitbucket.org/monorepo-template/monorepo-client-root#readme"
}


React app setup with Typescript Babel and Eslint

We also create a directory for our React application, we will call this folder “web-client”. After you navigate to this folder, once again run “npm init”, this one we will configure with the scripts and dependencies for our web application.

$ mkdir web-client && cd web-client && npm init

The next steps will be following the amazing tutorial by Carl Rippon, “Creating a React app with TypeScript and ESLint with Webpack 5”. The explanations he provides are so educational, I suggest you take the time to read it in full. If you are already comfortable with these concepts, the rest of our configuration till the deployment section are the steps highlighted in his post.

Install react, react-dom and typescript for our project.

$ npm i react react-dom
$ npm i --save-dev typescript
$ npm i --save-dev @types/react @types/react-dom

Init the tsconfig.json file using the following command

$ npx tsc --init

Edit the tsconfig.json to include the following.

<root>/web-client/tsconfig.json

"compilerOptions": {
   "target": "es5",
   "module": "commonjs",                
   "lib": ["DOM","DOM.Iterable","ESNext"],
   "allowJs": true,          
   "jsx": "react",
   "isolatedModules": true,
   "resolveJsonModule": true,
   "strict": true,
   "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,    
   "esModuleInterop": true,
    "skipLibCheck": true,                          
   "forceConsistentCasingInFileNames": true
}

Create a src folder within “web-client” and add our entry point there with index.html.

<root>/web-client/src/index.html

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8" />
    <title>Template React App</title>
    </head>
<body>
    <div id="root"></div>
</body>
</html>

Create index.tsx in the same folder with the following content.

<root>/web-client/src/index.tsx

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

const App = () => (
 <h1>Testing out automated deployments.</h1>
);

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

We need Babel to turn our Typescript code to Javascript so we can deploy our React application. Let’s install it.

$ npm i --save-dev @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-transform-runtime @babel/runtime

Create .babelrc to configure Babel.

<root>/web-client/.babelrc

{
   "presets": [
     "@babel/preset-env",
     "@babel/preset-react",
     "@babel/preset-typescript"
   ],
   "plugins": [
     [
       "@babel/plugin-transform-runtime",
       {
         "regenerator": true
       }
     ]
   ]
 }

Now that we configured Babel and Typescript, we might just as well add linting to our project. Let’s install and configure ESLint.

$ npm i --save-dev eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/parser @typescript-eslint/eslint-plugin

Create .eslintrc.json in the web-client directory.

<root>/web-client/.eslintrc.json

{
   "parser": "@typescript-eslint/parser",
   "parserOptions": {
     "ecmaVersion": 2018,
     "sourceType": "module"
   },
   "plugins": [
     "@typescript-eslint",
     "react-hooks"
   ],
   "extends": [
     "plugin:react/recommended",
     "plugin:@typescript-eslint/recommended"
   ],
   "rules": {
     "react-hooks/rules-of-hooks": "error",
     "react-hooks/exhaustive-deps": "warn",
     "react/prop-types": "off"
   },
   "settings": {
     "react": {
       "pragma": "React",
       "version": "detect"
     }
   }
 }


Webpack setup

Adding Webpack

$ npm i --save-dev webpack webpack-cli @types/webpack
$ npm install --save-dev webpack-dev-server @types/webpack-dev-server
$ npm install --save-dev babel-loader
$ npm install --save-dev html-webpack-plugin

To be able to use .ts extension with webpack configuration files, install ts-node

$ npm install --save-dev ts-node

Let’s also add typechecking and linting capabilities to webpack.

$ npm install --save-dev fork-ts-checker-webpack-plugin @types/fork-ts-checker-webpack-plugin
$ npm install --save-dev eslint-webpack-plugin

Now configure webpack.dev.config.ts for development environment

<root>/web-client/webpack.dev.config.ts

import path from "path";
import webpack from "webpack";
import * as webpackDevServer from 'webpack-dev-server';
import HtmlWebpackPlugin from "html-webpack-plugin";
import ESLintPlugin from "eslint-webpack-plugin";
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

const config: webpack.Configuration = {
 mode: "development",
 output: {
   publicPath: "/",
 },
 entry: "./src/index.tsx",
 module: {
   rules: [
     {
       test: /\.(ts|js)x?$/i,
       exclude: /node_modules/,
       use: {
         loader: "babel-loader",
         options: {
           presets: [
             "@babel/preset-env",
             "@babel/preset-react",
             "@babel/preset-typescript",
           ],
         },
       },
     },
   ],
 },
 resolve: {
   extensions: [".tsx", ".ts", ".js"],
 },
 plugins: [
   new HtmlWebpackPlugin({
     template: "src/index.html",
   }),
   new webpack.HotModuleReplacementPlugin(),
   new ForkTsCheckerWebpackPlugin({
     async: false
   }),
   new ESLintPlugin({
     extensions: ["js", "jsx", "ts", "tsx"],
   }),
 ],
 devtool: "inline-source-map",
 devServer: {
   contentBase: path.join(__dirname, "build"),
   historyApiFallback: true,
   port: 4000,
   open: true,
   hot: true
 },
};

export default config;

Configuring for production with slight differences.

<root>/web-client/webpack.prod.config.ts

import path from "path";
import webpack from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import ESLintPlugin from "eslint-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";

const config: webpack.Configuration = {
 mode: "production",
 entry: "./src/index.tsx",
 output: {
   path: path.resolve(__dirname, "build"),
   filename: "[name].[contenthash].js",
   publicPath: "",
 },
 module: {
   rules: [
     {
       test: /\.(ts|js)x?$/i,
       exclude: /node_modules/,
       use: {
         loader: "babel-loader",
         options: {
           presets: [
             "@babel/preset-env",
             "@babel/preset-react",
             "@babel/preset-typescript",
           ],
         },
       },
     },
   ],
 },
 resolve: {
   extensions: [".tsx", ".ts", ".js"],
 },
 plugins: [
   new HtmlWebpackPlugin({
     template: "src/index.html",
   }),
   new ForkTsCheckerWebpackPlugin({
     async: false,
   }),
   new ESLintPlugin({
     extensions: ["js", "jsx", "ts", "tsx"],
   }),
   new CleanWebpackPlugin(),
 ],
};

export default config;

CleanWebpackPlugin plugin will help us clear out the build folder at the start of the bundling.

    $ npm install --save-dev clean-webpack-plugin

Add the below scripts to your package.json

<root>/web-client/package.json

    ...
  "scripts": {
    "start": "webpack serve --config webpack.dev.config.ts",
    "build": "webpack --config webpack.prod.config.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Now that we configured our template app, we are ready to deploy.