Svelte — очень хороший фреймворк/библиотека, но она имеет чувствительный недостаток (где их нет...) — это система отслеживания изменений, она плохо ловит мутации данных, поэтому в Svelte комьюнити форсируется иммутабельный подход.
И у меня давно чесались руки попробовать систему отслеживания похожую на ту что используется в Angular для Svelte, и вот появились свободные выходные и я на скорую руку из «говна и палок» состряпал Svelte-подобный компилятор (Svelte-M), что дало положительный побочный эффект в плане размера бандла и скорости работы:
Размер бандла получился почти в 2 раза меньше (todo приложение):
Svelte: 4.7k (2.2k gzipped)
Svelte-M: 2.7k (1.2k gzipped)
Скорость работы:
Рендеринг 5000 элементов: Svelte 894ms, Svelte-M 563ms (Svelte-M быстрее: 63% от времени Svelte).
Удаление 1 элемента: Svelte 113ms, Svelte-M 38ms (Svelte-M быстрее в 3 раза).
Пере-рендеринг (удаление и добавление 5000 элементов): Svelte: 859ms, Svelte-M 418ms (в 2 раза быстрее).
Пример todo-приложения на Svelte-M: example.html и оно же собраное в бандл на jsfiddle.
Предупреждение: Svelte-M — это просто 2х дневный эксперимент, там мало функционала и куча ограничений, не нужно рассматривать его как конкурента или использовать в продакшене ;)
За счет чего скорость? В Svelte DOM собирается по элементам, + отдельно атрибуты и пр. что снижает производительность и раздувает код, а в Svelte-M шаблон вставляется одной операцией, и элементы списков клонируются целиком через Node.cloneNode (тоже одной командой, вместо поэлементной сборки), и меньше оберток в результирующем коде.
Теперь самое главное — это система отслеживания (я бы её назвал Bind-Checking), покажу отличия, где оно помогает:
Когда вы вызовите ф-ю
А в Svelte-M нормально работает и первый вариант (как и любой другой).
Ок, едем дальше — у меня выводится список этих задач и мне кликом по задаче нужно что-то в ней изменить, например поправить текст задачи:
Тут при клике вызывается ф-я «fix» и я меняю текст задачи — но оно не работает (изменений нет на экране), нужно вручную дернуть триггер — дописать
Конечно же я им не воспользовался ;), кроме того имутабельность тут не причем, тригер срабатывает потому что тут появилось присвоение
В итоге вариант с Bind-Checking делает приложение ещё ближе к javascript, требуется меньше трюков, и работает достаточно быстро. Таким бы я хотел видеть Svelte…
Если кто хочет попробовать исходники тут: github.com/lega911/svelte-m
Кроме того там есть одно синтаксическое отличие в биндингах: вместо
Надо писать так:
И у меня давно чесались руки попробовать систему отслеживания похожую на ту что используется в Angular для Svelte, и вот появились свободные выходные и я на скорую руку из «говна и палок» состряпал Svelte-подобный компилятор (Svelte-M), что дало положительный побочный эффект в плане размера бандла и скорости работы:
Размер бандла получился почти в 2 раза меньше (todo приложение):
Svelte: 4.7k (2.2k gzipped)
Svelte-M: 2.7k (1.2k gzipped)
Скорость работы:
Рендеринг 5000 элементов: Svelte 894ms, Svelte-M 563ms (Svelte-M быстрее: 63% от времени Svelte).
Удаление 1 элемента: Svelte 113ms, Svelte-M 38ms (Svelte-M быстрее в 3 раза).
Пере-рендеринг (удаление и добавление 5000 элементов): Svelte: 859ms, Svelte-M 418ms (в 2 раза быстрее).
Пример todo-приложения на Svelte-M: example.html и оно же собраное в бандл на jsfiddle.
Предупреждение: Svelte-M — это просто 2х дневный эксперимент, там мало функционала и куча ограничений, не нужно рассматривать его как конкурента или использовать в продакшене ;)
За счет чего скорость? В Svelte DOM собирается по элементам, + отдельно атрибуты и пр. что снижает производительность и раздувает код, а в Svelte-M шаблон вставляется одной операцией, и элементы списков клонируются целиком через Node.cloneNode (тоже одной командой, вместо поэлементной сборки), и меньше оберток в результирующем коде.
Теперь самое главное — это система отслеживания (я бы её назвал Bind-Checking), покажу отличия, где оно помогает:
let todos = [];
const add = () => {
todos.push({name: 'Hello!'});
};
Когда вы вызовите ф-ю
add()
, Svelte не отловит что todos изменился и DOM не обновится. Чтобы оно заработало в Svelte вам нужно куда-нибудь вставить присвоение ("=") в эту функцию, самое простое присвоение это todos=todos
— после этого пример начинает работать (это мне напоминает ручное обновление через setValue как это было раньше). Но Svelte форсурет использовать иммутабилити, поэтому в Svelte комьюнити мне подкинули такой вариант:todos = todos.concat([{name: 'Hello!'}]);
А в Svelte-M нормально работает и первый вариант (как и любой другой).
Ок, едем дальше — у меня выводится список этих задач и мне кликом по задаче нужно что-то в ней изменить, например поправить текст задачи:
<script>
let todos = [];
const fix = (todo) => {
todo.name += '!';
}
</script>
<ul>
{#each todos as todo }
<li>
<span on:click={() => fix(todo)}>{todo.name}</span>
</li>
{/each}
</ul>
Тут при клике вызывается ф-я «fix» и я меняю текст задачи — но оно не работает (изменений нет на экране), нужно вручную дернуть триггер — дописать
todos=todos
, хотя в комьюнити мне предложили более кошерный имутабельный вариант:$: {
fix = todo => {
todos = todos.map(i => {
return i === todo ? {
name: i.name + '!'
} : i;
})
}
}
Конечно же я им не воспользовался ;), кроме того имутабельность тут не причем, тригер срабатывает потому что тут появилось присвоение
todos = ...
, в Svelte-M, опять же, работает и первый вариант.В итоге вариант с Bind-Checking делает приложение ещё ближе к javascript, требуется меньше трюков, и работает достаточно быстро. Таким бы я хотел видеть Svelte…
Если кто хочет попробовать исходники тут: github.com/lega911/svelte-m
Кроме того там есть одно синтаксическое отличие в биндингах: вместо
<span on:click={() => fix(todo)}>
Надо писать так:
<span on:click={fix(todo)}>