webpack基础(二):认识Webpack

Webpack 基础第二部分,认识 Webpack 。从最简单的一个 index.js 文件认识 Webpack。

在开始了解 Webpack 核心概念之前,让我们先看看 Webpack 的生成文件。

我们先讨论的最简单的 js 文件,不包含 html 及 Webpack jsonp 等加载知识。

index.js 入口文件

我定义了一个最简单的 index.js 作为入口文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* index.js */
import nav from './nav';
import { top, bottom } from './footer';

console.log(nav(), top, bottom);
/*********************************************/

/* footer.js */
const top = 'top';
const bottom = 'bottom';

module.exports = { top, bottom };
/*********************************************/

/* nav.js */
export default () => 'nav';
/*********************************************/

这里我们就能够跑一下 Webpack 生成对应的 runtime 文件(要先安装 webpack-cli 哦):

1
2
$ yarn add webpack-cli webpack
$ npm run webpack -- --mode development

生成 WebpackBootstrap 代码

下面来对应的分析下dist目录下的生成代码(代码经过精简):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
(function(modules) {
// Webpack引导
// 1. 模块缓存
var installedModules = {};

// 2. The require function
function __webpack_require__(moduleId) {
// 检查请求模块是否在缓存中,如果在,直接返回缓存中模块
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建一个新的模块(并将新创建的模块放入缓存中)
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
});

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

// 标记模块为已加载完成
module.l = true;

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

// 3. 公开模块对象 (__webpack_modules__)
__webpack_require__.m = modules;

// 4. 公开模块缓存
__webpack_require__.c = installedModules;

// 5. 为 harmony exports(ES module exports) 定义 getter 函数
__webpack_require__.d = function(exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};

// 6. 为 exports 定义 __esModule 属性
__webpack_require__.r = function(exports) {
Object.defineProperty(exports, '__esModule', { value: true });
};

// 7. 定义 getDefaultExport function 用来兼容 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;
};

// 8. Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};

// 9. __webpack_public_path__
__webpack_require__.p = '';

// 10. 加载入口模块并返回 exports
return __webpack_require__((__webpack_require__.s = './src/index.js'));
})(
/************************************************************************/
/******/ {
/***/ './src/footer.js':
/*!***********************!*\
!*** ./src/footer.js ***!
\***********************/
/*! no static exports found */
/***/ function(module, exports) {
eval(
`const top = 'top';
const bottom = 'bottom';
module.exports = { top, bottom };
//# sourceURL=webpack:///./src/footer.js?`
);

/***/
},

/***/ './src/index.js':
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no exports provided */
/***/ function(module, __webpack_exports__, __webpack_require__) {
'use strict';
eval(
`__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _nav__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./nav */ "./src/nav.js");
const { top, bottom } = __webpack_require__(/*! ./footer */ "./src/footer.js");
console.log(Object(_nav__WEBPACK_IMPORTED_MODULE_0__["default"])(), top, bottom);
//# sourceURL=webpack:///./src/index.js?`
);

/***/
},

/***/ './src/nav.js':
/*!********************!*\
!*** ./src/nav.js ***!
\********************/
/*! exports provided: default */
/***/ function(module, __webpack_exports__, __webpack_require__) {
'use strict';
eval(
`__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (() => "nav");
//# sourceURL=webpack:///./src/nav.js?`
);

/***/
}

/******/
}
);

分析 WebpackBootstrap

上面的生成的代码主要生成了一个大的 IIFE (立即执行函数)。观察可以发现,三个 js 文件被封装成了三个函数传给这个 IIFE 立即执行函数。Webpack 通过 IIFE 函数作用域来实现模块化。

执行过程是:

  1. 定义模块缓存
  2. 定义 __webpack_require__ 函数
    • 检查请求模块是否在缓存中,如果在,直接返回缓存中模块
    • 创建一个新的模块(并将新创建的模块放入缓存中)
    • 执行模块
    • 标记模块为已加载完成
    • 返回模块的 exports
  3. 定义公开模块对象 (__webpack_require__.m)
  4. 公开模块缓存 (__webpack_require__.c)
  5. 为 harmony exports (ES module exports) 定义 getter 函数
  6. 为 exports 定义 __esModule 属性
  7. 定义 getDefaultExport function 用来兼容 non-harmony modules
  8. Object.prototype.hasOwnProperty.call
  9. 定义__webpack_public_path__
  10. 加载入口模块并返回 exports

其中,第 10 步调用入口模块之后,可以看到,传入的参数的函数中,模块的请求,继续调用__webpack_require__,实现了一个递归调用和解析模块,递归完成之后,Webpack 也就能生成对应的模块依赖图,相关的调用关系也就很清晰了。

因为有了模块缓存机制,所以请求过的模块执行过一次之后,就会加入installedModules中,下次调用所需模块就会直接从缓存对象中提取。

Webpack 模块加载对 ESM 和 CommonJs 的实现对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// index.js
function(module, __webpack_exports__, __webpack_require__) {
'use strict';
eval(
`__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _nav__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./nav */ "./src/nav.js");
const { top, bottom } = __webpack_require__(/*! ./footer */ "./src/footer.js");
console.log(Object(_nav__WEBPACK_IMPORTED_MODULE_0__["default"])(), top, bottom);
//# sourceURL=webpack:///./src/index.js?`
);
},

// nav.js
function(module, __webpack_exports__, __webpack_require__) {
'use strict';
eval(
`__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (() => "nav");
//# sourceURL=webpack:///./src/nav.js?`
);
}

// footer.js
function(module, exports) {
eval(
`const top = 'top';
const bottom = 'bottom';
module.exports = { top, bottom };
//# sourceURL=webpack:///./src/footer.js?`
);
},

上面代码__webpack_require__.r(__webpack_exports__)标记了这个模块使用了 ESM ,关于 ESM 的使用方法,定义的__webpack_require__中,去调用nav.js, __webpack_exports__["default"] = (() => "nav"); 通过定义在'default'上的函数来封装了返回的值,只有在需要调用的时候,再进一步去执行获取值,而 footer.js 则是直接执行获取了module.exports = { top, bottom };

关于 ESM 和 CommonJS 的互相引用则是利用了__webpack_require__.n来进行包装获取对应的 modules 对象的值。可以看对应的代码。

=> 7. 定义 getDefaultExport function 用来兼容 non-harmony module

最后,上面的生成的 WebpackBootstrap 代码 强烈建议放在浏览器的 devtools 的 snippet 中打上断点运行一遍来细细的感受一下。

评论

加载中,最新评论有1分钟延迟...