Articles synchronized with
Github blog

You probably know how to use Webpack and have a rough idea of how it works, but have you ever wondered what the bundle.js output from Webpack looks like? Why are the original module files merged into a single file? Why does bundle.js run directly in a browser?

Simple engineering package

Let’s build a CommonJS modularized project with Webpack. The project has a web page that displays Hello,Webpack, in JavaScript.

Before running the build, you need to set up the basic JavaScript files and HTML for this function. You need the following files:

Page entry file index.html

<html> <head> <meta charset="UTF-8"> </head> <body> <div id="app"></div> <! <script SRC ="./dist/bundle.js"></script> </body> </ HTML >Copy the code

JS tool function file show.js

// Manipulate the DOM element, The function to display the content to the website show (content) {window. The document. The getElementById (" app "). The innerText = 'Hello,' + content; Module. exports = show;Copy the code

JS execution entry file main.js

// Import show function from CommonJS const show = require('./show.js'); // execute show function show('Webpack');Copy the code

By default, Webpack reads the configuration from the webpack.config.js file in the project root directory when it executes the build, so you’ll need to create a new one as well, which looks like this:

const path = require('path'); Module. exports = {// JavaScript executes entry: './main.js', output: {// consolidates all dependent modules into a bundle.js file filename: Resolve (__dirname, './dist'),}};Copy the code

With all the files in place, run the webpack build in the root directory of your project, and you’ll find a dist folder with a bundle.js file, which is an executable JavaScript file. It contains two modules on which the page depends, main.js and show.js, and the built-in webpackBootstrap startup function. When you open the index.html page in your browser, you’ll see Hello,Webpack.

Webpack is a tool for packaging modular JavaScript. It starts from main.js, identifies modular import statements in the source code, recursively looks for all dependencies in the import and export file, and packages the entry and all its dependencies into a single file. Support for ES6, CommonJS, and AMD modular statements has been built in since Webpack2.

Output code analysis

Let’s take a look at the bundle.js file from the simplest project, which looks like this:

// webpackBootstrap starts the function // modules is an array of all modules, Function (modules) {var installedModules = {}; var installedModules = {}; // Load a module from the array, Function __webpack_require__(moduleId) {// If the module to be loaded has already been loaded, Exports; if (installedModules[moduleId]) {return installedModules[moduleId].exports; Var module = installedModules[moduleId] = {// Index of the module in the array I: ModuleId, // Whether the module is fully loaded l: false, // Exports of the module: {}}; // Get the function corresponding to the module whose index is moduleId from modules // call this function, Modules [moduleId]. Call (module.exports, module, module.exports, __webpack_require__); // Mark this module as loaded module.l = true; // returns the exported value of this module. } // publicPath in Webpack configuration, used to load split asynchronous code __webpack_require__.p = ""; // Use __webpack_require__ to load a module with index 0 and return the exported contents of that module. // The module with index 0 is the file corresponding to main.js. // __webpack_require__. S = 0; // __webpack_require__. } (// All modules are stored in an array, /* 0 */ (function (module, exports, exports, exports) __webpack_require__) {// Import the show function with the __webpack_require__ specification, Const show = __webpack_require__(1); // Execute show('Webpack');  }), /* 1 */ (function (module, exports) { function show(content) { window.document.getElementById('app').innerText = 'Hello,' + content; Module.exports = show;})];Copy the code

The above seemingly complex code is actually an immediate function, which can be abbreviated as:

Function (modules) {function(webpack_require__() {} function(modules) {function(webpack_require__) {} })([/* Array of all modules */])Copy the code

The reason bundle.js can run directly in the browser is that the output file defines a loading function that can be executed in the browser through the __webpack_require__ function to simulate the Require statement in Node.js.

The reason why separate module files are merged into a single bundle.js file is that browsers cannot load module files locally as quickly as Node.js, and must load files that are not yet available through network requests. If there are many modules, the load time will be very long, so put all modules in an array and perform a network load.

If you look closely at the implementation of the __webpack_require__ function, you will also find that Webpack is cache optimized: modules that have been loaded will not be executed a second time, the results of the execution will be cached in memory, and when a module is accessed a second time, the cached return value will be read directly from memory.

According to the need to load

When optimizing a single page application for on-demand loading, the following principles are generally used:

  • Divide the site into smaller features, and then categorize them according to how relevant each feature is.
  • Merge each category into a Chunk and load the corresponding Chunk as needed.
  • Don’t load on demand the features that the user will see when they first open your site. Instead, put them in the Chunk where the execution portal is located to reduce the user’s perceived page load time.
  • For individual function points that rely on a lot of code, such as those that rely on chart.js to draw charts and flv.js to play videos, they can be loaded on demand.

The loading of partitioned code needs to be triggered at a certain time, that is, when the user has operated or is about to operate the corresponding function, the corresponding code will be loaded. The load timing of partitioned code needs to be measured and determined by the developers themselves according to the requirements of the web page.

Since code that is partitioned off for on-demand loading takes time to load, you can predict what the user will do next and load the code in advance so that the user doesn’t know when the network load time is.

Load on Demand with Webpack Webpack comes with a powerful split code feature to load on demand, which is very simple to implement.

For example, you now need to make a page optimized for loading on demand:

When the page first loads, it only loads the main.js file, which displays a button. The main.js file contains only the code to listen for button events and load on demand. When the button is clicked, the partitioned show.js file will be loaded, and functions in show.js will be executed after loading successfully. The contents of the main.js file are as follows:

Window. The document. The getElementById (" BTN "). The addEventListener (' click ', function () {/ / when the button is clicked to load after the show. The js file, Import (/* webpackChunkName: "show" */ './show'). Then ((show) => {show('Webpack'); })});Copy the code

The contents of the show.js file are as follows:

module.exports = function (content) {
  window.alert('Hello ' + content);
};Copy the code

The most critical line in the code is import(/* webpackChunkName: “show” */ ‘./show’). Webpack has built-in support for import(*) statements.

  • Create a Chunk with./show.js as the entry./show.js.
  • The file generated by Chunk is loaded only when the code executes to the import statement.
  • Import returns a Promise, which can be retrieved from the show.js export in the Promise’s then method when the file loads successfully.

After splitting the code with import(), your browser needs to support the Promise API for the code to work, because import() returns a Promise, which relies on promises. For browsers that don’t natively support Promises, you can inject Promise Polyfill.

/* webpackChunkName: “show” */ means to give a name to the dynamically generated Chunk so that we can trace and debug the code. If you do not specify the name of dynamically generated Chunk, the default name will be [id].js. /* webpackChunkName: “show” */ is a new feature introduced in Webpack3. Before Webpack3, it was impossible to name dynamically generated chunks.

Load output code analysis on demand

The output file of Webpack changes when the optimization method of load on demand is used.

For example, change the source main.js to the following:

Js import('./show').then((show) => {// execute show function show('Webpack'); });Copy the code

The rebuild outputs two files, the execution entry file bundle.js and the asynchronous loading file 0.bundle.js.

The contents of 0. Bundle. js are as follows:

/ / load in this file (0. Bundle. Js) contained in the module webpackJsonp (/ / in the other file storage module ID [0], / / this file contains module [/ / show. The corresponding module will be js (function (the module, exports) { function show(content) { window.document.getElementById('app').innerText = 'Hello,' + content;  } module.exports = show; }) ] );Copy the code

Bundle.js contains the following contents:

(function (modules) {/*** * webpackJsonp is used to install modules from asynchronously loaded files. * webpackJsonp is mounted globally for easy invocation in other files. * * @param chunkIds Chunk IDS for modules to be installed in asynchronously loaded files * @param moreModules List of modules to be installed in asynchronously loaded files * @param executeModules After all modules to be installed are successfully installed in the asynchronously loaded file, ["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, Var moduleId, chunkId, I = 0, resolves = [], result; for (; i < chunkIds.length; i++) { chunkId = chunkIds[i]; if (installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][0]); } installedChunks[chunkId] = 0; } for (moduleId in moreModules) { if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } while (resolves.length) { resolves.shift()(); }}; Var installedModules = {}; // Store the loading status of each Chunk; Var installedChunks = {1: 0}; Function __webpack_require__(moduleId) {function __webpack_require__(moduleId) {//... } /** * is used to load partitioned, File of Chunk to be loaded asynchronously * @param chunkId ID of Chunk to be loaded asynchronously * @returns {Promise} */ __webpack_require__.e = function RequireEnsure (chunkId) {// Get the loading status of chunkId chunks from installedChunks defined above var installedChunkData = installedChunks[chunkId]; // If the loading status is 0, the Chunk has been loaded successfully. If (installedChunkData === 0) {return new Promise(function (resolve) {resolve(); }); If (installedChunkData) {// Returns a Promise object stored in the installedChunkData array return installedChunkData[2]; } // installedChunkData is empty, indicating that the Chunk has not been loaded. Var promise = new promise (function (resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); installedChunkData[2] = promise; // by DOM manipulation, To insert a script tag in the HTML head asynchronous loading the Chunk corresponding JavaScript file var head = document. The getElementsByTagName (' head ') [0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.charset = 'utf-8'; script.async = true; script.timeout = 120000; SRC = __webpack_require__.p + "" + chunkId + ".bundle.js"; // The file path is publicPath and chunkId. Var timeout = setTimeout(onScriptComplete, 120000); script.onerror = script.onload = onScriptComplete; Function onScriptComplete() {// Prevent memory leaks script.onerror = script.onload = null; clearTimeout(timeout); Var Chunk = installedChunks[chunkId]; var Chunk = installedChunks[chunkId]; if (chunk ! == 0) { if (chunk) { chunk[1](new Error('Loading chunk ' + chunkId + ' failed.')); } installedChunks[chunkId] = undefined; }}; head.appendChild(script); return promise; }; Return __webpack_require__(__webpack_require__.s = 0); }) (// store all non-asynchronously loaded, Function (module, exports, exports, function (module, exports, exports) __webpack_require__) {// Use __webpack_require__. E to load Chunk of show.js asynchronously __webpack_require__. E (0). Then (__webpack_require__. Bind (null, 1)), then ((show) = > {/ / execution show function show (' Webpack);}); })]);Copy the code

Bundle.js is very similar to bundle.js, except that:

An additional __webpack_require__. E is used to load files corresponding to chunks that need to be asynchronously loaded. A webpackJsonp function is used to install modules from asynchronously loaded files. The output file for CommonsChunkPlugin extraction is the same as the output file for asynchronous loading, with __webpack_require__. E and webpackJsonp. The reason is that both extracting common code and asynchronous loading are essentially code splitting.

reference

  • Easy to understand Webpack
  • A brief analysis of webpack packaged code