Как я увеличил производительность веб-страницы в 5 раз — оптимизация сборки

внешний интерфейс оптимизация производительности

Недавно была проведена общая оптимизация производительности на одном из ПК-сайтов компании.Из-за сложности системного дела и множества зависимостей скорость загрузки очень низкая.После оптимизации были значительно улучшены различные показатели производительности, а также скорость загрузки примерно в 5 раз быстрее.

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

Сегодня мы начнем с угла сборки, где оптимизация наиболее эффективна.

До оптимизации

Для начала посмотрим на загрузку ресурсов сайта до оптимизации:

самый большой видимыйvendorВ упаковке действительно есть3MB(проходить черезgzipПосле сжатия), если не производится дополнительная настройка,webpackВ этот пакет вносятся все сторонние зависимости, если вводить все больше и больше зависимостей, то пакет будет становиться все больше и больше.

Кроме того, дошел и логический пакет самой системы.600kb

Анализ зависимостей

мы можем использоватьwebpack-bundle-analyzerОтображая упакованный контент в виде удобной для взаимодействия древовидной диаграммы, мы можем интуитивно увидеть, какие более крупные модули, а затем провести целевую оптимизацию.

npm install --save-dev webpack-bundle-analyzer

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

Введение CDN

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

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

Если зависимость имеет один файл, который непосредственно упакован и сжатCDNресурсы, такие как на изображении вышеg6, вы можете использовать его напрямую.

Согласно официальной документации, если мы хотим сослаться на библиотеку, но не хотимwebpackупакованы и не влияют на наше использование в программеimport、requireилиwindow/globalЕсли он используется глобально, его можно использовать, настроивexternals.

externalsПараметры конфигурации позволяют «исключить зависимости из экспортируемых пакетов». Вместо этого созданныйbundleЗависит от тех, которые существуют в среде потребителя.

Первое местоCDNИмпортированные зависимости добавляются вexternalsсередина.

потом с помощьюhtml-webpack-pluginбудетCDNзапись в файлеhtml:

Здесь следует отметить одно, вhtmlнастроен вCDNСценарий импорта должен быть вbodyвнутри самого дна, потому что:

  • если поместить вbodyвыше илиheaderвнутри загрузка заблокирует отрисовку всей страницы.
  • если поместить вbodyКроме того, он будет загружен после загрузки бизнес-кода, и будет сообщено об ошибке, если в модуле используется модуль.

демонтировать поставщика

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

Тогда нам нужно идтиwebpackУстановите некоторые правила для упаковки зависимостей, которые мы хотим отделить отдельно.vendor.

Dynamic import

Посмотримv8оimportописание:

This syntactic form for importing modules is a static declaration: it only accepts a string literal as the module specifier, and introduces bindings into the local scope via a pre-runtime “linking” process. The static import syntax can only be used at the top-level of the file.

CommonJSиES ModuleЕсть большая разница в использовании,CommonJSПозволяет загружать модуль тогда, когда он вам нужен, вместо того, чтобы загружать все сразу. иES ModuleСинтаксис статический, статическийimportСинтаксис можно использовать только на верхнем уровне файла. В этом эталонном методе есть огромный недостаток, то есть невозможно импортировать модули по запросу.

<script type="module">
  import * as module from './utils.js';
  module.default();
  // → logs 'Hi from the default export!'
  module.doStuff();
  // → logs 'Doing stuff…'
</script>

к счастью,ES ModuleВ настоящее время также поддерживаетсяDynamic importиспользование, динамическийimportвернетpromise, вы можете дождаться загрузки модуля, прежде чем что-то делать, вместо того, чтобы загружать его при инициализации страницы.

<script type="module">
  const moduleSpecifier = './utils.js';
  import(moduleSpecifier)
    .then((module) => {
      module.default();
      // → logs 'Hi from the default export!'
      module.doStuff();
      // → logs 'Doing stuff…'
    });
</script>

<script type="module">
  (async () => {
    const moduleSpecifier = './utils.js';
    const module = await import(moduleSpecifier)
    module.default();
    // → logs 'Hi from the default export!'
    module.doStuff();
    // → logs 'Doing stuff…'
  })();
</script>

будетvendorПосле разделения зависимости будут по-прежнему загружаться на первом экране. Если зависимости не используются на первом экране, это все равно приведет к пустой трате сетевых ресурсов и блокировке рендеринга страницы. Для зависимостей, которые не нужно загружать на первом экране. первый экран, мы можем использовать динамическийimportПуть.

Например, вышеjs-export-excelЭта зависимость сама по себе почти500 kb, но он будет использоваться только тогда, когда пользователь нажмет кнопку [Экспорт].vendorудалить его.

При использовании,importЛогика изменена с первого экрана на асинхронную загрузку во время выполнения

В таком случае,js-export-excelЭтот пакет зависимостей будет импортирован только тогда, когда пользователь нажмет кнопку [Экспорт] и больше не будет импортирован на первом экране.

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

Реагировать на ленивую загрузку

Точно так же для некоторых сторонних зависимых компонентов, таких какmonaco editor, мы используем его только в очень немногих бизнес-сценариях, но он занимает свой собственный пакет5MB. . Нам приходится загружать его каждый раз, когда мы открываем страницу, что слишком интенсивно для производительности.

Для зависимого пакета мы можем динамическиimportспособ ленивой загрузки, но дляReactкомпоненты, используйте динамические напрямуюimportЭто может быть неуместно.Среда выполнения рендеринга компонента может запускаться несколько раз, и невозможно загружать компонент каждый раз, когда компонент рендерится.

React.lazyФункции позволяют обрабатывать динамически импортированные компоненты так же, как визуализацию обычных компонентов.React.lazyПринимает функцию, которую необходимо вызвать динамическиimport(). он должен вернутьPromise,ДолженPromiseнеобходимостьresolveОдинdefault exportизReactкомпоненты.

const MonacoEditor = React.lazy(() => import('react-monaco-editor'));

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

существуетSuspenseРендеринг в компонентеlazyкомпонент, который можно использовать во время ожидания загрузкиlazyизящно деградировать компоненты (такие какloading).fallbackСвойство принимает все, что вы хотите отобразить во время загрузки компонента.Reactэлемент. ты можешь поставитьSuspenseКомпоненты размещаются в любом месте поверх лениво загруженных компонентов. Вы даже можете использоватьSuspenseКомпонент оборачивает несколько лениво загруженных компонентов.

положить всеmonaco editorПосле перехода на ленивую загрузку первый экран больше не будет загружатьсяmonaco editor .

Отложенная загрузка маршрута

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

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

Оптимизация языкового пакета

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

Если вы выбираете библиотеку дат в начале, то прямо рекомендуется использоватьdayjs, если вы выберетеmoment, обязательно отфильтруйте неиспользуемые языковые пакеты, рекомендуется использоватьContextReplacementPlugin, это скажетwebpackКакой локальный файл мы будем использовать:

plugins: [
    new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/),
  ]

Эффект оптимизации

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

Если в статье есть ошибка, исправьте ее в области комментариев; если статья вам полезна, ставьте лайк, комментируйте, делитесь и надеюсь помочь большему количеству людей.

Эта статья впервые опубликована на паблике "Code Secret Garden". Просим обратить внимание всех. Оригинальный текст:Как я увеличил производительность веб-страницы в 5 раз — оптимизация сборки

Команде архитектуры внешнего интерфейса ByteDance IES срочно нужны таланты (p5/p7/p7 много HC), добро пожаловать, добавьте меня в WeChatConardLiДавайте делать вещи вместе.