Перейти к содержанию

Обновление до Lit 3

Если вы переходите с Lit 1.x на Lit 2.x, см. руководство по обновлению до Lit 2.

Обзор

В Lit 3.0 мало ломающих изменений относительно Lit 2.x:

  • Internet Explorer 11 больше не поддерживается.
  • Модули npm Lit публикуются как ES2021.
  • API, помеченные устаревшими в релизах Lit 2.x, удалены.
  • Модули поддержки гидратации SSR переехали в пакет @lit-labs/ssr-client.
  • Только типы: обновлены типы renderRoot и createRenderRoot() у ReactiveElement.
  • Убрана поддержка декораторов Babel версии 2018-09.
  • Поведение декораторов унифицировано между экспериментальными декораторами TypeScript и стандартными декораторами.
    • Из-за этого при использовании TypeScript нужна как минимум версия 5.2: в ней обновлены типы обоих видов декораторов.

Большинству пользователей не нужно менять код, чтобы перейти с Lit 2 на Lit 3. Большинство приложений и библиотек могут расширить диапазон версий npm так, чтобы в него входили и 2.x, и 3.x: "^2.7.0 || ^3.0.0".

Lit 2.x и 3.0 совместимы друг с другом: шаблоны, базовые классы и директивы одной версии работают с другой.

Lit публикуется как ES2021

Lit 2 публиковался как ES2019, Lit 3 — как ES2021. Этот уровень широко поддерживают современные браузеры и инструменты сборки. Изменение ломает сборку, если вам нужны старые браузеры, а текущие инструменты не разбирают ES2021.

Lit 3 и Webpack 4

Внутренний парсер Webpack 4 не понимает оператор ??, логическое присваивание ??= и опциональную цепочку ?.. Это синтаксис ES2021, поэтому Webpack 4 бросает Module parse failed: Unexpected token.

Лучше перейти на Webpack 5: он этот синтаксис разбирает. Если перейти нельзя, код Lit 3 можно преобразовать через babel-loader.

Установите пакеты Babel:

1
2
3
4
npm i -D babel-loader@8 \
    @babel/plugin-transform-optional-chaining \
    @babel/plugin-transform-nullish-coalescing-operator \
    @babel/plugin-transform-logical-assignment-operators

Добавьте правило, похожее на следующее. Его, возможно, придётся подстроить под проект:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// В webpack.config.js

module.exports = {
    // ...

    module: {
        rules: [
            // ... остальные правила

            // Понижает синтаксис ES2021 в Lit, чтобы его разобрал Webpack 4.
            // После перехода на Webpack 5 правило можно удалить.
            {
                test: /\.js$/,
                include: ['@lit', 'lit-element', 'lit-html'].map((p) =>
                    path.resolve(__dirname, 'node_modules/' + p)
                ),
                use: {
                    loader: 'babel-loader',
                    options: {
                        plugins: [
                            '@babel/plugin-transform-optional-chaining',
                            '@babel/plugin-transform-nullish-coalescing-operator',
                            '@babel/plugin-transform-logical-assignment-operators',
                        ],
                    },
                },
            },
        ],
    },
};

Изменения декораторов Lit

Декораторы JavaScript стандартизованы TC39 и находятся на стадии 3 из четырёх. На стадии 3 виртуальные машины и компиляторы начинают реализовывать уже стабильную спецификацию. TypeScript 5.2 и Babel 7.23 эту спецификацию реализовали.

Существует больше одной версии API декораторов: стандартные декораторы, экспериментальные декораторы TypeScript и прежние предложения, которые реализовывал Babel, в том числе версия 2018-09.

Lit 2 поддерживал экспериментальные декораторы TypeScript и декораторы Babel 2018-09. Lit 3 поддерживает стандартные декораторы и экспериментальные декораторы TypeScript.

Декораторы Lit 3 в основном обратно совместимы с декораторами TypeScript из Lit 2. Скорее всего, менять код не нужно.

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

Что изменилось в Lit 3.0:

Удалённые API

Если проект на Lit 2.x не выдаёт предупреждений об устаревании, этот список вас, скорее всего, не затронет.

Шаги обновления

Удалён псевдоним UpdatingElement

Замените UpdatingElement из Lit 2.x на ReactiveElement. Это не функциональное изменение: UpdatingElement был псевдонимом ReactiveElement.

1
2
3
4
5
// Удалено
import { UpdatingElement } from 'lit';

// Актуально
import { ReactiveElement } from 'lit';

Декораторы больше не реэкспортируются из lit-element

