Intro to Webpack
Alot of Webpack introdutions focus on how to set up your first webpack configuration.
What I want to do in this article is taking a step back and look at what webpack is actually doing before going into the setup and all its complications.
The problem
In the frontend we have a variety of targets that we want our applications to run in, and they are all browsers. Some old, some new.
While the Javascript ecosystem is evolving at an extreme pace and SPAs + more complex web applications become the norm, the browsers evolve slowly.
In addition alot of people still use outdated ones, which might be due to workplace restrictions or the proper longterm use of hardware.
Now we have a dilemma. We want to write complex applications in Javascript, but in the browser we usually load an app via a single file.
React is a good example.
You would usually load it with sth like:
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
But the developers definitely did not develop this complex piece of software in just one file! The complexity of doing that with a team of developers and even alone would have too many downsides.
Looking at the repository we can see a lot of different folders and files, that all host their specific part of the application logic.
In order for the different parts to use each other and share functonalities they are using the import
syntax in Javascript. If you do not know how this works you can read about it here or just do a quick duckduckgo search.
The downside of this approach is that most of the aforementioned browsers do not yet, or never will support it. In addition to that all of those would have to be loaded over the web individually. While this is one thing that HTTP/2 might make less of a problem, it still is one to date.
Enter webpack
This exact situation is where webpack fits in perfectly!
In order to get all your different components and Javascript files that import each other to work in the browser you will need to bundle them together somehow and in essence webpack will do just that.
It take multiple files and figures out how they import each other and then bundles them together into 1 big file that provides wrapper functions to mimic the process of importing.
Lets make this a bit clearer with the following example:
// mathHelpers.js
export function add(a, b) {
return a + b;
}
// main.js
import { add } from "./mathHelpers.js";
function main() {
const a = 5;
const b = 37;
const result = add(a, b);
console.log(`The result of ${a} + ${b} = ${result}`);
}
We have the main file which imports the mathHelpers
to do some difficult calculations. This helps us to split out different parts of the application into different files. The benefit being a good structure and reusability of f.e. the math functions.
Now we are going to have to bundle those 2 together into 1 file.
The first step is installing webpack. To use it on the command line we need to install it on the computer with npm i -g webpack-cli
.
Afterwards we can just run webpack main.js --mode=none
in our application folder and the bundling is done! Its really that easy.
The mode option just says that no optimizations should be done, and all we want is a bundled file to check out its contents.
After the operation has completed we can inspect the bundle in the newly generated dist
folder.
The content should be similar to this:
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _mathHelpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
function main() {
const a = 5;
const b = 37;
const result = Object(_mathHelpers_js__WEBPACK_IMPORTED_MODULE_0__["add"])(1 + 2);
console.log(`The result of ${a} + ${b} = ${result}`);
}
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add", function() { return add; });
function add(a, b) {
return a + b;
}
/***/ })
/******/ ]);
Dont worry too much about all of this machine generated code.
Whats important to understand is that the top of the file is the functionality that webpack provides to fake the importing that we are using in our files.
If you inspect the bottom of the file though, you can see that we still have our main
and add
function.
The difference here is though, that the main function now calls Object(_mathHelpers_js__WEBPACK_IMPORTED_MODULE_0__["add"])
instead of the add function directly.
We have now reached a basic understanding of webpack and can say that the different modules are bundled together under an object and named in a way that all other modules know where to find the functions that they need. All the calls to other modules are then also automatically replaced with the correct call to the Object+Name.
What now?
With this knowledge as a basis you can now start with understanding how the basic webpack main.js --mode=none
can be used with configurations to apply some transformations to your codebase.
This will allow things like using the latest EcmaScript features, code splitting and many more, some of which I will cover in later posts.