DEV Community

Njeri Ngigi
Njeri Ngigi

Posted on

Getting Started with React from Scratch Using Webpack

Understanding Webpack

What is webpack?

Webpack simply put is a module bundler. In the context of React and JavaScript, it bundles JS files for usage in a browser. It ingests raw react components (other frameworks too) and produces JS code understandable by browsers.

Why webpack?

Web browsers are designed to consume HTML, CSS & JavaScript, and as a project grows, configuring and keeping track of these files can be complicated. Webpack steps in to solve for this. An alternative to using webpack would be using task runners such as Gulp and Grunt. Other JavaScript packers include, (but not limited to), Parcel, Browserify, Rollup, and JSPM.

Webpack treats your project as a dependency graph starting from the index.js file pulling in dependencies using imports. It does all the preprocessing of files and results in bundles, guided by the configurations provided.

The bundling process

Webpack relies on loaders and plugins to transform specific inputs to proper outputs, for example, the babel-loader aids in transforming ES2015+ down to common JS, and to process HTML webpack uses a html-loader. Webpack loaders take in input and process it into an output called a bundle.

The bundling process begins from user-defined modules, that can point to other modules through imports. When bundling a project using webpack, it traverses the imports creating a dependency graph and generates outputs based on configurations from the webpack.config.js file.

Webpack evaluates each user entry, matching them against loader configurations that tell webpack how to transform them. It tries to match the entry to the file system using an entry's resolve configuration (which you can configure aliases for certain files or extensions).

Webpack evaluates matched loaders from bottom to top and from right to left. For example,

{
  use: ["style-loader", "css-loader"]
}

webpack will use the loaders from the right (last element in the array), so the order of how you add the loaders matter.

Loaders are pure JavaScript functions, taking in input data, transforming that data and returning it. When you use 2 or more loaders, the output data from one loader is passed onto the next one as input.

After all the loaders are found, webpack evaluates matched loaders while running the module through each loader, resulting in an output that will be injected into the resulting bundle. Once the evaluation of every module is complete, webpack writes an output, which includes a bootstrap script that describes how to start executing the result on the browser.

Plugins are more powerful than loaders and allow you to intercept runtime events at different stages of the bundling process, but are harder to maintain

Sources:

Let's write some code

Setting up the Project Directory

Create a project folder and add an src folder (where all react code is going to go into).

/my-react-project
    /src

Setup the project directory using npm and git:

  • npm init or yarn init to create a package.json file that will contain the scripts need to run the code and dependencies needed for the app.
  • git init to set up a new Git repository

Add a .gitignore file, for all the files you don't want to commit, and add node_modules to it

/my-react-project
    /src
    package.json
    .gitignore

Configure Webpack

Install dev dependencies: webpack and webpack-cli
npm i webpack webpack-cli --save-dev or yarn add webpack webpack-cli --dev (I will be using yarn throughout).

Create a webpack.config.js in the root directory.

Setup Babel

Install Dev Dependencies:
yarn add @babel/core @babel/preset-env @babel/preset-react babel-loader

  • babel-core: Transforms ES6 to ES5
  • babel-loader: helps webpack in transforming JavaScript dependencies
  • babel-preset-env: determines which transformations/plugins to use and also determines the polyfills to use to suit different browsers.
  • babel-preset-react: adds support for JSX

Create a .babelrc file in the root directory and add the 2 presets need to convert react into browser-friendly content

// .babelrc
{
    "presets": ["@babel/preset-env", "@babel/preset-react"]
}

Add rules to the webpack.config.js, they instruct webpack to use the babel-loader to transform files with the .js or .jsx extensions to JavaScript code understandable by browsers.

//webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,   
        exclude: /node_modules/,   
        use: {
          loader: "babel-loader"
        } 
      }
    ]
  }
}

Add React Components

To write react components we need to install dependencies: yarn add react react-dom.

react-dom acts as a glue between the components you create and the DOM. It lets you render components on the DOM.

