What does webpack do?

‘What the heck does webpack (https://webpack.js.org) do?’ One of my biggest fears in my current projekt was to use a module bundler without the support of angular cli. And so I thought lets have a look inside the ominous bundle.js.

deepdive

Task of my webpack should be to bundle all ts files, transpile them and output a bundle.js which I can include in my webpage. Our webpack.config.js looks like this.

const path = require('path');

module.exports = {
    // the entry module (will be explained later)
    entry: './src/main.ts', 
    // pretty print the bundle.js
    'mode': 'development',  
    module: {
        rules: [
            {
                // load all .ts files ...
                test: /\.ts$/,     
                // ... using the ts-loader ...      
                use: 'ts-loader',        
                // ... but exclude the node_modules folder
                exclude: /node_modules/  
            }
        ]
    },
    resolve: {
        extensions: ['.ts']
    },
    output: {
        // name the output bundle.js
        filename: 'bundle.js',                
        // write bundle.js into the dist folder of you project root
        path: path.resolve(__dirname, 'dist') 
    }
};

Most parts should be pretty straight forward but lets have a look at what the end result has to do with my configuration.

So for testing I start with my ./src/main.ts

import { Clip } from './models/clip';

const clip: Clip = new Clip('Webpack.movie');

And of course ./models/clip.ts

export class Clip {
    constructor(public description: string) { }
}

Lets run webpack and see what bundle.js looks like

/******/ (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, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// 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 = "./src/main.ts");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./src/main.ts":
/*!*********************!*\
  !*** ./src/main.ts ***!
  \*********************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _models_clip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./models/clip */ \"./src/models/clip.ts\");\n\r\nvar clip = new _models_clip__WEBPACK_IMPORTED_MODULE_0__[\"Clip\"]('Webpack.movie');\r\n\n\n//# sourceURL=webpack:///./src/main.ts?");

/***/ }),

/***/ "./src/models/clip.ts":
/*!****************************!*\
  !*** ./src/models/clip.ts ***!
  \****************************/
/*! exports provided: Clip */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Clip\", function() { return Clip; });\nvar Clip = (function () {\r\n    function Clip(toTime) {\r\n        this.toTime = toTime;\r\n    }\r\n    return Clip;\r\n}());\r\n\r\n\n\n//# sourceURL=webpack:///./src/models/clip.ts?");

/***/ })

/******/ });

So what is this, and where is my code? Lets have a look at what this code does. Simplified it does the following:

(function(modules) {
    // The module cache
    var installedModules = {};

    function __webpack_require__(moduleId) {
        // load module with id moduleId
    }

    // some global initialization

    // run entry module
    return __webpack_require__(__webpack_require__.s = "./src/main.ts");

})({
    // 'module 1'
    "./src/main.ts": function () { /* module 1 code  */ },

    // 'module 2'
    "./src/models/clip.ts": function () { /* module 2 code */ }

    // ...
    // 'module n'
})

When bundle.js was loaded, the function function(modules) gets executed and initializes webpack, which basically means it sets up the __webpack_require__ function. This function is the core when it comes to resolving modules by a module id. A module id is just the path to the filepath of the file containing the module. Then webpacks does

return __webpack_require__(__webpack_require__.s = "./src/main.ts"); 

We recognize that this load the file we specified as out entry module in webpack.config.js! How is this done? Lets look at the __webpack_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;
}

This function checks if the module was already loaded. Was it? No.

if(installedModules[moduleId]) {
    return installedModules[moduleId].exports;
}

Then the function creates a new entry in the module cache and initializes it:

var module = installedModules[moduleId] = {
    i: moduleId, // filepath
    l: false,
    exports: {} // remember this as 'the exports of the module'
};

Then webpack executes the module, but wait what is a ‘module’ in this context? It is just an entry int the dictionary which was passed to the startup function we are currently inspecting. An we are using the entry with key ‘./src/main.ts’. And what are we doing with this entry? We are executing it:

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

