Как работает вебпак? (один)

Webpack
Как работает вебпак? (один)

Webpack — самый популярный интерфейсный пакетный инструмент, который упаковывает код разработки и выводит код, который можно запускать в различных браузерах, повышая эффективность процесса от разработки до выпуска.

Как вы, возможно, уже знаете, это удобство обеспечивается системой плагинов Webpack, но сегодня мы отложим эти концепции в сторону и начнем с простой практики, чтобы изучить, как код, упакованный Webpack, работает в среде браузера.

Простая конфигурация

Файл конфигурации является ключом к использованию Webpack.Файл конфигурации в основном состоит из нескольких частей, таких как запись, вывод, режим, загрузчик и плагин, но если вам нужно только организовать файлы JS, укажите запись и вывод пути к файлу для завершения упаковка мини проекта:

Каталог проекта:

  • build
    • webpack.config.js — хранит объект конфигурации webpack
  • src
    • index.js -- исходный файл
  • package.json — в этой статье в качестве примера используется webpack ^4.23.0.

Чтобы лучше наблюдать за выходным файлом, мы устанавливаем режим наdevelopmentОтключите сжатие кода и снова включитеsource-mapПоддержка отладки исходного кода.

конфигурационный файлbuild/webpack.config.js:

const path = require('path');
const resolve = relativePath => path.resolve(__dirname, relativePath);

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: resolve('../src/index.js'),
  output: {
    path: resolve('../dist'),
  }
};

Исходный файлsrc/index.js:

document.writeln('Hello webpack!');

Теперь запускаем командуwebpack --config build/webpack.config.js, после упаковки будет дополнительный выходной каталог dist:

  • build
    • webpack.config.js
  • dist
    • main.js
  • src
    • index.js
  • package.json

main— это имя выходного файла по умолчанию для веб-пакета, давайте быстро взглянем на этот файл:

dist/main.js:

(function(modules){
  // ...
})({
  "./src/index.js": (function(){
    // ...
  })
});

Весь файл содержит только одинФункция немедленного выполнения (IIFE), мы называем егоwebpackBootstrap, который получает только один объект - незагруженныйКоллекция модулей (модулей), ключ этого объекта модуля — это путь, а значение — функция. Вы спросите, а при чем тут модуль? Как они загружаются?

модуль

Не волнуйтесь, прежде чем мы более подробно рассмотрим выходной код, давайте обогатим исходный код:

Каталог проекта:

  • build
    • webpack.config.js
  • src
    • utils
      • math.js
    • index.js
  • package.json

новый файлsrc/utils/math.js:

export const plus = (a, b) => {
  return a + b;
};

export const minus = (a, b) => {
  return a - b;
};

src/index.js:

import {plus, minus} from './utils/math.js';

document.writeln('Hello webpack!');
document.writeln('1 + 2: ', plus(1, 2));
document.writeln('1 - 2: ', minus(1, 2));

Мы написали простой модуль, следуя модульному синтаксису спецификации ES.src/utils/math.js,Датьsrc/index.jsЦитировать. В настоящее время, хотя основные браузеры начали поддерживать<script type="module">способ поддержки модуля ES6, новремя покрыть. Webpack по-своему поддерживает спецификацию модуля ES6.Вышеупомянутый модуль является концепцией, соответствующей модулю ES6.

Далее давайте посмотрим, как эти модули реализованы в коде ES5. запустите команду еще разwebpack --config build/webpack.config.jsПосле просмотра выходного файла:

dist/main.js:

(function(modules){
  // ...
})({
  "./src/index.js": (function(){
    // ...
  }),
  "./src/utils/math.js": (function() {
    // ...
  })
});

В объекте модуля, переданном IIFE, есть дополнительная пара ключ-значение, соответствующая новому модулю.src/utils/math.js, который перекликается с модулями, которые мы разделили в исходном коде. Однако наличие модулей — это только первый шаг, и конечным результатом этого документа должно быть выполнение каждого модуля в порядке, установленном разработчиком.

Изучите веб-пакетBootstrap

см. далееwebpackBootstrapЧто в функции:

