Part1.01 : Isn't create-react-app good enough?
Create-react-app is a great tool, it gets the project running in minutes. But it binds our hands with a lot of abstractions, according to the official react docs it is meant to be a tool to get anyone started with a good dev environment.
It comes out of the box with a lot of good defaults(many of which we may never need) but it's very difficult to extend or bend it according to our needs. So in this series of articles we'll learn how to build our react setup from scratch.
Part1.02: What is a module bundler
The name is self explanatory, it bundles modules. Usually in our projects we have multiple js libraries, bundlers them in a single javascript file which can be executed in the browser.
there are a lot of popular bundlers available : esbuild, parcel, vitejs, webpack, rollup are the most popular once.
create-react-app uses webpack under the hood. We'll use webpack-5 for our setup.
Part1.03: Project directory.
at the end of the project we'll end up with a folder structure like this.
📦my-app
┣ 📂public
┃ ┗ 📜index.html
┣ 📂src
┃ ┣ 📜App.jsx
┃ ┣ 📜app.module.css
┃ ┣ 📜index.js
┃ ┗ 📜logo.png
┣ 📜.babelrc.json
┣ 📜.browserslistrc
┣ 📜package-lock.json
┣ 📜package.json
┗ 📜webpack.config.js
Part1.04: Initialize Project
first thing first create a repository.
mkdir my-app
cd my-app
initialize the project
npm init --y
at this time you should have a package.json file available in the directory
part1.05: Run javascript ! Run!
run
npm i -D webpack webpack-cli
if you check the node_modules/.bin
directory you will find a file named webpack
. that is a binary file, that will run our webpack commands.
now if you run
./node_modules/.bin/webpack
you should get a promp saying
Can't resolve './src' ...
create src folder , by default webpack looks for a index.js file.
crate a index.js file inside src folder
src/index.js
console.log('hello world');
now if you run
./node_modules/.bin/webpack
a dist folder will be created. however you'll be prompted with this error
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
to get rid of the error modify the command to
./node_modules/.bin/webpack --mode development
now no errors will be prompted and and one dist
folder will be created.
in the project we need to run webpack multiple times, writing the command each time can be tiresome , create a script in package.json to overcome this.
PS: Package.json by default points to ./node_modules/.bin so we'll
just write webpack instead of ./node_modules/.bin/webpack
package.json
"scripts": {
...,
"start": "webpack --mode development",
"build": "webpack --mode production",
...,
}
run
npm start
the output should be same as
./node_modules/.bin/webpack --mode development
now we have a dist directory.
inside of dist we have main.js
file, to execute the file in browser we need to add it as a script in a html file.
Instead of doing it manually, we'll take the help of html-webpack-plugin.
npm i -D html-webpack-plugin
create webpack.config.js
in root directory
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, args) => {
return {
plugins: [new HtmlWebpackPlugin()],
};
};
npm start
now dist will contain a index.html
file as well, which points to the main.js
file.
if you open the index.html with browser and check console you'll see hello world
in the console
P.S: env and args in the snippet can be used to receive environment variables and detect mode, we'll discuss them in details in some other chapter.
Part 1.06: Setup the dev server
current setup doesn't provide us a developer friendly environment, we need to check the index.html file every time we make a change,
to resolve this we'll use webpack-dev-server.
npm i -D webpack-dev-serve
package.json
"scripts": {
...
"start": "webpack --mode development",
"start": "webpack serve --mode development"
...
},
npm start
open browser , go to http://localhost:8081
and open console, you'll see "hello world" .
To mock the behaviour of create-react-app and to run it on port 3000, make this changes
webpack.config.js
module.exports = (env, args) => {
return {
devServer: {
port: 3000,
open: true,
},
plugins: [new HtmlWebpackPlugin()],
};
};
npm start
the project will open browser and start on http://localhost:3000
.
Part 1.07: Set up HTML
create a public folder in root directory, create index.html
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
instead of using default index.html
in the dist folder we want to use the html file from public folder.
why? because it has the id root and we will render all our react element using this root element .
To use the public/index.html
modify the HtmlWebpackPlugin
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = (env, args) => {
return {
devServer: {
port: 3000,
open: true,
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'public', 'index.html'),
}),
],
};
};
npm run build
check dist/index.html
, the file should contain a div containing id root.
Part 1.8: Setup react
npm i react react-dom
src/App.jsx
import React from 'react';
const App = () => {
return <div>App</div>;
};
export default App;
src/index.js
import reactDom from 'react-dom';
import App from './App';
const root = document.getElementById('root');
reactDom.render(<App />, root);
npm start
you'll get an error
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
from the error we get the idea that we need a loader to load jsx syntax
Part 1.09: Enter babel
npm i -D babel-loader @babel/core
modify webpack.config.js to load js and jsx files with babel
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = (env, args) => {
return {
devServer: {
...,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: [{ loader: 'babel-loader' }],
},
],
},
plugins: [...],
};
};
npm start
you'll get an error
SyntaxError: /Users/fahadamin/workspace/test/src/index.js: Support for the experimental syntax 'jsx' isn't currently enabled
though we are loading our js and jsx with babel, babel isn't configured yet to handle jsx files.
we'll use @babel/preset-react to load jsx and @babel/preset-env to transpile modern js to es5 js for browser support.
create a .babelrc.json
file in the root directory
.babelrc.json
{
"presets": [
["@babel/preset-env"],
["@babel/preset-react", { "runtime": "automatic" }]
]
}
create a .browserslistrc
file for babel to transpile code for maximum brower support
.browserslistrc
1% # Browser usage over 1%
Last 2 versions # Or last two versions
IE 8 # Or IE 8
npm start
now your project can load jsx syntax
Part 1.10: Loading CSS
our project can run jsx but it can't load any css yet, css-loader and mini-css-extract-plugin to the resque.
we'll use css-loader to load css files and mini-css-extract-plugin to extract all the css in a single file.
npm i -D css-loader mini-css-extract-plugin
webpack.config.js
...,
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
module.exports = (env, args) => {
return {
...,
module: {
rules: [
...,
{
test: /\.(css)$/,
use: [
{ loader: MiniCSSExtractPlugin.loader },
{ loader: 'css-loader', options: { modules: true } },
],
},
],
},
plugins: [
...,
new MiniCSSExtractPlugin(),
],
};
};
ps: options: {modules: true}
has been set for css-loader so that we can use module css files
src/app.module.css
.test {
background-color: aquamarine;
}
src/App.jsx
import React from 'react';
import style from './app.module.css';
const App = () => {
return <div className={style.test}>App</div>;
};
export default App;
now your app will be able to load css.
part 1.11: Loading assets
webpack 5 comes with asset handlers out of the box, we just need to treat some file type as assets.
webpack.config.js
module.exports = (env, args) => {
return {
...,
module: {
rules: [
...,
{ test: /\.(png|jpg|jpeg)$/, type: 'asset' },
],
},
...
};
};
add a logo in src folder
src/App.jsx
import React from 'react';
import style from './app.module.css';
import logo from './logo.png';
const App = () => {
return (
<>
<div className={style.test}>App</div>
<img src={logo} alt='demo' />
</>
);
};
export default App;
now the project can load assets as well, our setup is ready for development.
Finishing up
This is a very basic setup, we can enhance it with typescript and also optimize our app for production build with plugins. I'll try to keep adding to the series , at the end of the day hopefully we'll end up with a production ready optimize build.
all the code of this article is available here.
Top comments (0)