Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: themes versioning #3581

Open
wants to merge 27 commits into
base: next
Choose a base branch
from
Open

feat: themes versioning #3581

wants to merge 27 commits into from

Conversation

zhzz
Copy link
Member

@zhzz zhzz commented Dec 28, 2024

Версионирование тем

Добавляем новую фичу для облегчения обновлений нашей библиотеки.

Проблема

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

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

С другой стороны, мы также стремимся и к тому, чтобы пользовательские дизайны были максимально актуальны и соответствовали гайдам и дизайн-системе в целом. А библиотека была синхронизирована с фигмой и гайдами. И проекты, готовые использовать все новое тоже есть. Например, в компании нередко запускаются новые проекты, в которых нет легаси. Они часто готовы сразу использовать последние версии дизайн-системы.

Таким образом, необходимо выработать решение, удовлетворяющее всем описанным требованиям:

  1. позволяет выпускать визуальные изменения настолько часто, насколько это нужно
  2. уменьшает трудоемкость обновления библиотеки на версии с визуальными изменениями
  3. позволяет использовать последние визуальные изменения всем желающим
  4. не побуждает новые и другие активные проекты использовать неактуальны стили
  5. не сильно повышает техническую сложность библиотеки и процесс ее разработки

Решение

В библиотеке появляется версионирование тем. Т.е. несколько разных версий одной и той же базовой темы (светлой или темной) с разным набором визуальных изменений.

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

В то же время, в библиотеке сохраняется тема, которая применяется по умолчанию без дополнительных настроек (в том числе в документации). В данный момент она называется LIGHT_THEME. Чтобы обеспечить требование №4 из списка выше, эта тема должна содержать в себе все существующие визуальные изменения. Т.е. по факту, LIGHT_THEME является плавающей и ссылается на самую последнюю версию светлой темы из тех, что присутствуют в данной версии библиотеки. То же самое относится и к DARK_THEME.

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

Пример. Текущая версия библиотеки пользователя: 5.5.0. Он решает обновить библиотеку до 5.10.0, в которой есть визуальные изменения, по сравнению с его текущей темой, которые он не хочет пока применять. При этом, он не пользовался версионированием тем до этого момента, используя просто LIGHT_THEME. В версии 5.10.0 набор тем может выглядеть примерно так:

  • LIGHT_THEME
  • LIGHT_THEME_5_0
  • LIGHT_THEME_5_4
  • LIGHT_THEME_5_8
  • LIGHT_THEME_5_10

Чтобы исключить все визуальные изменения, которых у пользователя нет на момент обновления, ему следует выбрать версию темы, наиболее близкую, но не превышающую ту, что он использовал до обновления. Т.е. на момент использования им версии 5.5.0 и дефолтной LIGHT_THEME внутри нее, он по факту использует LIGHT_THEME_5_4. И ему следует переключиться именно на нее, чтобы отложить применение визуальных изменений после обновления на 5.10.0.

import { ThemeContext } from '@skbkontur/react-ui/lib/theming/ThemeContext';
import { LIGHT_THEME_5_4 } from '@skbkontur/react-ui/lib/theming/themes/LightTheme';

<ThemeContext.Provider value={LIGHT_THEME_5_4}>
  <App />
</ThemeContext.Provider>

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

Создание новых версий тем

Создание новых версий тем состоит из двух шагов.

Первый — это создание самой темы. Тема состоит из класса с ее переменными и темы-прототипа, от которой она наследуется. Для этого служит хелпер createThemeFromClass. Как правило, тема-прототип это предыдущая версия темы. Класс с переменными должен содержать в себе изменившиеся по сравнению с предыдущей версией темы значения переменных. Но список изменившихся переменных может быть и пустым. Это нормально, если вносимые визуальные изменения основаны не на переменных, а только на верстке, например. Однако, класс и сама новая тема должны быть заведены в любом случае. Также, новой теме нужно обязательно проставить корректную версию в название и специальный "маркер". На основе этого маркера далее будет работать определение и сравнение версий тем.

// packages/react-ui/internal/themes/LightTheme5_1.ts

import { createThemeFromClass, markThemeVersion } from '../../lib/theming/ThemeHelpers';

