Skip to content

Ленивый режим

По умолчанию EffCSS генерирует CSS с опережением: правила таблицы стилей записываются в CSSOM сразу после вызова утилиты. В ленивом режиме фактическая генерация откладывается — CSS попадает в таблицу стилей только тогда, когда созданный резолвер фактически используется (вызывается или преобразуется в строку).

Ленивый режим доступен в двух вариантах, поэтому вы можете выбрать необходимый уровень контроля:

ВариантТриггерОбласть действия
Управляемыйопционально подключаемые утилиты lazy*для каждой утилиты, всегда ленивый
Уровень библиотекиconfigure({ lazy: true })каждая генерирующая утилита, возвращающая резолвер

Они работают независимо и могут быть объединены.

1. Управляемые — lazy* утилиты

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

  • не вызовете резолвер — fn() (или fn(...args)),
  • не преобразуете его в строку — `${fn}` или String(fn).

Содержимое правила может быть передано либо в виде объекта, либо в виде функции, которая возвращает этот объект. Функция вычисляется лениво, при первом запуске.

УтилитаВозвращаетГенерирует
lazyClassName(rule) (или className.lazy(rule))() => string (имя класса)одно анонимное правило класса
lazyAttribute(rule) (или attribute.lazy(rule))() => object ({ 'data-…': '' })одно анонимное правило атрибута
lazyClassNames(gen) (или classNames.lazy(gen))резолвер селекторовтаблица стилей селектора класса
lazyAttributes(gen) (или attributes.lazy(gen))резолвер селекторовтаблица стилей селектора атрибута
lazyCustomStyles(gen) (или customStyles.lazy(gen))резолвер селекторовпользовательская таблица стилей

До - незамедлительно

ts
import { className, attribute, classNames } from 'effcss';

// CSS is written into the shared stylesheet right here
const centerCls = className({ margin: 'auto' });
const markerAttr = attribute({ fontWeight: 'bold' });
const utils = classNames<Utils>((selectors) => {
    const { w } = selectors;
    return {
        [w.s]: { width: '12px' },
        [w.m]: { width: '16px' }
    };
});

Every call produces a rule immediately, even if the result is never used on the page.

После - контролируемо

ts
import { lazyClassName, lazyAttribute, lazyClassNames } from 'effcss';

// nothing is generated at creation time
const centerCls = className.lazy({ margin: 'auto' });
const markerAttr = attribute.lazy({ fontWeight: 'bold' });
const utils = classNames.lazy<Utils>((selectors) => {
    const { w } = selectors;
    return {
        [w.s]: { width: '12px' },
        [w.m]: { width: '16px' }
    };
});

// ... later, where the value is actually consumed:
<div className={centerCls()} {...markerAttr()}>   // generates the anonymous rules
<div className={utils({ w: 'm' })} />             // generates the stylesheet

Обратите внимание, что все ленивые обработчики, даже lazyClassName (className.lazy) и lazyAttribute (attribute.lazy), возвращают функции. Для ленивых обработчиков отдельных правил это дает дополнительное преимущество — преобразование такого обработчика в строку возвращает правильный селектор, который можно использовать в других стилях:

ts
const divider = className.lazy({
    width: '100%',
    height: '1px'
});

const layout = classNames.lazy<Layout>((selectors) => ({
    [selectors.row]: {
        display: 'flex'
    },
    // `divider` coerce to its real `.class` selector
    // when `layout` will be used at the first time
    [`& ${divider}`]: {
        background: 'red'
    }
}));

Более того, эти функции могут принимать не только объект, но и генератор:

ts
const divider = className.lazy(() => {
    // you can create other global rules inside
    // so that they are created the first time className is used
    const width = variable('100%');
    return {
        width: width(),
        height: '1px',
        '&:hover': {
            [width]: '50%'
        }
    };
});

Важно: className / attribute возвращают значение, не являющееся функцией (string / object) — они не являются ленивыми и здесь не задействованы.

2. На уровне библиотеки — configure({ lazy: true })

configure({ lazy: true }) мгновенно переключает каждую вспомогательную функцию генерации, возвращающую обработчик функций на её ленивую версию:

variable, variables, animation, animations, layer, layers, font, fonts, classNames, attributes, customStyles.

Вы продолжаете писать тот же самый API — нет необходимости использовать «ленивые» имена. Каждая из этих утилит теперь откладывает генерацию до тех пор, пока не будет использован её результат.

className и attribute не затрагиваются: они возвращают обычные значения (string / object), а не резолверы, поэтому генерация продолжается немедленно.

До - незамедлительно (по умолчанию)

ts
import { variable, animation, classNames } from 'effcss';

const shadowColor = variable('#58666d');
const spin = animation({ to: { transform: 'rotate(360deg)' } });

const utils = classNames<Utils>((selectors) => {
    const { w } = selectors;
    return {
        [w.s]: { width: '12px' }
    };
});

После — configure({ lazy: true })

ts
import { configure, variable, animation, classNames } from 'effcss';

configure({ lazy: true });

// same API, different timing
const shadowColor = variable('#58666d');
const spin = animation({ to: { transform: 'rotate(360deg)' } });
// nothing is written yet

const utils = classNames.lazy<Utils>((selectors) => {
    const { w } = selectors;
    return {
        [w.s]: { width: '12px' }
    };
});
// nothing is written yet

// CSS is produced only on first real use:
`${shadowColor()} or ${shadowColor}`
`${spin()} or ${spin}`
utils({ w: 's' })

Для множественных утилит (variables, animations, layers, fonts, ...) весь набор прогревается одновременно: обращение к любому отдельному резолверу генерирует весь набор, сохраняя порядок детерминированным для гидратации SSR.

Примечания к поведению

  • Триггеры прогрева — резолвер прогревается вызовом или преобразованием строки. Простое удержание (или вызов .get() / .set() для переменных) не генерирует CSS.
  • variable().get() / .set() — перед прогревом get() возвращает '', а set() ничего не делает; они становятся активными только после прогрева резолвера.
  • update() — в ленивом режиме не изменяет переменные, которые еще не созданы.
  • Гидратация SSR — порядок генерации детерминирован (общие внутренние счетчики), поэтому сервер и клиент генерируют идентичные селекторы, пока они прогревают резолверы в одном и том же порядке.
  • События subscribe — события EffCSSEvent генерируются в момент фактической генерации (прогрев резолвера), а не при первом вызове утилиты.

Выбор между двумя вариантами

  • Используйте управляемые утилиты lazy*, когда вам нужна ленивая генерация только для определенных правил, а остальные — для немедленной, или когда вы хотите гарантировать ленивую генерацию независимо от общеприложенийной настройки.
  • Используйте configure({ lazy: true }), когда вам нужно широкое поведение только то, что используется во всей библиотеке без изменения кода, например, в сочетании с vite-plugin-effcss для отправки только того CSS, который фактически отображается.

Опубликовано под лицензией Apache License 2.0