[webpack] Webpack package file analysis

[webpack] Webpack package file analysis

[webpack] Webpack package file analysis

Preface

  1. Understand how the code compiled and packaged by webpack runs in the browser.
  2. How webpack is modularized in the browser. What do different module loading methods look like after compilation.
  3. Analyze how different module loading methods run after compilation. For example, asynchronous loading

Basic environment configuration

  1. Environment configuration

    # New Directory midir webpack the Analysis &-cd-webpack the Analysis # initialize npm the init # installation webpack environment npm install webpack webpack-cli webpack- dev-server --save-devCopy code
  2. Write webpcack configuration file

    const path = require ( 'path' ); const HtmlWebpackPlugin = require ( 'html-webpack-plugin' ); module .exports = { //There are many differences between the webpack configuration in the development environment and the production environment mode: ' development' , entry : { index : "./src/index.js" , }, output : { path : path.resolve(__dirname, 'dist' ), //The output directory can only be an absolute directory filename: ' [name].js' }, module : {}, plugins : [ new HtmlWebpackPlugin({ template : './src/index.html' }) ] } Copy code

Package file analysis

1. Scenario 1: node module introduction (require)

When the entry file is a normal js, for example,

//src/index.js let name = require ( "./title" ); Copy code
//src/title.js module .exprots = "name: yueqi" Copy code

After being packaged by webpack, the compiled file looks like this, so simply analyze its structure

( function ( modules ) { ... })({ "./src/index.js" : ( function ( module, exports, __webpack_require__ ) { let name = __webpack_require__( "./src/login.js" ); console .log(name) }), "./src/login.js" : ( function ( module, exports ) { let name = "Yueqi" module .exports = name }) }) Copy code

First of all, you can see that this is a

IIFE
Self-executing function, the passed in modules parameter is an object, the js file packaged for us and its dependent files

The key of the modules object is the module ID, which is a relative path relative to the project root directory.

The value is a function, a module definition of common.js.

Any code written by the user will become the function body of common.js

Next we look at how it works.

The last of the self-executing functions is the call

__webpack_require__
Method, load the entry module, and return exports

Let's take a look at this method

( function ( modules ) { //Module cache var installedModules = {}; //In the browser, webpack implements a set of commonJs execution mechanism function __webpack_require__ ( moduleId ) { //Check whether the module exists in the cache, if If exists, directly return the module object in the cache if (installedModules[moduleId]) { return installedModules[moduleId].exports; } //If it does not exist in the cache, create a new module and place it in the module s cache var module = installedModules[moduleId] = { i : moduleId, //identify module ID, module identifier l: false , //loaded means whether it has been loaded successfully or initialized successfully exports: {} //the export object of this module }; //execute the function we passed in outside the self-executing function, that is, the current module function, the function we passed in modules[moduleId].call( module .exports, module , module .exports, __webpack_require__); module .l = true ; //Return the export object of this module return module .exports } .... //Load the entry module and return exports return __webpack_require__(__webpack_require__.s = "./src/index.js" ) })({ "./src/index.js" : ( function ( module, exports, __webpack_require__ ) { let name = __webpack_require__( "./src/login.js" ); console .log(name) }), "./src/login.js" : ( function ( module, exports ) { let name = "Yueqi" module .exports = name }) }) Copy code

Execute to

__webpack_require__
In the method, the module ID is written into the cache, and the module function passed in at the end of the self-executing function is called at the same time.

Get the content in the module js,

Complete the current dependency loading

2. Scenario 2: es module is compatible with commonJs module

Entry js

let title = reuqire( './ title ' ) console .log(person.name) Copy code

Title.js

export const name = "yueqi" export default age = 11 Copy code

After running the packaging command, I get the following js, which looks like this after formatting

( function ( modules ) { //Module cache var installedModules = {}; //function __webpack_require__ ( moduleId ) { if (installedModules[moduleId]) { return installedModules[moduleId].exports; } //Create a new module, and We know that var module = installedModules[moduleId] = { i : moduleId, l : false , exports : {} }; modules[moduleId].call( module .exports, module , module .exports, __webpack_require__); module .l = true ; return module .exports } //assign the modules object to the attribute of require.m __webpack_require__.m = modules; //put the cache object of the module in the attribute of require.c __webpack_require__.c = installedModules; //Add an attribute to an object //Define a getter function for compatibility with export __webpack_require__.d = function ( exports, name, getter ) { if (__webpack_require__.o(exports, name)) { Object . defineProperty(exports, name, { enumerable : true , get : getter }) } } //Indicates that this is an es6 module __webpack_require__.r = function ( exports ) { if ( typeof Swmbol !== 'undefined' && Symbol .toStringTag) { Object .defineProperty(exports, Symbol .toStringTag, { value : 'Module' }) } //Indicates that this is an es6 module Object .defineProperty(exports, '__esModule' , { value : true }) } //Create a simulated named object and wrap an arbitrary module (common.js es module) into the form of es module //{1: shouleRequire, 8: directReturn, 4: noWrapper, 2: copyProperties} //value It may be a module ID, or it may be the export object of a module //Create a simulated namespace object //mode & 1 value is a module Id, which needs to be loaded through require //mode & 2 Add es to the module Attribute, merge attributes, return the current module //mode & 4 means that value is an es6 module, return an esmodule module //mode & 8 directly return __webpack_require__.t = function ( value, mode ) { //if it is 1 If true, it means that the first bit is 1, then it means that the value is the module id, and it needs to be loaded directly through require if (mode & 1 ) { //Load the module Id, and re-assign the value to the everywhere object value = __webpack_require__(value) } //8 is equivalent to 1000, indicating that you can directly return if & 1 & 8 want to block the rain. 1001 behaves similar to require if (mode & 8 ) { return value } //0100 indicates that it is a packaged es6 module if (mode & 4 && value === 'object' && value.__esModule) { return value } var ns = Object .create( null ); //Create a new object Object .defineProperty(ns, 'default' , { enumerable : true , value }); //0001 mode === 2 means to copy all the attributes of value to the namespace. if (mode & 2 & typeof value !== 'string' ) { for ( let key in value) { __webpack_require__.d(ns, key, function ( key ) { return value[key].bind( null , key) }) } } //this The method will be used to return ns when it is loaded later ; } //Determine whether the object has a certain property __webpack_require__.o = function ( object, property ) { return Object .prototype.hasOwnProperty.call(object, property); } //If the module.__esModule attribute indicates that this is an es module, then module.default is returned. //If there is no __esModule attribute, it indicates that this is an ordinary common.js module, then directly return to module __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; } //publicpath of webpack __webpack_require__.p = "" ; return __webpack_require__(__webpack_require__.s = "./src/index.js " ) })({ "./src/hello.js" :( function ( module, __webpack_exports__, __webpack_require__ ) { __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "name" , function ( ) { return name; }); const name = "yueqi" const age = 11 __webpack_exports__[ "default" ] = (age); }), "./src/main.js" : ( function ( module, exports, __webpack_require__ ) { let person = __webpack_require__( "./src/hello.js" ) console .log(person.name) }) }) Copy code

Still starting from the package file, you can see that there is one in hello.js

__webpack_require__.r
Methods and
__webpack_require__.d
method

  • webpack_require .r : If the current module is an es6 module, add __esModule attribute to the module

  • webpack_require .d: Set the name attribute to the current module, compatible with es6 modules

  • webpack_require .t: key core method

    Its function is to create a simulated named object, and package an arbitrary module (common.js es module) into the form of es module

    • mode & 1 value is a module Id, which needs to be loaded through require
    • mode & 2 Add es attributes to the module, merge attributes, and return to the current module
    • mode & 4 means that value is an es6 module, return an esmodule module
    • mode & 8 returns directly to the last to add the default attribute to the current module, used as the default export

In the end, the object we get looks like this

In this way, the previous loading logic can be taken,

__webpack_require__
Method to get the content in the module

3. Scenario 3: Asynchronous import

main.js

let button = document .createElement( 'button' ); button.innerHTML = "Click me" ; document .body.appendChild(button) button.addEventListener ( 'click' , () => { import ( /* webpackChunkName: "hello "*/' ./hello' ).then( result => { console .log(result.default) }) }) Copy code

Hello.js

export const name = "yueqi" export default age = 11 Copy code

The packaged main.js looks like this

( function ( modules ) { ... See above for omitted code __webpack_require__.e = function requireEnsure ( chunkId ) { var promises = []; var installedChunkData = installedChunks[chunkId]; //If the code block has not been loaded , InstalledChunkData = 0 means it has been loaded if (installedChunkData !== 0 ) { if (installedChunkData) { promises.push(installedChunkData[ 2 ]); } else { //start to create promise var promise = new Promise ( function ( resolve, reject ) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); /* Equivalent to * installedChunkData = [resolve, reject, promise] */ promises.push(installedChunkData[ 2 ] = promise); var script = document .createElement( 'script' ); var onScriptComplete; script.charset = 'utf-8' ; script.timeout = 120 ; if (__webpack_require__.nc) { script.setAttribute( "nonce" , __webpack_require__.nc); } //Return the lazy loaded script path script.src = jsonpScriptSrc(chunkId); var error = new Error (); onScriptComplete = function ( event ) { script.onerror = script.onload = null ; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if (chunk !== 0 ) { if (chunk) { var errorType = event && (event.type === 'load' ? 'missing' : event.type); var realSrc = event && event.target && event.target.src; error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')' ; error.name = 'ChunkLoadError' ; error.type = errorType; error.request = realSrc; chunk[ 1 ](error); } installedChunks[chunkId] = undefined ; } }; var timeout = setTimeout( function () { onScriptComplete({ type : 'timeout' , //The data here is the content of hello.js function webpackJsonpCallback ( data ) { //Recursively execute webpackJsonpCallback, and set the resolve of the current nested module to true if (parentJsonpFunction) parentJsonpFunction(data);; i <jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); var parentJsonpFunction = oldJsonpFunction; })({ "./src/main.js" /*! ./hello */"./src/hello.js" )).then( result => { console .log(result) }) }) target : script }); }, 120000 ); script.onerror = script.onload = onScriptComplete; document .head.appendChild(script); } } return Promise .all(promises) ; }; function jsonpScriptSrc ( chunkId ) { return __webpack_require__.p + "" + ({ "c" : "c" }[chunkId]||chunkId) + '. js ' } var chunkIds = data[ 0 ]; //chunkID var moreModules = data[ 1 ]; //additional code block var moduleId, chunkId, i = 0 , resolves = []; //Add the current additional code block to the modules object for (;i <chunkIds.length; i++) { chunkId = chunkIds[i]; //title //If it has been loaded, then directly Get its resolves //installedChunks[chunkId] = [resolve, reject, promise] if ( Object .prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][ 0 ]); } //Set the current chunkId to 0, which means that the installedChunks[chunkId ] has been successfully loaded ] = 0 ; } //merge the code block above moreModules into the current modules for (moduleId in moreModules) { if ( Object .prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId ]; } } //Used when nested lazy loading, //call the push method of the webpackJsonp array of the parent module, or it may be an array. pu sh while (resolves.length) { //Execute the success status of promises one by one resolves.shift()(); } }; var jsonpArray = window [ "webpackJsonp" ] = window [ "webpackJsonp" ] || []; var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); jsonpArray.push = webpackJsonpCallback; jsonpArray = jsonpArray.slice(); for ( var i = 0 :( function ( module, exports, __webpack_require__ ) { let button = document .createElement ( 'Button' ); button.innerHTML = "point I" ; Document .body.appendChild (Button) button.addEventListener ( 'the Click' , () => { . __webpack_require __ E ( /* Import () | Hello! */"hello" ) .then(__webpack_require__.bind( null , }) Copy code

hello.js

//The execution here is actually webpackJsonpCallback, because the push method is rewritten above ( window [ "webpackJsonp" ] = window [ "webpackJsonp" ] || []).push( [ [ "hello" ], { "./src/hello.js" : ( function ( module, __webpack_exports__, __webpack_require__ ) { __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "name" , function ( ) { return name; }); const name = "yueqi" const age = 11 __webpack_exports__[ "default" ] = (age); }) } ] ); Copy code

One is used here

__webpack_require__.e
Fang, take a look at this method.

When there is an asynchronous loading module, webpack uses JSONP to load the code block,

Insert them into html after loading

Note here that there is such a piece of code at the end of the package file:

var jsonpArray = window [ "webpackJsonp" ] = window [ "webpackJsonp" ] || []; //Rewrite the push method of the array to webpackJsonpCallback var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); jsonpArray.push = webpackJsonpCallback; jsonpArray = jsonpArray.slice(); for ( var i = 0 ; i <jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); var parentJsonpFunction = oldJsonpFunction; Copy code

The push method of the array jsonpArray is rewritten here, pointing to

webpackJsonpCallback
on

So when the lazy loaded hello.js code block is executed ,

(window["webpackJsonp"] = window["webpackJsonp"] || []).push is actually executed

webpackJsonpCallback
,

Take a look at this method

//The data here is the content of hello.js function webpackJsonpCallback ( data ) { var chunkIds = data[ 0 ]; //chunkID var moreModules = data[ 1 ]; //additional code block var moduleId, chunkId, i = 0 , resolves = []; //Add the current additional code block to the modules object for (;i <chunkIds.length; i++) { chunkId = chunkIds[i]; //title //If it has been loaded, then directly Get its resolves //installedChunks[chunkId] = [resolve, reject, promise] if ( Object .prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][ 0 ]); } //Set the current chunkId to 0, which means it has been loaded successfully installedChunks[chunkId] = 0 ; } //merge the code block above moreModules into the current modules for (moduleId in moreModules) { if ( Object .prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } (parentJsonpFunction) parentJsonpFunction(data); while (resolves.length) { //The success state of executing promises one by one resolves.shift()(); } }; // Used when nested lazy loading, //call the push method of the webpackJsonp array of the parent module, or it may be an array. push //Recursively execute webpackJsonpCallback, and set the resolve of the current nested module to true if Copy code

This function actually merges the contents of the current module into the global cache object

installedModules
.

There is a detail, when there is a nested dynamic introduction, the parent module will be called recursively in turn

webpackJsonpCallback
,

Change the status to resolve.

Finally, to summarize... (to be updated)

This article uses mdnice typesetting