Трилогия по оптимизации производительности интерфейса (загрузка)

внешний интерфейс опрос

предисловие

Хотя front-end разработка является разновидностью разработки GUI, она имеет свою особенность.Особенность front-end заключается в слове «динамический».Традиционная разработка GUI, будь то настольное приложение или мобильное приложение, должна быть загружена. заранее. Приложение будет работать в локальной операционной системе, а внешний интерфейс отличается. Он «динамический инкрементный». Наши внешние приложения часто загружаются и выполняются в режиме реального времени, и их не нужно загружать заранее. , что вызывает проблему.Внешняя разработкаЧто часто больше всего влияет на производительность, так это не расчет или рендеринг, а скорость загрузки, которая напрямую влияет на взаимодействие с пользователем и удержание веб-сайта.

Проектирование для производительностиавторLara SwansonНаписал статью в 2014 году«Веб-производительность как пользовательский опыт», она упомянула в статье, что «быстрая загрузка страниц веб-сайта может повысить доверие пользователей к веб-сайту и увеличить количество повторных посещений. Большинство пользователей на самом деле ожидают, что страница будет загружена в течение 2 секунд, а когда это превышает 3 секундыоколо 40% пользователей покинут ваш сайт".

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

Например, анимация уже имеет 60 кадров, а вы оптимизируете ее до 120 кадров через алгоритм зависания, что бесполезно для вашего KPI, потому что сама по себе оптимизация бессмысленна, т. разница между 60 кадрами и 120 кадрами, которая не улучшает взаимодействие с пользователем.Наоборот, для веб-сайта, который загружается на первом экране за 4 секунды, у вас нет существенной оптимизации производительности, просто добавьте родственный дизайн дизайна Диаграмма загрузки также является очень значимой оптимизацией, потому что хорошая загрузка может снизить беспокойство пользователей и заставить их чувствовать, что они не ждали слишком долго.Это оптимизация производительности на уровне пользовательского опыта.

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


Каталог статей

  1. Оптимизация загрузки выше сгиба
  2. Оптимизация загрузки прыжков по маршруту

1. Загрузка первого экрана

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

Чтобы интерпретировать ключевые моменты первого экрана с точки зрения пользовательского опыта, каков наш мыслительный процесс после ввода URL-адреса в качестве пользователя?

Когда мы нажимаем Enter, наш первый вопрос:
"Это работает?" 
Этот вопрос был, пока пользователь не видит первый график страницы. В это время пользователь может определить, что их запрос эффективен (не на стене ...), то второй вопрос:
"Это работает?" 
Пользователю непонятно, если не по порядку отрисовываются только бессмысленные элементы.В это время, хотя страница и начинает загружаться, она не имеет значения для пользователя, пока не загрузятся такие элементы, как текстовый контент и интерактивные кнопки.Понять страницу В это время пользователь попытается взаимодействовать со страницей, и будет третий вопрос:
"Это работает?" 
Только когда пользователь успешно взаимодействует со страницей, загрузка верхней части страницы завершается.

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

1.1 Определение белого экрана

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

Итак, давайте сначала определим белый экран, чтобы мы могли легко рассчитать наше время белого экрана, потому что логика расчета белого экрана отличается, некоторые люди говорят, что его следует считать от первого рисунка (First Paint, FP) до первая отрисовка контента (First Contentful Paint), FCP) этот промежуток времени считается белым экраном, лично я не согласен, лично я предпочитаю начинать от смены маршрута (то есть момента, когда пользователь снова нажимает ввод) к первому отрисовка контента (то есть первый контент можно увидеть)) засчитывается как время белого экрана, потому что согласно психологии пользователя, когда он нажимает Enter, он думает, что инициировал запрос, и пока он не увидит первый элемент, нарисовано, сердце пользователя тревожится, потому что он не знает, будет ли ответ на этот запрос (веб-сайт не работает?), я не знаю, сколько времени потребуется, чтобы ответить (веб-сайт работает медленно?), этот период является первый период ожидания для пользователя.

Время белого экрана = firstPaint - performance.timing.navigationStart

Возьмем в качестве примера версию Weibo для веб-приложения (один из немногих продуктов для совести на Weibo), после Lighthouse (инструмент тестирования веб-сайтов Google) время загрузки белого экрана составляет 2 секунды, что является очень хорошим результатом.