import { BasicThemeClassForExtension } from './BasicTheme';
import { LightTheme5_0 } from './LightTheme5_0';

export const LightTheme5_1 = createThemeFromClass(class LightTheme5_1 extends BasicThemeClassForExtension {
  public static linkColor = 'black';
}, {
  prototypeTheme: LightTheme5_0,
  themeMarkers: [markThemeVersion(5, 1)],
});

Второй шаг — это экспорт темы из библиотеки под публичным именем и актуализация ссылки на последнюю версию.

// packages/react-ui/lib/theming/themes/LightTheme.ts

import { LightTheme5_0 } from '../../../internal/themes/LightTheme5_0';
+ import { LightTheme5_1 } from '../../../internal/themes/LightTheme5_1';

export const LIGHT_THEME_5_0 = LightTheme5_0;
+ export const LIGHT_THEME_5_1 = LightTheme5_1;

- export const LIGHT_THEME = LIGHT_THEME_5_0;
+ export const LIGHT_THEME = LIGHT_THEME_5_1;

Важно помнить, что все то же самое нужно проделать и с темной темой, даже если по факту в ней не было никаких изменений. В обратную сторону это тоже верно. Версионирование тем сквозное между светлой и темной темой. Это должно упростить разработку.

// packages/react-ui/internal/themes/DarkTheme5_1.ts

import { createThemeFromClass, markThemeVersion } from '../../lib/theming/ThemeHelpers';

import { BasicThemeClassForExtension } from './BasicTheme';
import { DarkTheme5_0 } from './DarkTheme5_0';

export const DarkTheme5_1 = createThemeFromClass(class DarkTheme5_1 extends BasicThemeClassForExtension {}, {
  prototypeTheme: DarkTheme5_0,
  themeMarkers: [markThemeVersion(5, 1)],
});
// packages/react-ui/lib/theming/themes/DarkTheme.ts

import { DarkTheme5_0 } from '../../../internal/themes/DarkTheme5_0';
+ import { DarkTheme5_1 } from '../../../internal/themes/DarkTheme5_1';

export const DARK_THEME_5_0 = DarkTheme5_0;
+ export const DARK_THEME_5_1 = DarkTheme5_1;

- export const DARK_THEME = DARK_THEME_5_0;
+ export const DARK_THEME = DARK_THEME_5_1;

Изменение общих переменных между светлой и темной темой

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

export const LightTheme5_2 = createThemeFromClass(class LightTheme5_2 extends BasicThemeClassForExtension {
  public static fontSizeSmall= '20px';
}, {
  prototypeTheme: LightTheme5_1,
  themeMarkers: [markThemeVersion(5, 2)],
});
export const DarkTheme5_2 = createThemeFromClass(class DarkTheme5_2 extends BasicThemeClassForExtension {
  public static fontSizeSmall= '20px';
}, {
  prototypeTheme: DarkTheme5_1,
  themeMarkers: [markThemeVersion(5, 2)],
});

Добавление новых переменных и компонентов

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

// packages/react-ui/internal/themes/BasicTheme.ts

...

