Skip to content

endorphinjs/e2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Прототип Endorphin2 (WIP)

Основные принципы будущего решения:

  • Точкой входа в компонент является JS-файл.
  • Поддержка реактивных примитивов (см. Vue или @preact/signals).

Из главных изменений (по сравнению с текущей версией Endorphin):

  • Шаблон указывается внутри фабрики компонента с помощью Tagged Templates. Это решает сразу несколько проблем: бесплатно получаем валидацию, type checking и автокомплит данных, а также используем готовые плагины типа Lit для подсветки синтаксиса внутри такой строки.
  • Локальные переменные, объявленные внутри фабрики компонента, автоматически становятся реактивными. То есть вместо const enabled = ref(false) как во Vue можно писать let enabled = false и компилятор автоматически сделает из него реактивное значение.

Пример описания компонента

import { defineComponent, html, computed, useComponent, onDestory } from 'endorphin';
import AnotherComponent from './AnotherComponent.ts';

// Импорт стилей компонента. Стили так же автоматически преобразуются и скоупятся
import './style.css';

export interface Props {
    enabled: boolean;
    name: string;
}

// Общая переменная модуля, как в обычном JS
let instances = 0;
const items = ['a', 'b', 'c'];

// Объявляем компонент через вызов `defineComponent()`.
// Вернётся класс, который в любом месте можно создать так:
// import MyComponent from './component.ts';
// const c = new MyComponent(props);
export default function MyComponent({ enabled, name }: Props) {
    // Вызов текущей функции работает как `willMount()` из предыдущей
    // версии эндорфина

    // Свободно обращаемся к общим переменным модуля
    instances++;

    // Локальные переменные, которые буду использоваться в шаблоне,
    // объявляем в JS: получим бесплатную типизацию из JS/TS
    let item: string;
    let innerValue = 1;

    // Объявляем реактивныем переменные: они будут автоматически пересчитываться,
    // когда поменяется любое значение внутри коллбэка
    const fullName = computed(() => enabled ? name : 'Disabled');

    const onItemClick = (item: string) => {
        console.log('Clicked on', item);
    };

    // Добавление методов жизненного цикла будет работать через вызов
    // функции модуля
    onDestory(() => {
        instances--;
        console.log('component destroyed')
    });

    // Тут самое неприятное место — необходимо явно зарегистрировать использование
    // вложенных компонентов в формате `{имяКомпонента: класс}`, чтобы не было
    // не использованных переменных и чтобы компилятор знал, как связывать имя
    // элемента с конструктором компонента
    useComponent({ AnotherComponent });

    // Шаблон указываем через tagged template literal.
    // Это даёт следующее:
    // * нативная валидация, code check и выведение типов, так как тут пишем
    //   обычный JS
    // * в этом месте подключается компилятор, который заменяет вызов `html`
    //   на скомпилированную функцию рендеринга
    // * все переменные, которые используются внутри tagged template, автоматически
    //   становятся реактивными: их изменение приводит к ре-рендеру шаблона
    // * можно использовать плагин для Lit, который подсветит HTML внутри такой строки
    return html`<div class="${innerValue ? 'foo' : 'bar'}"
                     class:enabled=${enabled}>
        <e:if test=${enabled}>
            <p @click=${innerValue++}>${fullName}</p>
            <AnotherComponent prop=${name} />
        </e:if>
        <ul>
            <e:for-each select=${items} as=${item}>
                <li class="item" @click|prevent=${onItemClick(item)}>${item}</li>
            </e:for-each>
        </ul>
    </div>`;
}

JSX

Особенность примера выше: с помощью метода defineComponent() мы подсказываем компилятору, что внутри функции описывается компонент и нужно модифицировать её тело таким образом, чтобы всё правильно обновлялось.

Такой же подсказкой может быть и наличие JSX узла в AST функции. Поэтому с небольшой модификацией парсера можно использовать React-подобный контракт и синтаксис, чтобы переиспользовать инфраструктуру React-инструментов для Endorphin. Вот как выглядит пример выше в JSX-стиле (вроде такой код нормально отрабатывается компилятором):

import { computed, onDestory } from 'endorphin';
import AnotherComponent from './AnotherComponent.ts';

// Импорт стилей компонента. Стили так же автоматически преобразуются и скоупятся
import './style.css';

export interface Props {
    enabled: boolean;
    name: string;
}

// Общая переменная модуля, как в обычном JS
let instances = 0;
const items = ['a', 'b', 'c'];

// В качестве описания компонента используем обычную функцию с JSX внутри
export default function MyComponent({ enabled, name }: Props) {
    instances++;

    let item: string;
    let innerValue = 1;
    const fullName = computed(() => enabled ? name : 'Disabled');

    const onItemClick = (item: string) => {
        console.log('Clicked on', item);
    };

    onDestory(() => {
        instances--;
        console.log('component destroyed')
    });

    return <div class={innerValue ? 'foo' : 'bar'}
                class:enabled={enabled}>
        <if test={enabled}>
            <p onClick={innerValue++}>{fullName}</p>
            <AnotherComponent prop={name} />
        </if>
        <ul>
            <for select={items} as={item}>
                <li class="item" onClick:prevent={onItemClick(item)}>{item}</li>
            </for>
        </ul>
    </div>;
}