<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Lyoha Plotinka</title><subtitle>1996 / JavaScript / music / JS-разработчик из Екатеринбурга</subtitle><author><name>Lyoha Plotinka</name></author><id>https://teletype.in/atom/lyohaplotinka</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/lyohaplotinka?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/lyohaplotinka?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-26T17:32:19.795Z</updated><entry><id>lyohaplotinka:disallow-commit</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/disallow-commit?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Как запретить коммит, если не поменялся определённый файл?</title><published>2021-09-13T19:12:52.464Z</published><updated>2021-09-14T08:35:31.205Z</updated><category term="mini-articles" label="Мини-статьи"></category><summary type="html">Уж не знаю, насколько это часто бывает в разработке, но недавно такое пригодилось мне. </summary><content type="html">
  &lt;p id=&quot;nPP6&quot;&gt;Уж не знаю, насколько это часто бывает в разработке, но недавно такое пригодилось мне. &lt;/p&gt;
  &lt;p id=&quot;5sco&quot;&gt;Вкратце опишу суть проблемы: есть файл, который должен меняться при каждом коммите (допустим, чейнджлог, или ещё какая-нибудь описательная штука). По идее, мы должны заполнять его по зову сердца, но часто бывает сильное желание сделать это &amp;quot;как-нибудь потом&amp;quot;, которое, естественно, приводит к тому, что ничего дальше не делается :) &lt;/p&gt;
  &lt;p id=&quot;fAKE&quot;&gt;И вот, решение: используя &lt;a href=&quot;https://git-scm.com/book/ru/v2/%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-Git-%D0%A5%D1%83%D0%BA%D0%B8-%D0%B2-Git&quot; target=&quot;_blank&quot;&gt;git-хуки&lt;/a&gt; мы можем сделать так, чтобы если в коммите нет какого-то файла (то есть, он не был изменён) — мы не разрешали сделать пуш. Так сказать, наводим твёрдый порядок жёсткой рукой! А то слишком удобно живётся.&lt;/p&gt;
  &lt;p id=&quot;VQf7&quot;&gt;Итак, речь пойдёт про окружение Linux и Mac. Предположим, что наш файл называется &lt;code&gt;changeme.txt&lt;/code&gt;. Нам понадобится такой вот шелл-скрипт:&lt;/p&gt;
  &lt;pre id=&quot;rpuy&quot; data-lang=&quot;bash&quot;&gt;#!/bin/bash

changes=$(git diff --cached --name-only)

if [[ $changes == *&amp;quot;changeme.txt&amp;quot;* ]]; then
  exit 0;
else
  echo &amp;quot;ERROR: changeme.txt is not updated&amp;quot;
  exit 1;
fi&lt;/pre&gt;
  &lt;p id=&quot;ODIS&quot;&gt;Теперь дадим ему разрешение выполняться:&lt;/p&gt;
  &lt;pre id=&quot;mKd7&quot; data-lang=&quot;bash&quot;&gt;chmod +x ./changeme-detect.sh&lt;/pre&gt;
  &lt;p id=&quot;nvmp&quot;&gt;Ну а дальше дело за малым — с помощью какого-нибудь &lt;a href=&quot;https://github.com/okonet/lint-staged&quot; target=&quot;_blank&quot;&gt;lint-staged&lt;/a&gt; или &lt;a href=&quot;https://github.com/typicode/husky&quot; target=&quot;_blank&quot;&gt;husky&lt;/a&gt; добавляем запуск этого скрипта на хук коммита.&lt;/p&gt;
  &lt;p id=&quot;CIqx&quot;&gt;Теперь при коммите будет происходить следующее: скрипт будет смотреть, есть ли в списке изменённых файлов тот, который мы ищем; если есть — всё ок, можно пушить, но вот если нет — коммит не пройдёт. Конечно, всегда можно отключить хуки и всё пройдёт нормально, но это уже бОльшая сделка с совестью, чем отложить что-то на потом. &lt;/p&gt;
  &lt;p id=&quot;Vd6i&quot;&gt;Вот как-то так. Вам слишком удобно работается? Тогда добавляйте это в свой проект :)&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:c1f8MdxIGIa</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/c1f8MdxIGIa?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Привет, мир!</title><published>2021-07-27T07:09:51.341Z</published><updated>2021-07-27T07:10:23.229Z</updated><summary type="html">Меня зовут Лёха, если предпочитаете длинно — Алексей Соловьев. Я уже больше четырёх лет работаю в сфере JavaScript-разработки. </summary><content type="html">
  &lt;p&gt;Меня зовут Лёха, если предпочитаете длинно — Алексей Соловьев. Я уже больше четырёх лет работаю в сфере JavaScript-разработки. &lt;/p&gt;
  &lt;p&gt;Я люблю JS, open-source и музыку. Ниже я оставлю несколько ссылок, которые могут быть полезны, чтобы узнать меня получше. &lt;/p&gt;
  &lt;p&gt;На этом сайте я буду публиковать мысли на профессиональные и не очень темы, которыми хочу поделиться. Многого не ждите, но интересно вполне может быть :) &lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;GitHub - &lt;a href=&quot;https://github.com/lyohaplotinka&quot; target=&quot;_blank&quot;&gt;https://github.com/lyohaplotinka&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;YouTube - &lt;a href=&quot;https://www.youtube.com/channel/UCMxrWsJZGW3JO4TSeJg-NxA&quot; target=&quot;_blank&quot;&gt;https://www.youtube.com/channel/UCMxrWsJZGW3JO4TSeJg-NxA&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;Telegram - &lt;a href=&quot;https://t.me/lyohajs&quot; target=&quot;_blank&quot;&gt;https://t.me/lyohajs&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;Music VK - &lt;a href=&quot;https://vk.com/lyohaplotinka&quot; target=&quot;_blank&quot;&gt;https://vk.com/lyohaplotinka&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;

