Setting up a React project from scratch is no small feat if you have not done it before. Not knowing what the packages and commands do can be quite scary. That is why projects like Create React App exist, to simplify the bootstrapping process.
However, we are not going to use Create React App today and instead do a deep dive into setting up a simple React project from the ground up.
Prerequisites for this tutorial include having node and npm installed. If you do not have these installed, there are many articles online that show you how to do just that. I recommend having nvm to easily switch between node versions. I am using node 16.19.1, but node 18 is also fine.
You can also find the project repository on GitHub, which has the completed code from this article.
Project Initialization
To start, let’s open up your terminal and create a new project folder in your preferred directory by running mkdir my-react-app
. Navigate into the project folder by running cd my-react-app
.
Once in the project folder, we want to initialize a new npm project. You can do this by running npm init
and answering the prompts. The answers don’t really matter at this point, they are just to set up a boilerplate project. Once finished, you will see a new package.json
file in your project. This file contains metadata about your project and the dependencies you will use.
Installing NPM Packages
Let’s install React and ReactDOM. We are going to need these to create our React app. Run npm install react react-dom
to install the packages.
Next, we will install some development dependencies to help us build this app. Install them by running the following:
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader mini-css-extract-plugin webpack webpack-cli webpack-dev-server
Here is what our package.json
looks like so far:
{
"name": "my-react-app",
"version": "1.0.0",
"description": "A sample React project setup without create-react-app",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development --open",
"build": "webpack --mode production"
},
"author": "Paul Kim",
"license": "ISC",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.2",
"css-loader": "^6.7.3",
"mini-css-extract-plugin": "^2.7.5",
"webpack": "^5.76.1",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
}
}
Observant readers will note that the package.json
above has an extra scripts section. Go ahead and manually add it to your package.json
.
By now you’ve installed a number of packages that you may or may not know what they do. Much of it might not mean anything yet, but stick with it and I promise you it will make more sense.
Babel Configuration
Babel is a compiler for modern JavaScript that transpiles new language features to older versions of JavaScript that are supported by most browsers. This means developers can use the latest features of JavaScript without having to worry about browser support and environment. When we installed @babel/core
, this is what we got.
Babel is also highly configurable and extendable with plugins and presets. We are using two presets in this project. @babel/preset-env
is a collection of plugins that allow Babel to understand and transpile modern JavaScript syntax to the appropriate browser targets. @babel/preset-react
is a preset that allows Babel to transform JSX syntax into plain JavaScript.
To begin working with Babel and its presets, we need to tell Babel what to use. Create a new file called babel.config.json
in your project directory and add the following:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
This tells Babel to run the modern JavaScript we write through these presets to generate older JavaScript code.
Webpack Configuration
Webpack can be a bit intimidating when first setting it up. Just the sheer number of configurations can leave any developer feeling lost. We are going to try and dispel some of that mystery today. Create a new file called webpack.config.js
in your project directory and add the following code. If you don’t know what all this does, don’t worry. We will walk through each section below.
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles.css',
}),
],
devServer: {
port: 8080,
},
};
Let’s step through each section of the configuration and look at what they do.
entry: './src/index.js',
This is the entry point of our application. This is where Webpack starts reading our code and building our dependency graph.
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
This is the output configuration for our bundled assets. This specifies where the bundle should be saved and what name it should have. In our case, we are saving the bundle as bundle.js
and saving it in the dist
directory.
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
This is the module configuration for our Webpack build. This tells Webpack how to handle different types of files in our application. It allows you to use loaders to transform and process different file types, like JavaScript, CSS, and images.
In our case, we have two rules. The first rule uses babel-loader
to transpile our JavaScript code using Babel. It applies the loader to all .js
files, except those in the node_modules
directory, and uses the babel.config.json
file to configure Babel. The second rule uses mini-css-exctract-plugin
and css-loader
to load and extract CSS code to create separate .css
files.
plugins: [
new MiniCssExtractPlugin({
filename: 'styles.css',
}),
],
The plugins configuration is where you can add plugins to extend the functionality of Webpack. Plugins can perform a variety of tasks, like generating an HTML file for your application, compressing your output files, and more.
In this case, we only have one plugin. We are using mini-css-exctract-plugin
to extract CSS code to a file called styles.css
and output it in our dist
directory.
devServer: {
port: 8080,
},
Lastly, we have the development server configuration. This tells Webpack how to server our application during development. In our case, we are telling Webpack to listen on port 8080 and serve index.html
, which we will create next.
This covers the basics of our Webpack configuration, but there are a lot more options. You can refer to the Webpack documentation for more information on configuration options.
Source Files
Let’s create a new directory called src
in our project root by running mkdir src
. This is where all our pre-compiled source files will live. In the public
directory, create a new file called index.html
and add the following HTML markup. This markup is extremely simple and would not be used in a production environment, but we will be using it to display your React application.
<html>
<head>
<meta charset="utf-8">
<title>My React App</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
<link rel="stylesheet" href="./styles.css">
</body>
</html>
Next, create a new file called index.js
in your src
directory and write the following code. This will render your React application in the DOM.
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
We will then create the <App />
component that we defined in the step above. Create an App.js
file in your src
directory and add the following. In our case, this will be a very simple Hello World application, but this can easily be any application we want it to be.
import React from 'react';
import './App.css';
const App = () => {
return (
<div>
<h1>Hello, World!</h1>
</div>
);
};
export default App;
Let’s also write the CSS file we imported in the step above. In your src
directory, create an App.css
file and write the following code:
h1 {
color: red;
font-size: 32px;
text-decoration: underline;
}
That’s it! We now have all our source files and are ready to start development.
Development and Build
Open your terminal and run npm run start
to start the development server. After a second or two, your browser should open up to http://localhost:8080
and show you your Hello World application. You can open your project folder in your favorite code editor and start editing your React application. Try changing the color in the App.css file. Or add a paragraph to your App.js file. Any changes you make will be automatically updated in your browser. So awesome!
Below is a demo of what you should see when you have everything running correctly. You can make changes in the files and see how they update. Go ahead, give it a try!
Once you are done with your changes, run npm run build
to compile the production-ready version of your application. This command will create a dist
folder in your project directory that contains all your files. Deploy the dist
folder to your preferred hosting platform to make your React application live.
At this point, we’ve set up a React project without using Create React App. I hope you found this helpful in understanding the underlaying mechanisms that go into building a React application. Thanks so much for reading!