DEV Community

Cover image for # Setup React app from scratch - Part-1(Get your project running)
B.M. Fahad-ul-Amin
B.M. Fahad-ul-Amin

Posted on

# Setup React app from scratch - Part-1(Get your project running)

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
Enter fullscreen mode Exit fullscreen mode

Part1.04: Initialize Project

first thing first create a repository.

mkdir my-app
cd my-app
Enter fullscreen mode Exit fullscreen mode

initialize the project

npm init --y   
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

you should get a promp saying

Can't resolve './src' ...
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

now if you run

./node_modules/.bin/webpack
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

to get rid of the error modify the command to

./node_modules/.bin/webpack --mode development
Enter fullscreen mode Exit fullscreen mode

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",
    ...,
  }
Enter fullscreen mode Exit fullscreen mode

run

  npm start
Enter fullscreen mode Exit fullscreen mode

the output should be same as

./node_modules/.bin/webpack --mode development
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

create webpack.config.js in root directory

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, args) => {
  return {
    plugins: [new HtmlWebpackPlugin()],
  };
};
Enter fullscreen mode Exit fullscreen mode
npm start
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

package.json

"scripts": {
...
"start": "webpack --mode development",
"start": "webpack serve --mode development"
...
},

npm start
Enter fullscreen mode Exit fullscreen mode

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()],
  };
};
Enter fullscreen mode Exit fullscreen mode
npm start
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'),
      }),
    ],
  };
};
Enter fullscreen mode Exit fullscreen mode
npm run build
Enter fullscreen mode Exit fullscreen mode

check dist/index.html, the file should contain a div containing id root.

Part 1.8: Setup react

npm i react react-dom
Enter fullscreen mode Exit fullscreen mode

src/App.jsx

import React from 'react';
const App = () => {
  return <div>App</div>;
};
export default App;
Enter fullscreen mode Exit fullscreen mode

src/index.js

import reactDom from 'react-dom';
import App from './App';
const root = document.getElementById('root');
reactDom.render(<App />, root);
Enter fullscreen mode Exit fullscreen mode
npm start
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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   
Enter fullscreen mode Exit fullscreen mode

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: [...],
  };
};
Enter fullscreen mode Exit fullscreen mode
npm start
Enter fullscreen mode Exit fullscreen mode

you'll get an error

SyntaxError: /Users/fahadamin/workspace/test/src/index.js: Support for the experimental syntax 'jsx' isn't currently enabled 
Enter fullscreen mode Exit fullscreen mode

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" }]
  ]
} 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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    
Enter fullscreen mode Exit fullscreen mode

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(),
    ],
  };
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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' },
      ],
    },
    ...
  };
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)