# Cross-Import Resolution Patterns How to resolve cross-imports between slices on the same layer. Cross-imports are a code smell, not an absolute prohibition. The strategies below are ordered, but the right choice depends on the project context. ## What is a cross-import? A cross-import is an import between different slices within the same layer. For example: - importing `features/product` from `features/cart` - importing `widgets/sidebar` from `widgets/header` The `shared` and `app` layers do not have slices, so imports within those layers are not cross-imports. ## Why is this a code smell? Cross-imports blur domain boundaries and introduce implicit dependencies. Four concrete problems: 1. **Unclear ownership and responsibility.** When `cart` imports from `product`, it becomes unclear which slice owns the shared logic. Changes to `product`'s internal implementation can break `cart` without warning. This makes bugs harder to localize and code harder to reason about. 2. **Reduced isolation and testability.** A core benefit of sliced architecture is that each slice can be developed, tested, and deployed independently. Cross-imports break this isolation. Testing `cart` now requires setting up `product`, and changes in one slice can cause unexpected test failures in another. 3. **Increased cognitive load.** Working on `cart` now requires accounting for how `product` is structured. As cross-imports accumulate, tracing the impact of a change requires following more code across slice boundaries. 4. **Path to circular dependencies.** Cross-imports often start as one-way dependencies but evolve into bidirectional ones (A imports B, B imports A). This locks slices together and makes refactoring increasingly costly. ## Entities layer: prefer boundary merge over @x Cross-imports in `entities` are usually caused by splitting entities too granularly. Before reaching for `@x`, consider whether the boundaries should be merged instead. The `@x` notation is available as a dedicated cross-import surface for `entities`, but it should be treated as a **last resort**, a **necessary compromise**, not a recommended approach. Think of `@x` as an explicit gateway for unavoidable domain references, not a general-purpose reuse mechanism. Overuse locks entity boundaries together and makes refactoring more costly over time. ### How @x works (when boundary merge is genuinely impossible) Each entity exposes a special `@x/` directory containing files named after the consuming entity. This makes the cross-import explicit and auditable. **Direction rule:** in the path `entities/A/@x/B`, **A is the producer and B is the consumer**. Read it as "A crossed with B": the file `A/@x/B.ts` is the public API that A exposes specifically for B. So in the example below, `entities/user/@x/order.ts` is what `user` exposes to `order`, and `order` imports from it. ```text entities/ user/ @x/ order.ts ← Exposed specifically for the order entity model/ user.ts index.ts order/ model/ order-summary.ts ← Imports from user/@x/order index.ts ``` ```typescript // entities/user/@x/order.ts: exposes only what order needs export { getUserDisplayName } from "../model/user"; // entities/order/model/order-summary.ts import { getUserDisplayName } from "@/entities/user/@x/order"; ``` ### Rules when using @x 1. Document why `@x` is needed and why merging boundaries does not apply. 2. Review periodically. Requirements change and `@x` may become unnecessary. 3. Minimize the surface area of `@x` exports. 4. Only between entities. Features and widgets should use Strategy C or D below, not `@x`. ## Features and widgets: four strategies In `features` and `widgets`, multiple strategies are available depending on project context. Cross-imports here are not always forbidden; they are dependencies that should be deliberate. The four strategies below are listed in preferred order, but each fits different situations. ### Strategy A: Slice merge If two slices are not truly independent and always change together, merge them into a single larger slice. ```text // Before: two features that always change together features/profile/ features/profile-settings/ // After: one cohesive feature features/profile/ ui/ Profile.tsx ProfileSettings.tsx model/ profile.ts profile-settings.ts index.ts ``` If two slices keep cross-importing each other and effectively move as one unit, they are likely one feature in practice. Merging is often the simpler and cleaner choice. ### Strategy B: Push shared domain flows down into entities If multiple features share a domain-level flow, move that flow into a domain slice inside `entities`. Key principles: - `entities` contains domain types and domain logic only. - UI remains in `features` and `widgets`. - Features import and use the domain logic from `entities`. For example, if both `features/auth` and `features/profile` need session validation, place session-related domain functions in `entities/session` and reuse them from both features. ```text entities/ session/ model/ validate-session.ts session.ts index.ts features/ auth/ ui/LoginForm.tsx model/login.ts ← imports validateSession from entities/session index.ts profile/ ui/ProfilePanel.tsx model/profile.ts ← imports validateSession from entities/session index.ts ``` ### Strategy C: Compose from an upper layer (IoC) Instead of connecting slices within the same layer via cross-imports, compose them at a higher level (`pages` or `app`). The upper layer assembles and connects the slices; the slices themselves do not know about each other. Common Inversion of Control techniques: - **Render props (React)**: pass components or render functions as props. - **Slots (Vue)**: use named slots to inject content from parent components. - **Dependency injection**: pass dependencies through props or context. #### Basic composition (React) ```typescript // features/user-profile/index.ts export { UserProfilePanel } from "./ui/UserProfilePanel"; // features/activity-feed/index.ts export { ActivityFeed } from "./ui/ActivityFeed"; // pages/UserDashboardPage.tsx import { UserProfilePanel } from "@/features/user-profile"; import { ActivityFeed } from "@/features/activity-feed"; export const UserDashboardPage = () => (