Хабрахабр

Легенда о Фреймворке Всевластия

В последнее время набирает популярность тренд «исчезающих фреймворков», локомотивом которого, без сомнения, можно считать SvelteJS — buildtime-фреймворк и компилятор в ванильный javascript.

Почему это не «yet another javascript framework»? Несмотря на то, что концептуально Svelte весьма прост, а в использовании еще проще, многие разработчики задаются вопросом, в чем же killer-фича данного фреймворка, да и подхода в целом?

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

Давайте разберемся, но сначала я расскажу вам одну легенду…

Легенда о Фреймворке Всевластия

Одни фреймворки были созданы ребятами из Google и Facebook, другие — крутыми чуваками, но все под под пристальным «вниманием» Рича Харриса.

Ещё три фреймворка (react, vue, angular) были для эльфов. Девять фреймворков были созданы для людей, семь, по всей видимости, для гномов.

После создания фреймворков и их имплементации в тысячи проектов, Рич Харрис самолично и тайно создал один фреймворк…

One framework to rule them all,
One framework to find them,
One framework to bring them all
And together bind them.
— The Lord of the Frameworks

Проблема

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

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

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

Что-то не срослось и не закрутилось. Но реальные проблемы начинаются, если где-нибудь в середине проекта, вы понимаете, что сделали не совсем верный выбор. А самое главное теперь ваш проект на 100% завязан на этом фреймворке и вы не можете просто взять и переписать его на чем-то другом. Фреймворк потребовал немного больше времени на освоение, немного большей команды, оказался немного менее быстрым, немного не подошел вашим целям или стилю разработки и т.д.

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

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

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

Ушлые дизайнеры уже рисуют новые кнопки и селекторы и строчат тысячи страниц гайдлайнов для нового единого UI-kit'а ваших компонентов. А тут еще ваше замечательное руководство, решило составить конкуренцию Google Material Design и отправить вас в крестовый поход на разношерстные интерфейсы ваших проектов дабы привести их к общему знаменателю. Ура товарищи!

Осталось только придумать как бы так натянуть все эти новые компоненты на все те проекты, которые вы уже успели понаписать на всех возможных фреймворках. Не жизнь, а сказка, правда? Это и правильно, потому что то унылое говно, на котором вы писали последние 2-3 года, уже морально устарело, а вот React будет вечен. Если времени и денег реально много и есть эстетическое желание, а главное вера, в то что «все надо унифицировать», то можно посадить пару тройку десятков команд переписать все это снова, например на React. Ну-ну)

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

Если у вас проект написан на Angular (~500Kb), а UI-kit вы решили писать на React (~98Kb), то тащить в каждый проект на одном фреймворке, другой фреймворк, да еще с кучей зависимостей и сам UI-kit, прям скажем не выглядит оптимальным.

Решение

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

Прекрасный пример такого фреймворка — SvelteJS, про который уже написано не мало статьей на Хабре.

Возможно он нам надоел, и мы хотим от него избавиться, но переписать все разом непозволительная роскошь. Итак, представим ситуацию, что у нас есть приложение на React. Ну или мы решили делать единую библиотеку компонентов, и поэтому теперь все компоненты будем писать на Svelte и использовать во всех проектах. А может быть, некоторые части приложения требуют улучшения или рефакторинга. Да конечно нет, ни у кого нет такой фантазии. Представили? Давайте лучше разберем на реальном примере.

Disclaimer

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

Для примера, возьму компонент осуществляющий поиск по пользователям GitHub, который я писал для предыдущей статьи «Как сделать поиск пользователей по GitHub без React + RxJS 6 + Recompose». Итак, задача внедрить в React приложение уже готовый Svelte-компонент, без изменения самого компонента и без накручивания дополнительного рантайма в приложение.

Код этого компонента можно посмотреть в REPL, а код примера из данной статьи в репозиторий.

Create React App

Для начала создадим новый React проект, воспользовавшись де-факто стандартной тулзой — create-react-app:

npx create-react-app my-app
cd my-app
npm start

Ок, если зайти на 3000-й порт, вроде бы работает.

Настраиваем Svelte

Если вы ничего не знаете о Svelte, то скажу так, в контексте задачи Svelte — это всего лишь еще один шаг вашего сборщика (webpack/rollup/gupl/grunt/etc), который позволит вам писать компоненты в формате SFC и компилировать их в vanilla javascript.

Однако, так как CRA использует webpack, то мы настроим Svelte через него. В сообществе Svelte больше предпочитают Rollup, что не мудрено, так как у них один автор — Рич Харрис. Делается это с помощью встроенной команды: Для этого, сначала надо вынести конфиги webpack из react-scripts в проект, чтобы мы могли менять их.