</content></entry><entry><id>lyohaplotinka:5AWTpaCtT9g</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/5AWTpaCtT9g?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Почему стоит отказаться от поддержки IE</title><published>2021-07-23T09:59:45.839Z</published><updated>2021-07-23T09:59:45.839Z</updated><category term="rassuzhdeniya" label="Рассуждения"></category><summary type="html">Я очень большой противник Internet Explorer. Всегда был таким, но к началу 2021 года стал ещё большим (хотя, казалось бы, куда уж).</summary><content type="html">
  &lt;h3&gt;Предисловие&lt;/h3&gt;
  &lt;p&gt;Я очень большой противник Internet Explorer. Всегда был таким, но к началу 2021 года стал ещё большим (хотя, казалось бы, куда уж).&lt;/p&gt;
  &lt;p&gt;Я решил написать коллегам по работе, что нам пора начинать предпринимать шаги к прекращению поддержки IE, и это не только для нас хорошо, но и для клиентов, и даже прежде всего — для клиентов. Чтобы не писать длинные простыни в мессенджере, я решил оформить эти доводы в отдельном файле.&lt;/p&gt;
  &lt;p&gt;Сейчас, немного отредактировав его, я решил его опубликовать.&lt;/p&gt;
  &lt;h3&gt;Почему нам стоит отказаться от поддержки IE?&lt;/h3&gt;
  &lt;p&gt;Коллеги, в конце прошлого года я понял, что хочу поднять одну важную тему, а именно — прекращение поддержки нашими сервисами отчётов браузера Internet Explorer, всех версий, даже 11. Если ещё год назад это можно было бы назвать «прихотью» фронтендеров, то сейчас это уже гораздо ближе к объективной необходимости.&lt;/p&gt;
  &lt;p&gt;Прежде всего, стоит обозначить, что такое «прекратить поддержку». В моём понимании это значит следующее: сервисы доступны в браузере, пользователь может взаимодействовать с ними на своё усмотрение, однако, в случае возникновения проблем мы не гарантируем их решение конкретно под этот браузер. Если коротко: хотите работать в IE — пожалуйста, но мы не гарантируем, что всё будет работать правильно.&lt;/p&gt;
  &lt;p&gt;Для прекращения поддержки есть много причин и аргументов, связанных как с пользователями, так и с нами (разработчики и бизнес). Я приведу самые критичные на мой вгляд:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;Для пользователей: безопасность&lt;/strong&gt;&lt;br /&gt;В IE бесчисленное количество дыр безопасности, которые постоянно закрываются патчами разного уровня качества и забагованности. Последняя из них была обнаружена в августе 2020 года, и была довольно критичной (&lt;a href=&quot;https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1380&quot; target=&quot;_blank&quot;&gt;ссылка&lt;/a&gt;).&lt;br /&gt;Мы можем быть уверены, что это не всё. До сих пор Microsoft выпускала обновления безопасности, однако, в скором времени они полностью прекратят его поддержку, а именно — 17-го августа этого года.&lt;br /&gt;Уязвимости позволяют делать много «весёлого»: от кражи куков и данных карт до выполнения произвольного кода в контексте пользователя. К сожалению, мы тут ничем не можем помочь пользователям, ведь даже те патчи, которые выпускаются, устанавливают далеко не все.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Для пользователей: юзабилити&lt;/strong&gt;&lt;br /&gt;Наши панели отчётов использую большое количество стилизаций и фронтовой логики. На поддерживаемых JS-движках, вроде V8, SpiderMonkey, JavaScriptCore это не создаёт проблем, однако на движке Chakra, который используется в IE, возникают проблемы со скоростью работы. Если мы не готовы отказаться от интерактивности и перейти целиком на статику, мы не можем это изменить, никакие оптимизации это не изменят.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Для пользователей: отказ от поддержки другими компаниями&lt;/strong&gt;&lt;br /&gt;В 2021 году, помимо прекращения поддержки самого IE, его перестанут поддерживать сервисы Microsoft и Google. Речь, конечно, не про поиск, но про Gmail, Google Forms и прочие решения. YouTube уже отказался от поддержки IE (&lt;a href=&quot;https://support.google.com/youtube/answer/175292?hl=ru&quot; target=&quot;_blank&quot;&gt;ссылка&lt;/a&gt;). Microsoft Teams, а также офисные пакеты, также прекратят поддержку IE в этом году (&lt;a href=&quot;https://www.vesti.ru/hitech/article/2445133&quot; target=&quot;_blank&quot;&gt;ссылка&lt;/a&gt;).&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Для нас: скорость релизов&lt;/strong&gt;&lt;br /&gt;Новые фичи появляются у нас достаточно часто, и, естественно, мы должны поддерживать и Explorer. Часто в нём не работают кажущиеся очевидными вещи, которые должны работать даже в IE6. На «раскопки» уходит много времени, плюс ещё затраты на поиск фоллбэк-решения.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Для нас: генерация лишних задач&lt;/strong&gt;&lt;br /&gt;Немного связано с предыдущим пунктом. Часто возникают сверхсрочные задачи, суть которых в том, что какой-то простейший отчёт внезапно перестаёт работать в IE11. Использование IE уже ничем не оправдано из-за его небезопасности, о которой трубят все, и громче всех — Microsoft. Но мы не можем оставить клиента без оплаченной услуги, хоть и не хочется поощрять беспорядочные интернет-связи. Тем не менее, такие задачи действительно довольно срочные, и связаны с переключением внимания от других срочных.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Для нас: стагнация&lt;/strong&gt;&lt;br /&gt;Пожалуй, самый незначительный пункт, который я всё-таки хочу написать. Огромное количество легаси-кода, написанного в старом синтаксисе, усложняет любую задачу. Каждое изменение требует длительного тестирования и связывания рук самим себе. Нам недоступен новый синтаксис, нам недоступно развитие языка, а следовательно — мы неконкурентны, как профессионалы и сервис. Скиллы изящной деградации или прогрессивного улучшения важны, но применять это к Explorer потихоньку становится бессмысленно.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;Это шесть причин, которые мне кажутся самыми важными.&lt;/p&gt;
  &lt;p&gt;Теперь я хотел бы рассказать, как я вижу само “прекращение поддержки”. Само собой, оно будет разделено на несколько этапов.&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;1 марта 2021 г.&lt;/strong&gt;&lt;br /&gt;В сервисах для пользователей IE мы показываем уведомление, что с 1 июня мы прекращаем поддержку IE. Можно приложить ссылку на объяснение, что это значит и почему так происходит. Получается, что мы дадим пользователям 3 месяца на то, чтобы адаптироваться к чему-то другому. В середине марта как раз будет прекращена поддержка сервисов Google, и первый “удар” уже успеет ощутиться.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;1 июня 2021 г.&lt;/strong&gt;&lt;br /&gt;В сервисах для пользователей IE мы показываем уведомление, что браузер не поддерживается. Мы не принимаем от клиентов задачи по “съехавшей” вёрстке, исправляя только критические white-screen баги, то есть, когда контент не виден вовсе.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;1 сентября 2021 г.&lt;/strong&gt;&lt;br /&gt;Сервисы более не приспосабливаются для работы в IE. Все задачи от клиентов, связанные с совместимостью с ним, не принимаются.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;На первый взгляд, такие действия могут показаться жёсткими (или жестокими), однако, как мне кажется, мы даём предостаточно времени для поиска другого решения.&lt;/p&gt;
  &lt;p&gt;Единственная проблема, которую я вижу — недовольство клиентов. К примеру, &lt;code&gt;&amp;lt;коллега&amp;gt;&lt;/code&gt; мне как-то говорил, что в некоторых организациях “не разрешено ничего, кроме IE”. Прежде всего, хочу сказать, что это решение выглядит очень странным, и, честно говоря, неправдоподобным. Я знаю, что на гос- и окологоскорпорациях сейчас полным ходом идёт “импортозамещение”, и на многие компьютеры устанавливается Astra Linux, в котором IE нет и в помине. Там, где этого не происходит, ограничений на браузеры нет, особенно если речь про один из четырёх самых известных.&lt;/p&gt;
  &lt;p&gt;Если же это не так, то поддержка Windows 7 закончилась в январе 2020 года, а для более новых версий ОС есть Microsoft Edge, к которому никаких претензий нет. Для топ-менеджмента на устройствах Apple есть Safari.&lt;/p&gt;
  &lt;p&gt;Я уверен, что бояться “ухода” клиентов не стоит. Наши клиенты лояльны к нам и любят нас не за то, что могут смотреть отчёты в IE, я в этом уверен. К тому же, если не в этом году, то уже через пару лет им придётся уйти из всего интернета: тенденция к прекращению поддержки IE прослеживается очень чётко. Чтобы сгладить углы, нужно действовать постепенно: именно поэтому я и хочу разбить всё на три этапа.&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:Qq_wL-nzi0O</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/Qq_wL-nzi0O?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>React Native: побеждаем lineHeight кроссплатформенно</title><published>2021-07-23T09:58:41.231Z</published><updated>2021-07-23T09:58:41.231Z</updated><category term="react-native" label="React Native"></category><summary type="html">&lt;img src=&quot;https://lyoha.info/assets/img/blog/rn-line-height.png&quot;&gt;React Native вызывает споры, но он определённо хорош двумя вещами: быстрый старт для веб-разрабов и кроссплатформенностью, то есть, пишете один раз - получаете приложение для Android и iOS сразу же.</summary><content type="html">
  &lt;p&gt;React Native вызывает споры, но он определённо хорош двумя вещами: быстрый старт для веб-разрабов и кроссплатформенностью, то есть, пишете один раз - получаете приложение для Android и iOS сразу же.&lt;/p&gt;
  &lt;p&gt;Конечно, иногда требуются доработки под определённую платформу, но, в целом, вы можете быть уверены, что результат будет плюс-минус одинаковым.&lt;/p&gt;
  &lt;p&gt;Проблемы начинаются вместе с кастомизацией. Мало того, что добавить свой шрифт в RN-приложение непросто, ещё и начинаются проблемы с тем, как текст с этим шрифтом выглядит.&lt;/p&gt;
  &lt;h3&gt;А как вообще использовать свой шрифт?&lt;/h3&gt;
  &lt;p&gt;Про добавление самого файла шрифта есть много публикаций в Интернете (к примеру, хорошо написано &lt;a href=&quot;https://medium.com/@mehrankhandev/ultimate-guide-to-use-custom-fonts-in-react-native-77fcdf859cf4&quot; target=&quot;_blank&quot;&gt;вот тут&lt;/a&gt;).&lt;/p&gt;
  &lt;p&gt;Но просто добавить файлы недостаточно. Здесь стоит сказать следующее: React Native немного ломает привычный флоу работы с HTML и CSS, что вполне логично. Лично мне это показалось диким и непривычным, но, чем раньше вы поймёте, что иначе никак - тем лучше :)&lt;/p&gt;
  &lt;p&gt;Самый простой вариант - написать свой компонент, наследуемый от &lt;code&gt;Text&lt;/code&gt;. Я в своём проекте использую библиотеку компонентов &lt;a href=&quot;https://nativebase.io/&quot; target=&quot;_blank&quot;&gt;Native Base&lt;/a&gt;, так что покажу на её примере.&lt;/p&gt;
  &lt;p&gt;Итак, я добавил файлы шрифта Gotham Pro, теперь время написать компонент:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import React from &amp;#x27;react&amp;#x27;
import { Text } from &amp;#x27;native-base&amp;#x27;

export const MyText = (props) =&amp;gt; {
  const style = {
    fontFamily: &amp;#x27;GothamPro&amp;#x27;,
  }
  return &amp;lt;Text {...props} style={style} /&amp;gt;
}&lt;/pre&gt;
  &lt;p&gt;Смотрим - шрифт правильный. Однако в дальнейшей работе вы увидите, что высота строки на iOS и Android различается.&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://lyoha.info/assets/img/blog/rn-line-height.png&quot; width=&quot;746&quot; /&gt;
    &lt;figcaption&gt;Источник: https://stackoverflow.com/questions/47925577/different-lineheight-on-android-ios-using-react-native&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3&gt;Исправляем высоту строки&lt;/h3&gt;
  &lt;p&gt;Это происходит из-за того, что дефолтные величины, используемые для настройки таких свойств, как &lt;code&gt;lineHeight&lt;/code&gt;, на iOS и Android с виду одинаковые, но работают чуть-чуть по-разному. Значит, нам нужно явно указать, какой &lt;code&gt;lineHeight&lt;/code&gt; должен использоваться сейчас.&lt;/p&gt;
  &lt;p&gt;Обновим компонент:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import React from &amp;#x27;react&amp;#x27;
import { Text } from &amp;#x27;native-base&amp;#x27;

export const MyText = (props) =&amp;gt; {
  const fontSize = props?.style?.fontSize ?? 16
  const lineHeight = props?.style?.lineHeight ?? fontSize
  const style = {
    ...props.style,
    fontFamily: &amp;#x27;GothamPro&amp;#x27;,
    fontSize,
    lineHeight
  }
  return &amp;lt;Text {...props} style={style} /&amp;gt;
}&lt;/pre&gt;
  &lt;p&gt;Минуточку, зачем &lt;code&gt;fontSize&lt;/code&gt;? К сожалению, без этого не обойтись. Нам нужно знать, какая должна быть высота строки, а для этого нужно знать, какой размер шрифта у текста. Если никакой - то мы должны понимать, какой размер у нас по-умолчанию.&lt;/p&gt;
  &lt;p&gt;Этот компонент позволяет нам “пробрасывать” стили, а также вручную управлять высотой строки и размером шрифта. Однако, если же мы хотим просто вывести текст - наш не ждёт никаких разочарований: текст будет выглядеть одинаково и на iOS, и на Android.&lt;/p&gt;
  &lt;h3&gt;Не очень красиво…&lt;/h3&gt;
  &lt;p&gt;Вам тоже показалось, что решение не очень-то изящное? Что ж, React Native - костыльная штука, которая позволяет делать многое, зная что-то одно. За то, что я ленюсь изучить нативную разработку, я плачу костылями. Пока что мне это подходит. А вам? :)&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:BCdjIWIKncD</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/BCdjIWIKncD?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Пишем компилируемый сервер на JS</title><published>2021-07-22T10:01:49.541Z</published><updated>2021-07-22T10:01:49.541Z</updated><category term="c-c" label="C, C++"></category><summary type="html">Согласитесь, то, что JS подходит не только для браузера - уже не новость. С появлением Node.js мы все поняли, что JS, как и любой другой язык, всего лишь инструмент, а как его использовать - дело фантазии, ну и, пожалуй, API.</summary><content type="html">
  &lt;h3&gt;Ситуация прямо сейчас&lt;/h3&gt;
  &lt;p&gt;Согласитесь, то, что JS подходит не только для браузера - уже не новость. С появлением Node.js мы все поняли, что JS, как и любой другой язык, всего лишь инструмент, а как его использовать - дело фантазии, ну и, пожалуй, API.&lt;/p&gt;
  &lt;p&gt;Спустя десять с лишним лет ситуация на поле серверного JS изменилась несущественно. Основные пути использования - Node и Deno, для любителей особых ощущений - fpm-решения.&lt;/p&gt;
  &lt;p&gt;Болячки Node известны всем. Deno частично решает их, но остаётся всё тем же наследником, перенося некоторые непонятные решения. К примеру, когда я смотрел последний релиз Deno, в котором появилась команда &lt;code&gt;deno compile&lt;/code&gt;, я был удивлён тем, что на выходе мы получаем бинарник размером ~36 мегабайт. В Node же есть только костыльные сторонние решения, которые чаще всего приводят к тому, что ваше приложение либо не собирается, либо не работает после сборки, из-за того, что какие-то модули не найдены.&lt;/p&gt;
  &lt;p&gt;Почему так происходит? Потому что все решения для ноды сейчас работают по принципу упаковки всего необходимого в одну “корзину”. В итоговый файл включается рантайм ноды, нужные модули и ваши исходные коды. Естественно, на этапе этой, с позволения сказать, “компиляции”, резолвится далеко не всё, поэтому если у вас работает собранное таким образом приложение - это большая удача.&lt;/p&gt;
  &lt;p&gt;Какие есть решения? Пожалуй, первое и самое логичное - писать не на JS. Но… как? Давайте признаем, что большинство из тех, кто пишут на клиентском или серверном JS хорошо знают только этот язык. По крайней мере, я буду говорить за себя.&lt;/p&gt;
  &lt;p&gt;И вот, в 2019 году появился человек, который решил помочь таким, как я. Уж не знаю, из побуждений сочувствия или саморазвития, но получилось у него суперски.&lt;/p&gt;
  &lt;h3&gt;Фабрис Беллар и QuickJS&lt;/h3&gt;
  &lt;p&gt;Вы можете не знать это имя, но вы знаете его дела. Фабрис Беллар, французский математик и программист, прежде всего известен четырьмя вещами:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Эмулятор QEMU;&lt;/li&gt;
    &lt;li&gt;C-компилятор Tiny C Compiler;&lt;/li&gt;
    &lt;li&gt;Конечно же, FFmpeg;&lt;/li&gt;
    &lt;li&gt;Конечно же, Git.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;В 2019 году к этому списку прибавилась ещё одна интересная вещица: рантайм для JavaScript, написанный на C, не использующий AST и обеспечивающий практически бесшовную интеграцию со всякими POSIX-штуками — QuickJS.&lt;/p&gt;
  &lt;p&gt;На своем сайте Беллар называет QJS “маленьким встраиваемым движком JavaScript”. Это действительно так: полный размер движка всего 620 килобайт, при этом он поддерживает практически все стандарты ES2020.&lt;/p&gt;
  &lt;p&gt;Всё это здорово, но давайте трезвым взглядом посмотрим на то, чем QJS является прямо сейчас:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Это довольно быстрый маленький JS-движок, способный составить конкуренцию даже V8 (но без JIT);&lt;/li&gt;
    &lt;li&gt;Это полная поддержка ES2020, но не без странностей. К примеру, вывод объектов в консоль покажет вам только &lt;code&gt;[object Object]&lt;/code&gt;;&lt;/li&gt;
    &lt;li&gt;Это очень скудная стандартная библиотека. Фактически, всё, что вы можете - взаимодействовать с файлами;&lt;/li&gt;
    &lt;li&gt;Это полное отсутствие методов для взаимодействия с сетью.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Последний пункт расстроил меня больше всего. Мне очень понравились идеи легковесности и компилируемости, которые несёт в себе QJS, но серверный JS без самого сервера — штука довольно-таки бесполезная.&lt;/p&gt;
  &lt;p&gt;Исходные коды QJS доступны на гитхабе всем желающим. Я решил взглянуть на них и понял, что даже несмотря на то, что на C я никогда всерьёз не писал, понять, что там происходит, оказалось не так сложно. И именно тогда во мне зародилась идея добавить в этот движок чуть-чуть сервера.&lt;/p&gt;
  &lt;h3&gt;А как работать-то будет&lt;/h3&gt;
  &lt;p&gt;QuickJS предполагает следующий процесс:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Сам функционал какого-либо модуля пишется на C;&lt;/li&gt;
    &lt;li&gt;На C же пишется “прослойка”, использующая QJS-api, которая говорит интерпретатору или компилятору, что вот тут-то мы и вызываем C-функции;&lt;/li&gt;
    &lt;li&gt;Внутри JS-скриптов мы импортируем JS-переменные из скомпилированной shared-библиотеки.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;В итоге мы получаем простое решение: если мы хотим добавить что-то в QJS, нужно найти или написать новый функционал на C, а затем просто написать API-прослойку.&lt;/p&gt;
  &lt;p&gt;Если честно, в этой работе я не сделал ничего особо важного. Я нашёл сервер на C, который показался мне идеально подходящим для моей задумки — библиотеку &lt;a href=&quot;https://github.com/jeremycw/httpserver.h&quot; target=&quot;_blank&quot;&gt;httpserver.h&lt;/a&gt;. Взяв его за основу, я написал немного связующего кода, и получился &lt;strong&gt;QuickWebServer&lt;/strong&gt; — динамическая библиотека, привносящая в QJS функционал веб-сервера с Express-подобным API. Давайте же посмотрим, как оно работает!&lt;/p&gt;
  &lt;h3&gt;Первый сервер на QJS&lt;/h3&gt;
  &lt;p&gt;Прежде всего, рекомендую вам скачать, собрать и установить QuickJS. Лишним точно не будет. Скачать можно из &lt;a href=&quot;https://github.com/bellard/quickjs&quot; target=&quot;_blank&quot;&gt;этого репозитория&lt;/a&gt;. Далее, для пользователей Mac и Linux проблем никаких - сперва запускаете &lt;code&gt;make&lt;/code&gt; и ждёте, пока проект соберётся, а затем &lt;code&gt;make install&lt;/code&gt;, чтобы все файлы поместились в нужные папки. Для пользователей Windows тоже можно собрать, но я, к сожалению, не проверял.&lt;/p&gt;
  &lt;p&gt;Также давайте убедимся, что у вас установлен Node.js и NPM. Это выглядит нелогичным, ведь мы не будем использовать Node, но установка пакетов удобнее с помощью NPM.&lt;/p&gt;
  &lt;p&gt;Давайте создадим тестовый проект, к примеру, в папке &lt;code&gt;first-server&lt;/code&gt;.&lt;/p&gt;
  &lt;pre&gt;# ~/first-server