Встроенные декораторы Lit 3.0 больше не экспортируются из lit-element. Их импортируют из lit/decorators.js.

1
2
3
4
5
// Реэкспорт декораторов из lit-element удалён
import { customElement, property, state } from 'lit-element';

// Актуально
import { customElement, property, state } from 'lit/decorators.js';

Удалена устаревшая сигнатура queryAssignedNodes

Если queryAssignedNodes вызывался с селектором, перейдите на queryAssignedElements.

1
2
3
4
5
// Удалено
@queryAssignedNodes('list', true, '.item')

// Актуально
@queryAssignedElements({slot: 'list', flatten: true, selector: '.item'})

Вызовы без selector теперь принимают объект параметров.

1
2
3
4
5
// Удалено
@queryAssignedNodes('list', true)

// Актуально
@queryAssignedNodes({slot: 'list', flatten: true})

Модули экспериментальной гидратации убраны из ядра

Экспериментальная гидратация вынесена из основных библиотек в @lit-labs/ssr-client.

1
2
3
4
5
6
7
// Удалено
import 'lit/experimental-hydrate-support.js';
import { hydrate } from 'lit/experimental-hydrate.js';

// Актуально
import '@lit-labs/ssr-client/lit-element-hydrate-support.js';
import { hydrate } from '@lit-labs/ssr-client';

Только типы: renderRoot и createRenderRoot()

Это изменение только типов, на выполнение оно не влияет.

Тип ReactiveElement.renderRoot изменён с Element | ShadowRoot на HTMLElement | DocumentFragment. Тип возврата ReactiveElement.createRenderRoot() изменён с HTMLElement | ShadowRoot на HTMLElement | DocumentFragment. Так они согласованы друг с другом и с render() из lit-html.

Код, который просто обращается к this.renderRoot, обычно менять не нужно. Явные аннотации со старыми типами стоит обновить.

По желанию: стандартные декораторы

Lit 3 поддерживает стандартные декораторы, но пользователям TypeScript по-прежнему рекомендуются экспериментальные. Код, который TypeScript и Babel сейчас выпускают для стандартных декораторов, довольно большой.

Стандартные декораторы для продакшена имеет смысл рекомендовать, когда их поддержат браузеры или когда преобразование декораторов появится в новом компиляторе Lit.

Попробовать их можно уже сейчас: они работают в TypeScript 5.2 и новее и в Babel 7.23 с плагином @babel/plugin-proposal-decorators.

Настройка

TypeScript

Поставьте TypeScript 5.2 или новее и уберите из tsconfig параметр "experimentalDecorators", если он есть.

Babel

Поставьте Babel 7.23 или новее и @babel/plugin-proposal-decorators. Плагину передайте опцию "version": "2023-05".

Изменения в коде

Ключевое слово accessor у декорированных полей

Стандартным декораторам нельзя менять вид члена класса, который они декорируют. Декораторы, которым нужны геттер и сеттер, применяются к уже существующим геттеру и сеттеру. Чтобы это было удобнее, стандарт добавляет ключевое слово accessor: применённое к полю класса, оно создаёт «автоаксессор». Автоаксессоры выглядят и ведут себя почти как поля класса, но создают на прототипе аксессоры с закрытым хранилищем.

Декораторам @property(), @state(), @query(), @queryAll(), @queryAssignedElements() и @queryAssignedNodes() нужно ключевое слово accessor.

1
2
3
4
5
class MyElement extends LitElement {
    @property()
    accessor myProperty = 'initial value';
    // ...
}

Перенесите декораторы с геттеров на сеттеры

Стандартный декоратор может заменить только тот член класса, к которому он применён напрямую. Декораторам Lit нужно перехватывать запись свойства, поэтому их ставят на сеттеры. В Lit 2 рекомендовалось ставить декораторы на геттеры.

Для @property() и @state() вызовы this.requestUpdate() в сеттере можно убрать: теперь это происходит автоматически. Если requestUpdate() вызывать не нужно, используйте параметр свойства noAccessor.

Для @property() и @state() декоратор при записи свойства вызывает геттер, чтобы получить старое значение. Поэтому нужно определить и геттер, и сеттер.

Было:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MyElement extends LitElement {
    private _foo = 42;
    set(v) {
        const oldValue = this._foo;
        this._foo = v;
        this.requestUpdate('foo', oldValue);
    }
    @property()
    get() {
        return this._foo;
    }
}

Стало:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class MyElement extends LitElement {
    private _foo = 42;
    @property()
    set(v) {
        this._foo = v;
    }
    get() {
        return this._foo;
    }
}

Комментарии