Разделение кода. Разделение кода везде. Но почему? Просто потому, что сейчас слишком много javascript и не все они используются одновременно.

JS - это очень тяжелая вещь. Не для вашего iPhone Xs или нового ноутбука i9, а для миллионов (возможно, миллиардов) владельцев более медленных устройств. Или, по крайней мере, на часы.

Итак - JS - это плохо, но что произойдет, если мы просто отключим его - проблема исчезнет ... для некоторых сайтов и исчезнет «с сайтами» для сайтов на основе React. Но в любом случае - есть сайты, которые могут работать без JS ... и есть чему поучиться у них ...

Разделение кода

Сегодня у нас есть два пути, два способа сделать это лучше или не сделать хуже:

1. Пишите меньше кода

Это лучшее, что ты можешь сделать. Хотя React Hooks позволяет вам отправлять немного меньше кода, а такие решения, как Svelte, позволяют генерировать только меньше кода, чем обычно, это не так-то просто сделать.

Речь идет не только о коде, но и о функциональности - чтобы код оставался «компактным», вы должны сделать его «компактным». Невозможно сохранить небольшой пакет приложений, если он выполняет так много задач (и поставляется на 20 языках).

Есть способы написать короткий и надежный код, а есть способы написать противоположную реализацию - кровавое предприятие. И, знаете, оба законны.

Но главный вопрос - сам код. Простое приложение для реагирования могло легко обойти рекомендуемые 250 КБ. И вы можете потратить месяц на его оптимизацию и уменьшение. Небольшие оптимизации хорошо документированы и весьма полезны - просто получите bundle-analyzer с size-limit и вернитесь в форму.
Существует множество библиотек, которые борются за каждый байт, пытаясь удержать вас в ваших пределах - preact и storeon и многие другие.

Но наше приложение чуть больше 200кб. Это ближе к 100 МБ. Убирать килобайты смысла нет. Даже убирать мегабайты нет смысла.

Спустя какое-то время ваше приложение становится уже маленьким. Со временем он станет больше.

2. Доставка без кода

В качестве альтернативы code split. Другими словами - сдаться. Возьмите свой пакет 100 МБ и сделайте из него двадцать пакетов по 5 МБ. Честно говоря - это единственный возможный способ справиться с вашим приложением, если оно стало большим - создать из него пакет небольших приложений.

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



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

Правда о расщеплении кода

Правда о разделении кода заключается в том, что его природа - РАЗДЕЛЕНИЕ ВРЕМЕНИ. Вы не просто разделяете свой код, вы разделяете его таким образом, чтобы вы использовали как можно меньше за один момент времени.

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

Легко сказать, сложно сделать. У меня есть несколько тяжелых, но недостаточно разделенных приложений, где любая страница загружается как 50% всего. Иногда code splitting становится code separation, я имею в виду - вы можете перемещать код в разные блоки, но все же использовать его все. Вспомните, что «Только не отправляйте код, который вам сейчас не нужен», мне нужно 50% кода, и это была настоящая проблема.

Иногда просто добавить import кое-где недостаточно. Пока это не будет разделения по времени, а только по пространству - это вообще не имеет значения.

Есть 3 распространенных способа разделения кода:

  1. Просто динамический import. В наши дни почти не используется в одиночку. Это больше о проблемах с отслеживанием состояния.
  2. Lazy Компонент, когда вы можете отложить рендеринг и загрузку компонента React. Вероятно, 90% «расщепления кода реакции» в наши дни.
  3. Ленивый Library, что на самом деле .1, но вам будет предоставлен код библиотеки через реквизиты рендеринга React. Реализовано в react-import-component и loadable-components. Довольно полезно, но малоизвестно.

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

Это самый популярный. Как разделение кода по маршруту или разделение кода по компонентам. Это не так просто сделать и в результате добиться хороших результатов восприятия. Это смерть от Flash of Loading Content.

Хорошие техники:

  • загрузить js chunk и data для маршрута параллельно.
  • используйте skeleton, чтобы отобразить что-то похожее на страницу до загрузки страницы (например, Facebook).
  • prefetch чанков, вы можете даже использовать guess-js для лучшего предсказания.
  • используйте некоторые задержки, индикаторы загрузки, animations и Suspense (в будущем), чтобы смягчить переходы.

И, знаете, это все о перцепционной способности.

Это не звучит хорошо

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

Иногда мне не удавалось уменьшить размер связки. Иногда мне не удавалось улучшить итоговую производительность, пока the _more_ code splitting you are introducing - the more you spatially split your page - the more time you need to _reassemble_ your page back *. Это называется волны загрузки.

  • без SSR или предварительного рендеринга. Правильный SSR в настоящий момент меняет правила игры.

