Within the React ecosystem, tools such as Create React App and Preact have evolved to make it easier to build and maintain React-based applications. These types of tools "black box" the underlying work that's necessary to get a React-based web application up and running.
From a long term outlook, I think that's great — the development community takes the "black box" nature of many other tools for granted, and at the end of the day, many developers are more productive for it. I don't see any reason that it shouldn't be similar for modern single page applications (SPAs).
It can, however, also be beneficial to understand how to set up a React web application from scratch because it makes the tools that enable easily creating and maintaining React web applications a little less opaque.
This article documents one way of building a minimum viable React web application. It's important to note that the resulting application isn't ready for production.
Before getting started, it's first necessary to define what's being built: a client-side application that supports React and JSX.
It's worth noting at this point that React can be used without JSX. In fact, installing React in a web page can be as simple as just including a script for it.
However, React is typically used with JSX, which is a syntax that enables specifying HTML directly within JavaScript. JSX isn't exclusive to the React framework, but it's typically paired with it because JSX provides a modern technique for constructing HTML. In my experience, it's a bit jarring to work with JSX initially — mostly because the technologies that came before it dealt with separating code by concerns, such that HTML, CSS and JavaScript were all used together but separately — but, I eventually found it to be one of the most compelling reasons to use React.
Once you throw JSX support into the mix, installing React becomes more complex because JSX support is considered too slow to run directly in the browser. So, you need to introduce build tools to transform that JSX into JavaScript prior to deploying a web application that contains it.
Adding JSX support to a React web application calls for an entire build toolchain, so once JSX support is added, other features that utilize those build tools tend to get layered on as well, leading to more complex tools.
Many popular libraries and frameworks make it easy to get started with something like a one-line CLI command that scaffolds a project so that developers have something to work with.
Since we're building from scratch, we don't have that luxury. Instead:
Next, let's add the packages that are relevant to React. In the "dependencies" field, add the react and react-dom packages.
Then, install those packages by running npm install (you'll need Node and NPM installed)
The next step in the project is to add the build tool. In this case, we'll use webpack. Webpack is responsible for bundling the JavaScript of the application so that we can easily deliver the application to an end user. It also enables, through the addition of plugins, loaders and other transformations, the alteration of the code that we're delivering to the browser.
By using Webpack in combination with a few plugins and loaders, it becomes possible to:
I'll also add a few packages related to a library called Babel, which is a JavaScript compiler (that will help with the backporting of code and transpilation of JSX), which I'll later configure Webpack to use.
So, in the package.json, add a few packages to the "devDependencies" field and npm install: webpack, webpack-cli, webpack-dev-server, webpack-merge, html-webpack-plugin, clean-webpack-plugin, babel-loader, @babel/core, and @babel/preset-react
The webpack & webpack-cli packages are the primary packages that will enable the webpack integration into the project and enable its use from npm "scripts" in package.json. The web-dev-server package provides a development server that will continuously watch the project files and reload the development page to enable rapid development.
There are multiple ways to configure webpack. One way is via configuration files (which this project will use), and the webpack-merge package allows for multiple and shared configuration files (for example, one configuration file for development, another for production, and a configuration file that's also shared between them for common settings).
Finally, the html-webpack-plugin and clean-webpack-plugin are utilities. The html-webpack-plugin automatically creates an HTML file and inserts bundled code into it. Meanwhile, Webpack doesn't handle cleaning up the directory where it places bundled files, and the clean-webpack-plugin can be utilized to clean that directory.
At this point, the project should contain a package.json file and a node_modules folder if you ran npm install, and a package lock file.
Next, let's create a few more directories: dist, public, and src
The dist folder will hold the bundled and transformed code that's delivered to the web browser.
The public folder will hold an HTML template that Webpack will use as a base into which the bundled code will be inserted.
The src folder will be where the application source is located.
Additionally, I'll add three more files at the base of the application directory: webpack.common.js, webpack.development.js, and webpack.production.js
The application should now look something like:
dist/
node_modules/
public/
src/
package.json
webpack.common.js
webpack.development.js
webpack.production.js
yarn.lock (or an npm lock file)
Next, let's create a really simple application just to be able to confirm that a fully built and working React application with JSX can be delivered to a web browser.
Create an HTML file
First, in the public folder, create an index.html file. This file will serve as a template that we'll later configure Webpack to use to automatically create an HTML file and insert the bundled application code into (via the html-webpack-plugin package).
The index.html file is incredibly simple and should look something like:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="root"><div>
</body>
</html>
All this really does is set the character encoding for the page and provide a div called "root" where the main component of the application will be inserted.
Create the Application File
In the src folder, create an app.js file. This will be the React application. To get started, this file can also be simple:
import React from 'react'
function App() {
return <div>An application!</div>
}
export default App
This file is actually accomplishing quite a bit, none of which would work without React and the build tools being properly configured. This file:
Create an Entrypoint for the Application
Next, in the src folder, create an index.js file. This file will serve as the entrypoint for the application, and Webpack will later be configured to recognize it as the entrypoint. The contents of this file can be something like:
import React from "react"
import ReactDOM from "react-dom"
import App from "./app"
ReactDOM.render(<App />, document.getElementById("root"))
Essentially, this file pulls in the exported function component from the app.js file and then renders it in the root div added to the index.html file.
The three files added above form the basis of a very, very simple React application. All that's left to do is wire up webpack and plugins so that all of this works together and results in an application that can be delivered to and run in a web browser.
Webpack configurations can be complex, and to really understand it, it's best to go piece-by-piece through the guides provided on the Webpack website.
webpack.common.js
Let's start by editing the webpack.common.js file created earlier. This file will contain settings that are applicable both to the development and build versions of the project.
In general, this configuration file will:
The file should look something like:
const path = require("path")
//Import plugins
const HtmlWebpackPlugin = require("html-webpack-plugin")
const CleanWebpackPlugin = require("clean-webpack-plugin")
//Obtain an absolute path to the src folder
var srcPath = path.resolve(__dirname, "src")
//Obtain an absolute path to the dist folder
var distPath = path.resolve(__dirname, "dist")
//Configure Webpack
module.exports = {
//This configures the entrypoint discussed above
entry:"./src/index.js",
//This configures the output path and file naming scheme discussed above
output: {
filename: "[name].[hash].js",
path: distPath
},
module: {
rules: [
{
//This configures Babel for transpiling the code and JSX.
//The test field indicates (via regular expression) the file types on which Babel should be utilized
test:/\\.(js|jsx)$/,
//Babel should be utilized on files contained in the /src folder
include: srcPath,
//Configure babel-loader will utilize Babel
loader: "babel-loader",
options: {
//Babel comes with a react preset. This includes other plugins, such as @babel/plugin-transform-react-display-name
presets:["@babel/preset-react"]
}
}]
},
plugins: [
//Automatically create the finished HTML file and insert the bundle script tag
new HtmlWebpackPlugin({
template: "./public/index.html"
}),
//Configure the clean plugin, which be default will clean the dist/ folder
new CleanWebpackPlugin()
]
}
webpack.development.js
The development webpack configuration will simply add a few things to the common configuration:
Really easy:
const merge = require("webpack-merge")
const common = require("./webpack.common.js")
//Use the merge package to merge the common configuration with additional options
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
contentBase: "./dist",
port: 3000
}
})
webpack.production.js
Even easier, all this file will do is change the webpack mode.
const merge = require("webpack-merge")
const common = require("./webpack.common.js")
module.exports = merge(common, {
mode: "production"
})
By this point, there's a skeleton application and Webpack is configured. All that's left to do is set up npm scripts to either: a) run Webpack and display the application via the webpack-dev-server in development mode, or b) bundle the application for deployment.
Editing package.json, we can add to the "scripts" field:
"scripts": {
"start":"webpack-dev-server --open --config webpack.development.js",
"build":"webpack --config webpack.production.js"
}
That's it! Run npm start or npm run build in the terminal to try either option.
This article demonstrated building a minimum viable React application. It shows precisely what's required to get a skeleton application running — nothing more, nothing less. A lot has been left out, like loading images or bundle optimization, so the result of this article isn't production ready.
By the time you add in a lot of other stuff that's necessary for production, you may find that it makes more sense to go with a pre-built tool that can be upgraded over time instead.
However, I hope that this article has helped demistify how a simple React application with JSX can be built.