Вместо предисловия
Данный пост является ответом на вчерашнюю статью «Боль и слёзы в Svelte 3» и появился как следствие сильно «располневшего» комментария к оригинальной статье, который я решил оформить в виде поста. Ниже я буду использовать слово автор для отсылки к автору оригинальной статьи и позволю себе сделать некоторые уточнения по всем пунктам. Поехали!
Кто такой Svelte?
Когда я увидел заголовок оригинальной статьи, сперва очень обрадовался. Вот сейчас, думаю, прочитаю какую-то глубокую и конструктивную критику. А главное интересные кейсы с подводными камнями не от «диванных экспертов», а от ребят «которые смогли». После прочтения, энтузиазма поубавилось, но все равно большое спасибо vds64_max за статью.
Несмотря на то, что не все получилось, мне кажется нужно больше статей описывающих реальные проблемы и их решения в Svelte. Хотя бы чтобы он не отставал от более именитых товарищей, таких как React или Vue. Ведь, в конечном итоге, у читателей может возникнуть ощущение, что Svelte слишком идеален, а это безусловно не так.
Tutorial
Так и не понял, что автор имел ввиду в этой части. Tutorial работал и работает прекрасно. Предполагаю, что автор лишь поверхностно ознакомился с ним из-за дедлайнов своего проекта и не смог понять принцип работы. Уже бывали случаи, когда люди не сразу улавливали смысл пошаговости туториала, когда чтобы пример заработал нужно сделать какое-то действие. Попробуйте сами!
UI Kit и Стили
Поиски UI Kit для Svelte были отдельной болью для всех нас. Хотелось воскликнуть: «Хоть Material, Bootstrap… хоть что нибудь...».
Во-первых, далеко не во всех проектах в принципе применимы UI киты. Во-вторых, далеко не все UI киты для других фреймворков работают хорошо.
Основные проблемы начинаются, когда проект имеет сильно замороченный и кастомный дийзан, а киты обычно все же имеют лимитированные средства для кастомизации. Если же речь об админке, согласен, иметь UI кит это полезно, но далеко не всех пишут админки, да и профит от Svelte для backoffice будет не так заметен.
По ссылке можете ознакомиться и с другими UI китами для Svelte.
Из-за того, что Svelte работает с DOM «по другому» в MaterialUI начали вылазить всякие гадости, связанные с тем как отображаются UI компоненты которые добавляются через js в dom. Например простой спиннер отображается через раз:
Так и не понял, что означает начало предложения и как именно «по-другому» работает Svelte с DOM, но в целом весь тезис звучит, по крайней мере, не серьезно. Если вы попробуете интегрировать стороннюю либу работающую с DOM в любой из фреймворков (React/Vue/Angular/Ember), управляющих DOM, у вас будут всплывать точно те же вопросы. Сразу возникает ощущение, что автор никогда не делал этого.
Более того, в Svelte есть прекрасный механизм называемый actions, с помощью которого интеграция с любой сторонней DOM либой сводится к написанию небольшой функции. Похоже автор не дочитал доку до этого момента. Что ж, бывает.
Для примера, имплементация MDCSlider за 2 минуты: REPL
Скажите, пожалуйста, куда уж проще?
Стилизация
Со стилями все предельно ясно, мы запихиваем все стили в как в Vue. Вы пишите стиль и все в порядке, и потом пишите компонент UI (так как у Вас нет UIKit) который должен принимать параметры props, например width и height, и логично делаете это так:
…
А… нет, в стиле у Вас не получится вставлять переменные.
Насколько мне известно, во Vue стейт также не может использоваться внутри стилей компонента, а в React вообще нет работы со стилями из коробки.
Хотя, я могу понять желание использовать динамику в стилях, но есть ряд причин почему так делать не надо:
- Как только кусок стейта компонента появляется в стилях эти стили надо дублировать для КАЖДОГО инстанса компонента. Сразу представляем список из 1000 айтемов со сложным дизайном. Оно нам надо?
- Использование динамики в css вызывает много лишних перерисовок и крайне не производительно.
Альтернативные способы использовать динамические стили в Svelte:
- Смена классов с помощью директивы class:
- Использование инлайн стилей с помощью атрибута style
- Использование css custom properties (variables)
- Использование css-in-js решения, таких как Emotion (статья на эту тему)
И раз уж был упомянут Vue, давайте сравним использование динамических инлайн стилей:
<button v-on:click="fontSize++">Increase font size</button>
<button v-on:click="fontSize--">Decrease font size</button>
<p v-bind:style="{ fontSize: fontSize + 'px' }">Font size is: {{ fontSize }}</p>
<button on:click={e => fontSize++}>Increase font size</button>
<button on:click={e => fontSize--}>Decrease font size</button>
<p style="font-size: {fontSize + 'px'}">Font size is: {fontSize}</p>
В целом, особых отличий я не вижу, но автор почему-то считает что в Vue все с этих хорошо, а в Svelte нет. Странно это.
Кстати, с помощью реактивных деклараций, можно довольно удобно собирать все динамические стили в одном месте:
<div {style}>....</div>
<script>
let fontSize = 10;
let color = 'red';
let width = 50;
$: style = `
font-size: ${fontSize}px;
color: ${color};
width: ${width}px;
`;
</script>
Обычно этого хватает для 99% случаев, а остальное решается с помощью тем и/или css custom properties.
Routing и ротуеры
Вообще, Svelte — это UI фреймворк и он агностичен к роутеру. Более того, внешний апи Svelte позволяет легко интегрировать любой независимый роутера буквально за пару минут и строк кода. На примере page.js:
import page from 'page';
import App from './App.svelte';
const app = new App({
target: document.body
});
page('/posts', ctx => {
// можно даже сразу с dynamic import & code-splitting
app.$set({ ctx, page: import('./Posts.svelte') });
});
page('/', ctx => {
app.$set({ ctx, page: import('./Home.svelte') });
});
page.start();
export default app;
<nav>
<a href="/">Home</a>
<a href="/posts">Posts</a>
</nav>
<main>
{#await page}
<p>Loading...</p>
{:then comp}
<svelte:component this={comp.default || comp} {...ctx} />
{/await}
</main>
<script>
export let page = null, ctx = null;
</script>
Разве это сложно? Автор столкнулся лишь с проблемами конкретной реализации, конкретного роутера. Который, кроме того, мягко говоря не самый лучший из того что имеется для Svelte, так как копирует React Router со всеми его проблемами. Можете ознакомиться с множеством других роутеров по ссылке.
Ошибки
В отличии от React который просто не даст Вам создать bundle и будет сильно «кричать» об ошибке, Svelte отлично соберёт всё с ошибкой. При этом если Вы забыли что-то npm install'нуть или export'ануть, все отлично соберется и просто покажет белый экран (Кстати говоря так было в первых версиях React).
React вам ничего не собирает. Собирает сборщик. В этом случае, вангую, что автор работал с Rollup и скорее всего делал это первый раз. Мне кажется имеет смысл либо изучать инструмент, который собираешься использовать, либо не использовать его. От себя добавлю, что того о чем пишет автор не наблюдается в Svelte. По крайней мере, какие-то проблемы возникают не чаще чем в других фреймворках. Скорее всего, для других фреймворков автор уже все настроил и отработал, а тут просто поленился это сделать.
Есть предположение, что автор имел ввиду также работу с пропсами, которая в Svelte ведется через export'ы из компонента, но даже тут всегда будет выведено предупреждение, если попытаться прокинуть пропс не определенный компонентом:
Svelte просто необходим naming convention
C этой проблемой Вы будете сталкиваться довольно часто — с отсутствием конвенции по наименованию.
Очень понравилась эта фраза, потому что по-сути ее можно использовать для любого проекта/библиотеки/языка программирования. Лукавство здесь заключается в том, что точно также вы будете сталкиваться с этим везде. Например, в том же React:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.dialog = null;
this.loading = false;
this.pins = [];
this.stitch = null;
}
render() {
return <dialog ref={el=> this.dialog = el} class="mdl-dialog» />;
}
}
В общем и целом все тоже самое и нужно будет как-то «понимать» что есть рефка на элемент, а что есть объект для работы с бекендом или флажок загрузки. Казалось бы, если у вас команда, которая собралась не вчера, все эти вопросы давно должны быть решены.
В целом конечно, если у вас в проекте и команде нет naming convention тут вам Svelte ничем не поможет. Некоторые вещи которые использую в своих проектах:
<!-- шаблон сверху, хотя в доках советуют внизу -->
<Modal {open}> <!-- использовать шорткаты как можно чаще -->
<!-- использовать модификаторы, чтобы отвязать функции от ивентов, там где это имеет смысл -->
<form on:submit|preventDefault={save}>
<!-- сначала директивы, потому атрибуты -->
<input bind:value={firstName} placeholder="First name">
<input bind:value={lastName} placeholder="Last name">
<input bind:value={birthDay} use:asDatepicker placeholder="Birthday">
<button bind:this={btnEl}>Save</button>
</form>
</Modal>
<button on:click={e => open = true}>Open</button>
<!-- далее логика -->
<script>
// группируем импорты по типам
// компоненты - это классы, поэтому с большой буквы
import Modal from '~/components/Modal';
// для экшенов использую префик `as`, получается читаемо `use:as<something>`
import asDatepicker from '~/actions/datepicker';
// так как сторы - это observerable, использую нейминг из cycle/rx
import user$ from '~/stores/user';
import { saveUser } from '/api';
// группируем переменные по смыслу
let firstName = '',
lastName = '',
birthDay = '';
let open = false;
let btnEl; // для рефов на элементы или компоненты можно давать понятные имена
let refs = {}; // либо просто хранить их в объекте
async function save() {
const patch = { firstName, lastName, birthDay };
await saveUser(patch);
$user$ = { ...$user$, ...patch };
}
// мне нравится видеть весь апи компонента в одном месте
export { open, save };
</script>
<!-- далее стили -->
<style>
/* ... */
</style>
Кроме того, обычно мои компоненты имеют «бюджет» в 200 строк кода вместе со стилями. Если чуть больше — не страшно. Если сильно больше, я создаю для компонента папку и начинаю выносить части в отдельный файлы с помощью svelte-preprocess, начиная со стилей. При этом сборщик настроен так, что импорты не меняются.
Что где изменилось и кто это сделал ?
В итоге Вы быстро пишете компоненты но понять, что написал джуниор в этом «венегрете» через несколько дней когда он сам уже забыл (иногда даже как его зовут) бывает очень сложно. Из-за этого будет невозможно понять что внутри без четкой организации кода внутри проекта или команды.
Забавный тезис, а главное опять же применимый вообще к любому фреймворку и проекту. Более того, синтаксис хуков в современном React, так полюбившийся создателю Vue, что он даже решил срочно внедрить его во Vue 3, будет иметь все те же коллизии:
import React, { useState, useCallback } from 'react';
function MyComponent() {
const [ loading, setLoading ] = useState(false);
const [ status, setStatus ] = useState(0);
const [ pins, setPins ] = useState([]);
const ReloadPins = useCallback(async () => {
setLoading(true);
setPins(await getAllPins());
setStatus(0);
});
return (<div></div>);
}
vs
<div></div>
<script>
let loading = false;
let status = 0;
let pins = [];
async function ReloadPins() {
loading = true;
pins = await getAllPins();
status = 0;
}
</script>
Как видите, все тоже самое, только писанины в 2 раза больше.
Шучу, конечно же ничего не ясно. Причем даже не понятно меняется состояние или это просто вспомогательная переменная. В React это решается state, который хоть как-то вносит ясности.
На самом деле, нет никакого смысл «понимать» какая переменная стейта компонента используется в шаблоне, а какая необходима только для работы скрипта. В Svelte вообще нет такого разделения, там переменные живут обычно JS жизнью — существуют в своем скоупе, некоторые имеют скоуп всего компонента, другие обитают в рамках одной функции или иного блока кода. Ничего нового или неожиданного. Важно понимать лишь одно — DOM пересчитывается исключительно тогда, когда изменяется связанная с ним часть стейта.
Например, в том же React, если сделать:
this.setState({ foo: 1 })
и при этом foo НЕ используется в шаблоне, механизм VDOM никак не сможет понять этого и произведет rerender/reconcile. Вы знали об этом? Поэтому, наверное, в React важно понимать, что используется в шаблонах, а что нет. В Svelte мы избавлены от подобных волнений.
В итоге, тут я могу лишь вторить автору — если у вашего проекта и команды нет четкой организации у вас будут проблемы с любым инструментом, на любом проекте. Собственно, пример проекта автора тому прямое доказательство.
Привязываем Stitch
Конечно же это связано со сборщиком. Автору не следует так легкомысленно относиться к новым инструментом. По-умолчанию, Svelte идет в поставке с Rollup, который «из коробки» поддерживает намного меньше фичей, чем Webpack, зато собирает более оптимизированные и более нативные бандлы. Любой дополнительный функционал можно донастроить с помощью плагинов. Если же не хочется париться, то можно взять официальный шаблон для Webpack, либо любой другой от сообщества, благо их прям много.
Для чего Вам пригодится Svelte ?
В этом пункте, буду более краток — для любых новых проектов, для которых достаточно React или Vue. С Angular или Ember сравнивать не имеет смысла, потому что это фреймворка другого масштаба.
Исходя из последнего я совсем не понимаю зачем мэйнтэйнеры Svelte собираются делать поддержку TypeScript? Мы же любим Svelte как раз за «стрельбу по ногам» и TypeScript на мой взгляд это как вседорожные гусеницы для спорткара Svelte. И знаете, поработав со Svelte, я понял на сколько сильно за последние годы изменился JS, и что он совсем уже не JS.
Svelte даёт возможность поработать в еще том, старом и ламповом JS, без PropTypes, Flow и TypeScript.
Не могу не согласиться с этим утверждением. В действительности, TS не так уж и нужен в Svelte. Хотя частичная его поддержка есть с помощью препроцессоров (без тайпчека шаблонов), но в целом это скорее довесок, чем то, без чего нельзя написать хорошие компоненты.
Почему НЕ стоит использовать Svelte ?
- Если ваш проект уже написан на другом фреймворке и работает сносно. Не нужно бежать и переписывать его.
- Даже в п.1 есть окно возможностей — можно переписать некоторые «тяжелые» компоненты на Svelte. Проект станет легче, а компоненты будут работать быстрее. При этом не придется переписывать все.
- Если вы пишете энтерпрайз, скорее всего лучше подойдет Angular или Ember. Svelte, ровно как и React и Vue, не всегда хороший выбор для этого.
- Если если вам нужна first-class поддержка Typescript.
- Если вы разрабатываете исключительно типовые решения и привыкли собирать проекты из готовых компонентов.
- До сих пор более слабый тулинг, чем у более матерых товарищей. Если это критично, тогда стоит подождать, либо присоединиться к нашему сообществу и развивать тулинг вместе.)))
***
В целом, к сожалению, большая часть выводов из оригинальной статьи основана на ложных предпосылках и вводит читателей в заблуждение. Учитывая, что я не видел автора в русскоязычном комьюнити Svelte в Телеграм @sveltejs (1К+ участников), делаю вывод, что автор и его команда приложили недостаточно усилий при освоения нового, неизведанного для себя инструмента. Думаю если бы ребята просто добавились в чатик, большей части проблем удалось бы избежать. В данный момент кажется ребята сделали довольно поспешные выводы, впрочем это их личное дело.
Автор: Павел Малышев