We will need to create a simple react component to test if we have set up the project correctly. Create a components folder under src and add a sample component file form.jsx. Create a simple component that takes user input and displays it in a paragraph.

// form.jsx
import React, { Component } from 'react';

class Form extends Component {
  constructor() {
    super();
    this.state = {
      name: ''
    }
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const { value } = event.target;
    this.setState({ name: value })
  }

  render() {
    return (
      <form>
        <input placeholder="Your name?" type="text" onChange={this.handleChange}/>
        <p>{this.state.name}</p>
      </form>
    )
  }
}

export default Form;

HTML plugin

To render the component we just created we need to hook it up to a HTML template. Webpack needs a loader to help it work with HTML and we need to add rules for that.

But first install dev dependencies: yarn add html-webpack-plugin html-loader --dev.

The html-webpack-plugin plugin generates a HTML file with the <script> tag injected, it writes this into the dist/index.html file and minifies the file.

Update the webpack.config.js:

// webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        ...
      },
      {
        test: /.html$/,
        use: [
          { 
            loader: "html-loader"
          }
        ]
      }
    ],
    plugins: [
      new HtmlWebPackPlugin({
        template: ".src/index.html" // absolute path to the html template
      })
    ]
  }
}

Now create the HTML template in an index.html file in the src folder and add a div with an id container, this is where the component we created will be mounted

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Dashboard</title>
  </head>
  <body>
    <div id="container"></div>
  </body>
</html>

Finally, you need to mount the component on the DOM. Create an index.jsx file in the src folder.

import React from 'react';
import ReactDOM from 'react-dom';
import Dashboard from './containers/Form';

const wrapper = document.getElementById('container');
wrapper ? ReactDOM.render(<Form/>, wrapper) : false;

Your final folder structure would be:

/my-react-project
    /src
        /components
            Form.jsx
        index.html
        index.jsx
    .babelrc
    .gitignore
    package.json
    webpack.config.js

CSS Setup

Install dependencies: yarn add css-loader node-sass sass-loader style-loader

  • css-loader (translates CSS to JS strings): reads CSS from the .css files and resolves the CSS correctly (such as the import and URL() statements)

  • style-loader (creates style tags from JS strings): adds the CSS to the DOM so that styles are active and visible on the page

  • sass-loader (translates SASS to CSS): reads from .scss files and pre-processes it to CSS.

  • node-sass (SASS compiler used by sass-loader): a library that provides a binding for node.js to LibSass (the C version of SASS), allowing fast compiling of .scss files.

Webpack needs loaders to understand CSS and sass.

Update the webpack.config.js file:

// webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        ...
      },
      {
        test: /.html$/,
        ...
      },
      {
        test: /\.(css|scss)$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ],
    plugins: [
      ...
    ]
  }
}

Tie up some loose ends

To import elements from files without using file extensions, you would need to add a rule in the webpack.config.js file to resolve them.

Update the webpack.config.js file:

// webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");

module.exports = {
  module: {
    rules: [
      ...
    ],
    resolve: {
        extensions: ['.js', '.jsx']
    },
    plugins: [
      ...
    ]
  }
}

Build and Run

We are ready to build our sample app.
In the package.json add a build script to do this:

{
  ...,
  "scripts": {
    "build": "webpack --mode production"
  },
  ...
}

Run yarn run build. This should create a dist folder where you'll find a HTML file, open this on your browser, you should see the form we created.

However, you want to be able to view changes as you work on your app without having to build it every time. To do this we need a server that will build and reload anytime changes are made.

Install dev dependencies: yarn add webpack-dev-server --dev.

Add a script to start the app:

{
  ...,
  "scripts": {
    "build": "webpack --mode production",
    "start": "webpack-dev-server --hot --open --mode development",
  },
  ...
}

The --open flag opens your default browser to display the running app.
The --hot flag only reloads the component changed, rather than doing a whole page reload (Hot Module Replacement).

Run yarn start, you should be able to view the form on your default browser.

You're all set up now! Go forth and create stuff!

Top comments (0)