В этой статье мы проведем вас по пути, по которому мы шли, работая над оптимизацией внешнего интерфейса в LitmusChaos.

Во-первых, почему нам пришлось работать над оптимизацией нашего интерфейса?

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

Ну, то же самое произошло и с нами. Мы работаем над ChaosCenter уже довольно давно, и он значительно вырос. В прошлом году мы обнаружили, что нашему приложению требуется около 50 секунд только для загрузки страницы входа. Это побудило нас к более глубокому изучению.

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

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

Итак, вы готовы к этому путешествию?

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

Изучая процесс сборки Webpack, мы познакомились с различными стилями импорта компонентов (статический/динамический). Что ж, если ваше приложение небольшое, не имеет значения, какой стиль вы выберете, но приложение, подобное нашему, имеет значение.

Это приводит нас к концепции ленивой загрузки и разделения кода.

Ленивая загрузка и разделение кода

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

Давайте посмотрим на приведенный ниже пример фрагмента из нашего файла Router.

import CreateWorkflow from '../../pages/CreateWorkflow';
import LoginPage from '../../pages/LoginPage';
import GetStarted from '../../pages/GetStartedPage';
import WorkflowDetails from '../../pages/WorkflowDetails'

# Router component
<Router history={history}>
  {/* <Routes /> */}
  <Routes />
</Router>

Итак, если вы видите здесь, роутер работал как положено, всякий раз, когда пользователь запускал приложение, оно направлялось на страницу входа. Но если мы проверим фон, он загружал все страницы/представления, а затем перенаправлял на страницу входа.
Здесь мы хотели просто загрузить знак -in страницу и маршрут к ней.

Итак, мы начали с разделения кода на основе маршрутизатора. Мы изменили весь статический импорт со статического на динамический, используя встроенную ленивую загрузку из Webpack & React.

const CreateWorkflow = lazy(() => import('../../pages/CreateWorkflow'));
const LoginPage = lazy(() => import('../../pages/LoginPage'));
const GetStarted = lazy(() => import('../../pages/GetStartedPage'));
const WorkflowDetails = lazy(() => import('../../pages/WorkflowDetails'));

# Router component
<Suspense fallback={<Loader />} >
  <Router history={history}>
     {/* <Routes /> */}
     <Routes />
  </Router>
</Suspense>

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

Сейчас, в это время, мы пытались построить наш интерфейс. И поверьте мне, мы знали, что у нас что-то есть, потому что мы разделили наши куски сборки с размера 3 МБ на 1,5–2 МБ.

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

Что ж, у вас может возникнуть вопрос, почему компонент, импортированный на страницу, должен быть импортирован как динамический?

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

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

Вкладка «Источники», ваш хороший друг?

Мы начали больше копаться в различных функциях Chrome, маяка и других инструментов. Мы обнаружили, что Chrome предоставляет вкладку источников в инструментах разработчика. Всякий раз, когда мы открываем приложение или веб-сайт, вкладка «Источник» предоставляет нам все ресурсы, импортированные в этот экземпляр, чтобы это программное обеспечение/веб-сайт/приложение работало идеально. Мы увидели, что когда мы просто пытались открыть страницу входа, она импортировала все компоненты из нашей библиотеки компонентов, хотя никакая другая страница/экран не загружалась.

Ладно ладно, я тебе не говорил, у нас тоже есть библиотека компонентов (litmus-ui), построенная на Rollup. Это очень хорошо поддерживаемая и оптимизированная библиотека компонентов, которую мы используем для наших различных продуктов.

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

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

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

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

# Previously only this was possible
import {ButtonFilled} from "litmus-ui";

# Now, all below given imports are possible
import { ButtonFilled } from "litmus-ui";
import { ButtonFilled } from "litmus-ui/core";
import { ButtonFilled } from "litmus-ui/core/Button";
import { ButtonFilled } from "litmus-ui/core/Button/ButtonFilled";
import ButtonFilled from "litmus-ui/core/Button/ButtonFilled";