image.png


1.2 Анализ проблемы загрузки белого экрана

При разработке современных интерфейсных приложений мы часто используем сборщики, такие как webpack для упаковки.Во многих случаях, если мы не оптимизируем, будет много огромных кусков, некоторые даже около 5M (я использовал webpack1 для первого time.x упаковывает 8M пакетов), эти куски убивают скорость загрузки.

Браузеры обычно имеют ограничение на одновременные запросы.В примере с Chrome количество одновременных запросов равно 6, а это означает, что мы должны запросить первые 6, прежде чем мы сможем продолжить делать последующие запросы, что также влияет на скорость загрузки наших ресурсов.

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

1.3 Оптимизация производительности белого экрана

Давайте сначала разберемся, что произошло во время белого экрана:

  1. Нажмите Enter, браузер анализирует URL-адрес, выполняет DNS-запрос, запрос возвращает IP-адрес и отправляет запрос HTTP (S) через IP-адрес.
  2. Сервер возвращает HTML, и браузер начинает парсить HTML, что запускает запрос ресурсов js и css.
  3. js загружается, начинает выполнение js, вызывает различные функции для создания DOM и отрисовывает его в корневой узел до тех пор, пока не будет сгенерирован первый видимый элемент
1.3.1 подсказка загрузки

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

	<div id="root"></div>

Мы будем рендерить весь упакованный код в этот корневой узел, и как мы его рендерим? Конечно, мы используем JavaScript для работы с различными рендерингами dom, такими как реакция. Он должен вызывать различные_React_._createElement_(), что очень трудоемко. В этот период, хотя html загружается, это все еще белый экран, поэтому есть место для манипуляций. Можем ли мы добавить подсказку во время выполнения js, чтобы увеличить пользовательский опыт?

Да, обычно у нас есть плагин для веб-пакетов, который называетсяhtml-webpack-plugin, где вы настраиваете html для вставки загружаемого изображения в файл.

конфигурация вебпака:

const HtmlWebpackPlugin = require('html-webpack-plugin')
const loading = require('./render-loading') // 事先设计好的 loading 图

module.exports = {
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      loading: loading
    })
  ]
}

1.3.2 (псевдо) рендеринг на стороне сервера

Итак, поскольку будет время ждать, когда HTML загрузится в js для выполнения, почему бы не отобразить его прямо на стороне сервера?Возвращенный напрямую HTML имеет полную структуру DOM, поэтому нет необходимости вызывать js для выполнения различных задач создание DOM, не только это, но и дружественное к SEO.

Именно из-за этого спроса и vue, и react поддерживают рендеринг на стороне сервера, а родственные фреймворки Nuxt.js и Next.js также популярны.Конечно, стоимость приложений, которые приняли рендеринг на стороне клиента, слишком высока. .

Итак, кто-то придумал решение. Google открыл исходный код библиотеки Puppeteer, которая на самом деле является безголовым браузером. Через этот безголовый браузер мы можем использовать код для имитации работы различных браузеров. Например, мы можем использовать узел для сохранения html Для pdf вы можете имитировать щелчки, отправку форм и другие операции на бэкенде, и, естественно, вы можете имитировать браузер, чтобы получить HTML-структуру первого экрана.

prerender-spa-pluginЭто подключаемый модуль, основанный на вышеуказанных принципах. Этот подключаемый модуль локально имитирует среду браузера и заранее выполняет наши упакованные файлы, так что HTML-код первого экрана может быть получен путем синтаксического анализа. В обычной среде мы можем вернуть предварительно проанализированный HTML.

1.3.3 Включить HTTP2

Мы видим, что после получения html нам нужно парсить сверху вниз, после парсинга вscriptСвязанные теги можно запрашивать только для связанных ресурсов, а из-за ограничений параллелизма браузера мы можем запрашивать не более 6 раз за раз, так есть ли способ решить эти дилеммы?

http2 — очень хорошее решение, сам механизм http2 достаточно быстр:

  1. http2 использует для связи двоичное кадрирование, тогда как http1.x использует текст, а http2 более эффективен.
  2. http2 может быть мультиплексирован, то есть для связи с одним и тем же доменным именем требуется только один TCP для установления канала запроса, и запросы и ответы могут передаваться двунаправленно на основе этого канала одновременно, в то время как http1.x необходимо установить TCP для каждого запроса, а для нескольких запросов требуется несколько соединений, а также ограничения параллелизма отнимают много времени

