DEV Community

Cover image for Webpack FUNdamentals

Webpack FUNdamentals

Hello super devs, πŸ‘‹

How is everybody doing there? Good?

Are you watching lot of sports on Olympic Games? Has your country conquered many medals?
Remember to leave your chair sometimes and go outside to make some exercises also. There is no git revert HEAD for life.

As you can see above it took me a little bit to publish this article. πŸ˜… Never mind about OG anymore. πŸ˜‰

Today I am going to talk a little bit about Webpack and we are going to try to recreate a very simple React application, step by step.

OK, but what the heck is Webpack?

What the heck

Webpack πŸ•ΈοΈ πŸ“¦

Webpack is an open-source JavaScript module bundler. I know, blah blah blah ...

Blah Blah

Let's break into pieces so it becomes easy (or not) to understand:

  • module: a component or part of a program that contains one or more routines.
  • bundler: A group of objects held together, wrapped in a package.

Until yesterday, browsers couldn't handle code divided into multiple modules. The Webpack's mission is to wrap all the source code into a single file containing all the application code.

Do you really need to know about it? Sincerely you do not. I particularly like to understand how things work under the hood. Believe me, it can surprise you.

Under the hood

If you are still here, it is time to get hands dirty!

Project Bare-bones πŸ’€ 🦴

Let's start by creating a project structure similar to the image below:

Folder Structure on VS Code

  • package.json file:
{
  "name": "webpack-fun",
  "version": "0.0.1",
  "description": "webpack fundamentals",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

Installing the Webpack package

npm install --save-dev webpack webpack-cli
Enter fullscreen mode Exit fullscreen mode

Here we are installing the Webpack package as a development dependency as well as its cli (Command Line Interface).

Once done that, we should define the initial Webpack settings (webpack.config.js file):

Webpack Configuration File

const path = require("path");

const config = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "build"),
    filename: "main.js"
  }
};

module.exports = config;
Enter fullscreen mode Exit fullscreen mode

Don't you worry I will explain it: πŸ™Œ

  • path: NodeJS path module used for handling file paths.

  • config: Object that holds the Webpack configuration.

    • entry: Where Webpack looks to start building the bundle. The context is an absolute string to the directory that contains the entry files.
    • output: contains set of options instructing Webpack on how and where it should output your bundles, assets the bundle or load with Webpack.
  • module.exports: NodeJS special object that represents the current module, and exports is an object that will be exposed as a module.

Defining the build script (package.json) πŸ—οΈ

Now, we need to define the build script responsible for triggering the Webpack bundling.

