Create a React App WITHOUT Create React App
Learn how to create a React web application using webpack, Babel, React, and ReactDOM. We will not be using the Create React App tool.
Table of Contents 📖
- What is Create React App?
- Why was Create React App Created?
- Why not use Create React App?
- What do we need to Create a React App?
- What is a Transpiler and Why do We Need one?
- What is a Module Bundler and Why do We Need one?
- What Transpiler will We be Using?
- What Module Bundler will we be Using?
- Creating our Project Folders
- Initializing our Project
- Installing React
- Installing ReactDOM
- Installing webpack
- Installing webpack-cli
- Installing webpack-dev-server
- Installing @babel/core
- Installing babel-loader
- Installing @babel/preset-react
- Installing @babel/preset-env
- Installing html-webpack-plugin
- Configuring webpack
- Telling webpack our Entry Point
- Adding Code to our Entry Point
- Creating our App Component
- Telling webpack where our Bundle File should be Generated
- Generate HTML File for Bundled Files
- Setting up our Babel Loader
- Creating npm Scripts
- npm Start Script
- npm Build Script
- Summary
What is Create React App?
Create React App is a command line tool from Facebook that allows us to create a React application without worrying about configuration and setup.
Why was Create React App Created?
The main reason behind Create React App is there is less to learn. Developing a React application can be very intimidating to newcomers as there are extra steps including transpiling code, bundling assets, and so on.
Create React App comes with a code transpiler and a module bundler already configured, saving lots of time. In fact, with Create React App, running one command will set up all the tools needed to start a React application.
Why not use Create React App?
Create React App comes with its disadvantages. Specifically, using Create React App makes it difficult to add cusotm build configurations. We could eject the application (stop hiding what is installed under the hood), but that defeats the point of Create React App. Create React App also adds a lot of abstraction and it is important to understand what is required to create/run a React application.
What do we need to Create a React App?
Technically, we can create a React application with just the React, ReactDOM, and Babel libraries. For example, we could just use the CDNs below.
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></ script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></ script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></ script>
Writing React code like this is fine for small projects or testing purposes, but for a production level web application we should create a React application by setting up a React environment. A React environment consists of a JavaScript transpiler and a module bundler.
What is a Transpiler and Why do We Need one?
React recommends the use of the latest version of JavaScript code such as arrow functions, class syntax, etc. Most browsers don't understand this new syntax which is why we need a transpiler.
A transpiler converts one form of code to another form of code. We need a JavaScript transpiler which converts one form of JavaScript code to another form of JavaScript code. Specifically, we need a transpiler that converts ES5 and ES6 code to code that browsers understand.
What is a Module Bundler and Why do We Need one?
A module bundler takes a bunch of files of different types such as JavaScript files, images, stylesheets, etc. and bundles them into a smaller group of files. Module bundlers are used with React because they help us manage dependecy relationships including loading modules in the correct order.
What Transpiler will We be Using?
The JavaScript transpiler we will be using is called Babel. Babel is a JavaScript transpiler that converts ECMAScript 2015+ code into backwards compatible versions of JavaScript in current and older browsers/environments.
What Module Bundler will we be Using?
The module bundler we will be using is webpack. Webpack takes files of different types such as JavaScript and front-end asset files (HTML, CSS, images, etc.) and packages them into a group of smaller files. Webpack also creates a dependency graph to import modules that are dependent on one another in the correct oder. We could use alternatives such as Browserify or Gulp but webpack is the most widely used module bundler for React.
Creating our Project Folders
But enough background information, lets get started by creating a folder to hold our React application and then step inside it.
mkdir react-without-cra
cd react-without-cra
Initializing our Project
We now need to initialize our project by running npm init. This will create a package.json file to keep track of our dependencies, development dependencies, metadata, and more.
npm init -y
We also specify the argument -y which is a shortcut for providing all the defaults when initializing an npm project.
Installing React
Now lets install React. React is just a library, so we can install it with npm.
npm install react
Installing ReactDOM
We will now need to install ReactDOM. ReactDOM is a package that acts as glue between React and the DOM.
DOM stands for document object model. The DOM treats a HTML document as a tree structure where each node is an object representing a part of the document. When a web page is loaded, the browser creates a DOM of the page.
ReactDOM provides DOM specific methods that can be used at the top level of a web application.
npm install react-dom
Installing webpack
Lets now install our module bundler, webpack. We install this package as a dev dependency because it isn't needed for production. In other words, webpack is only needed for when we are developing our application.
npm install webpack --save-dev
Installing webpack-cli
The package webpack-cli (webpack command line interface) provides a set of commands to increase speed/efficiency when setting up a custom webpack project. For example, it provides commands to initialize a new webpack project, run webpack, watch for file changes, etc.
npm install webpack-cli --save-dev
Installing webpack-dev-server
The package webpack-dev-server is another command line interface tool. The package webpack-dev-server gives us a single command to start a server with live reload. Live reload means that when we change the code and save the server will automatically reload.
npm install webpack-dev-server --save-dev
Installing @babel/core
The package @babel/core is the API for Babel, our code transpiler. We will install Babel as a dev dependency.
npm install @babel/core --save-dev
Installing babel-loader
We will also need to install the package babel-loader. Loaders are what webpack uses to handle and process different file types. Loaders dictate how certain file types should be preprocessed as they are imported/loaded.
As there are different file types, there are different loaders. For example, babel-loader transpiles JavaScript code, sass-loader compiles SASS files to CSS, style-loader adds CSS to the DOM using style tags, etc.
npm install babel-loader --save-dev
Installing @babel/preset-react
The package @babel/preset-react contains presets for all React plugins. In Babel, a preset is a set of plugins that support language features. By default, Babel has the preset es2015 which adds support for ES6 JavaScript and the preset react which adds support for JSX. JSX stands for JavaScript XML and it allows us to write HTML in JavaScript.
npm install @babel/preset-react --save-dev
Installing @babel/preset-env
The package @babel/preset-env is a preset that allows us to use the latest JavaScript code without having to manage which syntax transforms are needed by our target environment(s). This package will also make our JavaScript bundles smaller.
npm install @babel/preset-env --save-dev
Installing html-webpack-plugin
The final package we want to install is html-webpack-plugin, which simplifies HTML file creation to serve our webpack bundles. In other words, html-webpack-plugin will assist with adding our bundled files to our index.html file.
npm install html-webpack-plugin --save-dev
Configuring webpack
One of the great things about webpack is that it is very configurable. We will create a file called webpack.config.js to put all our configuration, loaders, and other build information. At the top of our project structure, lets create a file called webpack.config.js.
[object Object]
We need to export everything from our webpack.config.js file, so we will wrap all the contents of it with module.exports.
module.exports = {
}
Telling webpack our Entry Point
Inside our webpack.config.js file lets tell webpack where the entry point to our application is. The entry point of our application is essentially where our application begins. We want the entry point of our application to be a file called index.js inside a folder called src.
entry: './src/index.js,
The key entry tells webpack which file it should use to create a dependency graph. A dependency graph is used to resolve modules that are dependent on one another while also building the modules that are required by other modules first.
We need to create this file, so lets create a folder call src at the root of our project and then add an index.js file into it.
[object Object]
Adding Code to our Entry Point
Since webpack expects the starting point of our application to be index.js, lets add some React code to it.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './Components/App';
ReactDOM.render(<App />, document.getElementById('root'));
We first import our React and ReactDOM libraries. We then import the App component which we will make next. We are then going to be loading our App component into an HTML element with the ID root. This HTML element will be present in our index.html file that we will create soon.
Creating our App Component
Lets now create the App component that we imported into our index.js file. Remember, it is placed inside a folder called components.
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
<h1>Hello world!</h1>
</div>
)
}
}
export default App;
Extending the React component class gives us access to the component lifecycle methods. One of these functions is render. This render method will of course render our component. We then use export default which is used to export a single class, function, or primitive from a JavaScript file.
[object Object]
Telling webpack where our Bundle File should be Generated
We now need to tell webpack the name and location of our bundled file that will be generated when we produce a production build. This is done with the key output.
output: {
}
Remember, webpack is a module bundler, meaning it takes groups of files and bundles them into a smaller group of files. Here, we are going to be turning all our JavaScript files into one file called bundle.js when we make a production build of our React application.
output: {
filename: 'bundle.js'
}
When this bundle.js file is generated, we want it to be placed in a folder named dist. To make this happen, we pass our output object the key path. We then use the built in node path module and the keyword __dirname to specify the output location of bundle.js to be a folder called dist in our project.
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js'
}
Generate HTML File for Bundled Files
We now want our bundled JavaScript file to be loaded into an HTML file. To do this, we need to make use of the HTMLWebpackPlugin that we downloaded. To add this plugin to webpack, we need to first require it at the top of our webpack configuration file.
const HTMLWebpackPlugin = require('html-webpack-plugin');
Then lets register HTMLWebpackPlugin as a plugin in our webpack configuration file. This can be done with the key plugins.
plugins: [
new HTMLWebpackPlugin({
template: './src/index.html'
})
]
This tells webpack to inject the bundled files it generates into an HTML file called index.html located in our src folder. We need to make sure that we create this file. We also need to create a div with an ID of root so that our React App component can be loaded.
[object Object]
Setting up our Babel Loader
Now, lets inform webpack about the loader we will be using. Specifically, we will be using the babel-loader. To do this, we first use the module key with an object as the value.
module: {
}
What we pass into this module object determines how different types of modules in our project will be treated. We then use the rules key to specify the rules for how the module is created.
module: {
rules: [
]
}
Rules is an array of objects where each object is a rule. Our one and only rule here is that we are going to use Babel to transpile all files that end in .js excluding files located in the node_modules directory.
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
}
]
}
We want to do this with babel-loader using the preset-env and preset-react presets.
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
}
]
}
Creating npm Scripts
All we need to do now is create a couple npm scripts to run our application. One of these scripts will load up our application in development mode with a live reload server while the other will produce a production ready build.
npm Start Script
Our first npm script will be npm start. What this will do is run our webpack-dev-server in development mode.
"scripts": {
"start": "webpack-dev-server --mode development"
}
Adding webpack-dev-server will run a live reload server and specifying --mode as development will compile our code in development mode.
When this development environment starts, we want it to display our application in a new tab. We can do this by specifying the --open option.
"scripts": {
"start": "webpack-dev-server --mode development --open"
}
We also want to add hot module replacement (HMR) to our development environment. HMR exchanges, adds, or removes modules while the application is running without requiring a full reload, making our development environment more efficient.
"scripts": {
"start": "webpack-dev-server --mode development --open --hot"
}
So lets run npm start in the terminal and see what happens!
npm start
npm Build Script
So now that our development set up is all working correctly, lets write a script that will create a production ready bundle of our application.
"scripts": {
"build": "webpack --mode production"
}
Now, when we run npm run build all our JavaScript files will be minified and bundled into a file called bundle.js located in a folder called dist.
npm run build
Now, if we check the contents of our minified index.html file we can see that the bundle.js file is imported by a script tag.
<script defer="defer" src="bundle.js"></ script>
The text file produced just contains some licenscing and copyright information.
Summary
But there we have it! We have created our own React environment. Personally, I feel a lot better taking away as much abstraction as possible from a project I am working on. If this tutorial was helpful, please consider donating at the top of the page, sharing this with a friend, and checking out my YouTube channel WittCode!