Webpack记录

144 阅读3分钟

目前技术理解 webpack 打包做了什么

读取转换文件

  • 用 fs 模块读取以及写入文件
  • 获取打包命令行参数,通过 process.argv
  • 读取文件后,替换文件内容主要依据是根据 ast 抽象语法分析判断,如将 import 语句,转换成 webpack_require,最后写入文件
const fs = require("fs");
const path = require("path");
const acorn = require("acorn"); // 抽象语法分析插件

 async function startWork () {
  const fileBuffer = await fs.promises.readFile(path.resolve(__dirname,"../src/index.js"));
  const fileString = fileBuffer.toString();
  // console.log('buffer=', fileBuffer);
  // console.log('fileString=', fileString);
  
  // fs.promises.writeFile(path.resolve(__dirname, "../src/main.js"), fileString.replace("import", "webpack_require"))
  // webpack 做这些替换前会做 ast 抽象语法分析
  const result = acorn.parse(fileString, { ecmaVersion: "2020", sourceType: "module" });
  // console.log('ast result=', result.body);
  const importStr = fileString.substring(27, 68)
  // console.log('声明=', importStr)
  const replaceStr = fileString.replace(importStr, "")
  // console.log('替换后=', replaceStr)

  // console.log('命令行参数', process.argv)
  const [fst, sec, ...argsFromUser] = process.argv
  // console.log(fst)
  // console.log(sec)
  // console.log(argsFromUser)
}

startWork();

打包主要步骤:

模块解析

Webpack 从指定的入口文件(entry point)开始,通过解析模块之间的依赖关系。它能够识别各种类型的模块,如 JavaScript、CSS、图片、JSON 等。 例如,在 JavaScript 中使用 import 或 require 语句来引入其他模块,Webpack 会解析这些语句以确定依赖关系。

构建依赖图

根据解析出的模块依赖关系,Webpack 构建一个依赖图(Dependency Graph)。这个图描述了各个模块之间的引用和依赖关系。

代码转换

对模块中的代码进行转换处理。例如,使用 Loader 来处理不同类型的文件,如将 ES6 代码转换为 ES5,处理 CSS 文件,将图片转换为 Data URL 等。

模块合并

将所有相关的模块按照依赖关系进行合并,生成最终的输出文件。

代码优化

在打包过程中进行一些优化操作,如代码压缩、去除未使用的代码(Tree Shaking)、提取公共模块等,以减小输出文件的大小,提高性能。

输出打包结果

将处理和优化后的代码输出到指定的目录和文件中,通常会生成一个或多个打包后的文件,如一个包含所有代码的 bundle.js 文件,或者根据配置生成多个代码块。

结果解析

  1. webpack 打包结果是一个 iife(立即执行函数)。基于 webpack_require 和 webpack_exports 对象的一套模块化规范,把源码中的文件作为一个个 key + function 的形式存储在 webpack_modules,加载的时候执行 webpack_modules 里对应的 key 的函数,函数执行完以后把对应的 exports 对象写入值,默认导出会有一个 default 属性在 exports 对象上,具名导出就是对应的 key 在exports 对象上
  2. webpack_require 会返回写入值的 exports 对象
  3. 拿到对应的 exports 对象
{
    default: () => __WEBPACK_DEFAULT_EXPORT__,
    printHello: () => /* binding */ printHello,
}
  1. 使用的时候,将原来的模块方式,改成对象 .key 使用方式。打包示例
(() => {
  "use strict";
  var __webpack_modules__ = {
    "./src/utils.js": (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        default: () => __WEBPACK_DEFAULT_EXPORT__,
        printHello: () => /* binding */ printHello,
      });
      const printHello = () => {
        console.log("Hello");
      };

      const key = "yes";
      const __WEBPACK_DEFAULT_EXPORT__ = key;
    },
  };

  // The module cache
  var __webpack_module_cache__ = {};

  // The require function
  function __webpack_require__(moduleId) {
    // Check if module is in cache
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    // Create a new module (and put it into the cache)
    var module = (__webpack_module_cache__[moduleId] = {
      // no module.id needed
      // no module.loaded needed
      exports: {},
    });

    // Execute the module function
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    // Return the exports of the module
    return module.exports;
  }

  /* webpack/runtime/define property getters */
  (() => {
    // define getter functions for harmony exports
    __webpack_require__.d = (exports, definition) => {
      for (var key in definition) {
        if (
          __webpack_require__.o(definition, key) &&
          !__webpack_require__.o(exports, key)
        ) {
          Object.defineProperty(exports, key, {
            enumerable: true,
            get: definition[key],
          });
        }
      }
    };
  })();

  /* webpack/runtime/hasOwnProperty shorthand */
  (() => {
    __webpack_require__.o = (obj, prop) =>
      Object.prototype.hasOwnProperty.call(obj, prop);
  })();

  /* webpack/runtime/make namespace object */
  (() => {
    // define __esModule on exports
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, {
          value: "Module",
        });
      }
      Object.defineProperty(exports, "__esModule", { value: true });
    };
  })();

  var __webpack_exports__ = {};
  /*!**********************!*\
  !*** ./src/index.js ***!

  __webpack_require__.r(__webpack_exports__);
  /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ =
    __webpack_require__(/*! ./utils */ "./src/utils.js");
  // import _ from 'lodash';

  function component() {
    const element = document.createElement("div");

    // Lodash, currently included via a script, is required for this line to work
    // element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    (0, _utils__WEBPACK_IMPORTED_MODULE_0__.printHello)();
    console.log(_utils__WEBPACK_IMPORTED_MODULE_0__["default"]);
    return element;
  }

  document.body.appendChild(component());
})();

Webpack 4和5

5内置了插件 clean-webpack-plugin,清除打包目录