Архитектурные принципы Light
Этот документ фиксирует принципы, которым должны соответствовать изменения фреймворка. Принципы — не рекомендации, а требования: если изменение им противоречит, это повод пересмотреть изменение, а не принцип.
Принцип 1: только используемый код попадает в бинарник
Light не должен «стоить» ресурсов пользователю, если он не пользуется
функциональностью. Каждая подсистема фреймворка — layout элементов, paint,
AccessKit-семантика, обработка событий, оконные возможности — обязана быть
опциональной на уровне Cargo-фич. Отсутствие фичи должно гарантированно
вырезать соответствующий код из бинарника (через #[cfg]), а не «оставить,
но не вызывать».
Правила для разработчиков Light
- Новый элемент HTML-разметки (
<foo>) добавляется вместе с новой фичейelements-foo. Без фичи разбор<foo>даёт ошибку компиляции с подсказкой включить фичу. - Новая подсистема (валидация, анимации, i18n, devtools, modal-окна, второй
главный экран, скриптинг и т. п.) добавляется вместе со своей фичей. Код
подсистемы гейтится через
#[cfg(feature = "...")]. - Кросс-элементные возможности (курсор, z-порядок, align-items, динамические атрибуты) получают свои фичи, если их код сколько-нибудь существенный.
Default-набор фич должен быть минимальным, но практичным: базовые элементы + окно. Всё остальное — opt-in.- Каждая фича должна иметь rustdoc-документацию в
Cargo.toml(секция[features]с комментариями) и раздел вguide.md. - При добавлении фичи обязательно обновить:
crates/light/Cargo.toml(секция[features]);docs/guide.md(раздел «Установка и Cargo-фичи»);CHANGELOG.md.
- Проверка на регрессию:
cargo build -p light --no-default-featuresобязан собираться.cargo build -p light --no-default-features --features <X>обязан собираться для каждой фичиXв отдельности (кроме фич, явно требующих зависимостей). - Размер бинарника — метрика, которую мы измеряем. Раз в N релизов прогоняем
cargo bloatи сравниваем с предыдущим замером. Рост без обоснования — повод для ревью.
Компромисс по Style
Поля light::vdom::Style остаются объявленными всегда (несколько байт на
элемент). Гейтится не структура данных, а логика, которая эти поля
использует, и генерация разбора соответствующих атрибутов макросом. Это
позволяет сохранить PartialEq, Default и предсказуемость Style без
условной компиляции на уровне полей.
Как это устроено технически
- Фичи
lightобъявляются вcrates/light/Cargo.tomlи форвардятся вlight-fw-macros(elements-select = [..., "light-fw-macros/elements-select"]), чтобы#[template]знал о включённых элементах. - Варианты
NodeKind, относящиеся к опциональным элементам, гейтятся#[cfg(feature = "...")]. Как следствие, все ихmatch-ветки компилятор требует закрыть#[cfg]— мёртвый код исчезает сам. - Сообщение об ошибке при использовании выключенного элемента называет
недостающую фичу и подсказывает строку для
Cargo.toml.
Почему это важно
- Нативные приложения на Rust ценятся за малый footprint. Раздутый фреймворк обесценивает главный аргумент в пользу Rust-стека.
- Меньше кода — меньше поверхность атаки, меньше шансов на баги в неиспользуемых путях.
- Compile-time тоже сокращается: меньший крейт компилируется быстрее.
Принцип 2: фичи не должны ломать --all-features
Любое сочетание фич обязано собираться. --all-features — это эталон
корректности: фича, которая компилируется только в одиночку, не считается
готовой. Обратное тоже верно: базовый набор (--no-default-features плюс
минимальные фичи) должен оставаться рабочим.
Потенциальные будущие улучшения
light-features-suggest: утилита, которая сканирует#[template(...)]и манифест проекта, находит используемые элементы и атрибуты и предлагает минимальный набор фич. Не реализована: требует рекурсивного разбора шаблонов и манифестов, не критично для основной задачи.docs/footprint-report.md: замерыcargo bloat — --crates(сравнение с предыдущим релизом).- Разделение на подкрейты (
light-fw-core,light-fw-element-form,light-fw-behavior-validation, …) — начато: ядро вынесено вlight-fw-core, компоненты формы и валидации — в отдельные крейты,lightстал тонкой мета-обёрткой (см.docs/COMPONENT_MODEL.md).
Пример: ошибка при использовании гейтнутого элемента
Гейтинг работает на этапе компиляции: если шаблон использует элемент,
принадлежащий выключенной фиче, #[template] не «молча» превращает его в
неизвестный узел, а возвращает понятную ошибку с именем фичи и готовой
подсказкой.
Шаблон templates/form.html:
<select v-model="country"></select>
src/main.rs без фичи elements-select (light = { default-features = false, features = ["elements-basic"] }):
#![allow(unused)]
fn main() {
use light::prelude::*;
#[template("templates/form.html")]
struct Form;
}
Компилятор сообщает:
error: элемент <select> недоступен: Cargo-фича `elements-select` выключена у крейта `light`
help: включите её в зависимости, например `light = { features = ["elements-select"] }`
Элемент нужно подключать явно:
[dependencies]
light = { version = "0.1", features = ["elements-basic", "elements-select"] }
Аналогично для <checkbox> (elements-checkbox), <radio> (elements-radio)
и <password> (elements-input). Тег <text>/<textarea> входит в
elements-input и гейтится так же.