webpack最后打包生成了什么?

88 阅读2分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

你是否会好奇当我们使用 webpack 打包项目时最后生成的文件是什么样子的呢?假如我们有一个页面入口文件 index.js 和依赖模块 utils.js 文件,代码如下:

// 入口文件 index.js
import dateUtils from './utils'
dateUtils.print()

// 模块文件 utils.js
export default {
  print() {
    console.log('utils.js==>>print', new Date())
  }
}

在开发环境下执行 npm run build生成bundle.js文件。

首先我们看看bundle.js的上半部分:

;(() => {
  // 这就是我们对应的模块文件的内容,webpack编译index.js、utils.js文件后就放在这个__webpack_modules__变量中
  // 里面的代码被eval包裹,eval的作用就是把里面的字符串转化为代码进行执行
var __webpack_modules__ = {
  './src/index.js': (
    __unused_webpack_module,
    __webpack_exports__,
    __webpack_require__
  ) => {
    eval(
      // index.js里面有import,就调用__webpack_require__方法,这个方法返回util模块的exports
      var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/utils.js");
      
     _utils__WEBPACK_IMPORTED_MODULE_0__["default"].print()
    )
  },
  './src/utils.js': (
    __unused_webpack_module,
    __webpack_exports__,
    __webpack_require__
  ) => {
    eval(
      // 这个方法是往exports上挂载内容
      __webpack_require__.d(__webpack_exports__, {
      "default": () => (__WEBPACK_DEFAULT_EXPORT__) });
      const __WEBPACK_DEFAULT_EXPORT__ = { print() { 
          console.log(\'DateUtils.js==>>print\', new Date())  
          } 
      }
    )
  }
}

它定义了一个__webpack_modules__对象,这个对象的 key 是模块文件的相对路径,value是一个函数:

 (
    __unused_webpack_module,
    __webpack_exports__,
    __webpack_require__
  ) => {
    eval(
      __webpack_require__.r(__webpack_exports__);
      // 看到index.js里面有import,那么就调用__webpack_require__方法,这个方法返回utile模式的exports
      var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/utils.js");
     _utils__WEBPACK_IMPORTED_MODULE_0__["default"].print()
    )
  }

这个函数传入三个参数,分别是__unused_webpack_module__webpack_exports__对象,__webpack_require__方法。这个可以类比nodejs里面的模块,在nodejs代码执行的时候,也会给模块加上require,module,exports三个参数,也就是webpack实现了commonjs规范,这样webpack才会识别require import这样的语法。

webpack 兼容 esm 和 cjs 模块规范,在webpack项目中我们可以愉快的使用这两种规范。

接下来webpack实现了一个require函数,即__webpack_require__函数。当我们在模块中使用import from语法时,在编译过程中会把import from替换为__webpack_require__,这个方法的实现非常简单:

    var __webpack_module_cache__ = {};

    function __webpack_require__(moduleId) {
      // 缓存中是否存在
      var cachedModule = __webpack_module_cache__[moduleId];
      if (cachedModule !== undefined) {
        return cachedModule.exports;
      }
      // 创建一个模块,并缓存起来
      var module = (__webpack_module_cache__[moduleId] = {
        // 模块有个exports属性
        exports: {},
      });

      // 执行模块
      __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

      // 返回模块的exports对象
      return module.exports;
    }

最后就是执行入口文件:

var __webpack_exports__ = __webpack_require__('./src/index.js')

入口文件的函数如下:

// 通过__webpack_require__引用utils里面的内容
var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__('./src/utils.js') 

// _utils__WEBPACK_IMPORTED_MODULE_0__对象上有个default属性,调用这个属性上面的值,并执行 
_utils__WEBPACK_IMPORTED_MODULE_0__['default'].print()

utils模块文件的函数如下:

// __webpack_require__.d的作用是在module.exports挂载default属性,这个属性的值为__WEBPACK_DEFAULT_EXPORT__

__webpack_require__.d(__webpack_exports__, {
  default: () => __WEBPACK_DEFAULT_EXPORT__
})
const __WEBPACK_DEFAULT_EXPORT__ = {
  print() {
    console.log('DateUtils.js==>>print', new Date())
  }
}

上面是开发环境打包之后的文件结构,那么在生产环境打包的文件代码是什么样子的呢?

;(() => {
  'use strict'
  ;({
    print() {
      console.log('DateUtils.js==>>print', new Date())
    }
  }.print())
})()

可以看到代码非常简洁,没有了开发环境相关的代码,把模块内的所有代码都压缩在一个文件之中了,这样大大的减少了bundle.js的体积。