webpack 打包后代码分析

简单情况分析

前言

打包前各个脚本:

index.js

js 复制代码
// 
import { log } from './util'; 
log('abc'); 

util.js

js 复制代码
export function log(v) { console.log(v); }

打包后代码:

js 复制代码
(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赋值操作也有返回值,即等号右边的值。

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

核心逻辑:webpack_require 函数

js 复制代码
// 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;
  }

流程图:

js 复制代码
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

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

js 复制代码
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__函数
ini 复制代码
__webpack_require__.r(__webpack_exports__);

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

js 复制代码
// 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成员。

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

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

js 复制代码
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函数

js 复制代码
__webpack_require__.d(__webpack_exports__, 'log', function() {
  return log;
  });
js 复制代码
// 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对象是否有指定属性。

js 复制代码
// Object.prototype.hasOwnProperty.call
  __webpack_require__.o = function(object, property) {
    return Object.prototype.hasOwnProperty.call(object, property);
  };

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

js 复制代码
function log(v) {
  console.log(v);
}

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

js 复制代码
// util.js
export function log(v) {
  console.log(v);
}

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

javascript 复制代码
// 调用utils脚本导出的log函数执行最终的逻辑
Object(_util__WEBPACK_IMPORTED_MODULE_0__['log'])('abc');
相关推荐
liangshanbo12153 天前
面试题:Webpack 的 publicPath 有什么作用?
前端·webpack·node.js
liangshanbo12155 天前
Webpack vs Vite 底层原理高频追问的问题
前端·webpack·node.js
liangshanbo12155 天前
面试题:AI 对话上下文超限怎么办?
面试·webpack·devops
liangshanbo12156 天前
Webpack 文件指纹面试题整理
前端·webpack·node.js
liangshanbo12156 天前
Webpack 常见 Plugin 面试题
前端·webpack·node.js
liangshanbo12156 天前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
陪我一起学编程6 天前
vue3笔记
前端·webpack·vue3·vite·脚手架·vue3笔记·组件式
liangshanbo12156 天前
Webpack的分包策略面试题
前端·webpack·node.js
liangshanbo12157 天前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js
晚风醉蝶9 天前
webpack 模块提取
前端·webpack·node.js·ast·逆向分析