modules[moduleId].call(thisArg, arg1, ...) is the same as modules[moduleId].apply(thisArg, [arg1, ...]) and runs a function - just in the case you are wondering what .call() does.

So what does the module function look like?

function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _models_clip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./models/clip */ \"./src/models/clip.ts\");\n\r\nvar clip = new _models_clip__WEBPACK_IMPORTED_MODULE_0__[\"Clip\"]('Webpack.movie');\r\n\n\n//# sourceURL=webpack:///./src/main.ts?");

/***/ }

Well okay lets unwrap the eval:

__webpack_require__.r(__webpack_exports__);

/* harmony import */
var _models_clip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/models/clip.ts");
var clip = new _models_clip__WEBPACK_IMPORTED_MODULE_0__["Clip"]('Webpack.movie');

Much better! It requires the clip module and creates a new clip object.

__webpack_require__: the __webpack_require__ function from above

__webpack_exports__: this i named ‘the exports of the module’ (see above)

Lets analyze this. What does this code do? At first we have this:

__webpack_require__.r(__webpack_exports__);

It executes the r function on the webpack_require function … object well however you name it in javascript. This r function was initialized in the block I named ‘some global initialization’. This is the r function:

function(exports) {
    Object.defineProperty(exports, '__esModule', { value: true });
}

Okay this only creates a new property __esModule={value: true} on ‘the exports of the module’. Back to out module code. The next statement loads the clip module. Finally!!

var _models_clip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/models/clip.ts");

So loading a module means assiging the return value of __webpack_require__ to a variable created by webpack. But what is the return value? If we have a look at __webpack_require__ it returns ‘the exports of the module’ and as we already know before returning __webpack_require__ calls the module code, this time of the code of the clip module, and inside the clip module ‘the exports of the module’ is __webpack_exports__.

Here is the clip module (already eval unwrapped):

__webpack_require__.r(__webpack_exports__);

/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, "Clip", function() { return Clip; });

// the transpiled typescript code of the clip class
var Clip =  (function () {
                function Clip(description) {
                    this.description = description;
                }

                return Clip;
            }());

We know __webpack_require__.r(__webpack_exports__); already.

But we dont know __webpack_require__.d(__webpack_exports__, "Clip", function() { return Clip; });. Uff what is this again:

__webpack_require__.d = function(exports, name, getter) {
	if(!__webpack_require__.o(exports, name)) {
		Object.defineProperty(exports, name, {
			configurable: false,
			enumerable: true,
			get: getter
		});
	}
};

__webpack_require__.o = function(object, property) {
    return Object.prototype.hasOwnProperty.call(object, property);
};

At first a quick look at __webpack_require__.o = function(object, property), okay this checks if object has a property property.

Back to __webpack_require__.d. Ah okay it basically just creates a property with name name on ‘the exports of the module’ if there is not already a property with the same name. The getter of the property is the passed getter function. Fine, back to the clips module:

__webpack_require__.d(__webpack_exports__, "Clip", function() { return Clip; });

As we know __webpack_require__.d just creates a new property ‘Clip’ and the getter returns the Clip constructor.

So ‘the exports of the module’ contains a property which is the clip constructor. EASY!

Back to the main module:

__webpack_require__.r(__webpack_exports__);

/* harmony import */
var _models_clip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/models/clip.ts");
var clip = new _models_clip__WEBPACK_IMPORTED_MODULE_0__["Clip"]('Webpack.movie');

We said that __webpack_require__ returns ‘the exports of the module’ and ‘the exports of the module’[“Clip”] means the Clip constructor.

Thats all the magic!

Conclusion

We showed how webpack bundles several typescript files, they are just ‘modules’ and a ‘module’ is an entry inside the modules dictionary with its filepath as key. The value ‘module’ entry is a functions which sets properties on ‘the exports of the module’ which then can be accessed by other ‘modules’ using the __webpack_require__ function.

Fun Fact

If you review the whole bundle.js file there is absolutely no code which handles the cases when a module was not found. But why? Simply because webpack knows that will never ever happen!

Thank you for reading!