fractal-tree-file-structure

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Fractal tree file structure

分形树文件结构

This project follows a fractal tree approach to file organization, where the structure of any part mirrors the whole. This self-similar organization allows confident navigation without needing to understand the entire codebase.
本项目采用分形树方法进行文件组织,即任何部分的结构都与整体相似。这种自相似的组织方式让你无需了解整个代码库即可轻松导航。

Core principles

核心原则

  • Recursive structure: Every directory follows the same organizational patterns, creating predictable navigation at any depth.
    Developers should not need to learn the entire codebase structure to contribute meaningfully to any section.
  • No circular dependencies: Imports must form a directed acyclic graph. Circular import chains turn the fractal tree into a generic graph, breaking the tree's integrity and causing runtime issues.
  • Organic growth: Start with a single file; extract to subdirectories only when complexity demands it. No boilerplate structure upfront. Group resources by functional purpose, never by file "shape" (no project-wide
    components/
    ,
    hooks/
    , or
    utils/
    folders).
  • Encapsulation: Resources in a subdirectory are internal to the parent file unless explicitly re-exported. A
    shapes/
    directory is "owned" by
    shapes.ts
    . Direct imports from nested levels are prohibited—each sub-tree exports resources that can only be imported on the next level up.
  • Contextual sharing: Common logic lives at the closest common ancestor ("fork" in the tree). The
    shared/
    directory exists at the
    src/
    level because multiple entrypoints need it. Place shared logic as deep in the tree as possible while still serving all dependents.
  • Present-state focus: Structure reflects current reality, not anticipated future needs. Refactor freely as usage patterns evolve. This eliminates over-engineering and enables formal linting enforcement.
  • 递归结构:每个目录都遵循相同的组织模式,在任何深度都能实现可预测的导航。 开发者无需学习整个代码库的结构,就能在任意部分做出有意义的贡献。
  • 无循环依赖:导入必须形成有向无环图。 循环导入链会将分形树变成通用图,破坏树的完整性并导致运行时问题。
  • 有机增长:从单个文件开始;仅当复杂度要求时才提取到子目录。 无需预先设置样板结构。 按功能用途对资源进行分组,绝不要按文件“类型”分组(不要在项目层面设置
    components/
    hooks/
    utils/
    文件夹)。
  • 封装性:子目录中的资源默认归属于父文件,除非显式重新导出。 例如
    shapes/
    目录归
    shapes.ts
    所有。 禁止直接从嵌套层级导入——每个子树导出的资源只能在其上一层级导入。
  • 上下文共享:通用逻辑存放在最近的公共祖先(树中的“分叉点”)。
    src/
    层级的
    shared/
    目录是因为多个入口点都需要它。 应将共享逻辑放在尽可能深的树层级中,同时仍能服务所有依赖项。
  • 聚焦当前状态:结构反映当前实际情况,而非预期的未来需求。 随着使用模式的演变,可自由重构。 这能避免过度设计,并支持通过linting进行形式化约束。

Practical rules

实用规则

Naming

命名规范

All files and folders use kebab-case for cross-platform compatibility with case-sensitive filesystems. Enforced via
unicorn/filename-case
.
所有文件和文件夹均使用kebab-case命名,以兼容区分大小写的文件系统。 通过
unicorn/filename-case
规则强制执行。

No index files

不使用index文件

Avoid
index.ts
files that enable implicit folder imports. They cause path ambiguity where
./foo
could resolve to both
foo.ts
and
foo/index.ts
, and they hurt ESM compatibility.
避免使用
index.ts
文件实现隐式文件夹导入。 它们会导致路径歧义,例如
./foo
可能同时解析为
foo.ts
foo/index.ts
,并且会损害ESM兼容性。

Files as mini-libraries

文件作为迷你库