На прошлой неделе у меня было две неудачи:

  • Я проиграл одно сравнение библиотек; хотя моя библиотека была лучше она была НАМНОГО больше другой. Я не смог '1. Пишите меньше кода ».
  • Оптимизируем небольшой сайт, сделанный моей женой на React. В нем использовалось разделение компонентов на основе маршрута, но header и footer были оставлены в основном пакете, чтобы сделать переходы более «приемлемыми». Всего несколько вещей, тесно связанных друг с другом, увеличили размер пакета до 320 КБ (до gzip). Не было ничего важного, и я ничего не мог удалить. Смерть от тысячи порезов. Мне не удалось отправить меньше кода.

React-Dom составлял 20%, core-js был 10%, response-router, jsLingui, react-powerplug ... 20% собственного кода ... Мы уже закончили.

Решение

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

Что я сделал? Я перечислил все важные места, без которых приложения не работали бы вообще, и попытался понять, зачем мне остальные.

Это было сюрпризом. Но моя проблема была в CSS. В ванильном CSS-переходе.

Вот код

  • переменная control - componentControl, в конечном итоге будет установлена ​​на то, что DisplayData должно отображаться.
  • после установки значения DisplayData становятся видимыми, изменяя className, таким образом вызывая причудливый переход. Одновременно FocusLock становится активным, делая DisplayData модальным.
<FocusLock
 enabled={componentControl.value} 
 // ^ it's "disabled". When it's disabled - it's dead.
>
  {componentControl.value && <PageTitle title={componentControl.value.title}/>}
  // ^ it's does not exists. Also dead
  <DisplayData
    data={componentControl.value}
    visible={componentControl.value !== null}
    // ^ would change a className basing on visible state
  />
  // ^ that is just not visible, but NOT dead
</FocusLock>

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

  1. информация должна быть видна немедленно, как только она потребуется, без каких-либо задержек. Бизнес-требование.
  2. информационный хром должен существовать до перехода дескриптора свойства.

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

Должен быть способ получше!

TL; DR - здесь есть два ключевых момента:

  • DisplayData должен быть смонтирован и ранее существовал в DOM.
  • FocusLock также должен существовать раньше, чтобы не вызывать DisplayData перемонтирование, но это мозги не нужны вначале.

Итак, давайте изменим нашу ментальную модель

Бэтмен и Робин

Предположим, что наш код - это Бэтмен и Робин. Бэтмен может справиться с большинством плохих парней, но когда он не может, его приятель Робин приходит на помощь.

Бэтмен снова вступит в бой, Робин появится позже.

Это Бэтмен:

+<FocusLock
- enabled={componentControl.value} 
+>
-  {componentControl.value && <PageTitle title={componentControl.value.title}/>}
+  <DisplayData
+    data={componentControl.value}
+    visible={componentControl.value !== null}
+  />
+</FocusLock>

Это его приятель, Робин:

-<FocusLock
+ enabled={componentControl.value} 
->
+  {componentControl.value && <PageTitle title={componentControl.value.title}/>}
-  <DisplayData
-    data={componentControl.value}
-    visible={componentControl.value !== null}
-  />
-</FocusLock>

Бэтмен и Робин могут составить КОМАНДУ, но на самом деле это два разных человека.

И не забывайте - мы все еще говорим о расщеплении кода. И, с точки зрения разделения кода, где же помощник? Где Робин?

Коляска

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

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

В некоторых странах это трио было известно как replace reducer или другие способы ленивой загрузки логики редукции и побочных эффектов.

В некоторых других странах он известен как "3 Phased" code splitting.

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

изображения из Создание нового facebook.com с помощью React, GraphQL и Relay, где importForInteractions или importAfter - это sidecar.

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

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

Что можно переместить в коляску:

  • большинство useEffect, componentDidMount и друзья.
  • как и все модальные эффекты. Т.е. focus и scroll блокировки. Вы можете сначала отобразить модальное окно, а только потом сделать его модальным, т. Е. «Заблокировать» внимание клиента.
  • Формы. Переместите всю логику и проверки в сопроводительный документ и заблокируйте отправку формы, пока эта логика не будет загружена. Заказчик мог начать заполнять форму, не зная, что это всего лишь Batman.
  • Немного анимации. Целый react-spring в моем случае.
  • Некоторые визуальные элементы. Как Пользовательские полосы прокрутки, которые через секунду могут отображать причудливые полосы прокрутки. 🤷‍♂️ Дизайнеры 🤷‍♂️

Кроме того, не забывайте - каждый фрагмент кода, выгруженный в сопроводительный файл, также выгружает такие вещи, как core-js poly- и ponyfills, используемые удаленным кодом.