image.png

  1. HTTP2 может сжимать заголовки, что может сэкономить сетевой трафик, занимаемый заголовками сообщений, в то время как HTTP/1.x будет содержать много избыточной информации заголовков для каждого запроса, тратя впустую много ресурсов полосы пропускания.

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

image.png

  1. http2 может быть отправлен на сервер.Мы обычно запрашиваем ресурсы css и js после того, как обнаружим соответствующие теги после разбора HTML, в то время как http2 может напрямую отправлять связанные ресурсы напрямую без запроса, что значительно сокращает время, затрачиваемое на несколько запросов.

мы можем нажатьЭтот сайтТест с http2

Я когда-то делал тест, и производительность http2 при гладкой сети + высокопроизводительные устройства не имеет явного преимущества перед http1.1, но чем хуже сеть и чем хуже устройство, влияние http2 на загрузку качественное, может Можно сказать, что протокол http2 создан для мобильного интернета, но он имеет менее очевидные преимущества на высокопроизводительных ПК, поддерживаемых оптическими волокнами.

1.3.4 Включить кеш браузера

Поскольку http-запросы доставляют столько хлопот, можем ли мы избежать http-запросов или уменьшить нагрузку на http-запросы для оптимизации производительности?

Использование кеширования браузера — хороший способ свести к минимуму HTTP-запросы.Перед этим мы должны просмотреть соответствующие знания о http-кэшировании.

Давайте сначала перечислим заголовки ответа на запрос, связанные с кэшированием.

  • Expires

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

  • Cache-Control

Заголовки запроса/ответа, поля управления кешем для точного управления политикой кеша.

  • If-Modified-Since

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

  • Last-Modified

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

  • Etag

Заголовок ответа, идентификатор ресурса, сообщается сервером браузеру.

  • If-None-Match

Заголовок запроса, идентификатор кэшированного ресурса, сообщается браузером серверу.

Сопряженные поля используются:

  • If-Modified-Since и Last-Modified
  • Etags и If-None-Match

Когда локального кеша нет, это выглядит так:


Когда есть локальный кеш, но срок его действия не истек:
image.png

Согласовать кеш, когда срок действия кеша истечет:
image.png

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

Обычно наше веб-приложение состоит из нашего собственного кода и сторонних библиотек.Наш собственный код будет часто меняться, а сторонняя библиотека не изменится, если не будет обновлена ​​более крупная версия, поэтому первая сторонняя библиотека и наша код должен быть упакован отдельно.Мы можем установить длительное сильное время кэширования для сторонней библиотеки, так что код сторонней библиотеки не будет часто запрашиваться.

Итак, как извлекать сторонние библиотеки? В webpack4.x плагин SplitChunksPlugin заменяет плагин CommonsChunkPlugin для извлечения общих модулей. Мы можем настроить плагин SplitChunksPlugin для выполненияРаспаковкаработать.

Конфигурация SplitChunksPlugin показана следующим образом:

optimization: {
    splitChunks: { 
      chunks: "initial",         // 代码块类型 必须三选一: "initial"(初始化) | "all"(默认就是all) | "async"(动态加载) 
      minSize: 0,                // 最小尺寸,默认0
      minChunks: 1,              // 最小 chunk ,默认1
      maxAsyncRequests: 1,       // 最大异步请求数, 默认1
      maxInitialRequests: 1,     // 最大初始化请求书,默认1
      name: () => {},            // 名称,此选项课接收 function
      cacheGroups: {                // 缓存组会继承splitChunks的配置,但是test、priorty和reuseExistingChunk只能用于配置缓存组。
        priority: "0",              // 缓存组优先级,即权重 false | object |
        vendor: {                   // key 为entry中定义的 入口名称
          chunks: "initial",        // 必须三选一: "initial"(初始化) | "all" | "async"(默认就是异步)
          test: /react|lodash/,     // 正则规则验证,如果符合就提取 chunk
          name: "vendor",           // 要缓存的 分隔出来的 chunk 名称
          minSize: 0,
          minChunks: 1,
          enforce: true,
          reuseExistingChunk: true   // 可设置是否重用已用chunk 不再创建新的chunk
        }
      }
    }
  }

