• 【读书笔记】《深入浅出Webpack》


    Webpack版本

    分析版本为3.6.0
    4.0为最近升级的版本,与之前版本变化较大,编译输出的文件与3.0版本会不一致,目前项目中使用的版本3.0版本,所以基于3.0版本进行分析学习。

    Webpack构建流程

    • 初始化:启动构建,读取与合并配置参数,加载Plugin,实例化Complier
    • 编译:从Entry发出,针对每个Module串行调用对应的Loader去转换文件内容,再找到该Module依赖的Module,递归进行编译处理。
    • 输出:对编译后对Module组合成Chunk,把Chunk转换成文件,输出到文件系统。

    输出文件分析

    原输出文件结构:

    简化后的文件结构:

    (function(modules) {
      // 模拟 require 语句
      function __webpack_require__() {
      }
      // 执行存放所有模块数组中的第0个模块
      __webpack_require__(0);
    })([/*存放所有模块的数组*/])
    

    分割代码时输出

    当使用按需加载加载文件时,Webpack的输出文件会发生变化。

    // 异步加载 show.js
    import('./show').then((show) => {
      // 执行 show 函数
      show('Webpack');
    });
    

    重新构建后会输出两个文件,分别是执行入口文件bundle.js和异步加载文件0.bundle.js
    异步加载文件默认输入的文件名为 [id].js,可以在Webpack配置文件的output项中配置输出文件名
    其中0.bundle.js内容如下:

    // 加载在本文件(0.bundle.js)中包含的模块
    webpackJsonp(
      // 在其它文件中存放着的模块的 ID
      [0],
      // 本文件所包含的模块
      [
        // show.js 所对应的模块
        (function (module, exports) {
          function show(content) {
            window.document.getElementById('app').innerText = 'Hello,' + content;
          }
    
          module.exports = show;
        })
      ]
    );
    

    bundle.js内容如下:

    (function (modules) {
      /***
       * webpackJsonp 用于从异步加载的文件中安装模块。
       * 把 webpackJsonp 挂载到全局是为了方便在其它文件中调用。
       *
       * @param chunkIds 异步加载的文件中存放的需要安装的模块对应的 Chunk ID
       * @param moreModules 异步加载的文件中存放的需要安装的模块列表
       * @param executeModules 在异步加载的文件中存放的需要安装的模块都安装成功后,需要执行的模块对应的 index
       */
      window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
        // 把 moreModules 添加到 modules 对象中
        // 把所有 chunkIds 对应的模块都标记成已经加载成功 
        var moduleId, chunkId, i = 0, resolves = [], result;
        for (; i < chunkIds.length; i++) {
          chunkId = chunkIds[i];
          if (installedChunks[chunkId]) {
            resolves.push(installedChunks[chunkId][0]);
          }
          installedChunks[chunkId] = 0;
        }
        for (moduleId in moreModules) {
          if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
            modules[moduleId] = moreModules[moduleId];
          }
        }
        while (resolves.length) {
          resolves.shift()();
        }
      };
    
      // 缓存已经安装的模块
      var installedModules = {};
    
      // 存储每个 Chunk 的加载状态;
      // 键为 Chunk 的 ID,值为0代表已经加载成功
      var installedChunks = {
        1: 0
      };
    
      // 模拟 require 语句,和上面介绍的一致
      function __webpack_require__(moduleId) {
        // ... 省略和上面一样的内容
      }
    
      /**
       * 用于加载被分割出去的,需要异步加载的 Chunk 对应的文件
       * @param chunkId 需要异步加载的 Chunk 对应的 ID
       * @returns {Promise}
       */
      __webpack_require__.e = function requireEnsure(chunkId) {
        // 从上面定义的 installedChunks 中获取 chunkId 对应的 Chunk 的加载状态
        var installedChunkData = installedChunks[chunkId];
        // 如果加载状态为0表示该 Chunk 已经加载成功了,直接返回 resolve Promise
        if (installedChunkData === 0) {
          return new Promise(function (resolve) {
            resolve();
          });
        }
    
        // installedChunkData 不为空且不为0表示该 Chunk 正在网络加载中
        if (installedChunkData) {
          // 返回存放在 installedChunkData 数组中的 Promise 对象
          return installedChunkData[2];
        }
    
        // installedChunkData 为空,表示该 Chunk 还没有加载过,去加载该 Chunk 对应的文件
        var promise = new Promise(function (resolve, reject) {
          installedChunkData = installedChunks[chunkId] = [resolve, reject];
        });
        installedChunkData[2] = promise;
    
        // 通过 DOM 操作,往 HTML head 中插入一个 script 标签去异步加载 Chunk 对应的 JavaScript 文件
        var head = document.getElementsByTagName('head')[0];
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.charset = 'utf-8';
        script.async = true;
        script.timeout = 120000;
    
        // 文件的路径为配置的 publicPath、chunkId 拼接而成
        script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
    
        // 设置异步加载的最长超时时间
        var timeout = setTimeout(onScriptComplete, 120000);
        script.onerror = script.onload = onScriptComplete;
    
        // 在 script 加载和执行完成时回调
        function onScriptComplete() {
          // 防止内存泄露
          script.onerror = script.onload = null;
          clearTimeout(timeout);
    
          // 去检查 chunkId 对应的 Chunk 是否安装成功,安装成功时才会存在于 installedChunks 中
          var chunk = installedChunks[chunkId];
          if (chunk !== 0) {
            if (chunk) {
              chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
            }
            installedChunks[chunkId] = undefined;
          }
        };
        head.appendChild(script);
    
        return promise;
      };
    
      // 加载并执行入口模块,和上面介绍的一致
      return __webpack_require__(__webpack_require__.s = 0);
    })
    (
      // 存放所有没有经过异步加载的,随着执行入口文件加载的模块
      [
        // main.js 对应的模块
        (function (module, exports, __webpack_require__) {
          // 通过 __webpack_require__.e 去异步加载 show.js 对应的 Chunk
          __webpack_require__.e(0).then(__webpack_require__.bind(null, 1)).then((show) => {
            // 执行 show 函数
            show('Webpack');
          });
        })
      ]
    );
    

    这里的 bundle.js 和上面所讲的 bundle.js 非常相似,区别在于:

    • 多了一个 webpack_require.e 用于加载被分割出去的,需要异步加载的 Chunk 对应的文件;
    • 多了一个 webpackJsonp 函数用于从异步加载的文件中安装模块。

    参考

    深入浅出WebPack

  • 相关阅读:
    开源项目之Android Afinal框架
    DateTimePicker——开源的Android日历类库
    Android 教你打造炫酷的ViewPagerIndicator
    Android UI-仿微信底部导航栏布局
    Android 下拉刷新框架实现
    Android-设置PullToRefresh下拉刷新样式
    Android-PullToRefresh下拉刷新库基本用法
    android 在使用ViewAnimationUtils.createCircularReveal()无法兼容低版本的情况下,另行实现圆形...
    Android5.0新特性——兼容性(support)
    求訪问啊啊啊啊
  • 原文地址:https://www.cnblogs.com/GeniusLyzh/p/8823749.html
Copyright © 2020-2023  润新知