npm init -y&lt;/pre&gt;
  &lt;p&gt;Теперь нам нужно скачать и собрать QuickWebServer (далее - QWS). Он опубликован в NPM:&lt;/p&gt;
  &lt;pre&gt;npm i -S @lyohaplotinka/quickwebserver&lt;/pre&gt;
  &lt;p&gt;Появилась папка &lt;code&gt;node_modules&lt;/code&gt;, а также началась сборка shared-библиотеки. Это может занять определённое время, так что можем отвлечься.&lt;/p&gt;
  &lt;p&gt;Пока что начнём писать код нашего сервера. Первым делом импортируем библиотеку:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import QuickWebServer from &amp;#x27;./node_modules/@lyohaplotinka/quickwebserver/src/QuickWebServer.js&amp;#x27;;&lt;/pre&gt;
  &lt;p&gt;Важное замечание: QuickJS не Node.js, поэтому он:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Не ищет модули автоматически в node_modules;&lt;/li&gt;
    &lt;li&gt;не умеет загружать index.js из папки;&lt;/li&gt;
    &lt;li&gt;требует всегда указывать расширение файла после точки.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Двигаемся дальше. Создадим экземпляр:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import QuickWebServer from &amp;#x27;./node_modules/@lyohaplotinka/quickwebserver/src/QuickWebServer.js&amp;#x27;;

const server = new QuickWebServer();&lt;/pre&gt;
  &lt;p&gt;Ну а следующий шаг - написание обработчика маршрута - вызовет явное дежа-вю у тех, кто хоть раз писал серверы на Express:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import QuickWebServer from &amp;#x27;./node_modules/@lyohaplotinka/quickwebserver/src/QuickWebServer.js&amp;#x27;;

const server = new QuickWebServer();