npm run eject

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

Теперь, когда конфиги webpack в корне проекта, можно ставить Svelte:

npm i --save-dev svelte svelte-loader

Обратите внимание на флажок --save-dev, помните да, что рантайма-то нету.))))

Последний штрих, нужно подключить соответствующий лоадер в конфиги:

},

Однако, чтобы избежать вероятных коллизий, в некоторых случаях, лучше использовать кастомный формат файла .svelte. Вообще, в сообществе Svelte принято писать файлы компонентов с расширением .html, потому что компонент Svelte — это валидный HTML файл.

Так мы и сделали, теперь все файлы .svelte подключаемые в проект будут перехватываться этим лоадером и компилироваться Svelte.

Пишем компонент Svelte

Сперва лучше настроить редактор кода, например, чтобы он применял подсветку html-синтаксиса к файлам с соответствующим расширением. Примерно так это делается в VS Code:

"files.associations": { "*.svelte": "html" }

Теперь создадим папочку ./src/svelte_components/ и там папку самого компонента. После просто переносим все файлы из REPL примера в эту папку, попутно давая им новое расширение .svelte, а файл App.html назовем Widget.svelte.

В итоге должно получиться, что-то вроде этого:

Там же создаем файл index.js, в котором у нас будет располагаться код интеграции Svelte и React.

Интегрируем

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

Документация React даже содержит раздел посвященный этому: Integrating with Other Libraries . Серьезно, теперь мы можем использовать компоненты Svelte в нашем React приложении как совершенно обычные JS конструкторы, а значит код интеграции со Svelte ничем не будет отличаться от интеграции с любой другой standalone либой.

Код интеграции может выглядеть например так:

import React, { PureComponent } from 'react'; import Widget from './Widget.svelte'; export default class extends PureComponent { componentDidMount() { const { username } = this.props; this.widget = new Widget({ target: this.el, data: { username } }); } componentWillUnmount() { this.widget.destroy(); } render() { return ( <div ref={el => this.el = el}></div> ); }
}

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

То есть, если вышестоящий компонент прокинул другие пропсы в компонент-обертку, то они должны примениться к Svelte компоненту. Единственное, что мы пока не сделали — не синхронизировали значения стейта компонентов. И наоборот, если данные были изменены внутри Svelte компонента, они должны быть прокинуты обратно.

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

componentDidMount() { ... this.widget.on('state', ({ current: { username }, changed }) => { if (changed.username) { this.props.onChange({ username }); } }); } componentWillReceiveProps({ username }) { this.widget.set({ username }); }

Здесь мы использовали встроенное событие state, которое срабатывает каждый раз, когда стейт Svelte компонента меняется. В коллбек передается объект содержащий текущий стейт компонента (current), предыдущий стейт (previous) и список измененных свойcтв (changed). Соответственно, мы просто проверяем был ли изменен username и вызываем коллбек onChange, если это так.

В хуке componentWillReceiveProps мы устанавливаем новое значение username с помощью встроенного метода set().

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

Используем

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

import React, { Component } from 'react';
import './App.css'; import GithubWidget from './svelte_components/GithubWidget'; class App extends Component { constructor() { super(); this.state = { username: '' }; } handleChange = (state) => { this.setState({ ...state }); } render() { return ( <div className="App"> <header className="App-header"> <h1>Github Widget for: {this.state.username}</h1> <GithubWidget onChange={this.handleChange} username={this.state.username} /> </header> </div> ); }
} export default App;

Короче используем как обычный React компонент. И в результате получаем:

Уже не плохо, правда?) Обратите внимание, значение username, которое мы вводим в текстовое поле виджета сразу же пробрасывается наверх в React приложение.

Доработаем

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

Для простоты примера, я просто скопипастил шаблон и стили из User.svelte и адаптировал под данные репозитория. Во-первых, нужно создать новый компонент Repo.svelte, который как раз и будет отрисовывать карточку репозитория. Однако, теоретически это отдельный компонент.

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

То есть, если нужно поискать пользователя, вводим его username, а если репозиторий, тогда вводим username пользователя, далее "/" и имя репозитория. Мы будем использовать одно поле для ввода и определять тип данных по наличию "/" в значении.

Для начала подключим новый компонент и метод API, который обернем в debounce: На первый взгляд выглядит довольно заморочено, но на Svelte решение займет буквально 5-6 строк кода.