SplitChunksPluginЭлементов конфигурации много.Вы можете перейти на официальный сайт, чтобы узнать, как его настроить.Сейчас мы лишь кратко перечислим элементы конфигурации.

Если мы хотим извлечь сторонние библиотеки, мы можем просто настроить это так:

   splitChunks: {
      chunks: 'all',   // initial、async和all
      minSize: 30000,   // 形成一个新代码块最小的体积
      maxAsyncRequests: 5,   // 按需加载时候最大的并行请求数
      maxInitialRequests: 3,   // 最大初始化请求数
      automaticNameDelimiter: '~',   // 打包分割符
      name: true,
      cacheGroups: {
        vendor: {
          name: "vendor",
          test: /[\\/]node_modules[\\/]/, //打包第三方库
          chunks: "all",
          priority: 10 // 优先级
        },
        common: { // 打包其余的的公共代码
          minChunks: 2, // 引入两次及以上被打包
          name: 'common', // 分离包的名字
          chunks: 'all',
          priority: 5
        },
      }
    },

Итак, вроде бы вы закончили? Нет, у нас большая проблема с нашей конфигурацией:

  1. Возможно ли для нас упаковать сторонние библиотеки вместе?Конечно, есть проблема, потому что, когда сторонние библиотеки упаковываются вместе, пока есть библиотека, которую мы обновляем или вводим новую библиотеку, чанк изменится , то вариативность чанка будет очень высокой, и он не подходит для длительного кеширования.Еще один момент - мы хотим улучшить скорость загрузки главной страницы.Первоочередная задача уменьшить количество кода это зависит от загрузки домашней страницы.Могу ли я спросить вас, как реагировать vue reudx, основная библиотека всего приложения, мы должны для домашней страницы В дополнение к зависимостям, специальные библиотеки, такие как d3.js и three.js, которые появляются только на определенных страницах, не нужно загружать на первом экране, поэтому нам нужно отделить базовую библиотеку приложения от конкретных зависимых библиотек.
  2. Когда чанк находится в периоде сильного кеша, но код сервера изменился, как мы уведомляем клиента? Как мы видели на диаграмме выше, когда хит-ресурс находится в пределах периода кеша, браузер напрямую считывает кеш, не подтверждая это серверу.Что, если код сервера изменился в это время? На данный момент мы не можем кэшировать index.html (в любом случае, html-страница в эпоху вебпаков настолько мала, что нет необходимости в кэшировании), нам нужно обновлять сервер каждый раз, когда вводится скрипт, и включить hashchunk, который используется при изменении чанка.Будет сгенерировано новое хэш-значение.Если оно не изменится, оно не изменится, поэтому, когда индекс загружает последующие ресурсы скрипта, если hasshchunk не меняется, он попадет кеш.Если он изменится, он снова отправится на сервер для загрузки новых ресурсов.

На следующем рисунке показано, как распаковать сторонние библиотеки, основные реакции и другие библиотеки с помощью инструмента lodash и конкретной библиотеки Echarts.

      cacheGroups: {
        reactBase: {
          name: 'reactBase',
          test: (module) => {
              return /react|redux/.test(module.context);
          },
          chunks: 'initial',
          priority: 10,
        },
        utilBase: {
          name: 'utilBase',
          test: (module) => {
              return /rxjs|lodash/.test(module.context);
          },
          chunks: 'initial',
          priority: 9,
        },
        uiBase: {
          name: 'chartBase',
          test: (module) => {
              return /echarts/.test(module.context);
          },
          chunks: 'initial',
          priority: 8,
        },
        commons: {
          name: 'common',
          chunks: 'initial',
          priority: 2,
          minChunks: 2,
        },
      }

Мы хэшируем чанк, как показано на рисунке ниже, после того, как мы изменили код, относящийся к чанк2, никакие другие чанки не изменились, изменился только хеш чанк2

  output: {
    filename: mode === 'production' ? '[name].[chunkhash:8].js' : '[name].js',
    chunkFilename: mode === 'production' ? '[id].[chunkhash:8].chunk.js' : '[id].js',
    path: getPath(config.outputPath)
  }

