Appearance
Webpack 的模块机制
现在我们有以下的 4 个文件
- main.js
- common.js
- module1.js
- module2.js
js
// main.js
require("./common.js")
require("./module1.js")
import { module2Fun1 } from "./module2.js"
module2Fun1()
js
// common.js
export default {
name: "common",
}
js
// module1.js
require("./common.js")
let module1Var1 = "module1Var1"
let module1Var2 = "module1Var2"
export const module1Fun1 = function () {
console.log(`module1Fun1+${module1Var2}`)
}
export const module1Fun2 = function () {
console.log(`module1Fun2`)
}
let a = 1
export const b = a
a = 2
export const module1Var3 = 10
js
// module2.js
require("./common.js")
let module2Var1 = "module2Var1"
let module2Var2 = "module2Var2"
export const module2Fun1 = function () {
console.log(`module2Fun1+${module2Var2}`)
}
export const module2Fun2 = function () {
console.log(`module2Fun2`)
}
然后我们将 webpack.config.js 配置成以下 options
js
module.exports = {
// 禁用 webpack 内置优化
mode: "none",
// 入口文件
entry: {
main: "./public/js/main.js",
},
output: {
filename: process.env.NODE_ENV === "production" ? "[name].[contenthash].js" : "[name].js",
// 将输出的文件都放在dist目录下
path: path.resolve(__dirname, "dist"),
},
optimization: {
// 不压缩
minimize: false,
},
}
然后我们执行编译流程生成下面文件
js
/*
webpack的模块化机制 其核心是一个 自执行函数 (function(modules){})([function module1(){}, function module2(){}, function module3(){} ])
*/
// modules 当前环境中所有模块的数组对象 那么我们就可以通过 modules[下标]() 去执行模块
;(function (modules) {
// webpackBootstrap
// The module cache
// 已加载模块缓存
var installedModules = {}
// The require function
/**
*
* require('./module1') 或者 import from './modulex.js' 的核心方法
*
* @param moduleId 模块的ID
*/
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] = {
// 模块的ID
i: moduleId,
// 模块是否加载过 默认为false
l: false,
//
exports: {},
})
// Execute the module function
// 真正的去执行加载的模块
// 通过模块的下标去modules中找到相应的模块 然后执行此模块
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
/**
* 定义当前模块exports 导出的变量
*
* 例如 export const name = 1;
*
* exports = {
* name : function(){
* return 1;
* }
* }
*
* @param exports 通过对象地址引用的方式传入一个当前模块导出的变量保存的对象
* @param name 当前模块导出变量的名称
* @param getter 当前模块导出变量的执行函数
*/
__webpack_require__.d = function (exports, name, getter) {
// 判断当前exports 对象上是否存在此属性
if (!__webpack_require__.o(exports, name)) {
// 通过 defineProperty 去定义此属性
Object.defineProperty(exports, name, { enumerable: true, get: getter })
}
}
// define __esModule on exports
/**
* 定义 exports 的 __esModule 属性值
*
* @param exports 当前模块对象 { i: , l: , 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
}
__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
}
/**
* 判断property 是否是object对象自身包含的特定的自身(非继承)属性。
*/
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property)
}
// 公开的路径 publicPath
__webpack_require__.p = ""
// 从下标为0的第一个模块执行
return __webpack_require__((__webpack_require__.s = 0))
})(
/************************************************************************/
[
// main.js
function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
// import { module2Fun1 } from './module2.js';
// 通过__webpack_require__(moduleId)去加载module2 因为有依赖的值,所以存在一个返回值 _module2_js__WEBPACK_IMPORTED_MODULE_0__
var _module2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3)
// require('./module1.js'); 通过__webpack_require__(moduleId)去加载module1
__webpack_require__(1)
__webpack_require__(2)
Object(_module2_js__WEBPACK_IMPORTED_MODULE_0__["module2Fun1"])()
},
// common.js
function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
// 对于default 直接使用 exports['default']去保存
__webpack_exports__["default"] = {
name: "common",
}
},
// module1.js
function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
// 为什么需要通过 Object.defineProperty 将export的变量定义为一个函数的属性?
// 因为对于webpack的模块化机制除了default 其他的export 都有一个提升的机制,会在函数的最上面定义export的变量的属性,
// 那么如果不是函数那么 我们import module 这时候执行module函数,
/*
那么这种情况
let a = 1;
export const b = a ;
a = 2;
// 编译后的代码为
__webpack_require__.d(__webpack_exports__, 'b', a);
let a = 1;
a = 2;
// 这时候发现 a是undefined
如果是这样的话
// 编译后的代码为
__webpack_require__.d(__webpack_exports__, 'b', function () {
return a;
});
let a = 1;
a = 2;
// 只有执行到 我们需要 b 的时候才去获取当前的 a的值 这时候就是 2;
*/
__webpack_require__.d(__webpack_exports__, "module1Fun1", function () {
return module1Fun1
})
__webpack_require__.d(__webpack_exports__, "module1Fun2", function () {
return module1Fun2
})
__webpack_require__.d(__webpack_exports__, "module1Var3", function () {
return module1Var3
})
__webpack_require__(1)
let module1Var1 = "module1Var1"
let module1Var2 = "module1Var2"
const module1Fun1 = function () {
console.log(`module1Fun1+${module1Var2}`)
}
const module1Fun2 = function () {
console.log(`module1Fun2`)
}
const module1Var3 = 10
},
// module2.js
function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, "module2Fun1", function () {
return module2Fun1
})
__webpack_require__.d(__webpack_exports__, "module2Fun2", function () {
return module2Fun2
})
__webpack_require__(1)
let module2Var1 = "module2Var1"
let module2Var2 = "module2Var2"
const module2Fun1 = function () {
console.log(`module2Fun1+${module2Var2}`)
}
const module2Fun2 = function () {
console.log(`module2Fun2`)
}
},
]
)
从中我们发现 webpack 的模块化的机制是什么?
整体流程
webpack 将每一个文件变成一个 module,然后存放在一个统一的 modules 数组中,那么就可以通过 modules[下标]
去访问对应的文件的函数,其中入口文件存放在 modules 数组的第一个,那么其初始化的时候就可以通过__webpack_require__((__webpack_require__.s = 0))
去从入口文件开始执行整个流程
js
// 从下标为0的第一个模块执行
return __webpack_require__((__webpack_require__.s = 0))
那么其是怎么管理每一个依赖的?
上面我们知道所有的模块都存放在 modules 数组那么,那么在一个文件中通过 import
或者 require
依赖的其他文件都变成
js
var _module2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2)
所以 webpack 的模块化核心就是 __webpack_require__
js
// The require function
/**
*
* require('./module1') 或者 import from './modulex.js' 的核心方法
*
* @param moduleId 模块的ID
*/
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] = {
// 模块的ID
i: moduleId,
// 模块是否加载过 默认为false
l: false,
//
exports: {},
})
// Execute the module function
// 真正的去执行加载的模块
// 通过模块的下标去modules中找到相应的模块 然后执行此模块
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
}
从上面我们也可以发现其就是通过modules[moduleId]
去管理依赖的,然后通过modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
去执行依赖文件。
那么其如何管理依赖的 exports 和 import 的 ?
我们继续看上面的 __webpack_require__
发现在 call 依赖函数的时候传入了一个 空的module.exports === {}
对象,那么在文件函数执行的过程中通过 Object.defineProperty
将 export 的属性定义在 modules.exports 的属性上面
js
// function (module, __webpack_exports__, __webpack_require__) {} // __webpack_exports__ === module.exports
__webpack_require__.d(__webpack_exports__, "module2Fun1", function () {
return module2Fun1
})
// 对于default 直接使用 exports['default']去保存
__webpack_exports__["default"] = {
name: "common",
}
那么我们在模块中就可以通过
js
// 获取返回的 exports对象
var _module2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3)
// 获取相应的属性的值,其固定为一个函数
Object(_module2_js__WEBPACK_IMPORTED_MODULE_0__["module2Fun1"])()
其中为什么对于 export 的属性变成一个函数的方式去获取?
js
// module1.js
function (module, __webpack_exports__, __webpack_require__) {
'use strict';
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, 'module1Fun1', function () {
return module1Fun1;
});
__webpack_require__.d(__webpack_exports__, 'module1Fun2', function () {
return module1Fun2;
});
__webpack_require__.d(__webpack_exports__, 'module1Var3', function () {
return module1Var3;
});
__webpack_require__(1);
let module1Var1 = 'module1Var1';
let module1Var2 = 'module1Var2';
const module1Fun1 = function () {
console.log(`module1Fun1+${module1Var2}`);
};
const module1Fun2 = function () {
console.log(`module1Fun2`);
};
const module1Var3 = 10;
}
我们去看 module1 的 export 的变量,发现其在模块的最前面进行定义,那么如果我们通过 为什么需要通过 Object.defineProperty 将 export 的变量定义为一个函数的属性? 因为对于 webpack 的模块化机制除了 default 其他的 export 都有一个提升的机制,会在函数的最上面定义 export 的变量的属性, 那么如果不是函数那么 我们 import module 这时候执行 module 函数,
那么这种情况
js
let a = 1
export const b = a
a = 2
编译后的代码为
js
__webpack_require__.d(__webpack_exports__, "b", a)
let a = 1
a = 2
这时候发现 a 是 undefined 但是我们通过变成一个函数的话
js
// 编译后的代码为
__webpack_require__.d(__webpack_exports__, "b", function () {
return a
})
let a = 1
a = 2
只有执行到 我们需要 b 的时候才去获取当前的 a 的值 这时候就是 2;