server.get(&amp;#x27;/&amp;#x27;, async (request, response) =&amp;gt; {
  response.type(&amp;#x27;text/html&amp;#x27;);
  response.send(&amp;#x27;Hello from QuickJS!&amp;#x27;);
});&lt;/pre&gt;
  &lt;p&gt;Осталось лишь начать слушать соединения:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import QuickWebServer from &amp;#x27;./node_modules/@lyohaplotinka/quickwebserver/src/QuickWebServer.js&amp;#x27;;

const server = new QuickWebServer();

server.get(&amp;#x27;/&amp;#x27;, async (request, response) =&amp;gt; {
  response.type(&amp;#x27;text/html&amp;#x27;);
  response.send(&amp;#x27;Hello from QuickJS!&amp;#x27;);
});

server.listen(3000);&lt;/pre&gt;
  &lt;p&gt;Код выше - минимальный пример работы. О, кажется, у нас завершилась сборка. Запустим!&lt;/p&gt;
  &lt;pre&gt;# ~/first-server
qjs ./server.js&lt;/pre&gt;
  &lt;p&gt;Открываем в браузере &lt;code&gt;http://localhost:3000&lt;/code&gt; и видим:&lt;/p&gt;
  &lt;pre&gt;Hello from QuickJS!&lt;/pre&gt;
  &lt;p&gt;В целом, уже победа: у нас сервер на JS, но не Node.js. Круто!&lt;/p&gt;
  &lt;p&gt;В целом, всё можно так и оставить, и использовать QJS как рантайм. Но если мы хотим скомпилировать приложение, чтобы использовать всего лишь один бинарник, нам нужно сделать ещё кое-что.&lt;/p&gt;
  &lt;h3&gt;Компилируем сервер&lt;/h3&gt;
  &lt;p&gt;Сейчас QWS не часть QuickJS, а динамическая библиотека. Чтобы не таскать её за собой, нам нужно собрать программу со статической линковкой. Признаюсь честно, мне эта операция показалась не самым лёгким занятием, поэтому я решил это дело немного автоматизировать.&lt;/p&gt;
  &lt;p&gt;В составе пакета &lt;code&gt;quickwebserver&lt;/code&gt; распространяется скрипт &lt;code&gt;create-build-makefile.sh&lt;/code&gt;. Он, собственно, создаёт Makefile, в котором прописаны все необходимые шаги для сборки. Вызовем его так:&lt;/p&gt;
  &lt;pre&gt;# ~/first-server
./node_modules/@lyohaplotinka/quickwebserver/create-build-makefile.sh ~/first-server/server.js&lt;/pre&gt;
  &lt;p&gt;Как видите единственный аргумент - абсолютный путь к главному файлу нашего приложения. После выполнения команды рядом с файлом &lt;code&gt;server.js&lt;/code&gt; появится &lt;code&gt;Makefile&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Остался один шаг до сборки. Откроем &lt;code&gt;server.js&lt;/code&gt; и поправим импорт QWS так, чтобы подключалась версия для статической сборки:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import QuickWebServer from &amp;#x27;./node_modules/@lyohaplotinka/quickwebserver/src/QuickWebServer.build.js&amp;#x27;;&lt;/pre&gt;
  &lt;p&gt;Время выполнить самый простой шаг - просто вызвать команду &lt;code&gt;make&lt;/code&gt;. Совсем скоро рядом появится исполняемый файл &lt;code&gt;server&lt;/code&gt;. Если его запустить и перейти на &lt;code&gt;http://localhost:3000&lt;/code&gt;, мы увидим всё ту же приятную фразу:&lt;/p&gt;
  &lt;pre&gt;Hello from QuickJS!&lt;/pre&gt;
  &lt;p&gt;Отдельно радует размер исполняемого файла - чуть больше мегабайта. Кстати, при компиляции из QuickJS можно “выпилить” какие-либо фичи языка, если они не используются. Так вы сократите размер файла.&lt;/p&gt;
  &lt;p&gt;Очень важно, что это никакой не “хак”, типо упаковки рантайма и кода в один пакет чисто “для виду”. При желании вы можете использовать QuickJS не для компиляции в бинарник, а для превращения вашего кода в C.&lt;/p&gt;
  &lt;h3&gt;А зачем это вообще нужно?&lt;/h3&gt;
  &lt;p&gt;Это извечный вопрос. Однако, у меня есть пара ответов.&lt;/p&gt;
  &lt;p&gt;Во-первых, затем, зачем я вообще что-либо делаю всегда: это просто прикольно. Ну, круто же иметь сервер в одном бинарнике!&lt;/p&gt;
  &lt;p&gt;Во-вторых, это быстро. Почти настолько же, как Node.js, но куда более транспортабельно и без необходимости в рантайме. И гораздо меньше по размеру.&lt;/p&gt;
  &lt;p&gt;В-третьих, некоторые люди действительно не хотят светить свой код. Компиляция позволяет усложнить возможность заглянуть в наши каракули.&lt;/p&gt;
  &lt;p&gt;В-четвертых, мне уже давно хотелось бы иметь какую-нибудь маленькую альтернативу Node.js, созданную для каких-нибудь простецких целей. Поэтому я лично рад, что у меня получилось это реализовать вот так.&lt;/p&gt;
  &lt;h3&gt;Резюме&lt;/h3&gt;
  &lt;p&gt;В итоге, конечно, всё это пока что несерьёзно. Нет HTTP-клиента, нет библиотек для работы с БД. Да и сама версия сервера ещё нестабильная, к примеру, нет возможности принимать от клиента файлы.&lt;/p&gt;
  &lt;p&gt;Во всём Интернете сейчас всего лишь один сайт работает на QWS - и это сайт его документации (&lt;a href=&quot;https://qws.lyoha.info/&quot; target=&quot;_blank&quot;&gt;https://qws.lyoha.info&lt;/a&gt;). Он, кстати, загружает доки из маркдауна и рендерит их на стороне JS. Уже что-то!&lt;/p&gt;
  &lt;p&gt;Тем не менее, эта работа меня сильно вдохновила. Поэтому я завёл на гитхабе организацию &lt;a href=&quot;https://github.com/QuickJS-Web-project&quot; target=&quot;_blank&quot;&gt;QuickJS Web Project&lt;/a&gt;, в которой хочу попытаться привнести в QJS всё то, что нужно для работы хорошего серверного движка. Если вы чувствуете силы и желание - я буду только рад работать вместе :)&lt;/p&gt;
  &lt;p&gt;Вот так вот можно написать свой сервер на JS, скомпилировать его и почувствовать себя немного серьёзнее, пусть даже всё это появилось из-за несерьёзного порыва любопытства.&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:u5G5KN2AIzi</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/u5G5KN2AIzi?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Деплой JavaScript-приложений</title><published>2021-07-22T09:58:00.012Z</published><updated>2021-07-22T09:58:00.012Z</updated><category term="infrastructure" label="Инфраструктура"></category><summary type="html">В работа над любым проектом рано или поздно наступает момент, когда его нужно запустить на продакшн-сервере. Очевидно, чтобы запустить, нужно выгрузить.</summary><content type="html">
  &lt;h3&gt;Почему это важно?&lt;/h3&gt;
  &lt;p&gt;В работа над любым проектом рано или поздно наступает момент, когда его нужно запустить на продакшн-сервере. Очевидно, чтобы запустить, нужно выгрузить.&lt;/p&gt;
  &lt;p&gt;Всегда есть варианты и свобода выбора, и деплой не исключение. Само слово &lt;em&gt;deployment&lt;/em&gt; означает “развёртывание, размещение”, что, в целом, отражает конечную цель этого процесса.&lt;/p&gt;
  &lt;p&gt;Любой специалист, которому выпала возможность поработать в более-менее серьёзной организации с необходимостью регулярного обновления кода на сервере подтвердит, что хорошо настроенный процесс деплоя сделает вас немного счастливее.&lt;/p&gt;
  &lt;p&gt;Я расскажу вам историю о том, как я пришёл к хорошему деплою, каким способом я пользуюсь и почему он мне нравится. А заодно мы рассмотрим несколько возможных вариантов.&lt;/p&gt;
  &lt;h3&gt;Стадия 1: “Зачем мне что-то? Хостер уже дал мне FTP!”&lt;/h3&gt;
  &lt;p&gt;Итак, за окном 2015 год, и я только начинаю осваивать искусство вебдева. Мой сайт – PHP/jQuery/CSS, никаких сборщиков, гита и прочего, просто файлы, которые достаточно положить в правильную директорию на сервере. Чем я, собственно, и занимаюсь.&lt;/p&gt;
  &lt;p&gt;Сейчас уже трудно вспомнить, каким хостинг-провайдером я пользовался, но это было что-то в стиле “заплати 5 рублей и получи соответствующую услугу”, но я, как человек, у которого нет денег и есть самый первый заказ по фрилансу, счастлив этому безмерно.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Первый вариант&lt;/strong&gt; – самый простой – доступ к серверу по FTP и ручная загрузка файлов.&lt;/p&gt;
  &lt;p&gt;Это может быть хорошо, потому что:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Не требуется абсолютно ничего, кроме FileZilla или аналогов;&lt;/li&gt;
    &lt;li&gt;Сэкономит вам время, если сайт статический, и кодовая база в обозримом будущем не будет обновляться;&lt;/li&gt;
    &lt;li&gt;Есть на подавляющем большинстве платных и бесплатных хостингов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Однако, эта статья называется не “Деплой статических сайтов”, так что вы вполне законно можете спросить: при чём тут статика? И будете правы. Плох данный способ тем, что он лишает вас очень много полезных штук:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Версионирования релизов;&lt;/li&gt;
    &lt;li&gt;Возможности быстро “откатить” релиз, если в нём ошибка;&lt;/li&gt;
    &lt;li&gt;В случае использования сборщиков (Webpack и т.д.), помечающих собранные файлы хэшем, этот способ приведёт к файловой путанице.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Естественно, есть и самый очевидный минус – это неудобно, долго, да ещё и руками надо что-то делать. Мне, как чертовски ленивому человеку, это не подходит. Едем дальше!&lt;/p&gt;
  &lt;h3&gt;Стадия 2: “Окей, я буду как большие дяденьки! Где мой Git?..”&lt;/h3&gt;
  &lt;p&gt;По своему опыту (а также по опыту других разработчиков, подсмотренному мной) скажу, что когда в жизни молодого и горячего разработчика появляется Git, а особенно тогда, когда он понимает, как им пользоваться хотя бы на начальном уровне, он видится просветом в полном туч небе, способным решить любую проблему.&lt;/p&gt;
  &lt;p&gt;Хранить код? Git. Библиотека фоток? Git. Список контактов? Git. Деплой? Конечно же, тоже Git. И, вроде бы, это уже лучше, чем просто заливать файлы вручную. Особенно если на этой стадии наш герой, как и я в то время, уже более-менее мог в какую-нибудь автоматизацию, типа шелл-скриптов.&lt;/p&gt;
  &lt;p&gt;В голове у меня сидела мысль: если Git - система контроля версий, то кому, как ни ей, заниматься контролем версий на продакшне? Таким образом процесс деплоя представляет собой клонирование репозитория в нужную папку и периодические &lt;code&gt;git pull&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Плюсы такого способа:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Вы всегда знаете, что на продакшне тот же код, что и на вашем компьютере&lt;/li&gt;
    &lt;li&gt;Решение более-менее автоматизированное: достаточно лишь вызвать команду.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Однако, есть и минусы, и довольно серьёзные. Прежде всего, давайте признаем: как бы мы с вами ни отыгрывали знатоков, рано или поздно возникает момент, когда ты правишь что-то прямо на продакшне, к примеру, какую-нибудь критическую ошибку. Это вынуждает нас внимательно следить за состоянием кода в репозитории и на бою, убеждаться в том, что нет конфликтов, делать пуши в мастер прямо с продакшна, или делать пулл-реквесты. Кроме того, минусы такие:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Конфликты могут сломать ВСЁ. Поверьте, я знаю, о чём говорю. Если случается конфликт при обновлении кодовой базы, Git пометит мх прямо в файлах, ломая синтаксис. Если познания Git не очень большие, быстро устранить проблему может быть сложно.&lt;/li&gt;
    &lt;li&gt;Если доступ к серверу имеют несколько человек с разными логинами и правами, нужно обладать неплохими познаниями Linux, чтобы все они могли выполнять &lt;code&gt;git pull&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;Раз уж речь про JavaScript, то нужно сказать, что этот способ вынуждает нас хранить собранные файлы в репозитории. А это как-то не круто. Тем более, если проект серверный: нужно либо дёргать &lt;code&gt;npm install&lt;/code&gt; после обновления, либо хранить в репозитории папку &lt;code&gt;node_modules&lt;/code&gt;. Чувствуете мурашки на коже?&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Последний минус - банальная лень. Мне лично через какое-то время стало лень следить за синхроном репозитория и продакшна, и через какое-то время продакшн и гитхаб разошлись далеко друг от друга.&lt;/p&gt;
  &lt;p&gt;Мой вердикт: это тоже не подходит.&lt;/p&gt;
  &lt;h3&gt;Стадия 3: Shipit, потому что каждый инструмент должен делать своё дело&lt;/h3&gt;
  &lt;p&gt;Через какое-то время я увидел инструмент PHP Deployer, который сразу мне понравился. Я начал поиски аналога для JavaScript, и вскоре нашёл &lt;a href=&quot;https://github.com/shipitjs/shipit&quot; target=&quot;_blank&quot;&gt;Shipit&lt;/a&gt;. По заверению их репозитория, это универсальный инструмент для автоматизации и деплоя.&lt;/p&gt;
  &lt;p&gt;Вкратце, Shipit делает следующее:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Создаёт на вашем компьютере временную папку, куда клонирует ваш репозиторий для получения актуальной кодовой базы&lt;/li&gt;
    &lt;li&gt;Даёт возможность делать с этими файлами что угодно, к примеру, устанавливать зависимости и запускать сборку&lt;/li&gt;
    &lt;li&gt;Подключается к вашему серверу по SSH и загружает файлы в удалённую папку&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;Выглядит негусто, но это ещё и не всё. Главное, что даёт Shipit - систему событий, на которые можно подписаться и выполнить какую-либо команду на локальном либо удалённом сервере.&lt;/p&gt;
  &lt;p&gt;Начать использовать его очень просто. Проверьте, вы соблюли эти условия:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;У вас есть доступ к Git-репозиторию&lt;/li&gt;
    &lt;li&gt;У вас есть SSH-доступ к продакшн серверу&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;Далее, вам нужно установить два пакета:&lt;/p&gt;
  &lt;pre&gt;npm install --save-dev shipit-cli
npm install --save-dev shipit-deploy&lt;/pre&gt;
  &lt;p&gt;Первый пакет - инструменты командной строки, второй - набор тасков (от оригинального “tasks”), которые предназначены для деплоя.&lt;/p&gt;
  &lt;p&gt;Далее, в корне вашего проекта нужно создать конфиг-файл &lt;code&gt;shipitfile.js&lt;/code&gt;. В репозитории проекта есть пример этого файла, но я покажу вам конфигурацию, которая мне показалось наиболее удачной и которой я сам пользуюсь.&lt;/p&gt;
  &lt;h4&gt;I. Деплой компилируемого фронтенда&lt;/h4&gt;
  &lt;p&gt;Это может быть приложение на каком-нибудь фреймворке или библиотеке, в моём случае - на Vue.js. Будем рассматривать конфиг-файл на примере выдуманного проекта &lt;strong&gt;MyFrontend&lt;/strong&gt;&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// shipitfile.js
const path = require(&amp;#x27;path&amp;#x27;)
const WORKSPACE_DIR = &amp;#x27;/tmp/myfrontend&amp;#x27;&lt;/pre&gt;
  &lt;p&gt;Нам понадобится &lt;code&gt;path&lt;/code&gt; из стандартной библиотеки Node. Переменная &lt;code&gt;WORKSPACE_DIR&lt;/code&gt; – это временная папка, в которую Shipit будет клонировать репозиторий и производить манипуляции с ним.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// shipitfile.js
const path = require(&amp;#x27;path&amp;#x27;)
const WORKSPACE_DIR = &amp;#x27;/tmp/myfrontend&amp;#x27;

module.exports = (shipit) =&amp;gt; {
    require (&amp;#x27;shipit-deploy&amp;#x27;).(shipit)
    
    shipit.initConfig({
        default: {
            workspace: WORKSPACE_DIR,
            dirToCopy: path.join(WORKSPACE_DIR, &amp;#x27;dist&amp;#x27;),
            deployTo: &amp;#x27;/var/www/myapp/myfrontend&amp;#x27;,
            repositoryUrl: &amp;#x27;https://gitgit.git/myapp/myfrontend.git&amp;#x27;,
            ignores: [&amp;#x27;.git&amp;#x27;, &amp;#x27;node_modules&amp;#x27;],
            keepReleases: 5,
            keepWorkspace: false,
            shallowClone: false,
            deploy: {
                remoteCopy: {
                    copyAsDir: false
                }
            }   
        },
        staging: {
            servers: &amp;#x27;myuser@123.45.678.90&amp;#x27;
        }
    })
}&lt;/pre&gt;
  &lt;p&gt;Далее нужно написать конфиг для Shipit. Вообще, у него очень много разных ключей на все случаи жизни, и я очень рекомендую ознакомиться с ними в документации. Мы же рассмотрим только те, которые важны для нас сейчас.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;code&gt;workspace&lt;/code&gt; – передаем путь до локальной папки&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;dirToCopy&lt;/code&gt; – какую папку из склонированного репозитория мы будем загружать. Если не прописать этот ключ, Shipit загрузит всю папку &lt;code&gt;workspace&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;deployTo&lt;/code&gt; – куда на удалённом сервере нужно положить наши файлы из предыдущей папки&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;repositoryUrl&lt;/code&gt; – адрес вашего Git-репозитория&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;ignores&lt;/code&gt; – какие файлы и папки точно не нужно загружать. Вообще, указание &lt;code&gt;dirToCopy&lt;/code&gt; решает эту проблему, но лучше перестраховаться, как мне кажется.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;keepReleases&lt;/code&gt; - сколько релизов будет храниться на продакш сервере, и, соответственно, на сколько релизов назад вы сможете откатиться&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;copyAsDir&lt;/code&gt; – как копировать папку &lt;code&gt;dirToCopy&lt;/code&gt; на сервер: создавать на сервере эту же папку (&lt;code&gt;true&lt;/code&gt;) или же просто загрузить её содержимое в &lt;code&gt;deployTo&lt;/code&gt; (&lt;code&gt;false&lt;/code&gt;)&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;staging&lt;/code&gt; – это всего лишь название конфигурации деплоя, оно может быть любым&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;servers&lt;/code&gt; - один или несколько IP-адресов сервера с указанием имени пользователя.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Если мы прямо сейчас запустим процесс деплоя, мы вряд ли добьёмся успеха: склонированный из Git проект не собран, а папка &lt;code&gt;dist&lt;/code&gt; не существует. Значит, нам нужно “вклиниться” в процесс деплоя и вовремя собрать наш проект. В этом нам помогут уже упомянутые мной события.&lt;/p&gt;
  &lt;p&gt;Добавим в наш файл следующее:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// shipitfile.js
const path = require(&amp;#x27;path&amp;#x27;)
const WORKSPACE_DIR = &amp;#x27;/tmp/myfrontend&amp;#x27;

module.exports = (shipit) =&amp;gt; {
    require (&amp;#x27;shipit-deploy&amp;#x27;).(shipit)
    
    shipit.initConfig({
        default: {
            workspace: WORKSPACE_DIR,
            dirToCopy: path.join(WORKSPACE_DIR, &amp;#x27;dist&amp;#x27;),
            deployTo: &amp;#x27;/var/www/myapp/myfrontend&amp;#x27;,
            repositoryUrl: &amp;#x27;https://gitgit.git/myapp/myfrontend.git&amp;#x27;,
            ignores: [&amp;#x27;.git&amp;#x27;, &amp;#x27;node_modules&amp;#x27;],
            keepReleases: 5,
            keepWorkspace: false,
            shallowClone: false,
            deploy: {
                remoteCopy: {
                    copyAsDir: false
                }
            }   
        },
        staging: {
            servers: &amp;#x27;myuser@123.45.678.90&amp;#x27;
        }
    })

    shipit.on(&amp;#x27;fetched&amp;#x27;, async () =&amp;gt; {
        await shipit.start(&amp;#x27;app:build&amp;#x27;)
    })
    
    shipit.blTask(&amp;#x27;app:build&amp;#x27;, async () =&amp;gt; {
        await shipit.local(
          &amp;#x60;cd ${WORKSPACE_DIR} &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm run build&amp;#x60;
        )
    })
}&lt;/pre&gt;
  &lt;p&gt;У пакета &lt;code&gt;shipit-deploy&lt;/code&gt; есть набор событий на каждое действие (ознакомиться можно &lt;a href=&quot;https://github.com/shipitjs/shipit/tree/master/packages/shipit-deploy#workflow-tasks&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;). Нам интересно событие &lt;code&gt;fetched&lt;/code&gt;, которое происходит сразу после клонирования репозитория. В нём вызываем наш собственный таск &lt;code&gt;app:build&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;&lt;code&gt;app:build&lt;/code&gt; объявлен как &lt;code&gt;blTask&lt;/code&gt;: в Shipit это значит, что задача блокирующая, и начинать следующую задачу нельзя, пока не завершится эта. В ней мы при помощи метода &lt;code&gt;shipit.local&lt;/code&gt; запускам локальную команду: переходим в &lt;code&gt;WORKSPACE_DIR&lt;/code&gt;, устанавливаем зависимости и собираем проект.&lt;/p&gt;
  &lt;p&gt;Если в процессе сборки что-то пойдёт не так, деплой прекратится: “сломанный” код не окажется на продакшне.&lt;/p&gt;
  &lt;p&gt;Всё готово к деплою: в командной строке набираем &lt;code&gt;./node_modules/.bin/shipit staging deploy&lt;/code&gt;. Когда процесс завершится, в папке на сервере вы увидите два элемента: директорию &lt;code&gt;releases&lt;/code&gt; и симлинк &lt;code&gt;current&lt;/code&gt;. Ваш веб-сервер нужно направить именно на симлинк, чтобы в браузер отдавались только актуальные файлы.&lt;/p&gt;
  &lt;p&gt;Если что-то всё же пошло не так, и нужно срочно вернуть предыдущий релиз, вы можете вызвать команду &lt;code&gt;./node_modules/.bin/shipit staging rollback&lt;/code&gt;. Она переключит симлинк &lt;code&gt;current&lt;/code&gt; на предыдущий релиз.&lt;/p&gt;
  &lt;h4&gt;II. Деплой бэкенда на Node.js&lt;/h4&gt;
  &lt;p&gt;Здесь принципы те же, за исключением пары деталей: бэк на ноде не надо собирать, но нужно установить зависимости на продакшн-сервере. В итоге конфиг будет выглядеть как-то так:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// shipitfile.js
const path = require(&amp;#x27;path&amp;#x27;)
const WORKSPACE_DIR = &amp;#x27;/tmp/mybackend&amp;#x27;

module.exports = (shipit) =&amp;gt; {
    require (&amp;#x27;shipit-deploy&amp;#x27;).(shipit)
    
    shipit.initConfig({
        default: {
            workspace: WORKSPACE_DIR,
            deployTo: &amp;#x27;/var/www/myapp/mybackend&amp;#x27;,
            repositoryUrl: &amp;#x27;https://gitgit.git/myapp/mybackend.git&amp;#x27;,
            ignores: [&amp;#x27;.git&amp;#x27;, &amp;#x27;node_modules&amp;#x27;],
            keepReleases: 5,
            keepWorkspace: false,
            shallowClone: false,
            deploy: {
                remoteCopy: {
                    copyAsDir: false
                }
            }   
        },
        staging: {
            servers: &amp;#x27;myuser@123.45.678.90&amp;#x27;
        }
    })

    shipit.on(&amp;#x27;updated&amp;#x27;, async () =&amp;gt; {
        await shipit.start(&amp;#x27;app:install-deps&amp;#x27;)
    })
    
    shipit.blTask(&amp;#x27;app:install-deps&amp;#x27;, async () =&amp;gt; {
        await shipit.remote(
            &amp;#x60;cd ${shipit.releasePath} &amp;amp;&amp;amp; npm install&amp;#x60;
        ) 
    })
}&lt;/pre&gt;
  &lt;p&gt;Ключевые изменения:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Убрали &lt;code&gt;dirToCopy&lt;/code&gt;, так как обычно бэкенд-проекты начинаются от корня репозитория&lt;/li&gt;
    &lt;li&gt;Теперь мы завязываемся на событие &lt;code&gt;updated&lt;/code&gt;, которые происходит после создания новой папки внутри &lt;code&gt;releases&lt;/code&gt; на сервере и копирования файлов туда.&lt;/li&gt;
    &lt;li&gt;В блокирующей задаче &lt;code&gt;app:install-deps&lt;/code&gt; мы при помощи &lt;code&gt;shipit.remote&lt;/code&gt; запускаем установку зависимостей на сервере, перемещаясь в папку релиза. Заранее мы не знаем, какую папку создаст Shipit, но её можно легко получить на этом этапе при помощи &lt;code&gt;shipit.releasePath&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Также может понадобиться перезапустить приложение Node. Для этого можно использовать событие &lt;code&gt;deployed&lt;/code&gt;, и вызвать, к примеру, &lt;code&gt;pm2 reload&lt;/code&gt;, если для запуска ноды в бою вы используете &lt;a href=&quot;https://pm2.keymetrics.io/&quot; target=&quot;_blank&quot;&gt;PM2&lt;/a&gt;.&lt;/p&gt;
  &lt;h4&gt;IV. Деплой компилируемого бэкенда на Node.js&lt;/h4&gt;
  &lt;p&gt;Да-да, я говорю про TypeScript. Однако, я думаю, что сложив вышеописанные шаги, вы получите как раз то, что вам нужно :)&lt;/p&gt;
  &lt;h3&gt;Итоги&lt;/h3&gt;
  &lt;p&gt;Хороший деплой – это, прежде всего, сэкономленные нервы. Вы знаете, что можете быстро развернуть новую версию, или вернуть предыдущую, если что-то пошло не так. Вы знаете, что у вас никогда не возникнет путаницы на продакшне, и вы всегда будете видеть, сколько релизов было сделано.&lt;/p&gt;
  &lt;p&gt;Расширив &lt;code&gt;shipitfile.js&lt;/code&gt;, вы можете логировать деплои, собирать статистику, да и вообще делать всё, что вам нужно. Потратив на настройку минут 20 один раз, вы сохраните кучу времени в будущем.&lt;/p&gt;
  &lt;p&gt;Не в последнюю очередь – это красиво. Аккуратная структура директорий, полный контроль над продакшном, это эстетика, которая важна для нас, как для разработчиков, ведь видеть её – значит развиваться во всём.&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:7hlV_yfYOgi</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/7hlV_yfYOgi?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Страшная реактивность</title><published>2021-07-22T09:53:52.358Z</published><updated>2021-07-22T09:55:15.320Z</updated><category term="java-script" label="JavaScript 📜"></category><summary type="html">Не знаю, как у вас, но моя история в веб-разработке состоит из череды сражений с предрассудками и страхами. Естественно, всегда оказывается, что бояться было нечего, что нужно было лишь почитать документацию и попробовать, что всё, что я придумывал себе про ту или иную технологию – не более, чем рассуждения невежды.</summary><content type="html">
  &lt;figure class=&quot;m_column&quot;&gt;
    &lt;iframe src=&quot;https://www.youtube.com/embed/EE28s3Rx2WA?autoplay=0&amp;loop=0&amp;mute=0&quot;&gt;&lt;/iframe&gt;
  &lt;/figure&gt;
  &lt;h3&gt;Череда страхов&lt;/h3&gt;
  &lt;p&gt;Не знаю, как у вас, но моя история в веб-разработке состоит из череды сражений с предрассудками и страхами. Естественно, всегда оказывается, что бояться было нечего, что нужно было лишь почитать документацию и попробовать, что всё, что я придумывал себе про ту или иную технологию – не более, чем рассуждения невежды.&lt;/p&gt;
  &lt;p&gt;Так было с Node.js, с Typescript, с React и многим-многим другим. Та же участь постигла и реактивность. До относительно недавнего времени нутро фронтентд-библиотек казалось мне какой-то магией высшего порядка.&lt;/p&gt;
  &lt;p&gt;К сожалению, интернет помогает далеко не всегда. Очень часто я сталкивался с тем, что статьи “для начинающих” предполагают, что у тебя уже есть довольно серьёзный бэкграунд по теме. К примеру, я читал много статей про Node.js для начинающих, но до меня далеко не сразу дошло, что это про сервер. Конечно, это рождает много вопросов к моему интеллекту, но, всё же, я был довольно “зелёный”.&lt;/p&gt;
  &lt;h3&gt;Реактивность&lt;/h3&gt;
  &lt;p&gt;Уверен, все заметили, что это слово очень часто мелькает в материалах по фронтенду. В принципе, до определённой точки знать что это такое и как именно это работает необязательно, но рано или поздно настанет момент, когда станет либо слишком интересно, либо натурально необходимо.&lt;/p&gt;
  &lt;p&gt;Для начала, я бы хотел обозначить несколько утверждений, важных для понимания вот этого всего. Кому-то из вас они покажутся очевидными, но всё же, закрепить их нужно.&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;Реактивность - не от слова “реактивный”, как самолёт&lt;/strong&gt;&lt;br /&gt;Да, звучит глупо, но я правда так думал, и предполагаю, что я такой не один. Многое сразу становится понятно, когда осознаёшь, что это слово образовано от слова “реакция”. Впрочем, “реактивный” в контексте самолёта тоже произошло от слова “реакция”, но в обиходе оно вызывает ассоциации с чем-то быстрым, технологичным и т.д.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Вы используете реактивность, если работаете с JavaScript даже без фреймворков и библиотек&lt;/strong&gt;&lt;br /&gt;В общем смысле реактивность реализует идею “событие - реакция”. Каждый из нас работал с этим, вне всяких сомнений. К примеру, если вы писали обработчик нажатия на кнопку - в каком-то смысле это реактивность :)&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Реактивность - это не rocket science, это обычный код&lt;/strong&gt;&lt;br /&gt;И чаще всего довольно простой. Позволю себе небольшой спойлер: совсем скоро в этой публикации мы рассмотрим пример реактивного кода, и он займет менее 30 строк кода.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Спектр задач, который можно решить с помощью реактивного кода, шире, чем фронтенд-библиотеки&lt;/strong&gt; Всё ограничено вашей фантазией. Если вам кажется, что на бэкенде можно решить что-то с помощью реактивности - не бойтесь это делать.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3&gt;От слов к делу&lt;/h3&gt;
  &lt;p&gt;Давайте рассмотрим простейший пример работы с DOM. Допустим, вам нужно получить внутренний html элемента после того, как браузер построил дерево элементов. Предположим, что мы пишем код внутри &lt;code&gt;head&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;В вашем коде вы, скорее всего, напишете так:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;const elem = document.getElementById(&amp;#x27;myelem&amp;#x27;);
const html = elem.innerHTML&lt;/pre&gt;
  &lt;p&gt;Всё логично, но код не работает, потому что элемент ещё не создан. Всё, что нужно - дождаться, пока браузер построит дерево элементов. Есть вариант использовать таймаут или интервал, чтобы проверять, создан элемент или нет. Также можно использовать блокирующий while-цикл, который запретит &lt;strong&gt;вообще всё&lt;/strong&gt;, пока элемент не будет создан. Но ведь такие грязные решения не придут никому в голову, правильно?&lt;/p&gt;
  &lt;p&gt;Да и зачем, если есть событие &lt;code&gt;DOMContentLoaded&lt;/code&gt;, которое как раз сигнализирует, что DOM построен? Мы лучше сделаем так:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;document.addEventListener(&amp;#x27;DOMContentLoaded&amp;#x27;, function() {
  const elem = document.getElementById(&amp;#x27;myelem&amp;#x27;);
  const html = elem.innerHTML
})&lt;/pre&gt;
  &lt;p&gt;Поздравляю! Мы только что потрогали реактивность. Таким образом, основополагающий принцип реактивности можно сформулировать так:&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;h3 data-align=&quot;center&quot;&gt;Сделай определённое действие, когда узнаёшь, что произошло определённое событие&lt;/h3&gt;
  &lt;/section&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;h3&gt;Используем встроенные возможности&lt;/h3&gt;
  &lt;p&gt;Все браузеры реализовали спецификацию событий в том или ином виде. Есть два основных способа отправить событие: через &lt;code&gt;Event&lt;/code&gt; и &lt;code&gt;CustomEvent&lt;/code&gt; (если вам это не знакомо, почитайте &lt;a href=&quot;https://learn.javascript.ru/dispatch-events&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;).&lt;/p&gt;
  &lt;p&gt;Использование метода &lt;code&gt;dispatchEvent&lt;/code&gt;, а также прикрепление обработчика через &lt;code&gt;addEventListener&lt;/code&gt; как раз реализует принцип “событие - реакция”.&lt;/p&gt;
  &lt;p&gt;Допустим, вы хотите написать онлайн-калькулятор (не важно зачем), и хотите уведомлять пользователя каждый раз, когда результат вычислений больше 200 (да, у нас очень странный калькулятор).&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;function calculate(...args) {
/* 
    ...
    Тут проводим вычисления, и в конечном итоге
    получаем переменную result
  */
  if (result &amp;gt; 200) {
    const event = new Event(&amp;#x27;moreThan200&amp;#x27;)
    window.dispatchEvent(event)
  }
}
// А тут как раз добавляем обработчик
window.addEventListener(&amp;#x27;moreThan200&amp;#x27;, function() {
  alert(&amp;#x27;Результат больше 200!&amp;#x27;)
})&lt;/pre&gt;
  &lt;p&gt;Код выше “реагирует” на определённые условия, запуская функцию-обработчик. Реакция может быть любой, а можно и данные передать (используя &lt;code&gt;detail&lt;/code&gt;) в &lt;code&gt;CustomEvent&lt;/code&gt;. Чётко! Удобно!&lt;/p&gt;
  &lt;p&gt;Важный момент: используя &lt;code&gt;EventListener&lt;/code&gt;‘ы важно обязательно удалять прослушивания событий, если они не нужны, так как добавление нескольких обработчиков на одно и то же событие (особенно автоматизированное) ведет к неизбежным утечкам памяти. А на это совсем не надо.&lt;/p&gt;
  &lt;h3&gt;Паттерн Observable&lt;/h3&gt;
  &lt;p&gt;Есть шаблон проектирования, который называется “Observable” (наблюдаемый). С виду он позволяет делать практически то же самое, что и браузерные события. Разница лишь в том, что раз мы реализуем его самостоятельно, он может быть оптимизирован под конкретные задачи.&lt;/p&gt;
  &lt;p&gt;Главная задача Observable – уведомлять всех, кто наблюдает за его состоянием (они же называются Observer) о том, что оно поменялось. На языке умных и взрослых дяденек это называется связью “один ко многим”. Единственная задача наблюдаемого объекта – сообщить об изменениях и, возможно, передать изменённые данные. Что с этими данными делают наблюдатели ему, по задумке, не интересно.&lt;/p&gt;
  &lt;p&gt;Очень многие реактивные библиотеки имеют в основе этот паттерн. К примеру, &lt;code&gt;writable&lt;/code&gt; в Svelte – реализация Observable, как и &lt;code&gt;Rx.js&lt;/code&gt;. Интересно ещё и то, что этот паттерн можно реализовать практически в любом языке программирования (смотрите &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)#%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;, лично я отдельно умилился с Pascal).&lt;/p&gt;
  &lt;p&gt;Давайте напишем свою простейшую реализацию Observable, которая впоследствии поможет нам сделать зачатки своего фронтенд-фреймворка. На всякий случай оговорюсь, что в чём-то реализация неполная, и вообще, это совсем-совсем прототип.&lt;/p&gt;
  &lt;p&gt;Итак, мы будем работать с синтаксисом JavaScript-классов, потому что он мне нравится, однако, этого же можно добиться и через &lt;code&gt;Object.defineProperty&lt;/code&gt;.&lt;/p&gt;
  &lt;h3&gt;Наш собственный Observable&lt;/h3&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;class Observable {
    constructor(initialValue = null) {
        this._value = initialValue
    }
}&lt;/pre&gt;
  &lt;p&gt;Начнётся всё так. В конструктор нашего класса мы должны передать изначальное значение. Если мы его не передаём, мы считаем, что оно равно &lt;code&gt;null&lt;/code&gt;. Значок “земля” добавляем для того, чтобы впоследствии задать для объекта геттер и сеттер по ключу &lt;code&gt;value&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Кстати: лично я больше люблю TypeScript, и в плане изначальных значений там может быть больше вариантов. К примеру, лучше использовать дженерики, чтобы понимать, с каким типом данных работает наблюдаемый объект.&lt;/p&gt;
  &lt;p&gt;У Observable должно быть ещё одно свойство – “очередь” наблюдателей. Если называть вещи простыми словами, то очередь - обычный массив или объект, в который мы будем “складывать” новых подписчиков. Я предпочитаю объекты, потому что люблю обращаться по ключам:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;class Observable {
    constructor(initialValue = null) {
        this._value = initialValue
        // Добавляем очередь наблюдателей
        this.subscribersQueue = {}
    }
}&lt;/pre&gt;
  &lt;p&gt;Самое время реализовать метод подписки на изменение значения. Алгоритм такой:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Добавить переданный метод в очередь, присвоив ему идентификатор;&lt;/li&gt;
    &lt;li&gt;вернуть функцию “отписки”, чтобы можно быть отвязаться от наблюдения за объектом.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;class Observable {
    constructor(initialValue = null) {
        this._value = initialValue
        this.subscribersQueue = {}
    }

    // Метод subscribe - подписываемся на изменения
    subscribe(listener) {
        const listenerId = Math.random().toString(36).substr(2, 9)
        this.subscribersQueue[listenerId] = listener
        return () =&amp;gt; delete this.subscribersQueue[listenerId]
    }
}&lt;/pre&gt;
  &lt;p&gt;Мы уже очень близко к финалу. Осталось лишь добавить геттер и сеттер для значения. С геттером всё просто, а вот внутри сеттера нужно будет уведомлять всех подписчиков о том, что состояние изменилось. Предлагаю вынести это в отдельный метод:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;class Observable {
    constructor(initialValue = null) {
        this._value = initialValue
        this.subscribersQueue = {}
    }

    subscribe(listener) {
        const listenerId = Math.random().toString(36).substr(2, 9)
        this.subscribersQueue[listenerId] = listener
        return () =&amp;gt; delete this.subscribersQueue[listenerId]
    }
    
    // Геттер: получаем значение
    get value() {
        return this._value
    }
    
    // Сеттер: обновляем значение и вызываем метод-уведомитель
    set value(newValue) {
        this._value = newValue
        this.notifySubscribers()
    }
    
    // Вспомогательный метод-уведомитель
    notifySubscribers() {
        for (const listenerId in this.subscribersQueue) {
            const listener = this.subscribersQueue[listenerId]
            listener(this._value)
        }
    }
}&lt;/pre&gt;
  &lt;p&gt;И это всё. В общем смысле, естественно. У нас есть класс, который хранит состояние, принимает всех желающих наблюдать за состоянием, уведомляет подписчиков об изменениях этого состояния. Пока что всё это очень абстрактно, и, возможно, у вас нет понимания, где это можно применить.&lt;/p&gt;
  &lt;p&gt;Вернёмся к первому примеру из этой публикации: &lt;code&gt;div&lt;/code&gt;-элемент с id &lt;code&gt;myelem&lt;/code&gt;:&lt;/p&gt;
  &lt;pre data-lang=&quot;html&quot;&gt;&amp;lt;div id=&amp;quot;myelem&amp;quot;&amp;gt;
    Text inside
&amp;lt;/div&amp;gt;&lt;/pre&gt;
  &lt;p&gt;Сейчас мы можем связать наблюдаемую переменную с контентом этого блока, и в автоматическом режиме менять свойство &lt;code&gt;innerHTML&lt;/code&gt;. Для этого напишем функцию:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;function observeHtml(id) {
  const elem = document.getElementById(id)
  if (!elem) return
  const value = elem.innerHTML
  const observable = new Observable(value)
  observable.subscribe((value) =&amp;gt; {
    elem.innerHTML = value
  })
  return observable
}&lt;/pre&gt;
  &lt;p&gt;Осталось только вызвать эту функцию с нужным ID:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;const myElemObservable = observeHtml(&amp;#x27;myelem&amp;#x27;)&lt;/pre&gt;
  &lt;p&gt;Теперь если мы напишем, скажем, &lt;code&gt;myElemObservable.value = &amp;#x27;New content&amp;#x27;&lt;/code&gt;, содержимое блока &lt;code&gt;div&lt;/code&gt; сразу изменится. А что будет, если мы пройдёмся по всем-всем-всем элементам в документе и создадим их “тень”, состоящую из Observable, сохраняя иерархию через JSON? Получится тот самый пресловутый Virtual Dom.&lt;/p&gt;
  &lt;p&gt;Понятно, что далеко на этом не уедешь. Эта реализация не учитывает тысячи факторов и кейсов реактивного фреймворка. Однако начало, вернее, основа всему – положена.&lt;/p&gt;
  &lt;h3&gt;Реактивность как помощник&lt;/h3&gt;
  &lt;p&gt;Когда я понял, как всё это дело работает, я перестал воспринимать реактивность как господина, под диктовку которого мне следует плясать. Сразу же она стала помощником, который справляется со многими задачами за меня.&lt;/p&gt;
  &lt;p&gt;Понятное дело, что во многих случаях такой “велосипед” не оправдан. Если в вашем любимом фреймворке реализована реактивность, не следует писать в нём же свою реализацию. Не потому, что получится хуже, нет. Просто потому, что в этом нет особого смысла, ведь всё уже сделали за вас.&lt;/p&gt;
  &lt;p&gt;Я лишь в своё время хотел разобраться в этом исключительно из-за интереса. Плюс, понимание таких штук открывает множество интересных возможностей, таких как создание собственных шин событий, более многогранная работа с асинхронностью и т.д.&lt;/p&gt;
  &lt;p&gt;На данный момент я использую это в своих проектах, к примеру, в роутере &lt;a href=&quot;https://github.com/lyohaplotinka/svelte-easyroute&quot; target=&quot;_blank&quot;&gt;Easyroute&lt;/a&gt;. Понимание принципов реактивности помогло решить много проблем в нём.&lt;/p&gt;
  &lt;p&gt;Как я и говорил, нет ничего сверхъестественного в реактивности. Все поначалу удивительные задачи решаются в несколько строк кода.&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:HSb77q4s3uB</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/HSb77q4s3uB?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Асинхронные плагины для Vue.js</title><published>2021-07-22T09:46:39.000Z</published><updated>2021-07-22T09:46:39.000Z</updated><category term="vue-js" label="Vue.js"></category><summary type="html">Представим ситуацию. Вы делаете большое приложение на Vue.js: тут вам и отдельная команда бэкендеров, и вдумчивая разработка, и вообще всё, что полагается.</summary><content type="html">
  &lt;p&gt;Представим ситуацию. Вы делаете большое приложение на Vue.js: тут вам и отдельная команда бэкендеров, и вдумчивая разработка, и вообще всё, что полагается.&lt;/p&gt;
  &lt;p&gt;У приложения есть API, он версионный и часто меняющийся. Бэкендеры решают использовать для автоматизации спеки и визуализации методов какую-нибудь утилиту, допустим – Swagger. На стороне фронта вы используете модуль Swagger’a, чтобы при помощи спецификации обращаться к методам не по URL, так как в процессе разработки он часто, но незначительно меняется, а по имени метода, которое назначено вашим бэкендовым коллегой.&lt;/p&gt;
  &lt;p&gt;Вы пишите простой сервис-обёртку над сваггером, который делает с данными всё, что вам нужно, и оформляете его как &lt;a href=&quot;https://ru.vuejs.org/v2/guide/plugins.html&quot; target=&quot;_blank&quot;&gt;Vue-плагин&lt;/a&gt;, добавляющий в прототип вашего экземпляра Vue свойство &lt;code&gt;$api&lt;/code&gt;, и теперь вы можете с лёгкой душой обращаться к методам апи из любого места в вашем приложении.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Круто, да?&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Однако, есть одна вещь, о которой мы забыли. Наш сервис-обёртка в самую первую очередь должен загрузить спеку, а загрузка файла - асинхронная операция.&lt;/p&gt;
  &lt;p&gt;Пока мы не загрузим спеку, мы не сможем обрати ться ни к одному методу, ведь мы делаем это не по URL, а по имени.&lt;/p&gt;
  &lt;h3&gt;Есть несколько решений:&lt;/h3&gt;
  &lt;ol&gt;
    &lt;li&gt;Мы кешируем спеку из предыдущего сеанса работы с приложением, в localStorage, посредством ServiceWorker или как-то ещё.&lt;br /&gt;НО: что если человек впервые попадает на наш сайт?&lt;/li&gt;
    &lt;li&gt;Мы храним в глобальном стейте приложения переменную-идентификатор, позволяющую нам узнать, загружена спека или нет, и в зависимости от этого разрешаем или запрещаем какие-либо операции.&lt;/li&gt;
    &lt;li&gt;Мы напишем асинхронный плагин&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;Разумеется, под разные ситуации нужны разные решения, и можно использовать любое из вышеперечисленных. В моём случае не имело смысла запускать приложение, если не удалось загрузить спеку: это означало глобальные проблемы на сервере и отсутствие каких-либо данных вообще.&lt;/p&gt;
  &lt;p&gt;Итак, перейдем же к реализации!&lt;/p&gt;
  &lt;h3&gt;Реализация&lt;/h3&gt;
  &lt;p&gt;Есть у JavaScript (как, впрочем, и у других языков) замечательная особенность: возвращать данные из функции. В частности – объекты. Кроме того, в JavaScript функция может быть асинхронной: этим мы и воспользуемся.&lt;/p&gt;
  &lt;p&gt;В данном примере я буду использовать синтаксис &lt;code&gt;async-await&lt;/code&gt;, но того же эффекта можно добиться и промисами.&lt;/p&gt;
  &lt;p&gt;Итак, представим, что у нас есть проект на Vue, в папке src нам нужны три файла:&lt;/p&gt;
  &lt;pre&gt;# ./src

main.js                # Главный файл приложения
AsyncService.js        # Файл с классом того самого плагина, 
                       # который выполняет асинхронное действие при инициализации
AsyncService.plugin.js # Файл, который будет импортироватсья в main.js&lt;/pre&gt;
  &lt;p&gt;В контексте нашей статьи содержимое файла &lt;code&gt;AsyncService.js&lt;/code&gt; значения не имеет. Просто представим, что при его инициализации нужно вызвать один из его асинхронны методов.&lt;/p&gt;
  &lt;p&gt;Давайте займемся написанием асинхронного плагина. Для этого откроем &lt;code&gt;AsyncService.plugin.js&lt;/code&gt; и напишем туда следующее:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// ./AsyncService.plugin.js
import AsyncService from &amp;#x27;AsyncService&amp;#x27;

const AsyncServicePlugin = {
    install: function (Vue) {
      Vue.prototype.$service = new AsyncService()
    }
}

export default AsyncServicePlugin&lt;/pre&gt;
  &lt;p&gt;Примерно так выглядит обычный плагин Vue в общем смысле: в прототип добавляется что-либо, что пригодится в будущем.&lt;/p&gt;
  &lt;p&gt;Теперь представим, что у нашего сервиса есть некий метод &lt;code&gt;asyncInit&lt;/code&gt;, который выполняет, как не трудно догадаться, асинхронную инициализацию. Если мы вызовем его в методе &lt;code&gt;install&lt;/code&gt;, то поток программы не будет дожидаться его выполнения, даже если мы объявим &lt;code&gt;install&lt;/code&gt; как асинхронный. Решение – обернуть все это в асинхронную функцию, и возвращать уже готовый экземпляр &lt;code&gt;AsyncService&lt;/code&gt;:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// ./AsyncService.plugin.js
import AsyncService from &amp;#x27;AsyncService&amp;#x27;

const initAsyncServicePlugin = async () =&amp;gt; {
    const asyncService = new AsyncService()
    await asyncService.asyncInit()
    return {
        install: function (Vue) {
          Vue.prototype.$service = asyncService
        }
    }
}

export default initAsyncServicePlugin&lt;/pre&gt;
  &lt;p&gt;Мы экспортировали уже не сам объект плагина, а функцию, которая его возвращает.&lt;/p&gt;
  &lt;p&gt;Как же теперь использовать его с &lt;code&gt;Vue.use&lt;/code&gt;? На помощь приходят так называемые &lt;a href=&quot;https://developer.mozilla.org/ru/docs/%D0%A1%D0%BB%D0%BE%D0%B2%D0%B0%D1%80%D1%8C/IIFE&quot; target=&quot;_blank&quot;&gt;IIFE&lt;/a&gt;, или немедленно вызываемые функциональные выражения. И да – они могут быть асинхронными!&lt;/p&gt;
  &lt;p&gt;Все, что нужно сделать – обернуть создание экземпляра Vue в такое выражение, предварительно добавив подключение нашего плагина:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// ./main.js
import AsyncServicePlugin from &amp;#x27;./AsyncService.plugin&amp;#x27;
...

(async () =&amp;gt; {
    Vue.use(await AsyncServicePlugin())
    new Vue({
     ...
    })
})()&lt;/pre&gt;
  &lt;p&gt;Такое выражение превращает поток нашего файла в асинхронный. В итоге до того, как плагин закончит все свои действия, экземпляр Vue не создастся и приложение не начнет работу.&lt;/p&gt;
  &lt;p&gt;Конечно, будет очень хорошо, если вы добавите какую-нибудь визуализацию, типа Load Spinner, который пользователь будет видеть в то время, пока выполняются все асинхронные операции.&lt;/p&gt;
  &lt;p&gt;Так решил эту проблему я, однако я буду рад узнать другие решения :)&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:T8h0wwcdZP0</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/T8h0wwcdZP0?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Vue CLI: меняем индекс-файл при сборке</title><published>2021-07-22T09:42:39.452Z</published><updated>2021-07-22T09:43:34.923Z</updated><category term="vue-js" label="Vue.js"></category><summary type="html">Те, кто уже работал с Vue.js при помощи Vue CLI, знают, что эта надстройка над вебпаком берёт очень многие вещи на себя, упрощая процесс сборки проекта и развития его экосистемы.</summary><content type="html">
  &lt;p&gt;Те, кто уже работал с Vue.js при помощи Vue CLI, знают, что эта надстройка над вебпаком берёт очень многие вещи на себя, упрощая процесс сборки проекта и развития его экосистемы.&lt;/p&gt;
  &lt;p&gt;Можно, к примеру, добавить плагин для PWA и без лишних телодвижений превратить своё приложение в PWA. Также Vue CLI контролирует сборку приложения, создавая файловую структуру и заполняя файл &lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
  &lt;h3&gt;Поворотный момент&lt;/h3&gt;
  &lt;p&gt;Буквально неделю назад передо мной встала задача в результате сборки проекта при помощи Vue CLI получать не html-файл, а php, в котором реализуется некая серверная логика.&lt;/p&gt;
  &lt;p&gt;Поверхностное гугление показало, что можно использовать webpack-php-loader для сборки проекта. Однако это не решало мою проблему: насколько я понял, этот лоадер запускает php на машине, на которой происходит сборка, рендерит его и на выходе всё равно выдаёт html. Мне это не подходило, так как в php я работал с юзерагентом, передаваемым конкретным пользователем.&lt;/p&gt;
  &lt;h3&gt;Решение - vue.config.js&lt;/h3&gt;
  &lt;p&gt;В итоге мне удалось найти инструкцию, как указать файл, который при сборке будет считаться за “шаблон”, а также как переименовать выходной файл после сборки проекта. Делается это в файле &lt;strong&gt;vue.config.js&lt;/strong&gt;.&lt;/p&gt;
  &lt;p&gt;Давайте определим себе задачу: &lt;em&gt;закрыть доступ пользователям Internet Explorer 9.&lt;/em&gt;&lt;/p&gt;
  &lt;p&gt;Вкратце, этот файл позволяет (очень сложным путём, по-моему мнению) переопределить некоторые настройки вебпака для сборки проекта. Именно здесь мы и будем работать.&lt;/p&gt;
  &lt;p&gt;Если у вас ещё нет этого файла, создайте его в корне вашего проекта (на одном уровне с package.json) и поместите туда экспортный объект в синтаксисе Node.js:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// ./vue.config.js
module.exports = {

}&lt;/pre&gt;
  &lt;p&gt;Далее, в папке src вашего проекта, создайте файл-шаблон, который будет использоваться при сборке. &lt;strong&gt;ВАЖНО:&lt;/strong&gt; не удаляйте оригинальный файл index.html из папки public! Он нужен нам, чтобы работать в development-режиме и сохранить всякие прелести вроде хот-релоад.&lt;/p&gt;
  &lt;p&gt;Допустим, он называется index_template.php Скопируйте в него содержимое файла ./public/index.html, добавив где нужно логику на php.&lt;/p&gt;
  &lt;pre data-lang=&quot;php&quot;&gt;// ./src/index_template.php
&amp;lt;?php
  $isIE9 = strpos($_SERVER[&amp;#x27;HTTP_USER_AGENT&amp;#x27;], &amp;#x27;MSIE 9.&amp;#x27;) !== false;

  if ($isIE9) {
    die(&amp;#x27;Браузер не поддердживается!&amp;#x27;);
  }
?&amp;gt;

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ru&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot;&amp;gt;
    &amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width,initial-scale=1.0&amp;quot;&amp;gt;
    &amp;lt;link rel=&amp;quot;icon&amp;quot; href=&amp;quot;&amp;lt;%= BASE_URL %&amp;gt;favicon.png&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;Site title&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;noscript&amp;gt;
      &amp;lt;strong&amp;gt;We&amp;#x27;re sorry but website doesn&amp;#x27;t work properly without JavaScript enabled. Please enable it to continue.&amp;lt;/strong&amp;gt;
    &amp;lt;/noscript&amp;gt;
    &amp;lt;div id=&amp;quot;app&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- built files will be auto injected --&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Здесь мы просто для примера определяем, точно ли IE9 перед нами, и если да, прекращаем выполнение скрипта.&lt;/p&gt;
  &lt;p&gt;Осталось прописать настройки в vue.config.js&lt;/p&gt;
  &lt;p&gt;Первым делом сообщим, что после наполнения файла содержимым сохранять его нужно не как index.html, а как index.php:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// ./vue.config.js
module.exports = {
    indexPath: &amp;#x27;index.php&amp;#x27;
}&lt;/pre&gt;
  &lt;p&gt;Эта настройка создаст в папке dist файл с нужным расширением.&lt;/p&gt;
  &lt;p&gt;Далее напишем настройку для изменения файла-шаблона. Делается это, на первый вгляд, весьма нетривиально, но сейчас я не хотел бы углубляться в устройство конфиг-файла Vue. Так что рассмотрим лишь те моменты, которые важны для темы публикации.&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;// ./vue.config.js
module.exports ={
    indexPath: &amp;#x27;index.php&amp;#x27;,
    // Добавляем это:
        chainWebpack: config =&amp;gt; {
        if (process.env.NODE_ENV === &amp;#x27;production&amp;#x27;) {
          config
            .plugin(&amp;#x27;html&amp;#x27;)
            .tap(args =&amp;gt; {
              args[0].template = &amp;#x27;./src/index_template.php&amp;#x27;
              args[0].minify.removeAttributeQuotes = false
              return args
            })
        }
    }
}&lt;/pre&gt;
  &lt;p&gt;Здесь мы обращаемся к html-загрузчику вебпака, и сообщаем, что при сборке в production шаблон теперь находится по адресу &lt;strong&gt;./src/index_template.php&lt;/strong&gt;. Также мы говорим, что при минификации кода не нужно удалять кавычки атрибутов, так как иногда это может нарушить работу php-скрипта.&lt;/p&gt;
  &lt;p&gt;На этом всё! Попробуйте запустить &lt;code&gt;npm run build&lt;/code&gt; и вы увидите в папке dist файл index.php, полностью готовый к работе на продакшн-сервере!&lt;/p&gt;
  &lt;p&gt;Таким образом вы можете перенаправить вывод сборки в любой другой файл: TWIG, EJS и так далее.&lt;/p&gt;
  &lt;p&gt;Если у вас есть другое решение этого вопроса - я буду только рад с ним ознакомиться.&lt;/p&gt;

</content></entry><entry><id>lyohaplotinka:vue-workflow</id><link rel="alternate" type="text/html" href="https://teletype.in/@lyohaplotinka/vue-workflow?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=lyohaplotinka"></link><title>Организация работы с Vue: к чему я пришёл</title><published>2021-07-22T09:37:40.770Z</published><updated>2021-07-22T09:39:21.026Z</updated><category term="vue-js" label="Vue.js"></category><summary type="html">Моя первая публикация в блоге…</summary><content type="html">
  &lt;p&gt;Моя первая публикация в блоге…&lt;/p&gt;
  &lt;p&gt;…и сразу же про Vue.js. На самом деле, меня довольно многое связывает с этим фреймворком. Моё первое SPA-приложение было написано на jQuery: использовало AJAX для загрузки уже отрендеренного HTML и замены его в определённом блоке, который сейчас бы я назвал router-outlet.&lt;/p&gt;
  &lt;p&gt;Vue.js стал первым фреймворком в моей жизни и сразу показал мне, что SPA - это не больно. Круто было осознавать, что больше не нужно изобретать кучу велосипедов, чтобы “сайт менял страницы не перезагружаясь”.&lt;/p&gt;
  &lt;p&gt;В течение двух лет я пишу на Vue, и выработал несколько практик, которые с каждым днём делают мою работу легче. Прежде всего это касается организации работы с данными.&lt;br /&gt;Я жалею только об одном: что не додумался об этом раньше.&lt;/p&gt;
  &lt;p&gt;&lt;em&gt;Небольшой дисклеймер:&lt;/em&gt; я не претендую на первенство. Скорее всего, так до меня уже кто-то делал. Я всего лишь делюсь своими соображениями.&lt;/p&gt;
  &lt;h3&gt;Что предполагает Vue&lt;/h3&gt;
  &lt;p&gt;Основной способ разработки на Vue.js - использование однофайловых компонентов. На первый взгляд это безумно удобно: все составляющие вашего приложения под рукой, в пределах одного файла, главное лишь правильно “разделить” весь интерфейс на логические блоки.&lt;/p&gt;
  &lt;p&gt;И роутер работает с компонентами, и основной блок приложения - компонент. Это очень удобно, когда приложение небольшого размера и логики в нём не очень много. Но стоит подумать о чём-то более сложном, как сразу же возникают проблемы.&lt;/p&gt;
  &lt;p&gt;Если проблему с хранением данных решает &lt;a href=&quot;https://vuex.vuejs.org/ru/guide/&quot; target=&quot;_blank&quot;&gt;Vuex&lt;/a&gt;, то от избытка кода никуда не уйти. И хотя Webpack успешно решает вынос javascript-части приложения в отдельный файл через script src, размер этого файла легко может выйти из-под контроля.&lt;/p&gt;
  &lt;p&gt;Можно перенести часть логики во Vuex, но это также может оказаться не самой лучшей идеей. Во-первых потому, что рано или поздно хранилище разрастётся (даже если выносить всё в отдельные модули), а во-вторых потому, что (и это моё субъективное мнение) работа с Vuex довольна громоздкая в плане написания кода (this.$store.commit( ‘NameOfMutation’, payload )).&lt;/p&gt;
  &lt;p&gt;В итоге, спустя два года активного написания приложений на Vue, я пришёл к простой истине: &lt;strong&gt;разделение - это хорошо.&lt;/strong&gt;&lt;/p&gt;
  &lt;h3&gt;Разделение - это хорошо&lt;/h3&gt;
  &lt;p&gt;Мой подход заключается в простой вещи: мы должны поделить приложение на “зоны ответственности”:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Шаблон. Файл с расширением .vue, который нужен &lt;em&gt;исключительно&lt;/em&gt; для отрисовки данных. В нём не должно происходить ничего, кроме того, что нужно для отрисовки данных. Исключение составляют методы и computed-свойства, отвечающие за финальную подготовку данных к показу в шаблоне.&lt;/li&gt;
    &lt;li&gt;Стили. Если компонент или страница имеют большое количество стилей, то их лучше вынести в отдельный css-файл (или файл других стилевых движков). Для себя я определил порог в 100 строк. Если стилей больше, они переезжают в свой файл.&lt;/li&gt;
    &lt;li&gt;Логика. Я предпочитаю выносить всю работу с логикой компонента в отдельный сервис. Благо, новые стандарты javascript уже имеют синтаксический сахар для классов, поэтому я использую именно их. Если ваш проект пишется на typescript, то использовать сервисы-классы становится ещё на порядок приятнее.&lt;/li&gt;
    &lt;li&gt;Данные. Здесь нам приходит на помощь Vuex и его именованные пространства имён. Важно, что в vuex-хранилище я не провожу никакие операции с данными, несмотря на наличие actions. Сторы я использую только для хранения, а все операции производятся в сервисе.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3&gt;На практике&lt;/h3&gt;
  &lt;p&gt;Давайте представим, что мы решили написать компонент поиска фильмов по названию, с автокомплитом и обращением к серверу. Мы начинаем вводить название фильма, часть введённой строки отправляется на сервер, который ищет все подходящие варианты и отправляет их в ответе. Далее компонент показывает поле со списком фильмов, удовлетворивших условию поиска.&lt;/p&gt;
  &lt;p&gt;Весь дальнейший код – не пример готового приложения. Моя цель просто показать, как я организую работу с файлами и разделением кода.&lt;/p&gt;
  &lt;p&gt;Итак, представим, что мы находимся в папке src приложения. В ней я создаю директорию components/FilmSearcher. Создадим в ней следующие файлы:&lt;/p&gt;
  &lt;pre&gt;# ./src/components/FilmSearcher/

./FilmSearcher.vue
./FilmSearcher.service.js
./FilmSearcher.store.js
./FilmSearcher.style.scss&lt;/pre&gt;
  &lt;p&gt;Думаю, не трудно догадаться, за что отвечает каждый из этих файлов.&lt;/p&gt;
  &lt;p&gt;Файл шаблона будет выглядеть примерно так:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;film-searcher-component&amp;quot;&amp;gt;
    &amp;lt;input 
      class=&amp;quot;film-searcher-input&amp;quot; 
      type=&amp;quot;text&amp;quot; 
      @input=&amp;quot;searchFilms&amp;quot; 
      v-model=&amp;quot;filmSearcherText&amp;quot;
    &amp;gt;
    &amp;lt;div class=&amp;quot;film-searcher-autocomplete&amp;quot; v-if=&amp;quot;filmVariants.length&amp;quot;&amp;gt;
      &amp;lt;template v-for=&amp;quot;(variant, idx) in filmVariants&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;film-searcher-variant&amp;quot; :key=&amp;quot;idx&amp;quot; v-html=&amp;quot;variant.title&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;/template&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import FilmSearcherService from &amp;#x27;./FilmSearcher.service&amp;#x27;;

export default {
  name: &amp;#x27;FilmSearcher&amp;#x27;,
  data() {
    return {
      service: null,
      filmSearcherText: &amp;#x27;&amp;#x27;
    } 
  },
  computed: {
    filmVariants () {
      return this.service.filmVariants;
    }
  },
  methods: {
    async searchFilms () {
      await this.service.search(this.filmSearcherText);
    }
  },
  created () {
    this.service = new FilmSearcherService({
      store: this.$store
    });
  },
  beforeDestroy () {
    // Удаляем хранилище компонента
    this.service.unregisterStorage()
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot;&amp;gt;
@import &amp;quot;./FilmSearcher.style&amp;quot;;
&amp;lt;/style&amp;gt;&lt;/pre&gt;
  &lt;p&gt;В хуке created важно инициализировать наш сервис. Ещё важнее передать в него ссылку на глобальное хранилище приложения.&lt;/p&gt;
  &lt;p&gt;Пример файла-сервиса:&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;import http from &amp;#x27;http&amp;#x27;;
import FilmSearcherModule from &amp;#x27;./FilmSearcher.store&amp;#x27; // Импортируем стор

export default class FilmSearcherService {
    constructor(payload) {
        this.payload = payload;
        this.registerStorage(); // Вызов создания хранилища
    }

    // Динамически создаём хранилище для нашего компонента
    registerStorage () {
        this.payload.store.registerModule(&amp;#x27;filmSearcher&amp;#x27;, FilmSearcherModule);
    }   
    
    // Удаляем хранилище компонента
    unregisterStorage () {
        this.payload.store.unregisterModule(&amp;#x27;filmSearcher&amp;#x27;);
    }   
    
    async search (filmTitle) {
        const foundVariants = await this.http.get(&amp;#x27;https://server.com&amp;#x27;, {title: filmTitle});
        const result = // ...здесь обрабатываем результаты, если нужно
        // Записываем обработанные данные в хранилище
        this.payload.store.commit(&amp;#x27;filmSearcher/setFilms&amp;#x27;, result);
    }

    get filmVariants () {
        return this.payload.store.getters[&amp;#x27;filmSearcher/films&amp;#x27;];
    }

}&lt;/pre&gt;
  &lt;p&gt;Мы видим, что в сервисе происходят все операции, которые нужны для получения данных. Главный метод сервиса – search – возвращает результат, который передаётся в компонент. Задача сервиса – максимально подготовить данные к как можно более быстрой отрисовке.&lt;/p&gt;
  &lt;p&gt;Также здесь мы используем возможность динамической регистрации модулей Vuex. Вы можете использовать и глобальный модуль, если это нужно.&lt;/p&gt;
  &lt;p&gt;Вот так выглядит vuex-модуль компонента&lt;/p&gt;
  &lt;pre data-lang=&quot;javascript&quot;&gt;export default FilmSearcherModule {
    namespaced: true,
    state: {
        _films: []
    },
    mutations: {
        setFilms(state, payload) {
            state._films = payload;
        } 
    },
    getters: {
        films: (state) =&amp;gt; state.films;
    }
}&lt;/pre&gt;
  &lt;p&gt;Здесь наглядно видно, что vuex-модуль &lt;em&gt;не используется&lt;/em&gt; для обработки данных. Только хранение, запись и чтение. Всё.&lt;/p&gt;
  &lt;p&gt;Что касается файла со стилями, я не думаю, что есть смысл приводить его содержимое. Стилизация компонента в данной статье особой роли не играет, да и делается там всё как обычно.&lt;/p&gt;
  &lt;h3&gt;Проговорим вслух&lt;/h3&gt;
  &lt;p&gt;Чтобы закрепить вышесказанное, давайте пропишем путь нашего компонента внутри приложения.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;При создании компонента вызывается конструктор его сервиса. В нём же мы автоматически инициализируем хранилище компонента.&lt;/li&gt;
    &lt;li&gt;Вся работа с данными в компоненте никогда не происходит напрямую. Компонент отдаёт приказ сервису, который уже залезает в хранилище, обрабатывает всё должным образом и отдаёт своему “начальнику”.&lt;/li&gt;
    &lt;li&gt;Сервис не только забирает данные из хранилища, но и добавляет их туда, обращаясь к мутациям.&lt;/li&gt;
    &lt;li&gt;Все операции с данными внутри хранилища происходят &lt;em&gt;исключительно&lt;/em&gt; посредством мутаций и геттеров.&lt;/li&gt;
    &lt;li&gt;При запуске ивента &lt;strong&gt;beforeDestroy&lt;/strong&gt; внутри компонента хранилище компонента автоматически удаляется.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3&gt;А не лишнее?&lt;/h3&gt;
  &lt;p&gt;Кому-то такой подход может показаться чрезмерным усложнением, и такое мнение, безусловно, имеет право на существование. Однако, мой опыт vue-разработки показал, что очень часто приложение дорабатывается, переделывается и дополняется, и такое разделение очень хорошо помогает управлять имеющимся функционалом.&lt;/p&gt;
  &lt;p&gt;Поначалу применение такого подхода немного увеличивает время, потраченное на начальном этапе разработки, но это время с лихвой компенсируется впоследствии, когда вы возвращаетесь с новыми задачами в старый компонент. Все преимущества разделения становятся прозрачнее при разработке больших проектов (особенно если в разработке используется TypeScript с class-based синтаксисом).&lt;/p&gt;
  &lt;h3&gt;Итого&lt;/h3&gt;
  &lt;p&gt;Во всех проектах, которыми я сейчас занимаюсь на работе и в качестве хобби, я использую именно такую разбивку. И, пока такой подход не покажет свою несостоятельность, я вряд ли вернусь к однофайловым компонентам.&lt;/p&gt;
  &lt;p&gt;Интересно мнение сообщества на этот счёт! &lt;/p&gt;

</content></entry></feed>