超详细!webpack 4 编译后代码解读

1,896 阅读4分钟

背景

由于网上的总结看到的webpack2, 所以想看下webpack4下的编译结果,配置:

mode:“development”

dvetool: "inline-source-map"。

示例

一个简单的实例,后面会逐行进行解读,a是入口文件,依赖b和c模块。

a.js

import b from './b'
import c from './c'

console.log(b.name)
console.log(c.name)

b.js

console.log('module b runs')

export default {
    name: 'b'
}

c.js

import b from './b'

export default {
    name: 'c'
}

编译后代码

/******/ (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 = "./a.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./a.js":
/*!**************!*\
  !*** ./a.js ***!
  \**************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b */ "./b.js");
/* harmony import */ var _c__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./c */ "./c.js");



console.log(_b__WEBPACK_IMPORTED_MODULE_0__["default"].name)
console.log(_c__WEBPACK_IMPORTED_MODULE_1__["default"].name)



/***/ }),

/***/ "./b.js":
/*!**************!*\
  !*** ./b.js ***!
  \**************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
console.log('module b runs')
const name = 'b';
/* harmony default export */ __webpack_exports__["default"] = ({
    name
});

/***/ }),

/***/ "./c.js":
/*!**************!*\
  !*** ./c.js ***!
  \**************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b */ "./b.js");


/* harmony default export */ __webpack_exports__["default"] = ({
    name: 'c'
});

/***/ })

/******/ });
//# sourceMappingURL=...

执行步骤

上述编译后的代码,可以分为几个步骤,部分未使用到的代码已忽略

立即执行函数

/******/ (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: {}
/******/ 		};
/******/ 		// 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;
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = "./a.js");
/******/ })()

1.声明一个module缓存变量,对已加载的模块不会重复执行。

/******/ 	// The module cache
/******/ 	var installedModules = {};

2. 声明一个加载模块的方法 __webpack_require__。核心方法,整个执行过程,__webpack_require__会不断拓传,数据都会存在这个函数的属性上。

/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}

3. 对上面的__webpack_require__添加一系列的属性和方法,包括原始的modules对象,已加载的模块installedModules等。

/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

4. 执行第一个__webpack_require__,传入的moduleId是入口文件的id,该示例是2。

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 2);

5. __webpack_require__具体执行逻辑

  • 判断当前moduleId是否已加载过,已加载则直接return对应的module.exports
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])	return installedModules[moduleId].exports;
  • 未加载,则创建一个新的module,并缓存。
******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,  // 是否已执行 
/******/ 			exports: {} // 存储module的返回结果
/******/ 		};
  • 开始执行具体代码,this指向module.exports,传入三个参数,新建的modulemodule.exports, __webpack_require__
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  • 进入a文件编译后代码,可以看到,就是把文件a的代码通过函数包起来,this指向上面声明的module.exports
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__b__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__c__ = __webpack_require__(1);



console.log(__WEBPACK_IMPORTED_MODULE_0__b__["a" /* default */].name)
console.log(__WEBPACK_IMPORTED_MODULE_1__c__["a" /* default */].name)

/***/ })
  • 定义当前模块是es模块,这一步该示例中没用,留到后续分析。
__webpack_require__.r(__webpack_exports__);
  • 开始加载b模块,并在当前函数声明一个变量,指向moduleB.exports。同样也是使用__webpack_require__,通过递归的方式,循环执行步骤5。(这里的“harmony import”主要用于tree shaking,记录是否未使用。)
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__b__ = __webpack_require__(0);
  • 当依赖的模块执行返回结果后,继续执行剩余的代码。注意这里调用的是模块b和模块c的a属性,对应模块的default。
console.log(__WEBPACK_IMPORTED_MODULE_0__b__["a" /* default */].name)
console.log(__WEBPACK_IMPORTED_MODULE_1__c__["a" /* default */].name)

与webpack2的区别

moduleId

修改了每个模块的索引方式,webpack2是一个数组,通过数组下标来标识。webpack4是一个对象,通过文件对应的路径来作为key。

webpack_require.r

用于定义模块的输出方式

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

这里使用了Symbol.toStringTag,来重新定义exports的类型。主要在Object.prototype.toString来区分类型的时候,会用到,打印出来会是 [object Module]

然后添加通用标识__esMoudle,这一步webpack2也会有。

默认打包方式

webpack2默认是inline-source的方式编译代码。webapck4默认输出的是压缩后的线上代码,同时会将三个文件代码,合并在一起。

总结

webpack编译后,通过一个IIFE,来缓存加载模块到installedModules。

每个模块都通过一个唯一的require函数进行加载:__webpack_require__

通过入口文件进行相关依赖的逐次执行,每个模块传入require函数生成的module对象,并将返回值赋值到module.exports。

这就是webpack模块化的执行机制。

webpack的更多深入功能,比如tree shaking, 代码压缩, loader加载其他模块,后续继续了解。