...
import Repo from './Repo.svelte';
...
import { getUserCard, getRepoCard } from './api.js';
...
const getRepo = debounce(getRepoCard, 1000);

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

computed: { ... repo: ({ username }) => username.includes('/'), ...
}

Теперь добавим переключалку запросов к API:

computed: { ... card: ({ username, repo }) => username && (repo ? getRepo : getUser)(username), ...
}

И напоследок, переключалка компонентов карточки в зависимости от типа:

computed: { ... Card: ({ repo }) => repo ? Repo : User, ...
}

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

<svelte:component this={Card} {...card} />

Работает. Обратили внимание? Мы уже пишем на Svelte внутри React приложения! )))

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

В зависимости от этого свойства, будет отображаться или не отображаться поле для ввода. Введем новое свойство search, значение по-умолчания которого будет false. По-умолчанию, соответственно, поля не будет.

{#if search}
<input bind:value=username placeholder="username or username/repo">
{/if}
...
<script> export default { ... data() { return { username: '', search: false }; }, ... };
</script>

Теперь в App.js создадим поле для ввода на стороне React приложения и напишем соответствующую обработку события ввода:

... handleUsername = (e) => { this.setState({ username: e.target.value }); } ... <h1>Github Widget for: {this.state.username}</h1> <input value={this.state.username} onChange={this.handleUsername} className="Username" placeholder="username or username/repo" />

А еще копипастим в папку с виджетом вот такой вот svg спиннер на Svelte:

<svg height={size} width={size} style="animation-duration:{speed}ms;" class="svelte-spinner" viewbox="0 0 32 32"
> <circle role="presentation" cx="16" cy="16" r={radius} stroke={color} fill="none" stroke-width={thickness} stroke-dasharray="{dash},100" stroke-linecap="round" />
</svg> <script> export default { data() { return { size: 25, speed: 750, color: 'rgba(0,0,0,0.4)', thickness: 2, gap: 40, radius: 10 }; }, computed: { dash: ({radius, gap}) => 2 * Math.PI * radius * (100 - gap) / 100 } };
</script> <style> .svelte-spinner { transition-property: transform; animation-name: svelte-spinner_infinite-spin; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes svelte-spinner_infinite-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
</style>

И применим его в виджете, чтобы было совсем красиво:

...
{#await card} <Spinner size="50" speed="750" color="#38b0ee" thickness="2" gap="40" />
{:then card}
...

По-моему, получилось очень даже не плохо:

Верхняя шапка с черным фоном и полем для ввода — это React приложение, белый блок снизу — Svelte виджет. Вот такие пироги. )))

→ Репозиторий

Выводы

Svelte — отличный инструмент для разработки современных веб-приложений, основанных на компонентном подходе. Кроме того, на нем можно быстро и удобно писать переиспользуемые standalone UI компоненты и виджеты, которые могут быть использованы в любых веб-приложениях, даже совместно с другими фреймворками. Еще он прекрасно подходит для микрофронтендов.

Svelte прекрасно подойдет вам если:

  1. Вы хотите начать новый проект и не знаете какой фреймворк выбрать для этого.
  2. У вас есть проект, он работает и его лучше не трогать. Новые компоненты и модули, вы можете писать на Svelte и бесшовно интегрировать в существующий код.
  3. У вас уже есть проект, но он устарел частично или полностью и/или требует серьезного рефакторинга, вплоть до полного переписывания. Вы можете начать переписывать его по частям. При этом вам не нужно придумывать сложные конфигурации. Вы просто берете какой-то компонент, переписываете его на Svelte и оборачиваете новый компонент посредством старого. При этом остальные части приложения даже не догадываются об изменениях.
  4. У вас несколько проектов на разных кодовых базах и при этом, вам бы хотелось иметь единый UI-kit и использовать его в любом из этих проектов. Пишите UI-kit на Svelte и используйте его где угодно. Это приятно.

Хотите узнать больше интересных кейсов? Присоединяйтесь к нашему Telegram-каналу!

Продолжая пример: UPDATE: спасибо justboris за правильный вопрос.

import React, { PureComponent } from 'react'; import Widget from './Widget.svelte'; export default class extends PureComponent { componentDidMount() { ... this.widget = new Widget({ target: this.el, data: { username }, slots: { default: this.slot } }); ... } ... render() { return ( <div ref={el => this.el = el}> <div ref={el => this.slot = el}> {this.props.children} </div> </div> ); }
}

<GithubWidget onChange={this.handleChange} username={this.state.username}> <p>Hello world</p>
</GithubWidget>

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть