habrahabr

SvelteJS: Релиз второй версии

  • суббота, 21 апреля 2018 г. в 00:19:54
https://habrahabr.ru/post/353896/
  • Разработка веб-сайтов
  • Клиентская оптимизация
  • JavaScript
  • HTML


Буквально вчера вышла 2-я версия молодого, но весьма многообещающего фреймворка SvelteJS. Версия мажорная, а значит содержит не только новые фичи и исправленные баги, но и соответствующие «breaking changes». Что новенького предлагает разработчикам новая версия и почему Svelte стал еще лучше, читайте под катом.
image

Если вдруг, по какой-то неведомой причине, вы не знаете что такое Svelte и почему это не «yet another javascript framework». Предлагаю сперва наверстать упущенное, чтобы лучше понимать о чем речь.

Новый синтаксис шаблонов


Самое очевидное и глобальное изменение в новой версии — кардинальная смена синтаксиса шаблонов. Рич наконец-то решил избавиться от «усо»-подобного синтаксиса в пользу более лаконичного варианта:

Было

{{#if foo}}
  {{bar}}
{{else}}
  {{baz}}
{{/if}}

Стало

{#if foo}
  {bar}
{:else}
  {baz}
{/if}

Очевидно что синтаксис стал визуально проще и чище. Изменения коснулись всех конструкций в шаблонах, в том числе специальных элементов Svelte:

Было

<:Component {foo ? Red : Blue} name="thing" />

{{#if foo}}
<:Self />
{{/if}}

<:Window on:keydown="handleKey(event)" />

<:Head>
    <title>{{post.title}} • My blog</title>
</:Head>

Стало

<svelte:component this="{foo ? Red : Blue}" name="thing"/>

{#if foo}
<svelte:self/>
{/if}

<svelte:window on:keydown="handleKey(event)" />

<svelte:head>
    <title>{post.title} • My blog</title>
</svelte:head>

Предыдущий синтаксис специальных элементов был уж слишком необычен и большинство редакторов кода не справлялись с его подсветкой. Новый синтаксис напоминает синтаксис namespace из XML и значительно лучше воспринимается редакторами.

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

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

Годно, Рич! Прощайте «усы»!

image

ES6 only


Так как Svelte — это прежде всего компилятор, стоит сначала отметить, что итоговый код предыдущей версии компилировался в ES5. Поэтому для поддержки IE11 и других «прогрессивных» версий браузеров, не было нужды связываться с транспиллерами вроде Babel или Bublé.

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

Сам я полностью поддерживаю такой подход. Тем более что подключить Babel к Webpack или Rollup, уверен, ни для кого уже не составит труда. Особенно если учесть, что использовать Svelte без оных все равно не получится. ;-)

Actions


До сих пор не понимаю почему эта фича называется actions, но для себя решил, что носителям языка виднее. Хотя лично для меня — это не очевидное название.

В любом случае, фича полезная. Фактически это некий хук, который срабатывает, когда элемент рендерится в DOM. Для этого введена новая директива use:

<img src="placeholder.jpg" use:lazyload="{ src: 'giant-photo.jpg' }">

И соответствующая секция в поведении:

export default {
  actions: {
    lazyload(node, data) {
      // do something
      return {
        update(data) {},
        destroy() {}
      }
    }
  }
};

Экшн — это функция, которая принимает первым параметром элемент, к которому применена директива, и данные, которые были переданы в нее. Функция должна вернуть объект с обязательным методом destroy(), который будет вызван в тот момент, когда элемент будет удален из DOM. Также объект может содержать не обязательный методом update(), который будет вызываться каждый раз, когда связанные с экшеном данные были изменены.

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

Новые хуки жизненного цикла


В предыдущей версии были лишь 2 хука: oncreate() и ondestroy(). Теперь мы имеем также 2 дополнительных хука, отвечающих за работу с состоянием:

export default {
  onstate({ changed, current, previous }) {
    // вызывается до oncreate(), и каждый раз, когда состояние изменилось
  },
  onupdate({ changed, current, previous }) {
    // вызывается после oncreate(), и каждый раз, когда DOM был обновлен после изменения состояния
  }
};

Как видите, каждый хук принимает объект с 3-мя свойствами:

  • changed — включает в себя ключи, которые были изменены в стейте. Используется для проверки
  • current — измененный стейт
  • previous — предыдущий стейт


Иcпользовать можно так:

export default {
  onstate({ changed: { foo }, current, previous }) {
     if (foo) {
         console.log('foo has changed from %s to %s', previous.foo, current.foo);
     }
  }
};

Или даже так:

component.on('state', ({ changed, current, previous }) => {...});

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

import { observe } from 'svelte-extras';

export default {
  methods: { observe },
  oncreate() {
    this.observe('foo', (current, previous) => {...});
  }
};

Если вспомнить что Рич, как создатель Rollup, является фанатом tree-shaking'а, такой подход сразу становится очевидным.



Spread attributes


Да, знаю, это подсмотрели у JSX, но сути это не меняет. Многие проголосовали ЗА и теперь Svelte умеет также:

<Child {...childProps} />


Другие изменения


Немаловажные изменения произошли и в некоторых существующих api фреймворка. Вот основные из них:

Метод get() больше не принимает параметры и всегда возвращает весь стейт компонента:


Было

const foo = this.get('foo');
const bar = this.get('bar');

Стало

const { foo, bar } = this.get();

Это прикольно и мы можем использовать деструктурирующее присваивание для определения необходимых свойств. К тому же, теперь данный метод стал больше похож на своего антагониста, метод set(), который еще в предыдущей версии принимал исключительно объект:

this.set({ foo: 1 });
const { foo } = this.get();

Вообще у меня создается впечатление, что Svelte все больше склоняется к использованию RORO в своих интерфейсах. А полный переход на ES6 лишь способствует этому.

Это же наблюдение подтверждает новый синтаксис вычисляемых свойств:


Было

export default {
  computed: {
    d: (a, b, c) => a = b + c
  }
};

Стало

export default {
  computed: {
    d: ({ a, b, c }) => a = b + c
  }
};

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

<Child {...props}/>

<script>
  import Child from './Child.html';

  export default {
    components: { Child },
    computed: {
      props: state => {
        const { unwanted, alsoUnwanted, ...props } = state;
        return props;
      }
    }
  };
</script>

Думаю многие понимают насколько это круто. Надеюсь Рич запилит эту фичу в ближайшее время.

Обработчики кастомных ивентов теперь должны возвращать destroy() вместо teardown() для консистентности:


Было

export function eventHandler(node, callback) {
  //...
  return {
    teardown() {}
  }
}

Стало

export function eventHandler(node, callback) {
  //...
  return {
    destroy() {}
  }
}


Svelte больше не приводит значения аттрибутов компонентов к типу


Теперь нужно явно указывать тип отличный от строки с помощью выражения. Более всего это касается чисел:

Было

<Counter start="1"/>

Стало

<Counter start="1"/> <!-- строка -->
<Counter start="{1}"/> <!-- число -->

Думаю смысл понятен. Мудрое решение.

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


Было

<button on:click="store.set({ clicked: true })">click me</button>

Стало

<button on:click="$set({ clicked: true })">click me</button>

В предыдущей версии через префикс $ были доступны только данные из стора.

Тудушечка



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

Тудушечка умеет CRUD над задачами, эмуляцию асинхронного взаимодействия с персистентным стейтом (хранилищем, бекендом и т.п.) и querying'ом по одному параметру — типу todo-листа (work, family, hobby), а также легкими анимашками. Работает примитивно, пишется быстро. Все как я люблю ))))

Пощупать

Вот и все, всем спасибо! Хорошей пятницы и выходных!