export class BasicThemeClass {
  ...
+ public static newVariable = '#000';

Создание визуальных изменений

По сути, теперь в библиотеке может присутствовать одновременно несколько версий стилей и верстки одного и того же компонента. Конкретная версия будет зависеть от конкретной версии темы.

Для определения текущей версии темы добавлен новый хелпер:

const isThemeVersionGTE = (theme: Theme, major: number, minor: number) => boolean;

Суффикс GTE тут означает "greater than or equal", т.е. больше или равно (>=). Функция возвращает результат сравнения версии переданной в нее темы с указанными мажором и минором.

isThemeVersionGTE(theme5_5, 4, 0)  // true
isThemeVersionGTE(theme5_5, 5, 0)  // true
isThemeVersionGTE(theme5_5, 5, 5)  // true
isThemeVersionGTE(theme5_5, 5, 6)  // false
isThemeVersionGTE(theme5_5, 5, 10) // false
isThemeVersionGTE(theme5_5, 6, 0)  // false

Почему не semver (lite)

Например, не так:

isThemeVersionGTE(theme5_5, '5.0')

Я попробовал реализовать вариант сравнения строк вида 5.5 >= 5.0. Получается избыточно. Для полноценной реализации нужно подумать о корректном преобразовании строки в числа, о разделителях (. или _), написать много тестов. Если делать урезанную реализацию, только под этот кейс с темами, то вряд ли получится сделать проще, чем сейчас на двух готовых числах. Поэтому остановился на текущем. Если понадобится, сможем позже расширить или переделать.

Почему только GTE

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

Чтобы не ошибиться, думаю проще вовсе исключить такие проверки, которые могут привести к невыполнению названного условия (т.е. проверки на ==, <, <=).

Также, это способствует единообразному написанию стилей и верстки для разных версий тем.

Таким образом, предлагается использовать "наложение" визуальных правок друг на друга, чтобы упростить всю работу с версионированием тем.

Изменения в стилях

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

export styles = {
  root() {
    return css`
      ...
    `;
  },
  root5_1() {
    return css`
      text-transform: lowercase;
    `;
  },
  root5_2() {
    return css`
      font-style: italic;
    `;
  },
}

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

<Button className={cx(styles.root, {
    [styles.root5_1()]: isThemeVersionGTE(theme, 5, 1), 
    [styles.root5_2()]: isThemeVersionGTE(theme, 5, 2),
})} />

Изменения в верстке

В верстке важно соблюдать порядок сравнения версий и не забывать про дефолтное состояние.

const content = isThemeVersionGTE(theme, 1, 1) ? (
  <span>{children}</span>
) : isThemeVersionGTE(theme, 1, 0) ? (
  <div>{children}</div>
) : (
  <label>{children}</label>
);

<Button>{content}</Button>

Тестирование

Поскольку в тестах и сторибуке у нас всегда будет самая последняя версия темы, то при добавлении очередной версии нужно будет править дефолтные скриншоты.

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

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

// packages/react-ui/internal/ThemePlayground/__stories__/Theme5_0.stories.tsx
import React from 'react';

import { Button } from '../../../components/Button';
import { Story, Meta } from '../../../typings/stories';
import { ThemeContext } from '../../../lib/theming/ThemeContext';
import { LIGHT_THEME_5_0 } from '../../../lib/theming/themes/LightTheme';

export default {
  title: 'ThemeVersions/5_0',
  decorators: [
    (Story) => (
      <ThemeContext.Provider value={LIGHT_THEME_5_0}>
        <Story />
      </ThemeContext.Provider>
    ),
  ],
  parameters: {
    creevey: {
      skip: {
        'no themes': { in: /^(?!\bchrome2022\b)/ },
      },
    },
  },
} as Meta;

export const Button5_0: Story = () => <Button>Button</Button>;
Button5_0.storyName = 'Button5_1';

Удаление тем в мажорных релизах

Предполагается, что в мажорном релизе все промежуточные версии тем будут удаляться. Для этого все изменения переменных, которые они будут содержать на тот момент, будет необходимо последовательно перенести в базовую тему. А разные варианты верстки и стилей свести к единому, соответствующему самой актуальной теме, фактически убрав из кода все проверки на версию темы.

Тесты на промежуточные версии тем при этом можно будет просто удалить. А остальные тесты и скриншоты должны будут остаться без изменений.

Другие изменения В ПР

Смена стратегии именования версий

Было Стало
LIGHT_THEME_2022_0 LIGHT_THEME_5_0
LIGHT_THEME_2022_1 LIGHT_THEME_5_1

Это было обсуждено, согласовано и аргументировано выше. Имеет смысл привязываться к версии библиотеки. К тому же понятие THEME_2022 в рамках 5.0, где нет других тем, вроде как уже утратило значение.

Абстрактная или базовая тема

Думаю, термин "базовая тема" вызовет меньше вопросов у большинства пользователей (считая дизайнеров), поэтому выбрал его. Этот термин так или иначе будет всплывать в обсуждениях. Но это обсуждаемо.

Обратная совместимость

Класс BasicLightThemeInternal был переименован в BasicThemeClass и перемещен в соседний файл BasicTheme.ts. Однако старый класс используется в других наших пакетах для типизации их тем. Поэтому пока оставил прежний файл с прежним экспортом, но на новых рельсах, для обратной совместимости. А также добавил публичный экспорт нужной сущности (BasicThemeClassForExtension), чтобы другие пакеты смогли отвязаться от использования internal-сущности из react-ui.

BasicThemeClassForExtension это в принципе полезная сущность для удобной типизации тем и внутри react-ui. Название обсуждаемо.

Публичные и внутренние темы

Ранее в библиотеке было две локации хранения сущностей, связанных с темами:

  1. internal/themes/
  2. lib/theming/themes/

В первой хранились так называемые "internal"-темы. Это были классы с переменными, к которым там же применялся служебный хелпер exposeGetters.

Во второй локации хранились и экспортировались наружу уже "публичные" сущности тем, которые представляли из себя "internal"-темы, пропущенные через другие служебные хелперы и функцию ThemeFactory.create.

В этом ПР я постарался упростить существовавшую раньше схему до следующего вида:

  1. в internal/themes/ лежат внутренние, но полностью готовые и полноценные темы, для удобства и экономии разбитые на отдельные файлы (базовая тема и все версии конечных тем);
  2. в lib/theming/themes/ лежат публичные темы, которые являются просто ссылками на уже готовые внутренние темы (они экспортируются под публичными именами)

Таким образом должно быть проще создавать новые версии тем, применяя все служебные манипуляции с ними в одном месте. И, в целом, проще понимать где что лежит: в internal готовые, но служебные темы, а в lib только публичные ссылки на них.

ThemeFactory.create()

Раньше в публичных темах использовалась функция ThemeFactory.create({} , ...). От нее удалось избавиться, т.к. она казалась лишней. Мне видится, что это функция служит в основном для переопределения переменных и расширения наших тем конечными пользователями. А наши internal-темы могут быть уже готовыми к использованию.

Плюс, появился еще один нюанс. Внутри ThemeFactory есть сущность "тема по умолчанию". Она нужна, чтобы создавать полноценную тему на основе неполного списка переменных, переданных пользователем в ThemeFactory.create(). А также, для метода overrideBaseTheme, чтобы подменить тему по умолчанию (например, на темную).

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

Но у нас уже есть ссылка на самую актуальную версию "темы по умолчанию", это LIGHT_THEME. Появился смысл переделать ThemeFactory на ее использование.

После этого возможность использовать ThemeFactory.create для создания LIGHT_THEME пропала сама собой из-за циклической зависимости.

Другой нюанс вскрылся в том, что ThemeFactory.create() также делал публичные темы "замороженными", т.е. Readonly, закрытыми для изменений. И публичные темы реально должны быть такими. А новые внутренние темы сразу создаются такими, чтобы их можно было просто экспортировать без дополнительных манипуляций.

Но теперь это плохо работает для метода overrideBaseTheme. Дело в том, что он переопределяет свойства объекта через Object.defineProperty, но это невозможно на замороженных объектах. Раньше он применялся к внутренней BasicLightTheme, которая еще не была заморожена. А теперь к публичной замороженной LIGHT_THEME. Пришлось что-то менять.

Я заглянул в предыдущую реализацию ThemeFactory и нашел там решение проблемы через Object.create().

Суть в том, что объект с замороженным прототипом при обращении к их общих свойствам ведет себя тоже как замороженный, но позволяет переопределять его собственные свойства. Таким образом, удалось и сохранить замороженность тем и работоспособность метода overrideBaseTheme.

Добавил комментарий в код с описанием этого места ThemeFactory. Тесты на overrideBaseTheme уже были.

Ссылки

IF-2146

Чек-лист перед запросом ревью

  1. Добавлены тесты на все изменения
    ✅ unit-тесты для логики
    ✅ скриншоты для верстки и кросс-браузерности
    ⬜ нерелевантно

  2. Добавлена (обновлена) документация
    ⬜ styleguidist для пропов и примеров использования компонентов
    ⬜ jsdoc для утилит и хелперов
    ✅ комментарии для неочевидных мест в коде
    ⬜ прочие инструкции (README.md, contributing.md и др.)
    ⬜ нерелевантно

  3. Изменения корректно типизированы
    ✅ без использования any (см. PR 2856)
    ⬜ нерелевантно

  4. Прочее
    ✅ все тесты и линтеры на CI проходят
    ✅ в коде нет лишних изменений
    ✅ заголовок PR кратко и доступно отражает суть изменений (он попадет в changelog)

@zhzz zhzz marked this pull request as ready for review February 17, 2025 10:57
@zhzz zhzz changed the title feat: theme versions feat: add themes versioning Feb 17, 2025
@zhzz zhzz changed the title feat: add themes versioning feat: themes versioning Feb 17, 2025
@zhzz zhzz requested a review from sashachabin February 17, 2025 12:29
}, Object.create(theme));
}

export function createThemeFromClass<T extends object, P extends object>(
Copy link
Member

@mshatikhin mshatikhin Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

еще бы предложил упросить интерфейс функции (в один объект завернуть)
image

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Забыл сразу уточнить, почему один объект проще?

createThemeFromClass(class {...});
createThemeFromClass(class {...}, {
  prototypeTheme: ...,
  themeMarkers: ...,
})

vs

createThemeFromClass({ themeObject: class {...} });
createThemeFromClass({
  themeObject: class {...},
  prototypeTheme: ...,
  themeMarkers: ...,
})

В первом варианте меньше букв и есть подсказка про обязательность аргументов сразу в сигнатуре.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пример:

createTheme({
  themeClass: class {...},
  prototypeTheme: ...,
  themeMarkers: ...,
});

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Решили поменять createTheme, который принимает объект, а в нем поле themeClass.

prototypeTheme?: P;
themeMarkers?: Markers;
},
) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

еще бы хорошо добавить тип возвращаемого значения

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

хорошо бы

markedTheme = marker(theme);
});
return markedTheme;
export function applyMarkers<T extends object>(theme: T, markers: Markers): T {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут думаю что тоже можно тип сделать более узким typeof BasicThemeClass

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

надо попробовать

writable: false,
enumerable: false,
configurable: false,
},
});
};

export const isTheme2022 = (theme: Theme | ThemeIn): boolean => {
export const isThemeVersionGTE = (theme: Theme | ThemeIn, major: number, minor: number): boolean => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

слабая типизация, можно вызвать этот код вот так isThemeVersionGTE(ThemeFactory.create({}, TestTheme), 5, 0) - по типам все сойдется, но работать не будет,
изза внутренной особенности с "REACT_UI_THEME_MARKERS.themeVersion.key" - хорошо бы добавить это требование в тип

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему не будет работать? Если у переданной темы нет версии, то сравнение вернет false. Думаю, это норм поведение. В верстке и стилях будет какой-то фолбэк на такой случай (просто валидное поведение как у нулевой версии темы).

В реальности такой кейс возможен если мы сами забудем проставить версию для какой-то темы. Или если пользователь передаст какую-то полностью кастомную свою тему. Второй случай сложно представить, но поведение там все равно будет корректным. А вот на первый, кажется можно дописать тестов. Попробую.

Вообще, идейно темы задизайнены так, чтобы в них хранились только текстовые переменные, и ничего больше. Это помогает ограничить их сложность и ответственность. Иначе появляются соблазны положить в них много чего. Отражение наличия версии в типе будет противоречить этому дизайну. Да и нет неообходимости вроде, на самом деле.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил тестов на обязательное наличие версии у всех экспортируемых тем.

Copy link
Member Author

@zhzz zhzz Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Решили добавить ворнинг и выделить GTE в отдельную функцию

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cx(
  ifThemeNewer(theme, '5.1', styles.root5_1),
  ifThemeNewer(theme, '5.2', styles.root5_2),
)

@SchwJ
Copy link
Member

SchwJ commented Feb 24, 2025

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

@sashachabin
Copy link
Member

Предлагаю добавить тип у второго аргумента для автокомплита IDE:

type themeVersions = '5.0' | '5.1' | '5.2' | ...;

isThemeVersionGTE(theme5_5, '5.1')

Есть небольшой минус — это одно место куда нужно добавить версию. Но мы можем сделать тест при сборке, в котором мы сравниваем структуру файлов с темами и themeVersions.

@sashachabin
Copy link
Member

sashachabin commented Feb 25, 2025

И мелочь: если мы будем часто использовать isThemeVersionGTE в рендере, то можем немного упростить (навеяно cx):

isThemeVersionGTE(theme, '5.1')isThemeGTE(theme, '5.1')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants