Ember.js项目结构 #
一、项目根目录 #
一个典型的Ember项目根目录结构如下:
text
my-ember-app/
├── app/ # 应用核心代码
├── config/ # 配置文件
├── tests/ # 测试文件
├── public/ # 静态资源
├── vendor/ # 第三方库
├── node_modules/ # npm依赖
├── .ember-cli # Ember CLI配置
├── .eslintrc.js # ESLint配置
├── .template-lintrc.js # 模板检查配置
├── .prettierrc.js # Prettier配置
├── .watchmanconfig # Watchman配置
├── ember-cli-build.js # 构建配置
├── package.json # 项目依赖
├── testem.js # 测试运行器配置
└── README.md # 项目说明
二、app目录详解 #
app/ 目录是应用的核心,包含所有业务代码:
text
app/
├── app.js # 应用入口
├── index.html # HTML入口文件
├── router.js # 路由配置
├── adapters/ # 数据适配器
│ └── application.js
├── components/ # 组件
│ ├── my-component.hbs
│ └── my-component.js
├── controllers/ # 控制器
│ └── posts.js
├── helpers/ # 模板助手
│ └── format-date.js
├── models/ # 数据模型
│ └── post.js
├── modifiers/ # 模板修饰符
│ └── autofocus.js
├── routes/ # 路由
│ ├── application.js
│ └── posts.js
├── serializers/ # 序列化器
│ └── post.js
├── services/ # 服务
│ └── auth.js
├── styles/ # 样式文件
│ └── app.css
├── templates/ # 模板
│ ├── application.hbs
│ └── posts.hbs
└── transforms/ # 数据转换器
└── date.js
2.1 app.js #
应用入口文件,定义应用的基本配置:
javascript
import Application from '@ember/application';
import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from './config/environment';
export default class App extends Application {
modulePrefix = config.modulePrefix;
podModulePrefix = config.podModulePrefix;
Resolver = Resolver;
}
loadInitializers(App, config.modulePrefix);
2.2 router.js #
路由配置文件,定义应用的URL结构:
javascript
import EmberRouter from '@ember/routing/router';
import config from 'my-app/config/environment';
export default class Router extends EmberRouter {
location = config.locationType;
rootURL = config.rootURL;
}
Router.map(function () {
this.route('home', { path: '/' });
this.route('about');
this.route('posts', function () {
this.route('new');
this.route('edit', { path: '/:post_id/edit' });
});
});
2.3 index.html #
HTML入口文件,Ember应用挂载点:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>MyApp</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{{content-for "head"}}
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css" />
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/my-app.css" />
{{content-for "head-footer"}}
</head>
<body>
{{content-for "body"}}
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/my-app.js"></script>
{{content-for "body-footer"}}
</body>
</html>
三、核心目录详解 #
3.1 components/ #
组件是Ember应用的核心构建块:
text
components/
├── user-profile/
│ ├── index.hbs # 组件模板
│ ├── index.js # 组件逻辑
│ └── styles.css # 组件样式(可选)
├── todo-item.hbs
├── todo-item.js
└── nav-bar.gjs # 单文件组件格式
组件示例:
javascript
// app/components/user-profile.js
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class UserProfileComponent extends Component {
@tracked isExpanded = false;
@action
toggle() {
this.isExpanded = !this.isExpanded;
}
}
handlebars
{{! app/components/user-profile.hbs}}
<div class="user-profile">
<h2>{{@user.name}}</h2>
<button type="button" {{on "click" this.toggle}}>
{{if this.isExpanded "收起" "展开"}}
</button>
{{#if this.isExpanded}}
<p>{{@user.email}}</p>
<p>{{@user.bio}}</p>
{{/if}}
</div>
3.2 routes/ #
路由负责加载数据和处理路由逻辑:
javascript
// app/routes/posts.js
import Route from '@ember/routing/route';
export default class PostsRoute extends Route {
async model() {
const response = await fetch('/api/posts');
return response.json();
}
setupController(controller, model) {
super.setupController(controller, model);
controller.set('pageTitle', '所有文章');
}
}
3.3 templates/ #
模板定义视图层:
handlebars
{{! app/templates/posts.hbs}}
<h1>{{@model.length}} 篇文章</h1>
<ul>
{{#each @model as |post|}}
<li>
<LinkTo @route="posts.show" @model={{post.id}}>
{{post.title}}
</LinkTo>
</li>
{{/each}}
</ul>
{{outlet}}
3.4 models/ #
模型定义数据结构:
javascript
// app/models/post.js
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
export default class PostModel extends Model {
@attr('string') title;
@attr('string') body;
@attr('date') publishedAt;
@attr('boolean') isPublished;
@belongsTo('user') author;
@hasMany('comment') comments;
}
3.5 controllers/ #
控制器处理模板的交互逻辑:
javascript
// app/controllers/posts.js
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class PostsController extends Controller {
@tracked filter = 'all';
get filteredPosts() {
if (this.filter === 'all') {
return this.model;
}
return this.model.filter((post) => post.isPublished);
}
@action
setFilter(value) {
this.filter = value;
}
}
3.6 services/ #
服务是可注入的单例对象:
javascript
// app/services/session.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
export default class SessionService extends Service {
@tracked isAuthenticated = false;
@tracked currentUser = null;
async login(credentials) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
const user = await response.json();
this.currentUser = user;
this.isAuthenticated = true;
}
logout() {
this.isAuthenticated = false;
this.currentUser = null;
}
}
3.7 helpers/ #
助手用于模板中的数据转换:
javascript
// app/helpers/format-date.js
import { helper } from '@ember/component/helper';
export default helper(function formatDate([date], { format }) {
if (!date) return '';
const d = new Date(date);
if (format === 'short') {
return d.toLocaleDateString();
}
return d.toLocaleString();
});
handlebars
{{! 使用助手}}
<p>发布于:{{format-date @post.publishedAt}}</p>
<p>简短格式:{{format-date @post.publishedAt format="short"}}</p>
3.8 modifiers/ #
修饰符用于DOM操作:
javascript
// app/modifiers/autofocus.js
import { modifier } from 'ember-modifier';
export default modifier(function autofocus(element, positional, { delay = 0 }) {
setTimeout(() => {
element.focus();
}, delay);
});
handlebars
{{! 使用修饰符}}
<input {{autofocus}} />
<input {{autofocus delay=100}} />
四、config目录 #
配置文件目录:
text
config/
├── environment.js # 环境配置
├── targets.js # 目标浏览器配置
├── optional-features.json # 可选特性
└── ember-try.js # 版本测试配置
4.1 environment.js #
javascript
// config/environment.js
'use strict';
module.exports = function (environment) {
let ENV = {
modulePrefix: 'my-app',
environment: environment,
rootURL: '/',
locationType: 'history',
EmberENV: {
FEATURES: {},
},
APP: {
API_HOST: 'https://api.example.com',
},
};
if (environment === 'development') {
ENV.APP.API_HOST = 'http://localhost:3000';
}
if (environment === 'test') {
ENV.locationType = 'none';
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
}
if (environment === 'production') {
// 生产环境配置
}
return ENV;
};
4.2 targets.js #
javascript
// config/targets.js
'use strict';
const browsers = ['last 2 Chrome versions', 'last 2 Firefox versions', 'last 2 Safari versions'];
module.exports = {
browsers,
node: 'current',
};
五、tests目录 #
测试文件目录:
text
tests/
├── index.html # 测试入口
├── test-helper.js # 测试助手
├── acceptance/ # 验收测试
│ └── login-test.js
├── integration/ # 集成测试
│ └── components/
│ └── user-profile-test.js
├── unit/ # 单元测试
│ ├── models/
│ │ └── post-test.js
│ └── services/
│ └── session-test.js
└── helpers/ # 测试助手
└── setup-application.js
六、public目录 #
静态资源目录:
text
public/
├── favicon.ico
├── images/
│ └── logo.png
└── robots.txt
七、Pods结构(可选) #
Ember支持Pods结构,将相关文件组织在一起:
text
app/
├── pods/
│ ├── post/
│ │ ├── route.js
│ │ ├── template.hbs
│ │ ├── controller.js
│ │ └── model.js
│ ├── components/
│ │ └── user-profile/
│ │ ├── component.js
│ │ └── template.hbs
│ └── application/
│ ├── route.js
│ └── template.hbs
└── router.js
启用Pods结构需要在 config/environment.js 中配置:
javascript
let ENV = {
podModulePrefix: 'my-app/pods',
// ...
};
八、文件命名约定 #
8.1 命名规则 #
| 类型 | 文件名 | 类名 |
|---|---|---|
| Route | posts.js |
PostsRoute |
| Controller | posts.js |
PostsController |
| Component | user-profile.js |
UserProfileComponent |
| Model | post.js |
PostModel |
| Service | session.js |
SessionService |
| Helper | format-date.js |
format-date |
| Modifier | autofocus.js |
autofocus |
8.2 目录与URL对应 #
text
路由配置 URL
─────────────────────────────────────────
this.route('posts'); /posts
this.route('posts', function() {
this.route('new'); /posts/new
this.route('show', { /posts/123
path: '/:post_id'
});
});
九、总结 #
Ember项目结构遵循约定优于配置原则:
| 目录 | 用途 |
|---|---|
app/ |
应用核心代码 |
config/ |
配置文件 |
tests/ |
测试文件 |
public/ |
静态资源 |
vendor/ |
第三方库 |
理解项目结构是掌握Ember开发的基础,遵循约定可以让团队协作更加高效。
最后更新:2026-03-28