image.png
image.png

Мы заставляем интерфейсные проекты в полной мере использовать кэш с помощью стратегии http cache + webpack hash cache, но причина, по которой webpack нуждается в легендарноминженер по настройке webpackЕсть причина, потому что сам веб-пакет является метафизикой, или приведенное выше изображение является примером, что, если ваш код, связанный с chunk2, удалит зависимость или введет новую зависимость, которая уже существует в проекте?

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

image.png
Причина в том, что webpack добавит идентификатор к каждому чанку, а id самоувеличивается.Например, id в чанке 0 равен 0. Как только мы введем новые зависимости, самоинкремент чанка будет нарушен. на этот раз, потому что хешчанк основан на Контент генерирует хеш, что приводит к изменению идентификатора, что приводит к большим изменениям хешчанка, хотя содержание кода совсем не изменилось.
image.png

Для этой проблемы нам нужно ввести дополнительный подключаемый модуль HashedModuleIdsPlugin, который называет идентификатор фрагмента несамоинкрементным способом, что может решить эту проблему.Хотя веб-пакет утверждает, что имеет конфигурацию 0, эта общая функция не встроена. в и придется ждать до следующей версии.
image.png

Содержимое, связанное с хеш-кэшем Webpack, рекомендуется прочитать этостатьякак расширение

1.4 FMP (Первая значимая ничья)

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

Затем, хотя страница начинает прорисовываться между FCP и FMP, вся страница бессмысленна, пользователь все еще томительно ждет, и в это время могут быть неупорядоченные элементы или мерцающие элементы, что сильно влияет на опыт. В настоящее время нам может потребоваться выполнить некоторую оптимизацию взаимодействия с пользователем.
Скелет — хороший метод, сейчас широко используется скелет, его значение в том, чтобы заранее распределить элементы для рендеринга, избежать заставки и в то же время предложить пользователю что-то отрендерить, чтобы пользователь меньше беспокоился.

Например, Skeleton от Weibo проделал очень хорошую работу.

image.png

Существуют соответствующие реализации Skeleton на разных фреймворках.
React: встроенная скелетная диаграмма antdSkeletonстроить планы
Vue: vue-skeleton-webpack-plugin

Взяв в качестве примера vue-cli 3, мы можем настроить его непосредственно в vue.config.js.

//引入插件
const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');

module.exports = {
	// 额外配置参考官方文档
	configureWebpack: (config)=>{
		config.plugins.push(new SkeletonWebpackPlugin({
			webpackConfig: {
				entry: {
					app: path.join(__dirname, './src/Skeleton.js'),
				},
			},
			minimize: true,
			quiet: true,
		}))
	},
	//这个是让骨架屏的css分离,直接作为内联style处理到html里,提高载入速度
	css: {
		extract: true,
		sourceMap: false,
		modules: false
	}
}

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

1.5 TTI (время взаимодействия)

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

Мы видим, что большое количество скриптов все еще выполняется, даже когда страница в основном отрисована.

image.png

В настоящее время страница не является интерактивной до прибытия TTI. После прибытия TTI пользователи могут нормально взаимодействовать со страницей. Как правило, не существует особенно точного метода измерения для TTI. Обычно считается, что **FMP && Триггер события DOMContentLoader && визуальная загрузка страницы После 85%** этих условий наступил TTI.

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

  1. Размер скрипта JavaScript
  2. Сама скорость выполнения JavaScript

Мы объяснили некоторые проблемы объема JavaScript в предыдущем разделе. Мы можем уменьшить объем, используя метод разборки библиотеки SplitChunksPlugin. Кроме того, есть некоторые другие методы, которые мы объясним ниже.

1.5.1 Tree Shaking

Хотя Tree Shaking появился очень рано, например, rollup, де-факто стандартный инструмент упаковки базовой библиотеки js, является дедушкой Tree Shaking.После того, как react упакован с помощью rollup, объем уменьшается на 30%, что является силой встряхивания дерева.

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

В настоящее время Tree Shaking поддерживается по умолчанию в производственной среде после версии webpack4.x, поэтому Tree Shaking можно назвать готовой технологией, но это не значит, что Tree Shaking действительно будет работать, т.к. там еще много ям.

