Обсуждение веб-пакета

Webpack
Обсуждение веб-пакета

В этой статье используется«Подпись 4.0 International (CC BY 4.0)»Лицензионное соглашение, добро пожаловать в перепечатку или переделку для использования, но нужно указывать источник.

Автор: Baiying Front-end Team@Рыбы

1. Основные понятия

  • запись: запись. Webpack основан на модулях. Чтобы использовать webpack, вам сначала нужно указать запись синтаксического анализа модуля. Из этой записи webpack рекурсивно анализирует и обрабатывает все файлы ресурсов в соответствии с зависимостями между модулями.
  • выход: выход. Конечный продукт после обработки исходного кода webpack.
  • загрузчик: Конвертер модулей. Суть в функции, в которой происходит преобразование полученного контента и возврат преобразованного результата. Поскольку Webpack понимает только JavaScript, Loader становится переводчиком, предварительно обрабатывая другие типы ресурсов.
  • плагин: плагин расширения. на основе структуры потока событийTapable, Плагины могут расширять функции Webpack, и многие события будут транслироваться в течение жизненного цикла Webpack. Плагин может прослушивать эти события и изменять выходные результаты через API, предоставляемый Webpack в нужное время.
  • модуль: модуль. В дополнение к модулю es, commonJs, AMD и т. д. в категории js, css @import, url (...), изображения, шрифты и т. д. считаются модулями в веб-пакете.

Кроме того, режим запуска webpack4 является важной концепцией.Webpack предоставляет некоторые значения по умолчанию для различных режимов.Прилагаются жалобы г-на Руана Ифэна.

Конфигурация по умолчанию различных режимов выглядит следующим образом:

2. Процесс упаковки

  1. Параметры инициализации: чтение и объединение параметров из файлов конфигурации и операторов оболочки для получения окончательных параметров;
  2. Инициализировать компиляцию: инициализировать объект компилятора с параметрами, полученными на предыдущем шаге, зарегистрировать плагин и передать экземпляр компилятора (многие API-интерфейсы событий веб-пакета монтируются для вызова плагина);
  3. AST и граф зависимостей: начиная с входного файла (entry), вызовите механизм AST (желудь) для создания абстрактного синтаксического дерева AST и постройте все зависимости модуля в соответствии с AST;
  4. Рекурсивно компилировать модули: вызывать все настроенные загрузчики для компиляции модулей;
  5. Выходные ресурсы: в соответствии с зависимостями между записями и модулями соберите фрагменты, содержащие несколько модулей, один за другим, а затем преобразуйте каждый фрагмент в отдельный файл и добавьте его в список выходных данных.Этот шаг — последний шанс изменить выходное содержимое. ;
  6. Вывод завершен: после определения содержимого вывода определите путь вывода и имя файла в соответствии с конфигурацией и запишите содержимое файла в файловую систему;

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

Основные концепции процесса сборки:
  • Tapable: класс инструментов потока событий, основанный на публикации и подписке.Объекты Compiler и Compilation наследуются от Tapable.
  • Компилятор: основной объект, который компилируется веб-пакетом, глобальный синглтон, созданный на этапе инициализации компиляции, включая полную информацию о конфигурации, загрузчики, плагины и различные методы инструментов.
  • Компиляция: представляет собой процесс сборки веб-пакета и создания скомпилированных ресурсов. В режиме наблюдения каждая перекомпиляция, вызванная изменением файла, будет генерировать новый объект компиляции, включая текущий скомпилированный модуль модуля, скомпилированные ресурсы и измененные файлы. Состояние зависимостей и т. д. .

Более детальная схема строительства:

Нажмите здесь, чтобы увидеть большую картинку 👈

Источник блок-схемы:Front-end команда Amoy — подробное описание процесса веб-пакета

3. Loader

Загрузчик подобен транслятору, преобразующему исходный файл в целевой файл и передающему его следующему процессу для обработки.

инструкции
  • У каждого грузчика есть одна обязанность, как у рабочего на конвейере.
  • Порядок важен (справа налево)
Руководство по внедрению
  • Простой [Простой] загрузчик выполняет только одну задачу, несколько загрузчиков > многофункциональный загрузчик
  • Цепочка [Цепочка] следует принципу вызова цепочки
  • Stateless [Stateless] — это чистая функция в функциональном стиле, без побочных эффектов
  • Используйте библиотеку инструментов [Loader Utilities], чтобы в полной мере воспользоваться преимуществами пакета loader-utils.
Реализуйте простой загрузчик, который заменяет console.log, удаляет новые строки и добавляет строку пользовательского содержимого в конец файла.
/** webpack.config.js */

const path = require("path");

