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

chore: add post about cross-imports #711

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,124 @@ import WIP from '@site/src/shared/ui/wip/tmpl.mdx'

<WIP ticket="220" />

> Cross-imports appear when the layer or abstraction begins to take too much responsibility than it should. That is why the methodology identifies new layers that allow you to uncouple these cross-imports
> Cross-imports appear when a layer/abstraction starts taking on too much responsibility than it should. That is why the methodology allocates new layers that allow these cross-imports to be decoupled.

## See also
## Introduction {#start}

- [(Thread) About the supposed inevitability of cross-ports](https://t.me/feature_sliced/4515)
- [(Thread) About resolving cross-ports in entities](https://t.me/feature_sliced/3678)
**Cross-import** is a situation in which, within a layer, there is a need to import data from one slice to another.

:::warning Important

The article only deals with conflict situations when importing between **slices**. Within one slice, fragments can import each other, such a situation is not prohibited.

:::

Let's imagine that we are developing a TODO List application for storing notes. We need to store several task cards and display them on a board. There can be several boards with cards.
Let's look at a simplified example of the structure of such an application:

- `📂 entities`
- `📂 task`
- `📁 ui`
- `📄 TaskCard`
- `📁 board`
- `📁 ui`
- `📄 BoardTasks`

In this example, the `BoardTasks` component needs to import the `TaskCard` component to display cards.
You cannot import the `TaskCard` component directly, as this would violate the principle of low coupling and make it more difficult to maintain the project in the future.

## Cross-import solutions options {#solutions}

### Merging multiple slices into one {#slices-merge}
As you can see from the example above, the domain of the `TaskCard` and `BoardTasks` components are quite similar, which means it is possible to create a derived slice from the two existing ones, where both methods would be in the same directory.

The result of merging the `task` and `board` slices:
- `📂 entities`
- `📂 board`
- `📁 ui`
- `📄 TaskCard`
- `📄 BoardTasks`

As you can see from the example, we have eliminated the situation where one slice imports data from another and achieved high cohesion and low connectivity within a segment of a single slice.

### Moving code to a higher layer {#slices-moving}
This option is more suitable for those cases where combining several slices into one is difficult or impossible.

It is likely that the slice into which you are importing data from another slice is highly logically rich and can be moved to a higher layer to eliminate the connectivity of slices within a single layer.

Let's imagine that the `board` slice is more dense with business logic than in the example above, then merging it with the `task` slice will create additional clutter, so a good solution would be to restructure the logic of the `board` slice in a higher layer.

The result of moving the logic of the `task` and `board` slices:

- `📂 entities`
- `📂 task`
- `📁 ui`
- `📄 TaskCard`
- `📂 features`
- `📁 view-board`
- `📁 ui`
- `📄 BoardTasks`

By restructuring the logic of the `board` slice on the `features` layer (not necessarily on it, any layer higher in the hierarchy will do), its domain area was more specifically clarified, and it became possible to build a correct import mechanism from the point of view of the layer hierarchy.

### Duplication code {#duplication-code}
If the amount of data you are going to import from another slice is small, it may be worth duplicating it into the current slice. However, this option should not be a priority among all the above.

## What if cross-import is inevitable? {#cross-import-is-inevitable}

:::caution Warning

The approach described in this section is experimental and has not yet been standardized. However, in exotic situations, the information in this section may be useful.

:::

If your project requires cross-import and the three methods described above did not help, the Feature Sliced ​​Design community offers the following solution to the problem.

- `📂 entities`
- `📂 task`
- `📁 ui`
- `📄 TaskCard`
- `@x`
- `📁 board`
- `📁 ui`
- `📄 BoardTasks`
- `@x`

### @x {#export-private-api}
**@x** - this is an experimental approach where within a slice you can create an explicit public export of only those modules that can be used within multiple slices of the same layer.

Let's look at an example (TSX markup):
```ts title="entities/task/@x.ts"
export { TaskCard } from './ui/TaskCard.tsx';
```
Component inside slice that needs cross import:
```tsx title="entities/board/ui/BoardTasks.tsx"
import { TaskCard } from '/entities/task/@x/TaskCard';

export const BoardTasks = () => {  return (
    <div>
      <TaskCard title="Task#1" description="Description..." />
      <TaskCard title="Task#2" description="Description..." />
      <TaskCard title="Task#3" description="Description..." />
    </div>
  )
}
```
As you can see from the import path in the `BoardTasks` file, thanks to the export from the @x file, it is clear that this module is not intended for public use, but exists only to be imported into another slice.

### Lifting the ban on cross-imports {#disable-cross-imports-rule}
If you have tried all the methods in the article, but for some reason still could not get rid of cross-imports, perhaps it is worth considering lifting the ban on them within specific slices, or the entire project as a whole.
However, it is important to understand that lifting the ban on cross-imports could potentially have a negative impact on the overall structure of the project.

## Useful links {#useful-links}

1. The Feature Sliced ​​Design team has developed a tool that allows you to automatically check for cross-imports in a project: [Steiger](https://github.com/feature-sliced/steiger) 
2. More information about the cross-import rule: [Steiger-Forbidden-Imports](https://github.com/feature-sliced/steiger/blob/master/packages/steiger-plugin-fsd/src/forbidden-imports/README.md) 

## See also {#see-also}

- [(Thread) On the supposed inevitability of cross-imports](https://t.me/feature_sliced/4515)
- [(Thread) About resolving cross-imports in entities](https://t.me/feature_sliced/3678)
- [(Thread) About cross-imports and responsibility](https://t.me/feature_sliced/3287)
- [(Thread) About imports between segments](https://t.me/feature_sliced/4021)
- [(Thread) About cross-imports inside shared](https://t.me/feature_sliced/3618)
- [(Thread) About cross-imports inside shared](https://t.me/feature_sliced/3618)
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,126 @@ import WIP from '@site/src/shared/ui/wip/tmpl.mdx'

<WIP ticket="220" />

> Кросс-импорты появляются тогда, когда слой/абстракция начинает брать слишком много ответственности, чем должна. Именно поэтому методология выделяет новые слои, которые позволяют расцепить эти кросс-импорты
> Кросс-импорты появляются тогда, когда слой/абстракция начинает брать слишком много ответственности, чем должна. Именно поэтому методология выделяет новые слои, которые позволяют расцепить эти кросс-импорты.

## Введение {#start}

**Кросс-импорт** - ситуация, при которой в рамках какого-либо слоя появилась необходимость импорта данных одного слайса в другой.

:::warning Важно

Речь в статье идёт только о конфликтных ситуациях при импортах между **слайсами**. В рамках одного слайса, фрагменты могут импортировать друг друга, такая ситуация не запрещена.

:::

Представим, что разрабатываем приложение TODO List для хранения заметок. Необходимо хранить несколько карточек с задачами и отображать их на доске. Досок с карточками может быть несколько.
Рассмотрим упрощённый пример структуры такого приложения:

- `📂 entities`
- `📂 task`
- `📁 ui`
- `📄 TaskCard`
- `📁 board`
- `📁 ui`
- `📄 BoardTasks`

В данном примере компоненту `BoardTasks` для отображения карточек необходим импорт компонента `TaskCard`.
Импортировать компонент `TaskCard` напрямую нельзя, так как это нарушит принцип низкой связности и усложнит поддержку проекта в дальнейшем.

## Варианты решения кросс-импортов {#solutions}

### Объединение нескольких слайсов в один {#slices-merge}
Как видно из примера выше, доменная область компонентов `TaskCard` и `BoardTasks` довольно похожа, а значит существует возможность создания производного слайса из двух существующих, где оба метода находились бы в одной директории.

Результат объединения слайсов `task` и `board`:

- `📂 entities`
- `📂 board`
- `📁 ui`
- `📄 TaskCard`
- `📄 BoardTasks`

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

### Перемещение кода на более высокий слой {#slices-moving}
Данный вариант скорее подходит для тех случаев, для которых объединение нескольких слайсов в один затруднено или невозможно.

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

Представим, что слайс `board` более насыщен бизнес-логикой, чем на примере выше, тогда объединение его со слайсом `task` создаст дополнительный беспорядок, поэтому хорошим решением будет реструктуризовать логику слайса `board`, в более высоком слое.

Результат перемещения логики слайсов `task` и `board`:

- `📂 entities`
- `📂 task`
- `📁 ui`
- `📄 TaskCard`
- `📂 features`
- `📁 view-board`
- `📁 ui`
- `📄 BoardTasks`

Реструктуризовав логику слайса `board` на слое `features` (не обязательно на него, подойдёт любой слой выше по иерархии), его доменная область более конкретно уточнилась появилась возможность построить правильный с точки зрения иерархии слоёв механизм импортов.

### Дублирование кода {#duplication-code}
Если объем данных, который Вы собираетесь импортировать из другого слайса невелик, возможно стоит будет продублировать его в текущий слайс. Тем не менее, такой вариант не должен являться приоритетным, среди всех вышеописанных.

## А если кросс-импорт неизбежен? {#cross-import-is-inevitable}

:::caution Внимание

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

:::

Если в Вашем проекте необходим кросс-импорт и три вышеописанных способа не помогли, сообщество Feature Sliced Design предлагает следующий вариант решения проблемы.

- `📂 entities`
- `📂 task`
- `📁 ui`
- `📄 TaskCard`
- `@x`
- `📁 board`
- `📁 ui`
- `📄 BoardTasks`
- `@x`

### @x {#export-private-api}
**@x** - это эксперементальный подход, когда внутри слайса можно создать явный публичный экспорт только тех модулей, которые можно использовать в рамках нескольких слайсов одного слоя.

Рассмотрим пример (разметка TSX):
```ts title="entities/task/@x.ts"
export { TaskCard } from './ui/TaskCard.tsx';
```
Компонент внутри слайса, которому необходим кросс-импорт:
```tsx title="entities/board/ui/BoardTasks.tsx"
import { TaskCard } from '/entities/task/@x/TaskCard';

export const BoardTasks = () => {  return (
    <div>
      <TaskCard title="Task#1" description="Description..." />
      <TaskCard title="Task#2" description="Description..." />
      <TaskCard title="Task#3" description="Description..." />
    </div>
  )
}
```
Как видно по пути импорта в файле `BoardTasks`, благодаря экспорту из файла @x видно, что данный модуль не предназначен для публичного использования, а существует только для того, чтобы быть импортированным внутрь другого слайса.

### Отмена запрета на кросс-импорты {#disable-cross-imports-rule}
Если Вы попробовали все способы в статье, но по каким-либо причинам всё равно не смогли избавиться от кросс-импортов, возможно, стоить задуматься о том, чтобы снять запрет на них в рамках конкретных слайсов, либо всего проекта в целом.
Однако, важно понимать, что отмена запрета на кросс-импорты, потенциально может негативно сказываться на общей структуре проекта. 

## Полезные ссылки {#useful-links}

1. Команда Feature Sliced Design разработала инструмент, позволяющий автоматически проверить наличие кросс-импортов в проекте: [Steiger](https://github.com/feature-sliced/steiger) 
2. Больше информации о правиле кросс-импортов: [Steiger-Forbidden-Imports](https://github.com/feature-sliced/steiger/blob/master/packages/steiger-plugin-fsd/src/forbidden-imports/README.md) 

## См. также {#see-also}

- [(Тред) Про предполагаемую неизбежность кросс-импортов](https://t.me/feature_sliced/4515)
- [(Тред) Про резолвинг кросс-импортов в сущностях](https://t.me/feature_sliced/3678)
- [(Тред) Про кросс-импорты и ответственность](https://t.me/feature_sliced/3287)
- [(Тред) Про импорты между сегментами](https://t.me/feature_sliced/4021)
- [(Тред) Про кросс-импорты внутри shared](https://t.me/feature_sliced/3618)
- [(Тред) Про кросс-импорты внутри shared](https://t.me/feature_sliced/3618)