BetaДокументация для beta‑теста, возможны ошибки и неточности.
Перейти к содержимому

Common ​

Common — уровень, определяющий сущности для общего переиспользования. Это сущности, которые не привязаны к конкретной бизнес-логике и могут использоваться в любом месте проекта. А также одиночные сущности которые сложно причислить к какому-то конкретному модулю.

Важно

Несмотря на сходство common из FEOD и shared из FSD, они имеют разные цели и правила использования. Подробнее об отличии еще будет ниже.

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

Возможное альтернативное название: ​

  • shared — привычнее для пользователей FSD. Но стоит осознавать, что это не совсем то же самое что это не тот же самый shared из FSD.

Важность уровня ​

На самом деле данный уровень является опциональным и утилитарным. Он решает задачу: «куда положить сущности которые не привязаны ни к чему не привязаны и слишком мелкие чтобы стать полноценным модулем».

На самом деле common решается множество задач которые могут быть затруднительными если от него избавиться:

модуль ради модуля ​

Благодаря данному слою множество модулей которые вмещались бы в 1 файл и сложно объединить в один модуль будут иметь своё место. Вам не придется создавать новый модуль ради одной сущности.

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

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

Плавное привыкание после FSD ​

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

Как понять, что сущность — это часть Common? ​

  • Это не привязанная к конкретным бизнес-фичам логика
    • Если сущность привязана к бизнес-фиче, то лучше переместить её в соответствующий модуль
  • Вмещается в один файл
    • Если сущности необходимо разделить на несколько файлов, то лучше переместить её в отдельный модуль
  • В теории может быть использована в любом месте
    • Если это не универсальная сущность, то лучше переместить её в соответствующий модуль
  • Ей не нужно взаимодействовать с сущностями уровня Module
    • Если сущность взаимодействует с сущностями уровня Module, то лучше переместить её в соответствующий модуль или использовать IoC

Границу нельзя вычислить

Однозначного правила «common или модуль» нет, как нет его для «модуль или подмодуль». Практические ориентиры: сколько связей у сущности с другими и есть ли в ней бизнес-логика. Если связей много или логика бизнесовая, это кандидат в модуль. Если сущность без бизнес-логики и почти ни с чем не связана, она может жить в common. Решение остаётся за командой.

Важные правила ​

  • В отдельных случаях возможно использование двух и более файлов на одну сущность, но рекомендуется этого избегать и предпочитать в этом случае Module
  • Common могут взаимно использовать друг друга, пока это не создаёт слишком жёсткие связи
  • Сущности в Common не могут использовать индексные файлы

Почему common не импортирует modules ​

Главный довод: common образует набор листьев графа зависимостей. Пока он ничего не импортирует из modules, цикл с его участием невозможен, и проверять циклы нужно только среди модулей. Поэтому исключения из этого правила (см. «UI-модуль и Common» ниже) стоит делать осознанно: каждое из них ослабляет эту гарантию.

Почему запрещено использовать индексные файлы? ​

  • Это создаёт ненужные файлы, которые не несут в себе смысловой нагрузки
  • Меньше ненужных действий для создания сущности (не нужно создавать индексный файл и добавлять в него все импорты + следить за этим)
  • Это мотивирует при необходимости использовать модули
  • Это косвенно вынуждает держать уровень common тонким и не засорять его

Структура ​

  • common
    • ui
      • Button.vue
      • Input.vue
      • index.ts
    • composables
      • useDebounce.ts
      • useLocalStorage.ts
      • index.ts
    • utilities
      • formatDate.ts
      • hasEmailShape.ts
      • index.ts
    • types
      • api.types.ts
      • index.ts

Зачёркнутые index.ts показывают запрещённые barrel-файлы. Сущности common импортируются напрямую из своих файлов.

Примеры сущностей Common ​

Единый пример собирает UI, композаблы, утилиты и типы в одном дереве и наборе файлов.

  • common
    • ui
    • composables
    • utilities
    • types

Выберите файл в дереве слева

Общий тип не знает, какой модуль получает ответ. Потребитель задаёт тип данных:

ts
// modules/UserManagement/types/usersResponse.ts
import type { ApiResponse } from '@/common/types/api.types'
import type { User } from './user.types'
export type UsersResponse = ApiResponse<User[]>

Использование Common в проекте ​

Правильный импорт ​

ts
import Button from '@/common/ui/Button.vue'
import { formatDate } from '@/common/utilities/formatDate'
import { useDebounce } from '@/common/composables/useDebounce'

// ❌ Неправильно (индексные файлы запрещены)
import { Button, formatDate, useDebounce } from '@/common'

Использование IoC для работы с модулями ​