// webpackBootstrap
(function(modules){

  // 缓存 __webpack_require__ 函数加载过的模块
  var installedModules = {};
  
  /**
   * Webpack 加载函数,用来加载 webpack 定义的模块
   * @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")Присмотритесь к внутренней логике функции загрузки:

// ...

function __webpack_require__(moduleId) {
  // 重复加载则利用缓存
  if (installedModules[moduleId]) {
    return installedModules[moduleId].exports;
  }

  // 如果是第一次加载,则初始化模块对象,并缓存
  var module = installedModules[moduleId] = {
    i: moduleId,  // 模块 ID
    l: false,     // 模块加载标识
    exports: {}   // 模块导出对象
  };

  /**
    * 执行模块
    * @param module.exports -- 模块导出对象引用,改变模块包裹函数内部的 this 指向
    * @param module -- 当前模块对象引用
    * @param module.exports -- 模块导出对象引用
    * @param __webpack_require__ -- 用于在模块中加载其他模块
    */
  modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

  // 模块加载标识置为已加载
  module.l = true;

  // 返回当前模块的导出对象引用
  return module.exports;
}

// ...

Во-первых, функция загрузки использует переменную закрытияinstalledModules, используемый для хранения загруженных модулей в памяти. Следующим шагом является инициализация объекта модуля и его монтирование в кеш. Затем идет процесс выполнения модуля, при загрузке входного файлаmodules[moduleId]На самом деле это./src/index.jsСоответствующая функция модуля. Перед выполнением функции модуля передаются несколько фактических параметров, связанных с модулем, чтобы модуль мог экспортировать содержимое и загружать экспорт других модулей. Наконец, отмечается, что модуль загружен, и возвращается экспортированное содержимое модуля.

согласно с__webpack_require__Логика кэширования и экспорта, мы знаем, что в течение всего рабочего процесса IIFE, когда кэшированный модуль загружается, он будет напрямую возвращатьсяinstalledModules[moduleId].exports, другими словами, один и тот же модуль будет выполнять сам модуль только при первой ссылке на него.

функция выполнения модуля

__webpack_require__прошедшийmodules[moduleId].call()После запуска функции выполнения модуля мы введемwebpackBootstrapВ разделе параметров посмотрите на функцию выполнения модуля.


// webpackBootstrap
(function(modules){

  // ...

})({

  /*** 入口模块 ./src/index.js ***/
  "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
    "use strict";

    // 用于区分 ES 模块和其他模块规范,不影响理解 demo,战略跳过。
    __webpack_require__.r(__webpack_exports__);

    // 源模块代码中,`import {plus, minus} from './utils/math.js';` 语句被 loader 解析转化。
    // 加载 "./src/utils/math.js" 模块,
    /* harmony import */ var _utils_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/math.js */ "./src/utils/math.js");

    document.writeln('Hello webpack!');
    document.writeln('1 + 2: ', Object(_utils_math_js__WEBPACK_IMPORTED_MODULE_0__["plus"])(1, 2));
    document.writeln('1 - 2: ', Object(_utils_math_js__WEBPACK_IMPORTED_MODULE_0__["minus"])(1, 2));
  }),

  /*** 工具模块 ./src/utils/math.js ***/
  "./src/utils/math.js": (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";

    // 同 "./src/index.js"
    __webpack_require__.r(__webpack_exports__);

    // 源模块代码中,`export` 语句被 loader 解析转化。
    // 导出 __webpack_exports__
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "plus", function() { return plus; });
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "minus", function() { return minus; });
    const plus = (a, b) => {
      return a + b;
    };

    const minus = (a, b) => {
      return a - b;
    };
  })
});

Порядок выполнения следующий: входной модуль -> инструментальный модуль -> входной модуль. В входном модуле первый проход__webpack_require__("./src/utils/math.js")получил инструментальный модульexportsобъект. Снова взглянув на модуль инструмента, синтаксис экспорта ES преобразуется в__webpack_require__.d(__webpack_exports__, [key], [getter]),а также__webpack_require__.dФункция определена вwebpackBootstrapВнутри:

// ...

  // 定义 exports 对象导出的属性。
  __webpack_require__.d = function (exports, name, getter) {

    // 如果 exports (不含原型链上)没有 [name] 属性,定义该属性的 getter。
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, {
        enumerable: true,
        get: getter
      });
    }
  };

  // 包装 Object.prototype.hasOwnProperty 函数。
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty.call(object, property);
  };

// ...

видимый__webpack_require__.dНа самом деле этоObject.definePropertyПростая оболочка для (не зря она называется d ).

Подводя итог,__webpack_exports__изначально в__webpack_require__Создан в , начальное значение равно{}. Этот объект экспорта передается в модуль инструментаmath.js, добавляется кplusа такжеminus, а затем в__webpack_require__Наконец, экспортируется функция, которая является входным модулем.index.jsиспользуется исполнительной функцией.

Срок службы экспорта:

exports 的一生

После обращения к переменным, экспортированным модулем инструмента, модуль ввода выполняет оставшуюся часть. На этом базовый процесс выполнения модуля Webpack завершен.

Вышеупомянутое может бытьКлонировать образец кода для отладки, ветка естьdemo1.

В дополнение к спецификации модуля ES6, Webpack также поддерживает спецификации CommonJS и AMD, вы можете заменить модульную спецификацию и переупаковать, чтобы увидеть разницу.

резюме

Что ж, давайте суммируем идеи загрузки модулей Webpack с помощью блок-схемы:

webpack-module-implementation-sync

Ссылаться на

Глоссарий Webpack — Модуль