module.exports = {
  entry: {
    index: path.resolve(__dirname, "src/index.js"),
  },
  output: {
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: path.resolve("lib/loader/loader1.js"),
            options: {
              message: "this is a message",
            }
          }
        ],
      },
    ],
  },
};

/** lib/loader/loader1.js */

const loaderUtils = require('loader-utils');

/** 过滤console.log和换行符 */
module.exports = function (source) {

  // 获取loader配置项
  const options = loaderUtils.getOptions(this);

  console.log('loader配置项:', options);

  const result = source
    .replace(/console.log\(.*\);?/g, "")
    .replace(/\n/g, "")
    .concat(`console.log("${options.message || '没有配置项'}");`);

  return result;
};

Как написать асинхронный загрузчик
/** lib/loader/loader1.js */

/** 异步loader */
module.exports = function (source) {

  let count = 1;

  // 1.调用this.async() 告诉webpack这是一个异步loader,需要等待 asyncCallback 回调之后再进行下一个loader处理
  // 2.this.async 返回异步回调,调用表示异步loader处理结束
  const asyncCallback = this.async();

  const timer = setInterval(() => {
    console.log(`时间已经过去${count++}秒`);
  }, 1000);

  // 异步操作
  setTimeout(() => {
    clearInterval(timer);
    asyncCallback(null, source);
  }, 3200);

};


4. Plugin

Выполнение определенной функции на определенном узле в жизненном цикле компиляции веб-пакета.

Точки реализации:
  • Именованная функция JS или класс JS
  • Определите метод применения в прототипе (для вызова веб-пакета и внедрения объекта компилятора при вызове)
  • В функции применения должен быть хук события webpack, смонтированный через объект компилятора (текущий скомпилированный объект компиляции можно получить в функции хука)
  • Обрабатывать определенные данные во внутреннем экземпляре веб-пакета
  • Вызовите обратный вызов, предоставленный веб-пакетом, после завершения функции.
Базовая модель:
// 1、Plugin名称
const MY_PLUGIN_NAME = "MyBasicPlugin";

class MyBasicPlugin {
  // 2、在构造函数中获取插件配置项
  constructor(option) {
    this.option = option;
  }

  // 3、在原型对象上定义一个apply函数供webpack调用
  apply(compiler) {
    // 4、注册webpack事件监听函数
    compiler.hooks.emit.tapAsync(
      MY_PLUGIN_NAME,
      (compilation, asyncCallback) => {

        // 5、操作Or改变compilation内部数据
        console.log(compilation);      

        console.log("当前阶段 ======> 编译完成,即将输出到output目录");

        // 6、如果是异步钩子,结束后需要执行异步回调
        asyncCallback();
      }
    );
  }
}

// 7、模块导出
module.exports = MyBasicPlugin;
Реализуйте плагин, функцией которого является автоматическое создание файла README в каталоге dist:
const MY_PLUGIN_NAME = "MyReadMePlugin";

// 插件功能:自动生成README文件,标题取自插件option
class MyReadMePlugin {

  constructor(option) {
    this.option = option || {};
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      MY_PLUGIN_NAME,
      (compilation, asyncCallback) => {
        compilation.assets["README.md"] = {
          // 文件内容
          source: () => {
            return `# ${this.option.title || '默认标题'}`;
          },
          // 文件大小
          size: () => 30,
        };
        asyncCallback();
      }
    );
  }
}

// 7、模块导出
module.exports = MyReadMePlugin;

compiler.hooksНа него монтируются срабатывающие в разное время функции событий webpack (по аналогии с жизненным циклом React), которые могут выполнять другую логику или изменять результаты вывода на разных этапах компиляции, список поддерживаемых событий можно посмотреть здесь:compiler-hooks

Нажимаемый:

Архитектура подключаемых модулей веб-пакета в основном основана на Tapable.Tapable — это внутренняя библиотека команды проекта веб-пакета, которая в основном абстрагирует набор механизмов подключаемых модулей. Он похож на класс NodeJS EventEmitter и фокусируется на запуске и действии пользовательских событий.

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

Объект компилятора:

Он инициализируется один раз при запуске веб-пакета и уникален в глобальном масштабе. Его можно понимать как экземпляр компиляции веб-пакета. Он включает в себя исходную конфигурацию веб-пакета, загрузчик, ссылку на плагин и различные хуки.

Часть исходного кода: https://github.com/webpack/webpack/blob/10282ea20648b465caec6448849f24fc34e1ba3e/lib/webpack.js

5. Оптимизация производительности

1. С чего начать?
  • Используйте плагин speed-measure-webpack-plugin для измерения скорости упаковки

  • Анализ объема с помощью webpack-bundle-analyzer

    Из диаграммы анализа проекта видно, что очевидным пространством оптимизации является то, что BizCharts не вводится по запросу.В настоящее время мы можем выполнить анализ упаковки на пути импорта, чтобы увидеть эффект.

    Кроме того, каждый модуль на рисунке имеет три размера, а именно Stat Size, Parsed Size и Gzipped Size. Что эти три означают? Вы можете увидеть подключаемый модуль.github issue