Если common сущность нуждается в функциональности из модуля, используйте IoC:

ts
// common/composables/useNotification.ts
export interface NotificationService {
  show(message: string): void
}

export function useNotification(service: NotificationService) {
  return { notify: (message: string) => service.show(message) }
}

Реализацию выбирает app; вызов через объект сохраняет this сервиса.

ts
// modules/Notifications/index.ts
export class Notifications {
  constructor(private container: HTMLElement) {}

  show(message: string): void {
    this.container.textContent = message
  }
}
ts
// app/integrations/notifications.ts
import { useNotification } from '@/common/composables/useNotification'
import { Notifications } from '@/modules/Notifications'

const container = document.querySelector<HTMLElement>('#notifications')
if (!container) throw new Error('Нет контейнера уведомлений')
export const { notify } = useNotification(new Notifications(container))
notify('Изменения сохранены')

Контейнер #notifications находится в HTML приложения. common зависит только от интерфейса; app выбирает реализацию из Notifications.

FAQ ​

Строгий ли запрет на index.ts в Common? ​

Нет. Но это настоятельная рекомендация, которая помогает держать common уровень тонким и не засорять его. Мы настаиваем, чтобы вы 10 раз подумали перед тем, как начать использовать индексные файлы в common.

Это решение было принято для нескольких целей:

  • Сборщику придётся меньше бороться с barrel-файлами, что улучшает производительность проекта. В случае модулей мы вынуждены использовать index.ts-файлы, чтобы обеспечить публичный интерфейс модуля, но у common такой необходимости нет
  • Это мотивирует при необходимости использовать модули, так как связывать сущности между собой становится сложнее
  • Это косвенно вынуждает держать уровень common тонким и не засорять его, так как при разрастании импорты из common становятся более громоздкими

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

Когда выносить сущности из Common? ​

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

Это нормально если по мере жизни проекта вы будете выносить эти сущности в модули. Во многом это и есть задача common слоя.

Как использовать сущность из модуля, если это необходимо в Common? ​

Для этого можно применить IoC (Inversion of Control). И прокинуть необходимую сущность в Common.

UI-модуль и Common ​

Так как UI предполагает между собой тесно связанные элементы, то лучше держать его в отдельном модуле, а common оставить для примитивных сущностей. Однако нам часто необходимо использовать компоненты из UI-модуля в common-уровне. В этом случае у нас есть несколько путей: IoC или же исключение из правила про запрет на импорты modules из common. Да, это плохо. Но лучшее — враг хорошего: порой лучше поступиться, чем значительно усложнить проект. Либо же оформить его как псевдо-модуль внутри common.

Чем common отличается от однофайлового модуля? ​

Обе формы нужны для маленьких сущностей, которым не нужен полноценный модуль. Различий два. Однофайловый модуль может зависеть от других модулей, а common нет, поэтому common остаётся набором листьев. И для common не нужен публичный API (индексный файл), а для модуля он есть всегда. Если сущность зависит от модулей, ей место среди модулей, даже если это один файл.

Common как модуль ​

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

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

Однако существуют модификации FEOD, которые позволяют common быть модулем. Подробнее об этом вы можете прочитать в разделе Common Module.

Design System — это Common? ​

  • Design System не вполне укладывается в common-уровень.
  • Однако из-за сильной связанности элементов между собой, лучше держать его в отдельном модуле, а common оставить для примитивных сущностей.
  • Однако если это просто набор базовых компонентов для отображения, не образующих дизайн-систему, то это может быть Common.
  • Если вы используете стороннюю библиотеку компонентов, то вы вполне можете держать здесь переопределения компонентов и прочие донастройки.

Отличия common из FEOD и shared из FSD ​

В графе зависимостей common из FEOD и shared из FSD стоят на одном месте: внизу, ничего не импортируя из других слоёв, и могут взаимно использовать друг друга, пока это не создаёт слишком жёстких связей. Роль у них разная.

  • shared в FSD — постоянная база, из которой строятся остальные слои. В версии 2.1 он тоже наполняется извлечением из страниц и виджетов, но то, что туда попало, обычно остаётся там.
  • common в FEOD — перевалочный пункт для сущностей, которые ещё не оформились в модуль. Когда они оформляются, они уходят в модуль.

Отсюда разница в правилах. В common индексные файлы не рекомендуются (в shared FSD они стандартны), глубина ограничена, а связанные сущности (UI-кит, дизайн-система) чаще живут в отдельном модуле. common не пытается быть слоем абстракции, из которого строятся остальные.

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

Связанные разделы ​

  • Modules — модули, использующие common-сущности
  • Global — глобальные сущности, доступные без импорта
  • Pages — страницы, использующие common компоненты