Разделение кода может быть умнее, чем в наших современных приложениях. Мы должны понимать, что есть 2 типа кода, которые нужно разделить: 1) визуальные аспекты 2) интерактивные аспекты. Последнее может появиться через несколько мгновений. Sidecar позволяет легко разделить две задачи, создавая ощущение, что все загружается быстрее. И будет.

Самый старый способ разделения кода

Хотя все еще может быть не совсем ясно, когда и что такое sidecar, я дам простое объяснение:

Sidecar - это ВСЕ ВАШИ СЦЕНАРИИ. Sidecar - это способ, которым мы разделяем код до всего того внешнего интерфейса, который у нас есть сегодня.

Я говорю о рендеринге на стороне сервера (SSR) или просто HTML, к которому мы все привыкли еще вчера. Sidecar делает вещи такими же простыми, как раньше, когда страницы, содержащие HTML, и логика жили отдельно во встраиваемых внешних сценариях (разделение задач).

У нас были HTML, плюс CSS, плюс некоторые встроенные скрипты, плюс остальные скрипты, извлеченные в .js файлы.

_55 _ + _ 56 _ + _ 57_ было Batman, в то время как внешние скрипты были Robin, и сайт мог работать без Робина, и, честно говоря, частично без Бэтмена (он продолжит борьбу, сломав обе ноги (встроенные скрипты)). Это было только вчера, а многие «не современные и крутые» сайты остались прежними.

Если ваше приложение поддерживает SSR - попробуйте отключить js и заставить его работать без него. Тогда было бы ясно, что можно переместить в сопроводительную документацию.
Если ваше приложение является клиентским SPA - попробуйте представить, как бы оно работало, если бы SSR существовал.

Например, theurge.com, написанный на React, полностью функционально без каких-либо js .

Есть много вещей, которые можно переложить в коляску. Например:

  • Комментарии. Вы можете отправить код для display комментариев, но не answer, поскольку для этого может потребоваться дополнительный код (включая редактор WYSIWYG), который изначально не требуется. Лучше отложить окно комментариев или даже просто скрыть загрузку кода за анимацией, чем задерживать целую страницу.
  • видео проигрыватель. Отправляйте видео без элементов управления. Загрузите их через секунду, и клиент может попытаться с ним взаимодействовать.
  • галерея изображений, например slick. Нарисовать его несложно, но гораздо сложнее анимировать и управлять им. Понятно, что можно переместить в коляску.

Подумайте, что важно для вашего приложения, а что нет ...

Детали реализации

(DI) Разделение кода компонентов

Простейшую форму sidecar легко реализовать - просто переместите все в субкомпонент, вы можете разделить код, используя «старые» способы. Это почти разделение между компонентами Smart и Dumb, но на этот раз Smart не продолжает тупой компонент - это наоборот.

const SmartComponent = React.lazy( () => import('./SmartComponent'));

class DumbComponent extends React.Component {
  render() {
    return (
      <React.Fragment>
       <SmartComponent ref={this} /> // <-- move smart one inside
       <TheActualMarkup />           // <-- the "real" stuff is here
      </React.Fragment>
  } 
}

Для этого также требуется переместить код инициализации в "Dumb", но вы по-прежнему можете разделить самую тяжелую часть кода.

Теперь вы видите схему разделения кода parallel или vertical?

useSidecar

Создание нового facebook.com с помощью React, GraphQL и Relay, как я уже упоминал здесь, имело концепцию loadAfter или importForInteractivity, что очень похоже на концепцию sidecar.

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

Пожалуйста, отдайте предпочтение более декларативному компонентному пути. И вы можете использовать hooksinside SideCar компонент.

const Controller = React.lazy( () => import('./Controller'));
const DumbComponent = () => {
 const ref = useRef();
 const state = useState();

 return (
  <>
   <Controller componentRef={ref} state={state} />
   <TheRealStuff ref={ref} state={state[0]} />
  </>
 )
}

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

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

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

ССР

В отличие от обычного разделения кода, никаких специальных действий для SSR не требуется. Sidecarне может быть частью процесса SSR и не требуется до hydration шага. Это могло быть отложено «по замыслу».

Таким образом, не стесняйтесь использовать React.lazy (в идеале что-то без Suspense, здесь вам не нужны индикаторы возврата (загрузки)) или любую другую библиотеку с, но лучше без поддержки SSR для пропустить побочные фрагменты во время процесса SSR.

Плохие части

Но у этой идеи есть несколько плохих моментов.

Бэтмен - это не производственное имя

Хотя _79 _ / _ 80_ может быть хорошей идеей, а sidecar идеально подходит для самой технологии - для maincar нет «хорошего» названия. Не существует такой вещи, как maincar, и, очевидно, Batman, Lonely Wolf, Solitude, Driver и Solo не должны использоваться для обозначения деталей, не связанных с коляской.

Facebook использовал display и interactivity, и это может быть лучшим вариантом для всех нас.

Если у вас есть для меня хорошее имя - оставьте его в комментариях

Дерево трясется

Это больше о разделении проблем с точки зрения связки. Представьте, что у вас есть Batman и Robin. И stuff.js

export * from `./batman.js`
export * from `./robin.js`

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

//main.js
import {batman} from './stuff.js'

const Robin = React.lazy( () => import('./sidecar.js'));

export const Component = () => (
  <>
   <Robin />  // sidecar
   <Batman /> // main content
  </>
)

// and sidecar.js... that's another chunk as long as we `import` it
import {robin} from './stuff.js'
.....

Короче говоря, приведенный выше код будет работать, но не будет работать.