Как насчет Tree-shaking на уровне библиотеки компонентов?

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

Мы заглянули внутрь node_modules, после множества просмотров и испытаний, а также сравнения других библиотек мы обнаружили, что наша библиотека даже не поддерживает встряхивание дерева. Давайте посмотрим на причину на картинке ниже, а также на rollup.config.js, который у нас был ранее -

output: [
    {
      dir: "dist",
      format: "cjs",
      sourcemap: true,
    },
  ],

На приведенном выше рисунке, если вы видите, наша библиотека была связана только с форматом cjs (commonJS), который не поддерживает встряхивание деревьев.

Затем мы начали искать, что мы можем сделать, чтобы достичь этого. Что ж, это было время, когда мы обнаружили, что формат esm (модули ES) поддерживает это. Итак, мы изменили наш файл конфигурации библиотеки, чтобы создать пакет в формате esm.

output: [
    {
      dir: "dist",
      format: "cjs",
      sourcemap: true,
    },
    {
      dir: "dist",
      format: "esm",
      sourcemap: true,
    },
  ],

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

Таким образом, мы изменили выходной каталог для esm на dist/esm, с этим у нас был сгенерирован наш пакет esm.

Теперь наш rollup.config.js выглядел так:

output: [
    {
      dir: "dist",
      format: "cjs",
      sourcemap: true,
    },
    {
      dir: "dist/esm",
      format: "esm",
      sourcemap: true,
    },
  ],

Тем не менее, тот же результат, React не использовал модуль esm в нашем приложении. И у нас также был один вопрос: ну, мы объединили нашу библиотеку в 2 формата, но как мы собираемся сказать React использовать формат esm?

После небольшого исследования мы обнаружили, что нам нужно указать путь для пакета esm в поле module в package.json нашей библиотеки компонентов.

Итак, мы добавили оба пути для cjs и esm в package.json в поля main и module соответственно.

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

ПРИМЕЧАНИЕ. Традиционные упаковщики, которые не понимают тип esm, могут использовать cjs с этой конфигурацией.

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

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

input: ["./src/index.ts","./src/core/Button/ButtonFilled/index.ts" ],

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

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

# scripts/inputs.js
const fs = require("fs");
const path = require("path");

const getAllEntryPoints = function (dirPath, arrayOfFiles) {
  let files = fs.readdirSync(dirPath);
  arrayOfFiles = arrayOfFiles || [];

  files.forEach(function (file) {
    if (fs.statSync(dirPath + "/" + file).isDirectory()) {
      arrayOfFiles = getAllEntryPoints(dirPath + "/" + file, arrayOfFiles);
    } else {
      if (file === "index.ts") {
        arrayOfFiles.push(path.join(dirPath, "/", file));
      }
    }
  });

  return arrayOfFiles;
};

export default getAllEntryPoints;


# In rollup.config.js

import getAllEntryPoints from "./scripts/inputs";
const input = getAllEntryPoints("./src");

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

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

И с помощью различных методов сжатия с использованием brotli и gzip мы смогли добиться следующих результатов:

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

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

Заключение

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

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

Если вас интересует хаос-инжиниринг или вы хотите узнать больше об облачном хаос-инжиниринге, не забудьте посетить наш веб-сайт Litmus, ChaosHub и репозиторий Litmus. Оставьте звезду, если вы найдете это проницательным. 😊

Я хотел бы пригласить вас в наше сообщество, чтобы оставаться на связи с нами и развеять свои сомнения в Chaos Engineering.
Чтобы присоединиться к нашему slack, выполните следующие действия!

Шаг 1: Присоединяйтесь к Kubernetes slack по следующей ссылке: https://slack.k8s.io/

Шаг 2: Присоединяйтесь к каналу #litmus в slack Kubernetes или используйте эту ссылку после присоединения к slack Kubernetes: https://slack.litmuschaos.io/

Ваше здоровье!