Each file acts as a self-contained "mini-library" with cohesive exports serving a common semantic purpose. If a file contains only one export, name the file after that export. Avoid default exports unless externally required.
每个文件都作为一个独立的“迷你库”,其导出内容紧密相关,服务于共同的语义目标。 如果一个文件仅包含一个导出项,则以该导出项命名文件。 除非外部要求,否则避免使用默认导出。

Outgrown files become sub-trees

超出承载的文件转为子树

When a file grows unwieldy, extract logic into a sibling subdirectory bearing the original filename:
text
my-app.ts → my-app.ts (keeps public API)
          → my-app/
              ├── config.ts
              ├── lifecycle.ts
              ├── lifecycle/
              │   ├── something.ts
              │   └── something-else.ts
              └── helpers.ts
Only
my-app.ts
imports from the
my-app/
directory, and only
lifecycle.ts
imports from the
lifecycle/
directory – each file owns its namespace. If
my-app.ts
becomes unused, delete it together with its internal folder safely.
当一个文件变得难以维护时,将逻辑提取到与原文件同名的兄弟子目录中:
text
my-app.ts → my-app.ts(保留公共API)
          → my-app/
              ├── config.ts
              ├── lifecycle.ts
              ├── lifecycle/
              │   ├── something.ts
              │   └── something-else.ts
              └── helpers.ts
只有
my-app.ts
可以从
my-app/
目录导入,只有
lifecycle.ts
可以从
lifecycle/
目录导入——每个文件都拥有自己的命名空间。 如果
my-app.ts
不再被使用,可以安全地将其与内部文件夹一起删除。

Relative paths within workspaces

工作区内的相对路径

All imports within a workspace use relative paths. Avoid mixing path alias systems (e.g.
@/foo
) with relative imports, as this creates inconsistency. (This project uses
@/
aliases for the
src/
root as a convention.)
工作区内的所有导入均使用相对路径。 避免将路径别名系统(如
@/foo
)与相对导入混合使用,这会导致不一致。 (本项目使用
@/
别名作为
src/
根目录的约定。)

shared/
folder convention

shared/
文件夹约定

Shared resources between sub-trees go into
path/to/common-parent/shared/
. Think of
shared/
folders as lightweight
node_modules/
. Contents of parent-level
shared/
folders remain accessible, but sub-tree
shared/
folders are internal to that sub-tree.
子树之间的共享资源放入
path/to/common-parent/shared/
目录。 可以将
shared/
文件夹视为轻量级的
node_modules/
。 父层级
shared/
文件夹的内容仍可访问,但子树的
shared/
文件夹仅对该子树内部可见。

Multiple entry points

多入口点

Projects may have several entry points (pages, API handlers, scripts, tests). Keep their names distinct from mini-libraries using suffixes:
do-something.script.ts
,
xyz.test.tsx
. Entry points access shared resources but remain outside core logic.
项目可能有多个入口点(页面、API处理程序、脚本、测试)。 使用后缀区分它们与迷你库,例如:
do-something.script.ts
xyz.test.tsx
。 入口点可以访问共享资源,但不属于核心逻辑。

Colocate unit tests

单元测试与代码同目录

Place unit tests beside the files they cover:
foo.ts
pairs with
foo.test.ts
. Integration and end-to-end tests live in separate directories outside the source tree root.
将单元测试放在其对应的文件旁边:
foo.ts
foo.test.ts
配对。 集成测试和端到端测试存放在源码树根目录之外的单独目录中。

Exceptions are permitted

允许例外情况

Partial adoption works. Gradually migrate from leaves toward the root. Imperfect implementation still provides benefits by clarifying dependencies in sections of larger codebases.
可以部分采用该规范。 从叶子节点逐步向根节点迁移。 即使实现不完美,也能通过明确大型代码库各部分的依赖关系带来收益。

Scoped directories with
@
prefix

@
前缀的作用域目录