2. Оптимизация конфигурации загрузчика

Основная идея состоит в том, чтобы оптимизировать время поиска, сузить диапазон поиска файлов и уменьшить ненужную работу по компиляции.Подробности см. в следующих конфигурационных файлах.

module .exports = { 
  module : { 
    rules : [{
      // 如果项目源码中只有 文件,就不要写成/\jsx?$/,以提升正则表达式的性能
      test: /\.js$/, 
      // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
      use: ['babel-loader?cacheDirectory'] , 
      // 只对项目根目录下 src 目录中的文件采用 babel-loader
      include: path.resolve(__dirname,'src'),
      // 使用resolve.alias把原导入路径映射成一个新的导入路径,减少耗时的递归解析操作
      alias: {
        'react': path.resolve( __dirname ,'./node_modules/react/dist/react.min.js'),
      },
      // 让 Webpack 忽略对部分没采用模块化的文件的递归解析处理
      noParse: '/jquery|lodash/',
    }],
  }
}
3. DLL Plugin Or Externals

Разумное использование DLLPlugin для выноса менее часто изменяемого кода (сторонней библиотеки) в отдельную компиляцию.Я понимаю, что в большинстве сценариев роль настройки экстерналов аналогична (не надо паковать сторонние библиотеки), но экстерналы будут быть в некоторых сценариях.Есть проблема сбоя, вы можете увидеть деталиэта статья, кроме того, DLLPlugin специально используетобратитесь сюда

4. Многопроцессорная серия

В многопроцессорном лагере есть несколько известных игроков:

  • thread-loader (официальная рекомендация после v4)
  • happypack (не очень поддерживается)
  • параллельный веб-пакет (не особо поддерживается)

Вот только введениеthread-loader,использоватьthread-loaderПоместите дорогостоящие загрузчики (например, babel-loader) в отдельный процесс (официальное описание рабочего пула) для обработки.Существуют следующие меры предосторожности при использовании

  • Поставьте его перед загрузчиками, которые нужно загружать отдельно, порядок важен

    module.exports = {
      module: {
        rules: [
          {
            test: /\.js$/,
            include: path.resolve("src"),
            use: [
              "thread-loader",
              // your expensive loader (e.g babel-loader)
            ]
          }
        ]
      }
    }
  • Использование загрузчиков в пуле воркеров ограничено, например нельзя использовать пользовательский API загрузчика, нельзя получить элементы конфигурации вебпака

5. Разумное использование кеша для сокращения времени не первой сборки

В настоящее время в проекте используются следующие плагины:hard-source-webpack-plugin, эффект более существенный, но есть 3 минуса

  1. Сгенерированный файл кеша имеет большой размер и занимает больше места на диске (был случай, когда файл кеша был загружен на сервер по ошибке, что приводило к очень медленной публикации =.=, поэтому лучше указать кеш путь к файлу как внутри node_modules)
  2. Этот репозиторий давно не обновлялся.
  3. Существующие проекты время от времени меняют код, не вызывая перекомпиляции, думаю, это может быть связано с этим плагином.

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

6. Сжатие кода для уменьшения размера продукта
  • Оптимизация конфигурации webpack3 = true по умолчанию включает UglifyJsPlugin, а его многопроцессорная версия — ParallelUglifyPlugin.

  • В webpack4 отказались от webpack.optimize.UglifyJsPlugin.По умолчанию для сжатия и оптимизации кода используется плагин terser-webpack-plugin, который нативно поддерживает многопроцессорность (вспомните официальную документацию здесьBuild PerformanceПервый пункт мер по оптимизации, перечисленных в главе: Будьте в курсе, самое ароматное — последняя версия вебпака)

7. Code Splitting

Официальный документ описывает три положения разделения кода:

  1. Конфигурация с несколькими входами (многозаходность — это естественное разделение кода, но в принципе никто не будет менять одностраничное приложение на многозаходное из-за оптимизации производительности)

  2. использоватьSplitChunksPluginДедупликация и извлечение

  3. использоватьDynamic ImportУкажите разделение модулей и можете комбинировать предварительную загрузку и предварительную выборку, чтобы оптимизировать взаимодействие с пользователем.

6. Думайте дальше: эти вопросы стоит изучить 🤔

  • Принцип ГМР?
  • Принцип встряхивания дерева, зачем вам нужно писать модуль es?
  • В чем преимущества Module Federation webpack5, что интересного в сочетании с http2.0 и приложением на микрофронтенде?
  • Почему rollup больше подходит для упаковки библиотек компонентов, чем webpack?