大型js项目应采用功能域优先的模块目录结构,即按业务功能划分src/features/user/等目录,内含components、services等子目录,并通过index.js统一导出;通用逻辑抽离至src/shared/,保持单向依赖;入口明确、避免相对路径与循环依赖。

组织大型 JavaScript 项目的模块目录结构,核心是让功能可定位、职责可隔离、变更影响可控。ES Modules 本身不规定目录怎么放,但它强制依赖显式声明(import),这倒逼结构必须清晰、路径必须合理——否则导入会冗长、重构会痛苦。
按功能域划分(feature-first)
这是当前主流推荐方式,尤其适合中大型项目。把同一业务功能的所有相关代码聚在一起,而不是按技术类型切分。
- 比如用户管理模块,所有内容放在
src/features/user/下: -
src/features/user/components/(登录表单、资料卡片等) -
src/features/user/services/(api.js封装用户增删改查) -
src/features/user/utils/(仅本模块用的格式化函数) -
src/features/user/index.js(统一导出,供外部 import)
好处是新增一个功能时,只在一个地方增删改;团队成员交接或排查问题时,不用在 components/、services/、utils/ 之间来回跳转。
保留通用层作为支撑(shared layer)
功能域内部尽量自包含,但真正跨多个功能的逻辑要抽出来,避免重复或隐式耦合。
-
src/shared/utils/:如debounce、formatDate等纯函数 -
src/shared/services/:基础 API 请求封装(含拦截器、错误统一处理) -
src/shared/components/:原子级 UI 组件(按钮、输入框、弹窗等) -
src/shared/types/(TypeScript 项目)或src/shared/interfaces/:全局类型定义
这些目录里的内容不能依赖任何 feature 目录,保持“被引用”而非“引用别人”的单向关系。
入口与聚合点要明确
ES Modules 鼓励显式导出,所以每个功能模块应提供一个干净的入口文件。
- 每个
features/**/index.js只做一件事:重新导出该模块对外暴露的 API - 例如
src/features/auth/index.js可以写:
export { default as LoginButton } from './components/LoginButton';
export { login, logout } from './services/authApi'; - 上层页面或路由只需:
import { login, LoginButton } from '@/features/auth';
这样既隐藏了内部实现路径,又为将来做 lazy load 或包拆分留出空间。
避免常见陷阱
ES Modules 的静态特性会让某些结构问题立刻暴露:
- 别用相对路径层层回退:
../../../utils/date→ 改用别名(Vite/Webpack 配置@/指向src/) - 别让模块循环依赖:A 导入 B,B 又导入 A → 编译报错或运行时 undefined
- 不要把工具函数塞进组件文件里:一个
UserCard.jsx不该同时定义formatMoney—— 提到shared/utils或features/user/utils - 配置类文件(如路由、权限规则)建议单独成目录,不混在某个 feature 下
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











