fractal-tree-file-structure
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFractal 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/, orhooks/folders).utils/ -
Encapsulation: Resources in a subdirectory are internal to the parent file unless explicitly re-exported. Adirectory is "owned" by
shapes/. Direct imports from nested levels are prohibited—each sub-tree exports resources that can only be imported on the next level up.shapes.ts -
Contextual sharing: Common logic lives at the closest common ancestor ("fork" in the tree). Thedirectory exists at the
shared/level because multiple entrypoints need it. Place shared logic as deep in the tree as possible while still serving all dependents.src/ -
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-caseNo index files
不使用index文件
Avoid files that enable implicit folder imports.
They cause path ambiguity where could resolve to both and , and they hurt ESM compatibility.
index.ts./foofoo.tsfoo/index.ts避免使用文件实现隐式文件夹导入。
它们会导致路径歧义,例如可能同时解析为和,并且会损害ESM兼容性。
index.ts./foofoo.tsfoo/index.tsFiles 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.tsOnly imports from the directory, and only imports from the directory – each file owns its namespace.
If becomes unused, delete it together with its internal folder safely.
my-app.tsmy-app/lifecycle.tslifecycle/my-app.ts当一个文件变得难以维护时,将逻辑提取到与原文件同名的兄弟子目录中:
text
my-app.ts → my-app.ts(保留公共API)
→ my-app/
├── config.ts
├── lifecycle.ts
├── lifecycle/
│ ├── something.ts
│ └── something-else.ts
└── helpers.ts只有可以从目录导入,只有可以从目录导入——每个文件都拥有自己的命名空间。
如果不再被使用,可以安全地将其与内部文件夹一起删除。
my-app.tsmy-app/lifecycle.tslifecycle/my-app.tsRelative paths within workspaces
工作区内的相对路径
All imports within a workspace use relative paths.
Avoid mixing path alias systems (e.g. ) with relative imports, as this creates inconsistency.
(This project uses aliases for the root as a convention.)
@/foo@/src/工作区内的所有导入均使用相对路径。
避免将路径别名系统(如)与相对导入混合使用,这会导致不一致。
(本项目使用别名作为根目录的约定。)
@/foo@/src/shared/
folder convention
shared/shared/
文件夹约定
shared/Shared resources between sub-trees go into .
Think of folders as lightweight .
Contents of parent-level folders remain accessible, but sub-tree folders are internal to that sub-tree.
path/to/common-parent/shared/shared/node_modules/shared/shared/子树之间的共享资源放入目录。
可以将文件夹视为轻量级的。
父层级文件夹的内容仍可访问,但子树的文件夹仅对该子树内部可见。
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: , .
Entry points access shared resources but remain outside core logic.
do-something.script.tsxyz.test.tsx项目可能有多个入口点(页面、API处理程序、脚本、测试)。
使用后缀区分它们与迷你库,例如:、。
入口点可以访问共享资源,但不属于核心逻辑。
do-something.script.tsxyz.test.tsxColocate unit tests
单元测试与代码同目录
Place unit tests beside the files they cover: pairs with .
Integration and end-to-end tests live in separate directories outside the source tree root.
foo.tsfoo.test.ts将单元测试放在其对应的文件旁边:与配对。
集成测试和端到端测试存放在源码树根目录之外的单独目录中。
foo.tsfoo.test.tsExceptions 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.tsThis 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.tsExtract when necessary:
text
example.ts
example/
├── do-x.ts
└── do-x.test.tsAdd shared logic between extracted modules:
text
example.ts
example/
├── shared/
│ └── do-common-thing.ts
├── do-x.ts
└── do-y.tsWhen a second entry point () needs something previously nested, promote it to the closest common ancestor:
example-2.tstext
shared/
└── bar.ts
example.ts
example/
├── shared/
│ └── do-common-thing.ts
├── do-x.ts
└── do-y.ts
example-2.tsEach 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.tstext
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 files enabling implicit folder resolution and path synonyms
index.ts - 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 - 除非第三方要求,否则避免使用默认导出
- 不要为假设的未来需求过度设计结构
- 不要在出现维护问题之前过早拆分文件(可能永远不会出现)
- 不要从子树的内部文件导入(绕过封装)