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?
- It allows us to setup our continuous integration pipeline from the start.
- Also makes our application accessible to others for early feedback.
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 initAfter 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 initThe 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-domInit the tsconfig.json file using the following command
$ npx tsc --initEdit 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/runtimeCreate .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-pluginCreate .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-pluginTo be able to use .ts extension with webpack configuration files, install ts-node
$ npm install --save-dev ts-nodeLet’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-pluginNow 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-pluginAdd 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.