Webpack4 打包后解析原理

609 阅读4分钟

前言

通过前两篇文章

从0到1实现一个 Hello Webpack

Webpack 基础配置

我们可以知道 webpack 的安装与使用,以及一些基本配置,最终可以实现一个自动构建过程,那么构建之后的文件又是怎样的 ? 它又是如何解析的,今天我们来探讨一下

打包后的 bundle.js 文件

这里呢,我引用上述文章的示例,拿到直接打包后的 bundle.js 文件来分析,先来看源代码 我加了注释

 (function(modules) { 
       // 创建一个缓存,用于存储已加载过的模块
 	var installedModules = {};

       // 自定义 require 函数,用于加载模块
 	function __webpack_require__(moduleId) {

 		// 判断模块是否已加载过,在缓存
 		if(installedModules[moduleId]) {
 			return installedModules[moduleId].exports;
 		}
 		// 创建一个新的模块
 		var module = installedModules[moduleId] = {
 			i: moduleId, // 模块 id
 			l: false, // 是否倍加载过
 			exports: {} // 模块实体
 		};

 		// 执行对应模块实体,对应的自执行函数
 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

 		// 标识模块已被加载
 		module.l = true;

 		// 返回模块实体
 		return module.exports;
 	}

        // __webpack_require__ 函数自定义属性及方法 start

 	// 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, {
 				configurable: false,
 				enumerable: true,
 				get: getter
 			});
 		}
 	};

 	// 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 = "";

        // __webpack_require__ 函数自定义属性及方法 end

       // 加载入口文件模块
 	return __webpack_require__(__webpack_require__.s = 0);
 })


/***************************** 参数如下 *******************************************/

 ([

      /* 0 */ 下标
      (function(module, exports, __webpack_require__) {
      var show = __webpack_require__(1);
      console.log("我是入口文件main.js")
      show('Webpack');
      }),

      /* 1 */ 下标
      (function(module, exports) {
      console.log("我是utils.js")
      function show (content) {
        window.document.getElementById('app').innerText = 'Hello,' + content;
      }
      module.exports = show;
      })

 ]);

我们分析一下上边源码的结构,仔细看一下,其实是一个自执行函数,传入了模块参数,大概是这个样子

(
    function (modules) { /* 实体 */  }
)
(
    [
        ( function(module, exports, __webpack_require__) { /* 内容 */ } ),
        ( function(module, exports) { /* 内容 */  } )
    ]
 )

解析过程

了解一下前提,webpack 采用的是 common.js 规范,所以默认可以识别 js 文件,在 common.js 模块规范里, 一个 Js 文件就是一个模块 。

  • common.js 模块规范
  • 导入: var m = require("文件路径")
  • 导出: module.exports = m

好,了解 common.js 规范后,我们来看一下 webpack 到底是怎么解析的

首先进入自执行函数,看到传入的参数 modules,modules 是什么 ? modules 是一个数组,数组内容则是各个依赖的 Js 模块(包含初始化模块,也就是webpack entry 配置的入口文件)

然后就是执行 return __webpack_require__(__webpack_require__.s = 0);webpack_require 是什么 ? webpack_require 函数是用来替代 require 函数的,主要是创建一个新的模块,放入缓存,并执行对应的Js模块实体; ↓ ↓ ↓

// 自定义 require 函数,用于加载模块
function __webpack_require__(moduleId) {

        // 判断模块是否已加载过,在缓存
        if(installedModules[moduleId]) {
                return installedModules[moduleId].exports;
        }
        // 创建一个新的模块
        var module = installedModules[moduleId] = {
                i: moduleId, // 模块 id
                l: false, // 是否倍加载过
                exports: {} // 模块实体
        };

        // 执行对应模块实体,对应的自执行函数
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        // 标识模块已被加载
        module.l = true;

        // 返回模块实体
        return module.exports;
}

执行对应的 Js 模块实体又是什么 ? 就是这句代码

// 执行对应模块实体,自执行函数
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  • modules: 是参数,上边有说,是一个数组,数组内容则是各个依赖的 Js 模块
  • moduleId:在这里则是指下标,上述代码可以看出初始化传入的是 0

三个参数

  • module:创建新的模块对象
  • module.exports: 执行 webpack_require 函数传入的对应 Js 模块实体导出
  • webpack_require 函数自身

这里传入了 webpack_require 函数自身,可以看的出来,是为了引用自身,实质就是递归寻找依赖,再次解析其它相关的依赖模块就是webpack的本质,递归寻找依赖的所有模块

最后加载过的模块给予标识,标识自己经被加载过了,返回对应模块的导出 exports , 也就是 return module.exports,这里的 return module.exports 其实返回的就是参数里自执行函数返回的内容,拿上述例子来看:

 ([

      /* 0 */ 下标
      (function(module, exports, __webpack_require__) {
      var show = __webpack_require__(1);
      console.log("我是入口文件main.js")
      show('Webpack');
      }),

      /* 1 */ 下标
      (function(module, exports) {
      console.log("我是utils.js")
      function show (content) {
        window.document.getElementById('app').innerText = 'Hello,' + content;
      }
      module.exports = show;
      })

 ]);

有两个地方需要说明一下

  1. var show = __webpack_require__(1); 这里webpack在编译的时候将 require 替换为 __webpack_require__函数
  2. module.exports = show; 就是导出 show 函数自身了

结语

总体的逻辑,通过一个小小的 demo 做了简要分析,webpack 的核心知识点还有很多,后续会分享一下 loader 和plugin 的原理

小小鼓励,大大成长,欢迎点赞