{
  // ...
  "scripts": {
    "build": "webpack --mode=development"
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

I guess we are ready to test the application. Let's add some dummy code in a brand new src/index.js file, just to verify if it works:

const consoleLogTricks = name => {
  console.log("Look at this:")
  console.group("Question:")
    console.log(`Did you now about it, ${name}?`)
    console.log("Probably yes!")
  console.groupEnd()
};
Enter fullscreen mode Exit fullscreen mode

Now, if we run the build script created previously (npm run build), a new bundled file should be created at /build/main.js. It is the bundled content of our index.js file.

Isn't it amazing? Well, nothing special I guess. πŸ₯± πŸ₯±

Let's try to mimic a React-like application. Under the src directory create a file called App.jsx.

πŸ’‘ People often use the .js extension which is fine.
As my personal preference, when I am creating components I use the .jsx one. Their icons also change on VSCode and I know what that's about. πŸ˜‰ βš›οΈ

  • src/App.jsx.
const App = () => {
  return null;
}
// Remember to export :)
export default App;
Enter fullscreen mode Exit fullscreen mode
  • Import the App component in the index.js file we created previously:
import App from "./App"

const welcome = user => {
  console.log(`Welcome ${user}`)
}

App();
Enter fullscreen mode Exit fullscreen mode

We are almost there. At the moment your application is not doing too much. It is missing some packages that will help us to transform it in a minimal React application.

Go ahead and install them: πŸ’ͺ

npm install --save react react-dom
Enter fullscreen mode Exit fullscreen mode

Done that, it is time to rewrite your index.js and App.jsx files and use the packages we have just installed.

  • index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode
  • App.jsx
import React from "react";

const App = () => {
  return (
    <div>
      <h1>Hello from Webpack!</h1>
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Now we need to create an index.html file that will be the entry point of our application and load our bundled JavaScript code.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Minimal React App</title>
  </head>

  <body>
    <div id="root"></div>
    <script type="text/javascript" src="./main.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Loaders πŸ”„

We have an issue here. Do you remember that Webpack is a JavaScript bundler? Our components are using JSX that is a syntax extension to JavaScript.

πŸ—’οΈ If it confuses you, please refer to Introducing JSX.

The loaders come to our rescue.

  1. Installing the necessary Babel packages:
npm install @babel/core babel-loader @babel/preset-react --save-dev
Enter fullscreen mode Exit fullscreen mode
  1. Setting up a loader in the webpack.config.js file, under the module property.

This loader is responsible to transform JSX code into regular JavaScript.

// ...
module: {
  rules: [
    {
      test: /\.js$/,
      loader: "babel-loader",
      query: { presets: ["@babel/preset-react"] }
    }
  ]
}
// ...
Enter fullscreen mode Exit fullscreen mode

We should be able to bundle our application "properly" now. πŸŽ‰ 🎊 πŸ₯³

πŸ₯€ <Hydration Time> πŸ₯€

I know, it is quite overwhelming all this "setup" process. Luckily, you won't be configuring Webpack from scratch that often or maybe you never will.
Understanding at least the basics of how it works may be useful to you someday. Who knows? Β―_(ツ)_/Β―

Take your time to wrap up things in your head, eat a snack, drink a glass of Tang / Kool-aid and come back here when you feel ready.

No pressure at all! See you in 5 minutes. πŸ˜‚

πŸ₯€ </Hydration Time> πŸ₯€

There is just one important detail. If we try to make any async operation (e.g. REST API operations), it may happen that some browsers won't understand what is going on.

Joker

Babel has the polyfill package for solving this issue, so let's go for it. πŸ’ͺ

  • Installing polyfill:
npm install --save @babel/polyfill
Enter fullscreen mode Exit fullscreen mode
  • Add it into the entry property in our webpack.config.js file.
const config = {
+  entry: ['@babel/polyfill', './src/index.js'],
-  entry: "./src/index.js",
  output: {
    // ...
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Transpilers πŸ€” ⁉️

This word looks weird. Actually it sounds like a pile of Transformers. πŸ₯ 😭

Transformers pile

Bad jokes aside, it is the term used to mean that a source code is transformed from a language to another. (Maybe Transformed + Compiled ?)

Anyway, the question is why do we need a transpiler?
It is known that most browsers don't really support the newest JavaScript features such as ES6, ES7, ES11 and so on.
The function of a transpiler is (guess what? πŸ«‚) to transpile those new features into standard ES5.

  • Installing the preset:
npm install @babel/preset-env --save-dev
Enter fullscreen mode Exit fullscreen mode
  • Adding the @babel/preset-env plugin in the webpack.config.js file.
// ...
{
  test: /\.js$/,
  loader: 'babel-loader',
  query: {
               // πŸ‘‡ H e r e ! πŸ‘‡ 
    presets: ['@babel/preset-env', '@babel/preset-react']
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally we are able to write JavaScript code using all the latest features. πŸ‘¨β€πŸ’»

Adding CSS πŸ’…

A web application without CSS is like a cheese burger without the hamburger. πŸ” πŸ§€
I mean, it is totally possible but it is not the same thing. There is a flavor missing somewhere. 🀣

Cheese burger without a burger

  1. Let's create a CSS file on src/index.css:

⚠️ The commercial usage of this file is prohibited by law ⚠️

.wrapper {
  empty-cells: show;
  background-color: mediumaquamarine;;
  color: blanchedalmond;
}
Enter fullscreen mode Exit fullscreen mode
  1. Import it on index.js file:
import './index.css'
Enter fullscreen mode Exit fullscreen mode
  1. Apply it in the App.jsx component:
const App = () => {
  return (
    <div className="wrapper">
      <h1>Hello from Webpack</h1>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

🀺 TouchΓ© moment: Yes, we need to install more loaders in order to make our CSS styles work as well. How did you know that? πŸ€“

npm install style-loader css-loader --save-dev
Enter fullscreen mode Exit fullscreen mode

In short:

  • style-loader: Generates and injects a <style> element which contains all of the application styles.
  • css-loader: Transforms CSS into a JavaScript module and allows minification. E.g.(Input: CSS ➑ Output: JavaScript)

Please, don't forget to also add the loaders in your webpack.config.js file, otherwise all our effort and the RSI (Repetitive Strain Injury) acquired by typing npm install hundred times will be in vain: πŸ˜‚ πŸ‘Œ

{
  rules: [
    {
      // ... previous config
    },
    // ⬇️  πŸ‘‡  πŸ‘‡  ⬇️
    {      
      test: /\.css$/,
      loaders: ['style-loader', 'css-loader'],
     },
     // ⬆️  ☝️  ☝️  ⬆️
  ];
}
Enter fullscreen mode Exit fullscreen mode

Webpack Development Server πŸ–₯️ ⬆️

One thing that drive me nuts is having to manually refresh the page every time I make changes in the application. πŸ₯΄

Drive me nuts

Don't stress yourself, at this point of the article you have already mastered using npm to install packages. πŸŽ“ πŸ˜‚

  1. Install the server package:
npm install --save-dev webpack-dev-server
Enter fullscreen mode Exit fullscreen mode
  1. Define the start script in your package.json file.
{
  // ...
  "scripts": {
    "build": "webpack --mode=development",
    //  πŸ‘‡  πŸ‘‡  πŸ‘‡  πŸ‘‡
    "start": "webpack-dev-server --mode=development"  
    },
  // ...
}
Enter fullscreen mode Exit fullscreen mode
  1. Add the devServer property into webpack.config.js file:
const config = {
  entry: './src/index.js',
  output: {
    // ...
  },
+  devServer: {    
+    contentBase: path.resolve(__dirname, 'build'),
+    compress: true,    
+    port: 3000  
+   },
  // ...
};
Enter fullscreen mode Exit fullscreen mode

Running the npm start script in your terminal should start the server at http://localhost:3000.

Error Handling 🚫

The way Webpack shows error differs a little if compared to an application created using create-react-app.
Very often a error is shown but not its real location.

Source maps provide us with the source code that is actually causing the error.

Bug tracking

This time, as an rare exception, you won't need to install anything. πŸŽ‰ πŸ₯³ πŸ‘―β€β™€οΈ

Just add the devtool property in our webpack.config.js file and a source map will be generated (build/main.js.map).

const config = {
  entry: './src/index.js',
  output: {
    // ...
  },
  devServer: {
    // ...
  },
+  devtool: 'source-map', πŸ‘ˆ
  // ..
};

Enter fullscreen mode Exit fullscreen mode

Minifying the source code in production

Since we are using Webpack in its latest version, no further configuration is need in order to minify the source code.

Ah, ok. I almost forget to explain what minifiyng code means.

Minifying means that your code that was previously classified as illegible by your workmates becomes officially gibberish. πŸ˜‚ 🀣 πŸ˜† 😭

Pair Programming

Minifying is the process that removes both comments, whitespaces, newline characters, replaces variable names with a single character and so on, in order to minimize code and reduce file size (TLDR; to optimize performance).

As result of minifying we would have something like this:

!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;
Enter fullscreen mode Exit fullscreen mode

We need to add a npm script (package.json) to do so.
⚠️ Note: --mode=production. ⚠️

"scripts": {
+  "build": "webpack --mode=production",
  // ...
},
Enter fullscreen mode Exit fullscreen mode

Conclusion, Frenzies and Farewell πŸ’­πŸ’‘πŸ‘‹

Indeed, there a lot more concepts to cover regarding Webpack but hopefully this introduction is enough for you to go and start exploring it by your own. πŸ—ΊοΈ πŸ“

Albert Einstein once said: 🧠

If you can't explain it to a six year old, you don't understand it yourself.

After finish reading this whole article to my 2 years old sibling, he starts to uncontrollably crying.
I guess it is happy crying because it looks like he got it even being younger than the expected! πŸ‘Ά

Happy Crying

Sorry, I must go now. He is becoming really loud. πŸƒβ€β™‚οΈπŸ’¨
Maybe I should read him my other article about Regex. 😬 🀭

Congratulations for reaching the end πŸŽ‰ and I wish you all a great weekend. πŸ™

See ya!

Discussion (0)