Under the Hood of the Tool Create-React-App
2022-10-07 . 4 min read
Disclaimer
There are already a lot of free resources talking about what CRA does and how to manually set up a project with webpack and babel which react uses under the hood. So instead, this blog is a learning note for me and a summary for you: The Reader.
Module
Let's start with the fact that most modern browsers already support modular code. Just add the value to the type attribute in the script tag as module and you are good to go. Say, You have this module.
export default function sayHello() {
const helloDiv = document.createElement('div')
helloDiv.innerHTML = 'Hey, How you do\'in?'
document.getElementsByTagName('body')[0].appendChild(helloDiv)
}
Than, all you have to do is:
<body>
<script type='module'>
import sayHello from './sayHello.js'
sayHello()
</script>
<!-- Or move the above js code into its own file and -->
<script type='module' src='./index.js'></script>
</body>
Learn more about modules:
Bundlers
After node introduced and started a wave of modular programming in JS with CommonJS Modules, there was a need for this in the browser too but there was no support for it then. Module bundlers came to the rescue. When we use CRA to lay out all the boilerplate, it installs and uses webpack as a module bundler.
First, let's use react to make a counter without any module bundler and transpiler. Create an HTML file and get the react and react-dom scripts.
<body>
<div id="root" style="height: 100vh; background: #020f17;"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script>
const rootElem = document.getElementById('root')
const root = ReactDOM.createRoot(rootElem)
const CE = React.createElement
function Counter() {
const [count, setCount] = React.useState(0)
const counterContainer = CE('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}, [
CE('div', null,
[
CE('button',
{
onClick: () => setCount(count + 1),
style: { background: '#24a0ed', border: 'none', marginRight: '8px', color: 'white', fontWeight: 'bold', 'padding': '10px 16px', borderRadius: '8px' }
},
'Plus'
),
CE('button',
{
onClick: () => { if (count === 0) return; setCount(count - 1)},
style: { background: '#ed1b2b', border: 'none', color: 'white', fontWeight: 'bold', 'padding': '10px 16px', borderRadius: '8px' }
},
'Minus'
)
]
),
CE('p', { style: { fontWeight: 'bold', color: '#A7C7E7', fontSize: '1.6rem' }}, count)
])
return counterContainer
}
root.render(React.createElement(Counter))
</script>
</body>
The counter looks like this:
Learn More
- https://reactjs.org/docs/react-without-jsx.html
- https://reactjs.org/docs/add-react-to-a-website.html
Now add webpack
pnpm i -D webpack webpack-cli
Webpack can bundle files without any config but it's customizable and knowing how to is always better. Add a config file at the root of your project.
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
devtool: 'inline-source-map',
output: {
filename: '[name][contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({ template: './index.template.html' })
]
}
The actual meat of webpack are loaders and plugins. As per the documentation:
Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph.
Using the style and css loader allows us to import and use CSS files in a js module. The out-of-the-box asset modules can be used for assets like images.
As per the documentation:
Plugins are the backbone of webpack. They serve the purpose of doing anything else that a loader cannot do.
Notice that I've used the htmlWebpackPlugin in the above config file. If not used, we'd have to manually add a html file in the output folder and add the bundled scripts to the file. This plugin does this for us, and we have the option to add a HTML template file.
And the 2 files:
import React from 'react'
import { createRoot } from 'react-dom/client'
import Counter from './Counter'
import '../style/index.css'
const root = createRoot(document.getElementById('root'))
root.render(React.createElement(Counter))
import React, { useState } from 'react'
const CE = React.createElement
export default function Counter () {
const [count, setCount] = useState(0)
const CounterContainer = CE('div', {style: { height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }},
... same as above...
,
CE('p', { style: { fontWeight: 'bold', color: '#A7C7E7', fontSize: '1.6rem' }}, count)
)
return CounterContainer
}
If you've been doing it yourself, the CSS is injected inside the style tag in the head of the document.
Learn More
Transcompiler
We have not used JSX at all till now. That is because JSX is not a core JS feature but is an extension, a syntactic sugar to the language. Using a transcompiler like Babel allows us to write JSX which is very much easier to write, read, and debug than React's createElement method but under the hood, babel compiles the JSX to the same createElement method passing all props and children to the function as its parameters. One of the videos mentions that Babel uses AST(Abstract Syntax Trees) to understand what the code actually says and once it has an understanding of the code, it is simply translating it into a different version or even a different language. It's like translating an actual human spoken language, once we understand the lexis and semantics, translating is fairly easy. But easier said than done.😐
To start, install the dev dependencies:
pnpm i -D @babel/core @babel/cli @babel/preset-env @babel/preset-react babel-loader
The babel env preset transpiles modern js that we developers write to older version of js and the react preset allows us to write JSX. The other modules are self explanatory.
Add a .bablrc config file and add the following to let babel know which presets we are using.
{
"presets": ["@babel/env", "@babel/preset-react"]
}
And also let webpack know that we are using babel to transpile js and jsx files by adding the following to the webpack config file.
module.exports = {
...
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
options: { presets: ['@babel/env']}
},
...
]
},
...
}
And we are good to go. Still, we have not implemented the webpack-dev-server, react hot reload to our projects. These can be added with much ease and really helps in the development process. Everything is mentioned in the documentation and the awesome community we have has already many helpful blog posts, answers and videos.
References and Resources
- MDN: JavaScript Modules
- Moz://a Hacks: ES modules: A cartoon deep-dive by Lin Clark
- The Role of Babel in React
- Webpack Docs
- Webpack 5 Crash Course | Frontend Development Setup
- Babel Docs
- Babel Videos
- Creating a React App… From Scratch.
Hey I assume you finished reading, I would love to know your feedback or if found any error or mistake in this blog post, please do not hesitate to reach out to me.