专栏名称: JavaScript
面向JavaScript爱好人员提供:前端最新资讯、原创内容、JavaScript、HTML5、Ajax、jQuery、Node.js等一系列教程和经验分享。
目录
相关文章推荐
51好读  ›  专栏  ›  JavaScript

Webpack 是怎样运行的?

JavaScript  · 公众号  · Javascript  · 2019-05-29 10:24

正文

请到「今天看啥」查看全文


  • * @param {String} moduleId 模块 ID,一般为模块的源码路径,如 "./src/index.js"

  • * @returns {Object} exports 导出对象

  • */

  • function __webpack_require__ ( moduleId ) {

  • // ...

  • }


  • // 在 __webpack_require__ 函数对象上挂载一些变量及函数 ...


  • // 传入表达式的值为 "./src/index.js"

  • return __webpack_require__ ( __webpack_require__ . s = "./src/index.js" );

  • })(/* modules */);

  • 可以看到其实主要做了两件事:

    1. 定义一个模块加载函数 __webpack_require__

    2. 使用加载函数加载入口模块 "./src/index.js"

    整个 webpackBootstrap 中只出现了入口模块的影子,那其他模块又是如何加载的呢?我们顺着 __webpack_require__("./src/index.js") 细看加载函数的内部逻辑:

    1. function __webpack_require__(moduleId) {

    2. // 重复加载则利用缓存

    3. if (installedModules[moduleId]) {

    4. return installedModules[moduleId].exports;

    5. }


    6. // 如果是第一次加载,则初始化模块对象,并缓存

    7. var module = installedModules[moduleId] = {

    8. i: moduleId, // 模块 ID

    9. l: false, // 模块加载标识

    10. exports: {} // 模块导出对象

    11. };


    12. /**

    13. * 执行模块

    14. * @param module.exports -- 模块导出对象引用,改变模块包裹函数内部的 this 指向

    15. * @param module -- 当前模块对象引用

    16. * @param module.exports -- 模块导出对象引用

    17. * @param __webpack_require__ -- 用于在模块中加载其他模块

    18. */

    19. modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);


    20. // 模块加载标识置为已加载

    21. module.l = true;


    22. // 返回当前模块的导出对象引用

    23. return module.exports;

    24. }

    首先,加载函数使用了闭包变量 installedModules ,用来将已加载过的模块保存在内存中。 接着是初始化模块对象,并把它挂载到缓存里。然后是模块的执行过程,加载入口文件时 modules [ moduleId ] 其实就是 . /src/ index . js 对应的模块函数。执行模块函数前传入了跟模块相关的几个实参,让模块可以导出内容,以及加载其他模块的导出。最后标识该模块加载完成,返回模块的导出内容。

    根据 __webpack_require__ 的缓存和导出逻辑,我们得知在整个 IIFE 运行过程中,加载已缓存的模块时,都会直接返回 installedModules [ moduleId ]. exports ,换句话说,相同的模块只有在第一次引用的时候才会执行模块本身。

    模块执行函数

    __webpack_require__ 中通过 modules [ moduleId ]. call () 运行了模块执行函数,下面我们就进入到 webpackBootstrap 的参数部分,看看模块的执行函数。

    1. /*** 入口模块 ./src/index.js ***/

    2. "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {

    3. "use strict";

    4. // 用于区分 ES 模块和其他模块规范,不影响理解 demo,战略跳过。

    5. __webpack_require__.r(__webpack_exports__);

    6. /* harmony import */

    7. // 源模块代码中,`import {plus} from './utils/math.js';` 语句被 loader 解析转化。

    8. // 加载 "./src/utils/math.js" 模块,

    9. var _utils_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/utils/math.js");

    10. console.log('Hello webpack!');

    11. console.log('1 + 2: ', Object(_utils_math_js__WEBPACK_IMPORTED_MODULE_0__["plus"])(1, 2));

    12. }),


    13. "./src/utils/math.js": (function (module, __webpack_exports__, __webpack_require__) {

    14. "use strict";

    15. __webpack_require__.r(__webpack_exports__);

    16. /* harmony export (binding) */

    17. // 源模块代码中,`export` 语句被 loader 解析转化。

    18. __webpack_require__.d(__webpack_exports__, "plus", function () {

    19. return plus;

    20. });

    21. const plus = (a, b) => {

    22. return a + b;

    23. };

    24. })

    执行顺序是:入口模块 -> 工具模块 -> 入口模块。入口模块中首先就通过 __webpack_require__ ( "./src/utils/math.js" ) 拿到了工具模块的 exports 对象。再看工具模块, ES 导出语法转化成了 __webpack_require__ . d ( __webpack_exports__ , [ key ], [ getter ]) ,而 __webpack_require__ . d 函数的定义在 webpackBootstrap 内:

    1. // 定义 exports 对象导出的属性。

    2. __webpack_require__.d = function (exports, name, getter) {


    3. // 如果 exports (不含原型链上)没有 [name] 属性,定义该属性的 getter。

    4. if (!__webpack_require__.o(exports, name)) {

    5. Object.defineProperty(exports, name, {

    6. enumerable: true,

    7. get: getter

    8. });

    9. }

    10. };


    11. // 包装 Object.prototype.hasOwnProperty 函数。

    12. __webpack_require__.o = function (object, property) {

    13. return Object.prototype.hasOwnProperty.call(object, property);

    14. };

    可见 __webpack_require__ . d 其实就是 Object . defineProperty 的简单包装.
    引用工具模块导出的变量后,入口模块再执行它剩余的部分。至此,Webpack 基本的模块执行过程就结束了。

    好了,我们用流程图总结一下 Webpack 模块的加载思路:

    异步加载

    有上面的打包我们发现将不同的打包进一个 main . js 文件。 main . js 会集中消耗太多网络资源,导致用户需要等待很久才可以开始与网页交互。

    一般的解决方式是:根据需求降低首次加载文件的体积,在需要时(如切换前端路由器,交互事件回调)异步加载其他文件并使用其中的模块。

    Webpack 推荐用 ES import() 规范来异步加载模块,我们根据 ES 规范修改一下入口模块的 import 方式,让其能够异步加载模块:







    请到「今天看啥」查看全文