Skip to content

CSS-in-TS

Функции StyleSheet maker могут описывать генерируемые стили с помощью специальных типов, так что при их использовании TypeScript подскажет доступные CSS-селекторы. Эта возможность объединяет в себе методологию БЭМ и дженерики TypeScript, скрывая реализацию стилей за внешним интерфейсом.

Описание стилей

Первый шаг в разработке стилей с помощью EffCSS - это зафиксировать предоставляемую функциональность в виде типа, которые описывает ваши стили на трех уровнях методологии БЭМ

  • блоках (1 уровень),
  • элементах (2 уровень),
  • модификаторах (3 уровень).

Для всех уровней рекомендуется использовать осмысленные названия, написаные в стиле lowerCamelCase.

WARNING

Не используйте в названиях символы точки и нижнего подчеркивания, так как эти символы используются для вывода селекторов и аттрибутов EffCSS

INFO

Вам не обязательно использовать все три уровня. Если вам не хочется описывать стили в терминах БЭМ, просто описывайте свои селекторы на уровне отдельных блоков.

ts
export type TCustomMaker = {
    /**
     * Default block
     */
    '': {
        /**
         * Logo element
         */
        logo: {
            /**
             * Spin animation
             */
            spin: '';
            /**
             * Logo scale
             */
            scale: 1 | 2;
        };
        /**
         * Body element
         */
        body: {
            /**
             * Padding size
             */
            p: 's' | 'm' | 'l';
        };
        /**
         * Footer element
         */
        footer: {
            /**
             * Is footer hidden
             */
            hidden: '';
        };
    };
    /**
     * Panel block
     */
    panel: {
        /**
         * Default block
         */
        '': {
            /**
             * Is panel modal
             */
            modal: '';
            /**
             * Max panel width
             */
            max: 'vw' | 'parent';
        };
        /**
         * Close button block
         */
        closeBtn: {};
    }
};

Блок не обязательно должен содержать элементы. Элемент не обязательно должен содержать модификаторы.

Обратите внимание, что модификаторы блока также описываются на 3 уровне внутри элемента с ключом ''. Блок с ключом '' считается дефолтным или основным. Возможна также ситуация, когда стили описывают некоторые утилиты, не привязанные к конкретному блоку и/или элементу (как у Tailwind) - тогда на помощь приходит следующее описание

ts
export type TCustomMaker = {
    '': {
        '': {
            /**
             * Width 
             */
            w: 's' | 'm' | 'l';
            /**
             * Height 
             */
            h: 's' | 'm' | 'l';
        }
    };
};

Реализация стилей

Второй шаг в разработке стилей с помощью EffCSS - это реализовать описанную функциональность в рамках функции StyleSheet maker. Для генерации БЭМ-селекторов в EffCSS используется утилита bem. Эта утилита также проверяет допустимость реализуемых CSS-селекторов, поскольку является дженериком.

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

ts
import { TStyleSheetMaker } from 'effcss';

export type TCustomMaker = {...};

const maker: TStyleSheetMaker = ({
  bem,
}) => {
  return {
    // селектор корневого блока
    [bem<TCustomMaker>('')]: {...},
    // селектор элемента внутри корневого блока
    [bem<TCustomMaker>('.logo')]: {...},
    // селектор булева модификатора элемента
    [bem<TCustomMaker>('.logo.spin')]: {}
    // селектор модификатора блока с конкретным значением
    [bem<TCustomMaker>('panel..max.vw')]: {}
  }
};

export default maker;

Использование стилей

Готовые стили EffCSS можно использовать, передавая соответствующую функцию StyleSheet maker в метод use объекта Style consumer. Использование аналогично утилите bem - введенные аргументы также будут проверены на соответствие типу TCustomMaker.

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 возвращает функцию-резолвер,
    // а EffCSS v4 - массив резолверов
    const [css] = use(customStyle);
    return {
        block: css<TCustomMaker>(''),
        logo: css<TCustomMaker>('.logo'),
        spinLogo: css<TCustomMaker>('.logo.spin'),
        footer: css<TCustomMaker>('.footer'),
        body: css<TCustomMaker>('.body.p.m'),
    };
};

Вы также можете передать объект для генерации сразу множества селекторов

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 возвращает функцию-резолвер,
    // а EffCSS v4 - массив резолверов
    const [css] = use(customStyle);
    return css<TCustomMaker>({
        '': {
            logo: {
                spin: '',
                scale: 2
            };
        }
    });
};

Или вы можете передать массив строк

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 возвращает функцию-резолвер,
    // а EffCSS v4 - массив резолверов
    const [css] = use(customStyle);
    return css<TCustomMaker>(['.logo.spin', '.logo.scale.2']);
};

Кроме того вы можете сначала получить объект MonoResolver, а затем доуточнять его по мере необходимости

ts
import { IStyleProvider } from 'effcss';
import { default as customStyle, TCustomMaker } from './styles/custom';

const getStyle = (use: IStyleProvider['use']) => {
    // EffCSS v3 возвращает функцию-резолвер,
    // а EffCSS v4 - массив резолверов
    const [css] = use(customStyle);
    const block = css<TCustomMaker>();
    const logo = block.e('logo');
    const logoSpin = logo.m({
        spin: ''
    });
    return {
        block: block.$,
        logo: logo.$,
        spinLogo: logoSpin.$,
        footer: block.e('footer').$,
        body: block.e('body').m({
            p: 'm'
        }).$,
    };
};

Используйте наиболее понравившийся способ.

Режимы генерации атрибутов

Компонент Style provider может влиять на генерацию БЭМ-селекторов с помощью 2 атрибутов:

  • mode - может иметь значение a (использует data-*-атрибуты) и c (использует атрибут class),
  • min - булев атрибут, который позволяет минифицировать БЭМ-селекторы.

Генерация CSS-классов более предсказуема, а генерация data-атрибутов позволяет визуально отделить селекторы разных таблиц. Используйте то, что вам больше по душе.

Минификация сохраняет уникальный префикс в составе селектора, поэтому стили разных таблиц останутся изолированными, при этом длина селекторов существенно сократится.

Польза

Описанный подход позволяет отделить контракт от реализации - при разработке вы изначально знаете, какие CSS-селекторы должны предоставить, а любой сторонний разработчик сможет импортировать ваш тип TCustomMaker и получить нужные стили, не заглядывая в вашу функцию StyleSheet maker. Учитывая, что в при сборке JS-файлы обычно подвергаются минификации, предложенный подход здорово экономит время.

Кроме того EffCSS генерирует для каждой таблицы стилей свои уникальные БЭМ-селекторы, что исключает риск их переопределения.

Таким образом, EffCSS позволяет шагнуть дальше обычного создания CSS, приближаясь к роли CSS-in-TS.

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