Улучшена производительность загрузки страниц Next.js и Gatsby за счет детального разбиения на блоки.

Новая стратегия разделения веб-пакетов в Next.js и Gatsby сводит к минимуму дублирующийся код и повышает производительность загрузки страниц.

Chrome сотрудничает с инструментами и платформами в экосистеме JavaScript с открытым исходным кодом. Недавно был добавлен ряд новых оптимизаций для улучшения производительности загрузки Next.js и Gatsby . В этой статье рассматривается улучшенная стратегия детального разбиения на фрагменты, которая теперь поставляется по умолчанию в обеих платформах.

Введение

Как и многие веб-фреймворки, Next.js и Gatsby используют webpack в качестве основного сборщика. В webpack v3 введен CommonsChunkPlugin , позволяющий выводить модули, совместно используемые различными точками входа, в один (или несколько) «общий» фрагмент (или фрагменты). Общий код можно загрузить отдельно и заранее сохранить в кеше браузера, что может повысить производительность загрузки.

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

Общая точка входа и конфигурация пакета

Несмотря на практичность, концепция объединения всего кода общего модуля в один фрагмент имеет свои ограничения. Модули, не используемые в каждой точке входа, могут быть загружены для маршрутов, которые их не используют, в результате чего загружается больше кода, чем необходимо. Например, когда page1 загружает common чанк, она загружает код для moduleC хотя page1 не использует moduleC По этой причине, наряду с некоторыми другими, в webpack v4 удален плагин в пользу нового: SplitChunksPlugin .

Улучшенное разбиение на части

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

Однако многие веб-фреймворки, использующие этот плагин, по-прежнему следуют подходу «единого общего» к разделению блоков. Например, Next.js будет генерировать пакет commons , содержащий любой модуль, который используется более чем на 50% страниц, и все зависимости фреймворка ( react , react-dom и т. д.).

