When building a React App, the create-react-app
npm package builds a very modern setup in one command, ensuring all the underlying pieces work together seamlessly so that we don’t need to build and compose the whole toolchain ourselves.
The create-react-app
works well, when you are new to React and want to learn the inner workings of React without having to worry about going through the complicated bootstrapping process.
It also works well with a simple blog or a small project that you want to quickly put together and wouldn’t need to override many configurations.
However, if you are working on an enterprise website or an app, you would want to build your project from scratch. This will not bloat your codebase with a lot of extra features that create-react-app
comes with that your app doesn’t need or use.
create-react-app
also hides your webpack config under node_modules, making it impossible to add or update the configuration without ejecting the package – which is not recommended.
Using a Webpack module bundler instead allows you to have total control of your app. You can add dependencies and configurations as per your needs.
In this post, I will cover the process of building a basic React app from scratch. .
Prerequisites:
NPM
and Node.js
When you install node, npm
will get installed automatically.
Here’s how you can create the app from scratch:
- Create a new directory for your project giving a name of your choice – eg.
my-react-app
mkdir my-react-app cd my-react-app mkdir app cd app
- Now initialize the project using the following
npm
command, this will createpackage.json
in your root directory. The –y command will initialize the app with basic configuration and scaffolding for your project.
npm init --y
- Install the following app dependencies:
npm install --save react react-dom typescript npm install --save-dev webpack webpack-dev-server html-webpack-plugin webpack-cli npm install --save-dev @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/preset-typescript css-loader style-loader
The flag --save
will save the dependencies under the dependencies
section of the package.json
The flag --save-dev
will save the dependencies under the devDependencies
section of the package.json
. This will make sure these dependencies are not included in the production bundle – keeping the production build light and performant.
What are those dependencies?
- React: A UI library for creating modular components
- React-dom: Provides us with virtual DOM that React needs to render and re-render the components.
- Webpack: A module bundler – it makes our application code usable in a web browser – often converting the entire package into a single bundle.js file which can be embedded into an HTML file easily and used by our application.
- Webpack-dev-server: Transforms the react code and serves it with a development server that provides live reloading of the output in the browser, making development faster.
- Html-webpack-plugin: This will generate an HTML5 file – index.html, that includes all our webpack bundles in the body using script tags.
- @babel/core: A JavaScript transpiler that converts modern JavaScript into a backwards compatible version of JavaScript that all browsers could read.
- Babel-loader: The webpack loader responsible for talking to Babel, it allows users to add custom handling of Babel’s configuration for each file that it processes.
- @babel/preset-env: Out-of-the-box feature that bundles all the plugins needed to transpile all ES6 features into widely compatible syntax
- @babel/preset-react: Out-of-the-box feature that bundles all React-specific Babel plugins responsible for converting JSX syntax into plain JavaScript that browsers can understand.
- Create the app structure: folders and base files: Under the
/app
directory. complete the following steps:
mkdir src cd src
Create a file called index.html
and copy the following code:
<!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 Boilerplate</title> </head> <body> <div id="root"> </div> </body> </html>
Create another file called index.tsx
and copy the following code:
import React from 'react'; import ReactDOM from 'react-dom'; import App from './app'; ReactDOM.render(<App />, document.getElementById('root'));
Create another file called app.tsx
and copy the following code:
import React from "react"; import "./index.css" const App = () => ( <div> <h1>Hello World!</h1> </div> ); export default App;
Create another file called index.css
and copy the following code:
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; }
Change directory and navigate to the /app folder.
Under the /app folder – create the following files:
.gitignore
– a plain text file that contains the list of files and folders for git to ignore
node_modules dist build coverage
.babelrc:
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ], "plugins": [ "@babel/proposal-class-properties", "@babel/proposal-object-rest-spread" ] }
webpack.config.js
:
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { // webpack will take the files from ./src/index entry: './src/index', // and output it into /build as bundle.js output: { path: path.join(__dirname, '/build'), filename: 'bundle.js' }, // adding .ts and .tsx to resolve.extensions will help babel look for .ts and .tsx files to transpile resolve: { extensions: ['.ts', '.tsx', '.js'] }, module: { rules: [{ test: /\.bundle\.js$/, use: { loader: 'bundle-loader', options: { name: 'my-chunk', cacheDirectory: true, presets: ['@babel/preset-env'] } } }, { test: /\.(ts|js)x?$/, use: { loader: 'babel-loader', options: { cacheDirectory: true, presets: [ ["@babel/preset-env", { "targets": { "browsers": [">0.03%"] }, "useBuiltIns": "entry", "corejs": 3 } ], "@babel/preset-typescript", "@babel/preset-react" ] }, }, }, // css-loader to bundle all the css files into one file and style-loader to add all the styles inside the style tag of the document { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] };
Update the package.json
file with the following:
"scripts": { "bundle": "webpack --display-error-details", "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack-dev-server --mode development --open --hot", "build": "webpack --mode production", "check-types": "tsc" }
- Your folder structure should look something like this now:
my-react-app
app
node_modules
src
app.tsx
Index.tsx
Index.html
index.css
- package.json
- webpack.config.js
- .babelrc
- .gitignore
- Run the app:
In your terminal, type: npm start
This should start the compile process in your terminal and open your browser window automatically and load the following url: http://localhost:8080
And you should be able to see “Hello World! in your browser.
Make a change to your app.js
and you should be able to see the browser reload with new contents, this is because of Webpack’s Hot Module Replacement feature which gets enabled when we add --hot
flag to npm start
with webpack.
This is just a basic boilerplate. As and when your app grows – you can configure additional dependencies and configurations through webpack and babel.
A few handy webpack plugins:
- mini-css-extract-plugin: This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS.
- clean-webpack-plugin: By default, this plugin will remove all files inside webpack’s output.path directory, as well as all unused webpack assets after every successful rebuild.
- optimize-css-assets-webpack-plugin: It will search for CSS assets during the Webpack build and will optimize \ minimize the CSS
- webpack-bundle-analyzer plugin: Visualize size of webpack output files with an interactive zoomable treemap.