  • если вы используете только batman из stuff.js - тряска дерева сохранит только его.
  • если вы используете только robin из stuff.js - тряска дерева сохранит только его.
  • но если вы используете и то, и другое, даже в разных частях, оба будут объединены в первое вхождение stuff.js, то есть в основной комплект.

Встряхивание дерева не поддерживает разбиение кода. Вы должны разделять проблемы по файлам.

Отменить импорт

Еще одна вещь, о которой все забывают, - это стоимость javascript. В эпоху jQuery, эпоху jsonp полезной нагрузки, было довольно распространено загружать скрипт (с json полезной нагрузкой), получать полезные данные и удалить скрипт.

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

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

Вероятно, способность un-import (webpack может это сделать) - одна из причин, по которой мы должны придерживаться компонентного API, если это дает нам возможность обрабатывать unmount.

Пока что в стандартах модулей ESM нет ничего подобного - ни об управлении кешем, ни об отмене действия импорта.

Создание библиотеки с поддержкой коляски

На сегодняшний день есть только один способ создать библиотеку с sidecar:

  • разделите ваш компонент на части
  • выставить main часть и connected часть (чтобы не нарушать API) через index
  • выставить sidecar через отдельную точку входа.
  • в целевом коде - импортируйте main часть, и sidecar - встряхивание дерева должно вырезать connected часть.

На этот раз встряхивание дерева должно работать правильно, и единственная проблема - как назвать main часть.

//main.js
export const Main = ({sidecar, ...props}) => (
  <div>
    {sidecar} 
    ....
  </div>
);

// connected.js
import Main from './Component';
import Sidecar from './Sidecar';

export const Connected = props => (
  <Main
    sidecar={<Sidecar />}
    {...props}
  />
);

//index.js
export * from './Main';
export * from './Connected';

//sidecar.js
import * from './Sidecar';

// -------------------------

//your app BEFORE
import {Connected} from 'library'; //

// -------------------------

//your app AFTER, compare to `connected.js`
import {Main} from 'library';
const Sidecar = React.lazy(import( () => import('library/sidecar')));
// ^ all the difference ^

export SideConnected = props => (
  <Main
    sidecar={<Sidecar />}
    {...props}
  />
);

// ^ you will load only Main, Sidecar will arrive later.

Теоретически dynamic import можно было бы использовать внутри node_modules, делая процесс сборки более прозрачным.

В любом случае - это не более чем шаблон _117 _ / _ 118_, столь распространенный в React.

Будущее

Facebook доказал, что идея верна. Если вы еще не видели это видео - сделайте это прямо сейчас. Я только что объяснил ту же идею под немного другим углом (и начал писать эту статью за неделю до конференции F8).

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

Sidecar, наверное, единственный способ, кроме олдскульного SSR, обрабатывать БОЛЬШИЕ кодовые базы. Последний шанс отправить минимальное количество кода, когда у вас его много.

Это может сделать БОЛЬШОЕ приложение меньше, а МАЛЕНЬКОЕ - еще меньше.

10 лет назад веб-сайт среднего размера был «готов» за 300 мсек и был действительно готов через несколько миллисекунд. Сегодня обычные числа - секунды и даже больше 10 секунд. Какая жалость.

Давайте сделаем паузу и подумаем - как мы могли бы решить проблему и снова сделать UX отличным ...

Общий

  • 1. Разделение кода компонентов - это самый мощный инструмент, дающий вам возможность полностью что-либо разделить, но за это приходится платить - вы можете не отображать ничего, кроме пустой страницы или скелета. на время. Это горизонтальное разделение.
  • 2. Разделение кода библиотеки может помочь, когда разделение на компоненты - нет. Это горизонтальное разделение.
  • 3. Код, выгруженный в коляску, завершит картину и может позволить вам улучшить взаимодействие с пользователем. Но также потребуются некоторые инженерные усилия. Это вертикальное разделение.

Давай поговорим об этом.

Стоп! Так что насчет проблем, которые вы пытались решить?

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