Яма 1: перевод Babel, мы уже упоминали, что при использовании Tree Shaking необходимо использовать модуль es6.Если вы используете динамический модуль, такой как common.js, Tree Shaking будет недействительным, но Babel включен по умолчанию с common.js, поэтому Нам нужно закрыть его вручную.
Яма 2: Сторонние библиотеки неконтролируемы. Мы уже знаем, что анализ программы Tree Shaking опирается на ESM, но многие библиотеки на рынке по-прежнему предоставляют только коды версии ES5 для совместимости, что делает Tree Shaking недействительным для многих сторонних библиотек. поэтому мы должны максимально полагаться на библиотеки с ESM, например, раньше была ESM-версия lodash (lodash-es), мы можем ссылаться на нее вот такimport { dobounce } from 'lodash-es'

1.5.2 Динамическая загрузка полифиллов

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

Способ решения этой проблемы очень прост, непосредственно введен<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>Это все, и это более удобно для разработчиков Vue Шаблон, сгенерированный vue-cli, теперь поставляется с этой ссылкой.

image.png

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

image.png

1.5.3 Динамическая загрузка кода ES6

Поскольку полифиллы могут загружаться динамически, можно ли динамически загружать код es5 и es6+?Да, но какой в ​​этом смысл?Будет ли es6 быстрее?

Мы должны сначала дать понять, что в целом после выпуска нового стандарта производители браузеров сосредоточатся на оптимизации производительности нового стандарта, в то время как оптимизация производительности старого стандарта будет постепенно стагнировать. В будущем производительность es6 будет все больше и больше, более быстрое развитие.
Во-вторых, код, который мы обычно пишем, может быть es6+, а выпущенный es5 переводится babel или ts.В большинстве случаев код, переведенный инструментами, часто уступает по производительности рукописному коду.Сайт сравнения производительностиТо же самое относится и к отображению .Хотя инструменты перевода, такие как babel, совершенствуются, производительность переведенного кода по-прежнему снижается, особенно для перевода кода класса снижение производительности очевидно.
Наконец, объем кода после транспиляции будет завышен.Транслятор использует множество уловок и приемов для преобразования es6 в es5, что приводит к резкому увеличению объема кода.Использование es6 представляет меньший объем.

Итак, как динамически загружать код es6?<script type="module">Этот тег используется, чтобы определить, поддерживает ли браузер es6.Я видел перевод на Nuggets раньше.статьяСуществует подробный динамический процесс упаковки, который можно расширить, чтобы прочитать.

Сравнение размеров объема

image.png

Сравнение времени выполнения

image.png

Результат сравнения двух сторон заключается в том, что размер кода es6 в два раза меньше, а производительность в два раза выше.

1.5.4 Дизассемблированный код уровня маршрутизации

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

  1. Базовые библиотеки Framework, такие как vue redux и т. д.
  2. Некоторые компоненты формы и компоненты кнопок пользовательского интерфейса и т. д.
  3. Простой компонент макета
  4. Другая второстепенная логика и стили

Код интерфейса входа очень мал, почему бы просто не загрузить код интерфейса входа?
Это требует от нас разделения кода на уровне роутинга.Помимо основного фреймворка и библиотеки UI, нам нужно только загрузить код текущей страницы, что требует использования технологии Code Splitting для разделения кода, нам нужно это на самом деле довольно просто сделать.
Мы должны настроить babel с плагином-синтаксисом-динамическим импортом, плагином динамического импорта, а затем мы можем использовать импорт в теле функции.

Для Vue вы можете импортировать маршрутизацию следующим образом

