Использование AngularJS в паре с RequireJS — достаточно популярный подход к разработке веб приложений в последнее время. И один из основных вопросов — структура приложения. Существует достаточно известный seed для такого приложения tnajdek/angular-requirejs-seed, но мне это не походит, так как при увеличении функционала приложения — данная структура просто будет засоряться кучей файлов, не будет никакого логического разделения скриптов и достаточно сложно будет их менеджить.
Целью было создать приложение с модульной и гибкой архитектурой (ну скорее просто разбиение приложение не логические части), с простым и понятным описанием зависимостей между частями приложения и уменьшить зависимость кода от структуры приложения.
Модуль
В данном случае, это логически отдельная часть приложения, включающая в себя набор компонентов:
- ngModule;
- Controller;
- FIlter;
- Directive;
- Service;
- Template;
- Configs — содержат config() и run() методы для текущего ngModule.
Проблема
При использовании RequrieJS, файлы приложения чаще всего подключаются как-то так:
require('modules/foo/controller/foo-controller.js');
require('modules/foo/service/foo-service.js');
require('modules/foo/directive/foo-controller.js');
require('text!modules/foo/templates/foo.html');
require('modules/bar/directive/bar-controller.js');
Здесь есть явные минусы:
- Код очень зависит от структуры проекта;
- Код очень зависит от названий модулей;
- Достаточно много нужно писать руками.
Решение
Были написаны RequireJS плагины для загрузки компонентов модуля.
К примру есть такая структура приложения (кстати, очень похожая на структуру бандлов в Symfony2):
app |-modules | |-menu | | |-controller | | | |-menu-controller.js | | |-menu.js | | | |-user | |-controllers | | |-profile.js | |-resources | | |-configs | | | |-main.js | | | | | |-templates | | | |-user-profile.html | | |-directives | | |-user-menu | | |-user-menu.js | | |-user-menu.html | |-src | | |-providers | | | |-profile-information.js | | |-factory | | |-guest.js | |-user.js | |-application.js |-boot.js
В данном случаем у нас есть 2 модуля: user и menu. Файлы /app/modules/menu/menu.js и /app/modules/user/user.js — скрипты с инициализацией angularJS модулей. Все остальное — думаю понятно.
Теперь нужно задать конфигурацию для подключения всех компонентов. Делается это с помощью requirejs.config:
requirejs.config({
baseUrl: '/application',
paths: {
'text': '../bower_components/requirejs-text/text',
// Structure plugins:
'base': '../bower_components/requirejs-angualr-loader/src/base',
'template': '../bower_components/requirejs-angualr-loader/src/template',
'controller': '../bower_components/requirejs-angualr-loader/src/controller',
'service': '../bower_components/requirejs-angualr-loader/src/service',
'module': '../bower_components/requirejs-angualr-loader/src/module',
'config': '../bower_components/requirejs-angualr-loader/src/config',
'directive': '../bower_components/requirejs-angualr-loader/src/directive',
'filter': '../bower_components/requirejs-angualr-loader/src/filter'
},
structure: {
prefix: 'modules/{module}',
module: {
path: '/{module}'
},
template: {
path: '/resources/views/{template}',
},
controller: {
path: '/controllers/{controller}'
},
service: {
path: '/src/{service}'
},
config: {
path: '/resources/configs/{config}'
},
directive: {
path: '/resources/directives/{directive}/{directive}'
},
filter: {
path: '/resources/filters/{filter}'
}
}
});
Все пути каждого компонента определены в рамках модуля. Поле structure.prefix — путь к корню модуля, после baseUrl.
Теперь, если мы хотим подключить файл /app/modules/user/user.js из:
1. /app.js:
require('module!user')
2. /app/modules/user/controllers/profile.js:
require('module!@')
В рамках одного модуля — имя модуля можно не писать, достаточно символа '@'. Тем самым, если придется переименовать модуль — не нужно будет менять код.
Теперь, если мы хотим подключить файл /app/modules/user/controllers/profile.js
из:
1. /app.js:
require('controller!user:profile')
До двоеточия — название модуля, после двоеточия — название контроллера.
2. /app/modules/user/user.js:
require('controller!profile')
В рамках одного модуля — имя модуля можно не писать, достаточно указать только название контроллера. Так же, если контроллер лежит на уровень ниже, то возможно подключать так:
require('controller!additional/path/to/profile')
Точно так же и для всех других компонентов.
Результат
Получилось очень гибкая структура приложения с поддержкой разделения кода на модули и с минимальной зависимостью кода от структуры проекта даже если придется перенести какой либо компонент из одного модуля в другой — то практически ничего менять не придется. И лишнего кода так же стало меньше.
Так же не возникает никаких проблем при сброке проекта. В тестовом приложение есть пример собранного проекта в папке /build и Gruntfile для сборки, но в нем нету ничего не обычного.
Ссылки:
- Репозиторий с плагинами — requirejs-angular-loader. Устанавливать можно с помощью bower;
- Репозиторий с тестовым приложением — tuchk4/requirejs-angular-loader-bootstrap.
Данный подход используем в большом корпоративном приложении, поддержка и развитие данного подхода будет поддерживаться и развиваться.
Автор: tuchk4