likes
comments
collection
share

webpack 打包后代码分析

作者站长头像
站长
· 阅读数 4

简单情况分析

前言

打包前各个脚本:

index.js

// 
import { log } from './util'; 
log('abc'); 

util.js

export function log(v) { console.log(v); }

打包后代码:

(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 = './webpack-sourcecode/index.js'));
})({
  './webpack-sourcecode/index.js':
  /*! no exports provided */
    function(module, __webpack_exports__, __webpack_require__) {
      'use strict';
      __webpack_require__.r(__webpack_exports__);
      /* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ './webpack-sourcecode/util.js');

      Object(_util__WEBPACK_IMPORTED_MODULE_0__['log'])('abc');
    },

  './webpack-sourcecode/util.js':
  /*! exports provided: log */
    function(module, __webpack_exports__, __webpack_require__) {
      'use strict';
      __webpack_require__.r(__webpack_exports__);
      /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, 'log', function() {
        return log;
      });

      function log(v) {
        console.log(v);
      }
    },
});

概述

以js脚本路径为key,一个函数为值去调用最外层的这个IFFE函数(IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数)。对于作为entry的js脚本来说,这个函数为 “main” 函数,对于它所依赖的脚本,这个函数则会返回utils脚本export出的成员。

代码逐行分析

entry脚本作为参数执行__webpack_require__函数,同时以__webpack_require__.s记录entry脚本路径。 JS赋值操作也有返回值,即等号右边的值。

// Load entry module and return exports
return __webpack_require__((__webpack_require__.s = './webpack-sourcecode/index.js'));

核心逻辑:webpack_require 函数

// 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;
  }

流程图:

webpack 打包后代码分析
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

该行函数会去调用index脚本对应的函数(即初始时传入IIFE函数的参数:以脚本路径为key,函数为值),这里会去调用此函数

function(module, __webpack_exports__, __webpack_require__) {
      'use strict';
      __webpack_require__.r(__webpack_exports__);
      /* harmony import -- 用于tree-shaking的一种标记 */ 
      // 使用__webpack_require__加载依赖脚本返回其中的exports成员,这里是utils脚本
      var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ './webpack-sourcecode/util.js');
      // 调用utils脚本导出的log函数执行最终的逻辑
      Object(_util__WEBPACK_IMPORTED_MODULE_0__['log'])('abc');
    }

此时,会在新创建出来的module对象上调用上述函数,各参数对应的值如下:

参数名
module新建出来出来的module对象:{ i: moduleId, l: false, exports: {}}
__webpack_exports__新建出来出来的module对象的exports属性,初始为空
__webpack_require____webpack_require__函数
__webpack_require__.r(__webpack_exports__);

调用__webpack_require__.r函数,主要是对exports对象新定义一个属性。

// define __esModule on exports
__webpack_require__.r = function(exports) {
  //判断是否支持Symbol,支持的话则直接将exports对象的对象描述设置为 Module 
  if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  }
  Object.defineProperty(exports, '__esModule', { value: true });
};

关于Symbol,在JavaScript中,Symbol是一种基本数据类型,它用于创建唯一的标识符, 引入自ES6.Symbol类型的值是通过调用Symbol()函数生成的,每次调用都会生成一个独一无二的Symbol值,即使你传入相同的参数。Symbol通常用作对象属性的键,以确保属性名的唯一性,避免属性名的冲突。

关于Symbol.toStringTag,这是一个内置的Symbol,当调用 Object.prototype.toString() 方法时,如果对象有 Symbol.toStringTag 属性,这个属性的值会出现在 toString() 方法返回的字符串中,表示对象的类型。这允许开发者自定义对象的字符串描述,而不是使用默认的 “[object Object]”

使用__webpack_require__函数加载utils脚本并返回其中的exports成员。

var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ './webpack-sourcecode/util.js');

同上述index脚本一样,__webpack_require__函数会去执行utils脚本对应的函数:

function(module, __webpack_exports__, __webpack_require__) {
  'use strict';
  __webpack_require__.r(__webpack_exports__);
  /* harmony export (binding) */ 
  __webpack_require__.d(__webpack_exports__, 'log', function() {
    return log;
  });

  function log(v) {
    console.log(v);
  }
}

此处调用__webpack_require__.d函数

__webpack_require__.d(__webpack_exports__, 'log', function() {
  return log;
  });
// 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 });
  }
};

__webpack_require__.o 函数用来检测exports对象是否有指定属性。

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

这里则是检查utils是否已经导出过log函数,如果没有则给exports新增一个log属性,值为

function log(v) {
  console.log(v);
}

即打包前utils脚本中定义的log函数。

// util.js
export function log(v) {
  console.log(v);
}

_util__WEBPACK_IMPORTED_MODULE_0__即为utils脚本对应模块的exports属性, 此处传入abc参数调用log函数,至此所有逻辑完成了。

// 调用utils脚本导出的log函数执行最终的逻辑
Object(_util__WEBPACK_IMPORTED_MODULE_0__['log'])('abc');