export default new Router({ 
  routes: [ 
  { 
  path: '/', 
  name: 'Home', 
  component: Home 
  }, 
  { 
  path: '/login', 
  name: 'login', 
  component: () => import('@components/login') 
  } 
  ]

Ваша страница входа будет упакована отдельно.
Для реакции встроенныйReact.lazy()Вы можете динамически загружать маршруты и компоненты, эффект аналогичен vue, конечноlazy()В настоящее время нет поддержки рендеринга на стороне сервера. Если вы хотите использовать рендеринг на стороне сервера, вы можете использоватьReact Loadable.

2 компонентная загрузка

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

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

Например, домашняя страница графитового документа выглядит так:

image.png

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

Результат моего теста с Lighthouse: интерактивное время достигает 17,2 с.
image.png

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

Мы видим более 6000 мс разбора и выполнения JavaScript в процессе загрузки.

image.png

2.1 Ленивая загрузка компонентов

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

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

Разделение маршрута и разделение компонентов

image.png

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

Наша демонстрация выглядит так:

QQ20190611-105233.gif

Давайте сначала сравним ситуацию загрузки ресурсов с сегментацией компонентов и без сегментации компонентов (без сжатия в среде разработки)

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

image.png

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

image.png

По сути, метод сегментации компонентов аналогичен методу сегментации маршрутов, а загружать компоненты методом lazy + Suspense тоже лениво.

// 动态加载图表组件
const Chart = lazy(() => import(/* webpackChunkName: 'chart' */'./charts'))

// 包含着图表的 modal 组件
const ModalEchart = (props) => (
    <Modal
    title="Basic Modal"
    visible={props.visible}
    onOk={props.handleOk}
    onCancel={props.handleCancel}
  >
      <Chart />
  </Modal>
)

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

Мы уменьшили объем ресурсов начальной отрисовки страницы за счет ленивой загрузки компонентов, что улучшило производительность загрузки, но с производительностью компонентов снова возникли проблемы.В последней демке мы уменьшили объем начальной страницы с 3,9 м до 1,7 м. Страница загружается быстро, но компонент загружается медленнее.

Причина в том, что давление оставшихся 2 млн ресурсов ложится на компонент диаграммы (объем Echarts), поэтому, когда мы нажимаем меню для загрузки диаграммы, будет задержка загрузки 1-2 секунды, как показано ниже:

image.png

Можем ли мы загрузить диаграмму заранее, чтобы избежать проблемы слишком долгой загрузки при рендеринге диаграммы?Этот метод ранней загрузки — это предварительная загрузка компонентов.

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

/**
 * @param {*} factory 懒加载的组件
 * @param {*} next factory组件下面需要预加载的组件
 */
function lazyWithPreload(factory, next) {
  const Component = lazy(factory);
  Component.preload = next;
  return Component;
}
...
// 然后在组件的方法中触发预加载
  const preloadChart = () => {
    Modal.preload()
  }

демонстрационный адрес

2.3 keep-alive

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

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

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

  1. Используйте глобальные инструменты управления состоянием, такие как redux, для кэширования состояния.
  2. использоватьstyle={{display: 'none'}}взять под контроль

Если вы прочитаете эти два предложения, вы поймете, что это ненадежно. Redux уже достаточно многословен. Мы используем Redux как глобальное решение для кэширования состояния. Дополнительная рабочая нагрузка и увеличение сложности не стоят потерь.dispalyУправление отображением — очень простой метод, но он достаточно грубый, и мы теряем много места для манипулирования, например анимации.

react-keep-aliveЧтобы решить эту проблему в определенной степени, ее принцип заключается в использовании React's Portals API для монтирования компонента кеша в DOM, отличном от корневого узла, а затем вешать компонент кеша на соответствующий узел, когда его нужно восстановить. циклcomponentWillUnactivateУничтожьте это.


резюме

Конечно, есть много распространенных решений по оптимизации производительности, о которых мы не упомянули:

  1. Представьте схему ленивой загрузки, это технология, которая использовалась в доисторических временах, и есть зрелые схемы в JQuery или различных фреймворках.
  2. Сжатие ресурсов в основном автоматически включается инструментами обратного прокси.
  3. cdn, я не видел несколько веб-продуктов, которые не используют cdn, особенно после подъема поставщиков облачных вычислений, cdn очень дешев
  4. Конвергенция доменных имен или расхождение доменных имен, эта ситуация имеет ограниченное значение после использования http2, поскольку доменное имя может напрямую устанавливать двустороннее мультиплексирование каналов.
  5. Карта спрайтов, очень старая технология, и эффект от http2 ограничен после использования
  6. CSS плита, JS, наконец, подходит для проектирования, теперь в основном использует инструменты упаковки.
  7. разное...

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


Ссылка на ссылку:

  1. Ссылка на показатели эффективности
  2. Принцип встряхивания дерева
  3. предварительная загрузка компонента
  4. http2
  5. развернуть es6
  6. Практика оптимизации производительности Tree-Shaking
  7. стратегия кэширования