const splitChunksConfigs = {
  
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

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

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

Чтобы решить эту проблему, Next.js принял другую конфигурацию SplitChunksPlugin , которая сокращает количество ненужного кода для любого маршрута.

  • Любой достаточно большой сторонний модуль (более 160 КБ) разбивается на отдельный фрагмент.
  • Для зависимостей фреймворка создается отдельный блок frameworks ( react , react-dom и т. д.).
  • Создается столько общих чанков, сколько необходимо (до 25).
  • Минимальный размер создаваемого фрагмента изменен на 20 КБ.

Эта детальная стратегия фрагментации обеспечивает следующие преимущества:

  • Время загрузки страницы улучшено . Создание нескольких общих фрагментов вместо одного минимизирует количество ненужного (или дублирующего) кода для любой точки входа.
  • Улучшено кеширование во время навигации . Разделение больших библиотек и зависимостей платформы на отдельные фрагменты снижает вероятность аннулирования кэша, поскольку они вряд ли изменятся до тех пор, пока не будет выполнено обновление.

Вы можете увидеть всю конфигурацию, принятую Next.js, в webpack-config.ts .

Больше HTTP-запросов

SplitChunksPlugin определил основу для детального разбиения на фрагменты, и применение этого подхода к такой платформе, как Next.js, не было совершенно новой концепцией. Однако многие платформы по-прежнему продолжали использовать единую эвристическую и «общую» стратегию пакетов по нескольким причинам. Это включает в себя опасения, что большое количество HTTP-запросов может отрицательно повлиять на производительность сайта.

Браузеры могут открывать только ограниченное количество TCP-соединений с одним источником (6 для Chrome), поэтому минимизация количества фрагментов, выводимых сборщиком, может гарантировать, что общее количество запросов останется ниже этого порога. Однако это справедливо только для HTTP/1.1. Мультиплексирование в HTTP/2 позволяет передавать несколько запросов параллельно, используя одно соединение через один источник. Другими словами, нам обычно не нужно беспокоиться об ограничении количества фрагментов, создаваемых нашим сборщиком.

Все основные браузеры поддерживают HTTP/2. Команды Chrome и Next.js хотели посмотреть, повлияет ли каким-либо образом увеличение количества запросов за счет разделения единого пакета Next.js на несколько общих фрагментов на производительность загрузки. Они начали с измерения производительности одного сайта, одновременно изменяя максимальное количество параллельных запросов с помощью свойства maxInitialRequests .

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

В среднем за три запуска нескольких испытаний на одной веб-странице время load , начала рендеринга и первой отрисовки контента оставалось примерно одинаковым при изменении максимального количества начальных запросов (от 5 до 15). Интересно, что мы заметили небольшое снижение производительности только после агрессивного разделения на сотни запросов.

Производительность загрузки страниц при сотнях запросов

Это показало, что соблюдение надежного порога (20–25 запросов) обеспечивает правильный баланс между производительностью загрузки и эффективностью кэширования. После некоторого базового тестирования в качестве счетчика maxInitialRequest было выбрано 25.

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

Сокращение полезной нагрузки JavaScript за счет увеличения фрагментации

Этот эксперимент заключался только в изменении количества запросов, чтобы увидеть, будет ли какое-либо негативное влияние на производительность загрузки страницы. Результаты показывают, что установка maxInitialRequests на 25 на тестовой странице была оптимальной, поскольку она уменьшала размер полезной нагрузки JavaScript без замедления страницы. Общий объем JavaScript, необходимый для увлажнения страницы, остался примерно таким же, что объясняет, почему производительность загрузки страницы не обязательно улучшилась при уменьшении объема кода.

webpack использует 30 КБ в качестве минимального размера по умолчанию для создаваемого фрагмента. Однако сочетание значения maxInitialRequests , равного 25, с минимальным размером 20 КБ вместо этого привело к улучшению кэширования.

Уменьшение размера за счет гранулированных кусков

Многие фреймворки, включая Next.js, полагаются на маршрутизацию на стороне клиента (управляемую JavaScript) для внедрения новых тегов сценария при каждом переходе маршрута. Но как они заранее определяют эти динамические фрагменты во время сборки?

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

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Вывод нескольких общих фрагментов в приложении Next.js.

Эта новая стратегия детального разбиения на фрагменты была впервые реализована в Next.js под флагом, где она была протестирована на ряде первых пользователей. Многие заметили значительное сокращение общего объема JavaScript, используемого для всего сайта:

Веб-сайт Общее изменение JS % Разница
https://www.barnebys.com/ -238 КБ -23%
https://sumup.com/ -220 КБ -30%
https://www.hashicorp.com/ -11 МБ -71%
Уменьшение размера JavaScript — по всем маршрутам (сжатым)

Окончательная версия по умолчанию поставляется в версии 9.2 .

Гэтсби

Гэтсби использовал тот же подход, используя эвристику на основе использования для определения общих модулей:

config.optimization = {
  
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

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

Веб-сайт Общее изменение JS % Разница
https://www.gatsbyjs.org/ -680 КБ -22%
https://www. Thirdandgrove.com/ -390 КБ -25%
https://ghost.org/ -1,1 МБ -35%
https://reactjs.org/ -80 Кб -8%
Уменьшение размера JavaScript — по всем маршрутам (сжатым)

Взгляните на PR , чтобы понять, как они реализовали эту логику в конфигурации своего веб-пакета, который по умолчанию поставляется в версии 2.20.7.

Заключение

Концепция доставки фрагментированных фрагментов не является специфичной для Next.js, Gatsby или даже веб-пакета. Каждому следует рассмотреть возможность улучшения стратегии разбивки своего приложения на фрагменты, если она следует подходу «больших «общих» пакетов», независимо от используемой платформы или сборщика модулей.

  • Если вы хотите, чтобы те же оптимизации разбиения на фрагменты были применены к стандартному приложению React, взгляните на этот пример приложения React . Он использует упрощенную версию стратегии детального разбиения на фрагменты и может помочь вам начать применять ту же логику на своем сайте.
  • Для Rollup по умолчанию фрагменты создаются гранулярно. Если вы хотите настроить поведение вручную, посмотрите manualChunks .
,

Новая стратегия разделения веб-пакетов в Next.js и Gatsby сводит к минимуму дублирующийся код и повышает производительность загрузки страниц.

Chrome сотрудничает с инструментами и платформами в экосистеме JavaScript с открытым исходным кодом. Недавно был добавлен ряд новых оптимизаций для улучшения производительности загрузки Next.js и Gatsby . В этой статье рассматривается улучшенная стратегия детального разбиения на фрагменты, которая теперь поставляется по умолчанию в обеих платформах.

Введение

Как и многие веб-фреймворки, Next.js и Gatsby используют webpack в качестве основного сборщика. В webpack v3 введен CommonsChunkPlugin , позволяющий выводить модули, совместно используемые различными точками входа, в один (или несколько) «общий» фрагмент (или фрагменты). Общий код можно загрузить отдельно и заранее сохранить в кеше браузера, что может повысить производительность загрузки.

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

Общая точка входа и конфигурация пакета

Несмотря на практичность, концепция объединения всего кода общего модуля в один фрагмент имеет свои ограничения. Модули, не используемые в каждой точке входа, могут быть загружены для маршрутов, которые их не используют, в результате чего загружается больше кода, чем необходимо. Например, когда page1 загружает common чанк, она загружает код для moduleC хотя page1 не использует moduleC По этой причине, наряду с некоторыми другими, в webpack v4 удален плагин в пользу нового: SplitChunksPlugin .

Улучшенное разбиение на части

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

Однако многие веб-фреймворки, использующие этот плагин, по-прежнему следуют подходу «единого общего» к разделению блоков. Например, Next.js будет генерировать пакет commons , содержащий любой модуль, который используется более чем на 50% страниц, и все зависимости фреймворка ( react , react-dom и т. д.).

const splitChunksConfigs = {
  
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

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

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

Чтобы решить эту проблему, Next.js принял другую конфигурацию SplitChunksPlugin , которая сокращает количество ненужного кода для любого маршрута.

  • Любой достаточно большой сторонний модуль (более 160 КБ) разбивается на отдельный фрагмент.
  • Для зависимостей фреймворка создается отдельный блок frameworks ( react , react-dom и т. д.).
  • Создается столько общих чанков, сколько необходимо (до 25).
  • Минимальный размер создаваемого фрагмента изменен на 20 КБ.

Эта детальная стратегия фрагментации обеспечивает следующие преимущества:

  • Время загрузки страницы улучшено . Создание нескольких общих фрагментов вместо одного минимизирует количество ненужного (или дублирующего) кода для любой точки входа.
  • Улучшено кеширование во время навигации . Разделение больших библиотек и зависимостей платформы на отдельные фрагменты снижает вероятность аннулирования кэша, поскольку они вряд ли изменятся до тех пор, пока не будет выполнено обновление.

Вы можете увидеть всю конфигурацию, принятую Next.js, в webpack-config.ts .

Больше HTTP-запросов

SplitChunksPlugin определил основу для детального разбиения на фрагменты, и применение этого подхода к такой платформе, как Next.js, не было совершенно новой концепцией. Однако многие платформы по-прежнему продолжали использовать единую эвристическую и «общую» стратегию пакетов по нескольким причинам. Это включает в себя опасения, что большое количество HTTP-запросов может отрицательно повлиять на производительность сайта.

Браузеры могут открывать только ограниченное количество TCP-соединений с одним источником (6 для Chrome), поэтому минимизация количества фрагментов, выводимых сборщиком, может гарантировать, что общее количество запросов останется ниже этого порога. Однако это справедливо только для HTTP/1.1. Мультиплексирование в HTTP/2 позволяет передавать несколько запросов параллельно, используя одно соединение через один источник. Другими словами, нам обычно не нужно беспокоиться об ограничении количества фрагментов, создаваемых нашим сборщиком.

Все основные браузеры поддерживают HTTP/2. Команды Chrome и Next.js хотели посмотреть, повлияет ли увеличение количества запросов за счет разделения единого пакета Next.js на несколько общих фрагментов каким-либо образом на производительность загрузки. Они начали с измерения производительности одного сайта, одновременно изменяя максимальное количество параллельных запросов с помощью свойства maxInitialRequests .

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

В среднем за три запуска нескольких испытаний на одной веб-странице время load , начала рендеринга и первой отрисовки контента оставалось примерно одинаковым при изменении максимального количества начальных запросов (от 5 до 15). Интересно, что мы заметили небольшое снижение производительности только после агрессивного разделения на сотни запросов.

Производительность загрузки страниц при сотнях запросов

Это показало, что соблюдение надежного порога (20–25 запросов) обеспечивает правильный баланс между производительностью загрузки и эффективностью кэширования. После некоторого базового тестирования в качестве счетчика maxInitialRequest было выбрано 25.

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

Сокращение полезной нагрузки JavaScript за счет увеличения фрагментации

Этот эксперимент заключался только в изменении количества запросов, чтобы увидеть, будет ли какое-либо негативное влияние на производительность загрузки страницы. Результаты показывают, что установка maxInitialRequests на 25 на тестовой странице была оптимальной, поскольку она уменьшала размер полезной нагрузки JavaScript без замедления страницы. Общий объем JavaScript, необходимый для увлажнения страницы, остался примерно таким же, что объясняет, почему производительность загрузки страницы не обязательно улучшилась при уменьшении объема кода.

webpack использует 30 КБ в качестве минимального размера по умолчанию для создаваемого фрагмента. Однако сочетание значения maxInitialRequests , равного 25, с минимальным размером 20 КБ вместо этого привело к улучшению кэширования.

Уменьшение размера за счет гранулированных кусков

Многие фреймворки, включая Next.js, полагаются на маршрутизацию на стороне клиента (управляемую JavaScript) для внедрения новых тегов сценария при каждом переходе маршрута. Но как они заранее определяют эти динамические фрагменты во время сборки?

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

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Вывод нескольких общих фрагментов в приложении Next.js.

Эта новая стратегия детального разбиения на фрагменты была впервые реализована в Next.js под флагом, где она была протестирована на ряде первых пользователей. Многие заметили значительное сокращение общего объема JavaScript, используемого для всего сайта:

Веб-сайт Общее изменение JS % Разница
https://www.barnebys.com/ -238 КБ -23%
https://sumup.com/ -220 КБ -30%
https://www.hashicorp.com/ -11 МБ -71%
Уменьшение размера JavaScript — по всем маршрутам (сжатым)

Окончательная версия по умолчанию поставляется в версии 9.2 .

Гэтсби

Гэтсби использовал тот же подход, используя эвристику на основе использования для определения общих модулей:

config.optimization = {
  
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

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

Веб-сайт Общее изменение JS % Разница
https://www.gatsbyjs.org/ -680 КБ -22%
https://www. Thirdandgrove.com/ -390 КБ -25%
https://ghost.org/ -1,1 МБ -35%
https://reactjs.org/ -80 Кб -8%
Уменьшение размера JavaScript — по всем маршрутам (сжатым)

Взгляните на PR , чтобы понять, как они реализовали эту логику в конфигурации своего веб-пакета, который по умолчанию поставляется в версии 2.20.7.

Заключение

Концепция доставки фрагментированных фрагментов не является специфичной для Next.js, Gatsby или даже веб-пакета. Каждому следует рассмотреть возможность улучшения стратегии разбивки своего приложения на фрагменты, если она следует подходу «больших «общих» пакетов», независимо от используемой платформы или сборщика модулей.

  • Если вы хотите, чтобы те же оптимизации разбиения на фрагменты были применены к стандартному приложению React, взгляните на этот пример приложения React . Он использует упрощенную версию стратегии детального разбиения на фрагменты и может помочь вам начать применять ту же логику на своем сайте.
  • Для Rollup по умолчанию фрагменты создаются гранулярно. Если вы хотите настроить поведение вручную, посмотрите manualChunks .
,

Новая стратегия разделения веб-пакетов в Next.js и Gatsby сводит к минимуму дублирующийся код и повышает производительность загрузки страниц.

Chrome сотрудничает с инструментами и платформами в экосистеме JavaScript с открытым исходным кодом. Недавно был добавлен ряд новых оптимизаций для улучшения производительности загрузки Next.js и Gatsby . В этой статье рассматривается улучшенная стратегия детального разбиения на фрагменты, которая теперь поставляется по умолчанию в обеих платформах.

Введение

Как и многие веб-фреймворки, Next.js и Gatsby используют webpack в качестве основного сборщика. В webpack v3 введен CommonsChunkPlugin , позволяющий выводить модули, совместно используемые различными точками входа, в один (или несколько) «общий» фрагмент (или фрагменты). Общий код можно загрузить отдельно и заранее сохранить в кеше браузера, что может повысить производительность загрузки.

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

Общая точка входа и конфигурация пакета

Несмотря на практичность, концепция объединения всего кода общего модуля в один фрагмент имеет свои ограничения. Модули, не используемые в каждой точке входа, могут быть загружены для маршрутов, которые их не используют, в результате чего загружается больше кода, чем необходимо. Например, когда page1 загружает common чанк, она загружает код для moduleC хотя page1 не использует moduleC По этой причине, наряду с некоторыми другими, в webpack v4 удален плагин в пользу нового: SplitChunksPlugin .

Улучшенное разбиение на части

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

Однако многие веб-фреймворки, использующие этот плагин, по-прежнему следуют подходу «единого общего» к разделению блоков. Например, Next.js будет генерировать пакет commons , содержащий любой модуль, который используется более чем на 50% страниц, и все зависимости фреймворка ( react , react-dom и т. д.).

const splitChunksConfigs = {
  
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

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

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

Чтобы решить эту проблему, Next.js принял другую конфигурацию SplitChunksPlugin , которая сокращает количество ненужного кода для любого маршрута.

  • Любой достаточно большой сторонний модуль (более 160 КБ) разбивается на отдельный фрагмент.
  • Для зависимостей фреймворка создается отдельный блок frameworks ( react , react-dom и т. д.).
  • Создается столько общих чанков, сколько необходимо (до 25).
  • Минимальный размер создаваемого фрагмента изменен на 20 КБ.

Эта детальная стратегия фрагментации обеспечивает следующие преимущества:

  • Время загрузки страницы улучшено . Создание нескольких общих фрагментов вместо одного минимизирует количество ненужного (или дублирующего) кода для любой точки входа.
  • Улучшено кеширование во время навигации . Разделение больших библиотек и зависимостей платформы на отдельные фрагменты снижает вероятность аннулирования кэша, поскольку они вряд ли изменятся до тех пор, пока не будет выполнено обновление.

Вы можете увидеть всю конфигурацию, принятую Next.js, в webpack-config.ts .

Больше HTTP-запросов

SplitChunksPlugin определил основу для детального разбиения на фрагменты, и применение этого подхода к такой платформе, как Next.js, не было совершенно новой концепцией. Однако многие платформы по-прежнему продолжали использовать единую эвристическую и «общую» стратегию пакетов по нескольким причинам. Это включает в себя опасения, что большое количество HTTP-запросов может отрицательно повлиять на производительность сайта.

Браузеры могут открывать только ограниченное количество TCP-соединений с одним источником (6 для Chrome), поэтому минимизация количества фрагментов, выводимых сборщиком, может гарантировать, что общее количество запросов останется ниже этого порога. Однако это справедливо только для HTTP/1.1. Мультиплексирование в HTTP/2 позволяет передавать несколько запросов параллельно, используя одно соединение через один источник. Другими словами, нам обычно не нужно беспокоиться об ограничении количества фрагментов, создаваемых нашим сборщиком.

Все основные браузеры поддерживают HTTP/2. Команды Chrome и Next.js хотели посмотреть, повлияет ли каким-либо образом увеличение количества запросов за счет разделения единого пакета Next.js на несколько общих фрагментов на производительность загрузки. Они начали с измерения производительности одного сайта, одновременно изменяя максимальное количество параллельных запросов с помощью свойства maxInitialRequests .

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

В среднем за три запуска нескольких испытаний на одной веб-странице время load , начала рендеринга и первой отрисовки контента оставалось примерно одинаковым при изменении максимального количества начальных запросов (от 5 до 15). Интересно, что мы заметили небольшое снижение производительности только после агрессивного разделения на сотни запросов.

Производительность загрузки страниц при сотнях запросов

Это показало, что соблюдение надежного порога (20–25 запросов) обеспечивает правильный баланс между производительностью загрузки и эффективностью кэширования. После некоторого базового тестирования в качестве счетчика maxInitialRequest было выбрано 25.

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

Сокращение полезной нагрузки JavaScript за счет увеличения фрагментации

Этот эксперимент заключался только в изменении количества запросов, чтобы увидеть, будет ли какое-либо негативное влияние на производительность загрузки страницы. Результаты показывают, что установка maxInitialRequests на 25 на тестовой странице была оптимальной, поскольку она уменьшала размер полезной нагрузки JavaScript без замедления страницы. Общий объем JavaScript, необходимый для увлажнения страницы, остался примерно таким же, что объясняет, почему производительность загрузки страницы не обязательно улучшилась при уменьшении объема кода.

webpack использует 30 КБ в качестве минимального размера по умолчанию для создаваемого фрагмента. Однако сочетание значения maxInitialRequests , равного 25, с минимальным размером 20 КБ вместо этого привело к улучшению кэширования.

Уменьшение размера за счет гранулированных кусков

Многие фреймворки, включая Next.js, полагаются на маршрутизацию на стороне клиента (управляемую JavaScript) для внедрения новых тегов сценария при каждом переходе маршрута. Но как они заранее определяют эти динамические фрагменты во время сборки?

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

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Вывод нескольких общих фрагментов в приложении Next.js.

Эта новая стратегия детального разбиения на фрагменты была впервые реализована в Next.js под флагом, где она была протестирована на ряде первых пользователей. Многие заметили значительное сокращение общего объема JavaScript, используемого для всего сайта:

Веб-сайт Общее изменение JS % Разница
https://www.barnebys.com/ -238 КБ -23%
https://sumup.com/ -220 КБ -30%
https://www.hashicorp.com/ -11 МБ -71%
Уменьшение размера JavaScript — по всем маршрутам (сжатым)

Окончательная версия по умолчанию поставляется в версии 9.2 .

Гэтсби

Гэтсби использовал тот же подход, используя эвристику на основе использования для определения общих модулей:

config.optimization = {
  
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

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

Веб-сайт Общее изменение JS % Разница
https://www.gatsbyjs.org/ -680 КБ -22%
https://www. Thirdandgrove.com/ -390 КБ -25%
https://ghost.org/ -1,1 МБ -35%
https://reactjs.org/ -80 Кб -8%
Уменьшение размера JavaScript — по всем маршрутам (сжатым)

Взгляните на PR , чтобы понять, как они реализовали эту логику в конфигурации своего веб-пакета, который по умолчанию поставляется в версии 2.20.7.

Заключение

Концепция доставки фрагментированных фрагментов не является специфичной для Next.js, Gatsby или даже веб-пакета. Каждому следует рассмотреть возможность улучшения стратегии разбивки своего приложения на фрагменты, если она следует подходу «больших «общих» пакетов», независимо от используемой платформы или сборщика модулей.

  • Если вы хотите, чтобы те же оптимизации разбиения на фрагменты были применены к стандартному приложению React, взгляните на этот пример приложения React . Он использует упрощенную версию стратегии детального разбиения на фрагменты и может помочь вам начать применять ту же логику на своем сайте.
  • Для Rollup по умолчанию фрагменты создаются гранулярно. Если вы хотите настроить поведение вручную, посмотрите manualChunks .
,

Новая стратегия разделения веб-пакетов в Next.js и Gatsby сводит к минимуму дублирующийся код и повышает производительность загрузки страниц.

Chrome сотрудничает с инструментами и платформами в экосистеме JavaScript с открытым исходным кодом. Недавно был добавлен ряд новых оптимизаций для улучшения производительности загрузки Next.js и Gatsby . В этой статье рассматривается улучшенная стратегия детального разбиения на фрагменты, которая теперь поставляется по умолчанию в обеих платформах.

Введение

Как и многие веб-фреймворки, Next.js и Gatsby используют webpack в качестве основного сборщика. В webpack v3 введен CommonsChunkPlugin , позволяющий выводить модули, совместно используемые различными точками входа, в один (или несколько) «общий» фрагмент (или фрагменты). Общий код можно загрузить отдельно и заранее сохранить в кеше браузера, что может повысить производительность загрузки.

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

Общая точка входа и конфигурация пакета

Несмотря на практичность, концепция объединения всего кода общего модуля в один фрагмент имеет свои ограничения. Модули, не используемые в каждой точке входа, могут быть загружены для маршрутов, которые их не используют, в результате чего загружается больше кода, чем необходимо. Например, когда page1 загружает common чанк, она загружает код для moduleC хотя page1 не использует moduleC По этой причине, наряду с некоторыми другими, в webpack v4 удален плагин в пользу нового: SplitChunksPlugin .

Улучшенное разбиение на части

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

Однако многие веб-фреймворки, использующие этот плагин, по-прежнему следуют подходу «единого общего» к разделению блоков. Например, Next.js будет генерировать пакет commons , содержащий любой модуль, который используется более чем на 50% страниц, и все зависимости фреймворка ( react , react-dom и т. д.).

const splitChunksConfigs = {
  
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

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

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

Чтобы решить эту проблему, Next.js принял другую конфигурацию SplitChunksPlugin , которая сокращает количество ненужного кода для любого маршрута.

  • Любой достаточно большой сторонний модуль (более 160 КБ) разбивается на отдельный фрагмент.
  • Для зависимостей фреймворка создается отдельный блок frameworks ( react , react-dom и т. д.).
  • Создается столько общих чанков, сколько необходимо (до 25).
  • Минимальный размер создаваемого фрагмента изменен на 20 КБ.

Эта детальная стратегия фрагментации обеспечивает следующие преимущества:

  • Время загрузки страницы улучшено . Создание нескольких общих фрагментов вместо одного минимизирует количество ненужного (или дублирующего) кода для любой точки входа.
  • Улучшено кеширование во время навигации . Разделение больших библиотек и зависимостей платформы на отдельные фрагменты снижает вероятность аннулирования кэша, поскольку они вряд ли изменятся до тех пор, пока не будет выполнено обновление.

Вы можете увидеть всю конфигурацию, принятую Next.js, в webpack-config.ts .

Больше HTTP-запросов

SplitChunksPlugin определил основу для детального разбиения на фрагменты, и применение этого подхода к такой платформе, как Next.js, не было совершенно новой концепцией. Однако многие платформы по-прежнему продолжали использовать единую эвристическую и «общую» стратегию пакетов по нескольким причинам. Это включает в себя опасения, что большое количество HTTP-запросов может отрицательно повлиять на производительность сайта.

Браузеры могут открывать только ограниченное количество TCP-соединений с одним источником (6 для Chrome), поэтому минимизация количества фрагментов, выводимых сборщиком, может гарантировать, что общее количество запросов останется ниже этого порога. Однако это справедливо только для HTTP/1.1. Мультиплексирование в HTTP/2 позволяет передавать несколько запросов параллельно, используя одно соединение через один источник. Другими словами, нам, как правило, не нужно беспокоиться о ограничении количества кусков, излучаемых нашим бундлером.

Все основные браузеры поддерживают HTTP/2. Команды Chrome и Next.js хотели увидеть, будет ли увеличение количества запросов, разделяя единственную пучку «Commons» на следующем. Они начали с измерения производительности одного сайта при изменении максимального количества параллельных запросов с использованием свойства maxInitialRequests .

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

В среднем три прогона нескольких испытаний на одной веб-странице, load , начальная-модернизация и первое содержательное время краски оставалось примерно одинаковым при изменении количества первоначальных запросов MAX (от 5 до 15). Интересно, что мы заметили небольшие накладные расходы только после того, как агрессивно разделились на сотни запросов.

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

Это показало, что пребывание под надежным порогом (20 ~ 25 запросов) достигло правильного баланса между производительностью загрузки и эффективностью кэширования. После некоторого базового тестирования 25 были выбраны в качестве подсчета maxInitialRequest .

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

Снижение полезной нагрузки JavaScript с увеличением Chunking

Этот эксперимент был только о изменении количества запросов, чтобы увидеть, будет ли какое -либо отрицательное влияние на производительность загрузки страницы. Результаты показывают, что установление maxInitialRequests на 25 на тестовой странице было оптимальным, поскольку он уменьшил размер полезной нагрузки JavaScript, не замедляя страницу. Общее количество JavaScript, необходимое для увлажнения страницы, все еще оставалось примерно таким же, что объясняет, почему производительность загрузки страницы не обязательно улучшалась с уменьшенным количеством кода.

WebPack использует 30 КБ в качестве минимального размера по умолчанию для генерируемого куски. Тем не менее, сочетание значения maxInitialRequests 25 с минимальным размером 20 кб, вместо этого привело к лучшему кэшированию.

Сокращение размера с гранулированными кусками

Многие рамки, в том числе Next.js, полагаются на маршрутизацию на стороне клиента (обрабатываются JavaScript) для внедрения новых тегов сценариев для каждого перехода маршрута. Но как они предопределяют эти динамические куски во время сборки?

Next.js использует манифестный файл на стороне сервера, чтобы определить, какие выводимые куски используются различными точками входа. Чтобы предоставить эту информацию клиенту, был создан сокращенный файл манифеста на стороне клиента, чтобы отобразить все зависимости для каждой точки входа.

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Вывод нескольких общих кусков в приложении Next.js.

Эта более новая гранулированная стратегия кункинга была впервые развернута в следующем.js за флагом, где она была проверена на ряде ранних пользователей. Многие видели значительное сокращение общего JavaScript, используемого для всего их сайта:

Веб-сайт Общее изменение JS % Разница
https://www.barnebys.com/ -238 КБ -23%
https://sumup.com/ -220 КБ -30%
https://www.hashicorp.com/ -11 МБ -71%
Снижение размера JavaScript - на всех маршрутах (сжатые)

Окончательная версия была отправлена ​​по умолчанию в версии 9.2 .

Гэтсби

Гэтсби использовал тот же подход использования эвристики на основе использования для определения общих модулей:

config.optimization = {
  
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

Оптимизируя их конфигурацию WebPack для принятия аналогичной гранулированной стратегии Chunking, они также заметили значительное сокращение JavaScript на многих крупных сайтах:

Веб-сайт Общее изменение JS % Разница
https://www.gatsbyjs.org/ -680 кб -22%
https://www.thirdandgrove.com/ -390 КБ -25%
https://ghost.org/ -1,1 МБ -35%
https://reactjs.org/ -80 кб -8%
Снижение размера JavaScript - на всех маршрутах (сжатые)

Взгляните на PR , чтобы понять, как они реализовали эту логику в свою конфигурацию WebPack, которая отправляется по умолчанию в v2.20.7.

Заключение

Концепция доставки гранулированных кусков не зависит от Next.js, Gatsby или даже Webpack. Каждый должен рассмотреть вопрос о улучшении стратегии Chunking своего приложения, если она следует за большим подходом к пакетам «Commons», независимо от используемой структуры или модуля.

  • Если вы хотите, чтобы те же оптимизации применения применены к приложению ванильного реагирования, взгляните на это приложение React . Он использует упрощенную версию гранулированной стратегии кункинга и может помочь вам начать применение той же вида логики на ваш сайт.
  • Для подзора кусочки создаются гранулярно по умолчанию. Взгляните на manualChunks , если вы хотите вручную настроить поведение.