Directories prefixed with
@
group related utilities under a namespace, similar to npm scoped packages:
text
src/shared/
├── @foo/
|  ├── a.ts
|  └── b.ts
├── @bar/
|  ├── m.ts
|  └── n.ts
├── x.ts
└── y.ts
This prevents naming collisions and clearly signals "this is a utility namespace, not a feature."
@
为前缀的目录将相关工具按命名空间分组,类似于npm作用域包:
text
src/shared/
├── @foo/
|  ├── a.ts
|  └── b.ts
├── @bar/
|  ├── m.ts
|  └── n.ts
├── x.ts
└── y.ts
这可以防止命名冲突,并清晰地表明“这是一个工具命名空间,而非功能模块”。

Import rules

导入规则

As a consequence of encapsulation, imports should only target "public" resources:
typescript
// ✓ Correct: import from the mini-library entry point
import { something } from "../../../shared/foo.ts";
import { other } from "../../../shared/@scope/bar.ts";

// ✗ Incorrect: import from internal files (owned by their parent)
import { internal } from "../../../shared/foo/helpers.ts";
import { deep } from "../../../shared/@scope/bar/internal.ts";

// ✗ Incorrect: import from a scope directly (like npm, scopes aren't packages)
import { wrong } from "../../../shared/@scope";
基于封装性的要求,导入应仅针对“公共”资源:
typescript
// ✓ 正确:从迷你库入口点导入
import { something } from "../../../shared/foo.ts";
import { other } from "../../../shared/@scope/bar.ts";

// ✗ 错误:从子树的内部文件导入(绕过封装)
import { internal } from "../../../shared/foo/helpers.ts";
import { deep } from "../../../shared/@scope/bar/internal.ts";

// ✗ 错误:直接从作用域导入(与npm不同,作用域不是包)
import { wrong } from "../../../shared/@scope";

Organic growth example

有机增长示例

A real project evolves step by step. Starting with a single file:
text
example.ts
Extract when necessary:
text
example.ts
example/
├── do-x.ts
└── do-x.test.ts
Add shared logic between extracted modules:
text
example.ts
example/
├── shared/
│   └── do-common-thing.ts
├── do-x.ts
└── do-y.ts
When a second entry point (
example-2.ts
) needs something previously nested, promote it to the closest common ancestor:
text
shared/
└── bar.ts
example.ts
example/
├── shared/
│   └── do-common-thing.ts
├── do-x.ts
└── do-y.ts
example-2.ts
Each step reflects actual code relationships without predicting future needs.
一个真实项目会逐步演变。 从单个文件开始:
text
example.ts
必要时进行提取:
text
example.ts
example/
├── do-x.ts
└── do-x.test.ts
在提取的模块之间添加共享逻辑:
text
example.ts
example/
├── shared/
│   └── do-common-thing.ts
├── do-x.ts
└── do-y.ts
当第二个入口点(
example-2.ts
)需要之前嵌套的内容时,将其提升到最近的公共祖先:
text
shared/
└── bar.ts
example.ts
example/
├── shared/
│   └── do-common-thing.ts
├── do-x.ts
└── do-y.ts
example-2.ts
每一步都反映了实际的代码关系,而非预测未来需求。

Anti-patterns

反模式

  • Do not group files by type/shape rather than function (
    components/
    ,
    hooks/
    ,
    utils/
    )
  • Do not use
    index.ts
    files enabling implicit folder resolution and path synonyms
  • Avoid default exports unless required by third-party
  • Do not over-engineer structures for hypothetical future needs
  • Do not prematurely split files before maintenance issues emerge (they may not)
  • Do not import from a sub-tree's internal files (bypassing encapsulation)
  • 不要按文件类型/形态而非功能进行分组(如
    components/
    hooks/
    utils/
  • 不要使用
    index.ts
    文件实现隐式文件夹解析和路径同义词
  • 除非第三方要求,否则避免使用默认导出
  • 不要为假设的未来需求过度设计结构
  • 不要在出现维护问题之前过早拆分文件(可能永远不会出现)
  • 不要从子树的内部文件导入(绕过封装)