Pull to refresh

Comments 66

Angular Seed структурирует код, разделяя файлы по назначению.

Хотя такой подход может быть неплохим начальным пособием для наглядности, никогда не делайте так в реальных проектах средних и больших размеров. Больше информации по теме в блоге AngularJS:

docs.google.com/document/d/1XXMvReO8-Awi1EZXAXS4PzDzdNvV6pGcuaF4Q9821Es/pub
Пользуюсь данным подходом (с некоторыми доработками) и не могу сказать что он имеет какие-либо существенные недостатки. Конечно стоит добавить, что независимые модули приложения удобнее разбивать на несколько таких структур как в примере.
например
app/
	contollers/
	directives/
	...
admin/
	...
forum/
	...



Так же если у вас множество контроллеров, то удобнее распределить контроллеры по директориям, указав в загрузчике модуля (index.js) соответствующие пути:
например
controllers/
	user/
		login.js
		profile.js
	catalog
		...
	contacts
		...


Чтобы судить о недостатках, нужно попробовать разные подходы. Вы сначала попробуйте структуру, которую официально советуют разработчики AngularJS по приведенной мной ссылке выше, и тогда уже можно будет сравнивать.
Я пробовал различные подходы, и это мне показался наиболее удобным.
Что касается структуры, которую вы привели в пример — я ее обязательно изучу, хотя на первый взгляд она мне кажется менее удобной и структурированной (простите за каламбур!).
Смысл в том, что в рекурсивной многоуровневой модульной структуре отдельным юнитом считается не программная сущность, а кусок функциональности проекта. Все html и css/less/sass (и иногда даже картинки и прочие файлы) хранятся вместе с соответствующими js и их тестами, чего нет смысла делать, когда у вас в структуру проекта введены чисто программные понятия вроде controllers и services. Это невероятно повышает читаемость проекта и эффективность разработки, не надо постоянно лазить по дереву файлов, большинство юнитов получаются хорошо инкапсулированными. Попробуйте, вам понравится.
Судя по всему вы больше работаете на front end. Не всегда есть возможность получать html шаблоны разбросанные по различным директориям, в которых к тому же содержатся «css/less/sass (и иногда даже картинки и прочие файлы)», так как зачастую шаблоны — это не статические файлы. Их генерацией занимается серверная часть приложения, которая может расставить имена полей формы в соответствии с моделью или перевести статические тексты на язык, полученный из GET параметра.
Таким образом вам придется настраивать сложные маршруты, которые будут перенаправлять такие запросы на соотв. контроллер на сервере, или же получать их централизованно от специально созданного контроллера (/templates/<templatename>.html?lang=en), что на мой взгляд удобнее.

Что качается такой статики как изображения и файлы стилей, то на мои взгляд держать из вместе с js скриптами не удобно, хотя это наверно дело привычки.

Может быть подскажете, есть ли заготовка приложения со структурой описанной вами? Было бы интересно попробовать.
Ну, в идеале, если это отдельное приложение — оно вообще целиком статика, работающее с Web-API (http/websockets). И его проще воспринимать именно как отдельное приложение, а не как некую структуру, части которой генерируются на сервере. То есть, после загрузки страницы, все данные идут в виде JSON.

Стили я тоже не сторонник хранить разбросанными по приложению. Они вообще лежат за пределами папки приложения — у них просто сильно другая структура организации и очень много абстрактных/общих компонентов.
Я только что привел пример части приложения, которое не может быть статичным. Не удобно хранить различные шаблоны для каждого языка приложения и править их все при малейшем изменении, поэтому они генерируются на сервере. То же самое касается изображений. Они могут отдаваться сервером в различном размере в зависимости, например, от размера экрана клиента, хотя и доступны по одному адресу. Да и стили могут быть различны.
Мне думается что приложение которое «целиком статика» — в некотором роде миф.
Да отнюдь не миф — более того, еще и легко упаковывается в Cordova/PhoneGap и запускается на мобильниках (обычно спец-версия с дополнительными оптимизациями).

Не удобно хранить различные шаблоны для каждого языка приложения и править их все при малейшем изменении, поэтому они генерируются на сервере.

Для какого «каждого языка»? Шаблоны Angular-приложения — это шаблоны, которые доступны только этому приложению, это его view-слой.

Они могут отдаваться сервером в различном размере в зависимости, например, от размера экрана клиента, хотя и доступны по одному адресу.

Зачем? А media-queries? А если прямо отдельное что-то, например, для особых платформ, вроде старых мобильников — то проще делать отдельное приложение для них, нежели утяжелять/усложнять основное, поскольку самый камень преткновения, обычно, скрипты, а не разметка.
Мне думается что приложение которое «целиком статика» — в некотором роде миф.

en.wikipedia.org/wiki/Single-page_application

Забыл упомянуть, что речь идет, естественно, о SPA-приложениях — то есть о том, для чего в первую очередь создавался AngularJS и на что он заточен. Просто последние 2 года я пишу только их и поэтому уже подзабыл, что нынче кто-то еще делает иначе.
любопытно. расскажите про ваш способ организации исходников приложения и стилей подробнее.
А тут все очень просто.

Папки:
static/
  css/     (здесь хранится скомпилированный, но еще не уменьшенный css, содержит source-maps)
  fonts/   (здесь подключаемые шрифты)
  img/     (здесь общие картинки и SVG, которые не меняются, лого, иконки)
  js/
    app/ (здесь Angular приложение)
      ...
      components/ (повторно используемые, независимые от бизнес-логики компоненты)
      core/       (здесь основа приложения, layout-директивы, лэйауты, аутентификация, и так далее)
        controllers/
        models/
        services/
        templates/      (использование отдельных подпапок "templates" - дело вкуса и размера проекта)
          header.html
          main-content.html
          ...
      features/         (различные элементы, "фичи", специфичные для приложения)
        account/
          controllers/
          services/
          models/
          templates/
          init.js       (подключаемый модуль "фичи", собирает все контроллеры, сервисы, роуты)
          routes.js     (роуты "фичи")
        some-feature1/
        some-feature2/
      ...
      app.js      (сам файл приложения, подключение зависимостей, и прочее)
      init.js     (загрузочный файл приложения, аналог bootstrap.js из топика)
      routes.js   (общие маршруты приложения и маршруты по-умолчанию)
    main.js   (файл настроек require.js, обычно он же запускает по-умолчанию модуль "app/init", директория "static/js" устанавливается базовой для require.js)
  lib/   (здесь находятся все зависимости для клиента, bower устанавливает свои компоненты сюда)
  sass/
    ...
    _some-styles.scss (файлы с префиксами - партиалы, подключаются в app.css
    app.scss (файл стилей, только он компилируется sass-ом)
index.html (точка входа, загружаемая страница может генерироваться сервером, а может отдаваться в статическом виде напрямую из корня проекта)


Пара замечаний:
— Во время запуска build-процесса в корне создается папка dist. Ее структура повторяет папку проекта в целом, но содержит оптимизированные версии js/css-файлов.
— Для production все шаблоны собираются в отдельный js-файл, являющийся AMD-модулем, содержимое которого включается в основной JS-файл приложения (то есть, шаблоны не загружаются асинхронно в продакшене).
Весьма неплохо! По предложенной мной структуре выше, кстати — она постоянно развивается, улучшается и упрощается — что очень хорошо.

Например, ввиду использования Angular UI Router, я перестал создавать отдельные папки для контроллеров, и стал помещать контроллеры прямо в папку конкретной «feature», или в отдельные файлы наподобие some-list-state.js — где описывается как контроллер, так и другие элементы «состояния» (в терминах ui-router), всякие resolve, onEnter, и некоторые кастомные расширения (например, breadcrumbs).
artch частично прав, так как html/css/js это только слой ui компонент приложения, а любое приложение должно быть разделено на слои. Самые распространённые это: хэлперы, модели, бизнес логика, слой базы данных, сервисы, ui. Мы к дополнению делим любое приложение на проекты, это перекочевало у нас ещё с .net: есть solution и projects. Каждый проект это можно сказать одно законченное приложение, которое может соответственно зависеть от других проектов. Для примера могу привести недавно начатый полу-хобби проект: task scheduler. Хоть это и nodejs приложение, но структура идентична и для single-page. И вот то, что относится к ui, мы делим на компоненты/контролы и прочее, и вот там (видите как мы уже глубоко зашли), каждый отдельный юнит держится со всеми своими зависимостями и ресурсами. Как например ajax-loader. Огромным плюсом является, то что с таким подходом компоненты легко разрабатывать( это можно делать иногда вне контекста всего проекта), и переносить в другие приложения. А вот для релиза, когда собирается приложение, весь код и ресурсы которые вне проекта складываются в папку build.
Конечно же подобная структура зависит от технологий, а как мы знаем Web — это просто джунгли из языков, фреймворков и разных типов приложений, так что серебренной пули здесь нет.
А теперь представьте, что вам потребуется выпилить один модуль. Например, модуль user, т.к. в другом вашем приложении не будет регистрации и т.п.
А в чем собственно проблема? Если модуль независим, и в остальных модулях нет с ним множества связей, то решение задачи будет не сложнее чем в подходе предложенном artch.

Я согласен, что для больших предложений и в определенных условиях лучше использовать такую структуру, но как я уже говорил, хранение файлов изображений, стилей, шаблонов и скриптов в одной директории для меня несколько засоряет ее, к тому же это не всегда возможно. Например, когда изображения (или стили, шаблоны) отдаются пользователям из хранилища (например из базы) с предварительной обработкой.

tamtakoe наверное имеет в виду, что вам придется выдирать этот модуль из дерева несколько раз, так как у вас несколько точек входа в один и тот же модуль (controllers, services, directives, и т.д.). Это повышает риск чего-то где-то забыть или сделать не так, по сравнению с тем, как если бы точка входа была одна, а структура делилась бы по функциональности, а не по типу.

А еще в рекурсивной структуре удобно делать вот такие штуки:
Скрытый текст
students/  
    students.js
    students-list.js  
    students-list.spec.js  
    students-view/
        students-view.js
        students-view.spec.js
        students-edit-dlg/
            students-edit-dlg.js
            students-edit-dlg.spec.js
        students-sidebar/
            students-sidebar.js
            students-sidebar.spec.js
    students-filtering/
        students-filtering.js
        students-filtering.spec.js



В вашей же структуре это все будет на одном плоском уровне. Про себя скажу, что я где-то года полтора назад ушел с подобного вашему подхода, когда понял, что он не дает масштабировать большое приложение, и очень рад, что ребята из AngularJS наконец-то сами подтвердили, что структура Angular Seed неэффективна для больших SPA-приложений.
… удобно делать вот такие штуки

Выше я показывал как делать такие штуки при подходе, описанном в переводе. Никто не запрещает делать многоуровневую структуру из плоской. Другое дело что в этих подходах модулем считаются различные структуры.
Не соглашусь, что ваш пример демонстрирует эти же штуки. Одно дело — просто распределить файлы по подкаталогам, и совсем другое — моделировать с помощью дерева файлов иерархию связей между компонентами. Ваш пример — это такая же одноуровневая плоская структура, просто с подкаталогами внутри.
Так же если у вас множество контроллеров, то удобнее распределить контроллеры по директориям, указав в загрузчике модуля (index.js) соответствующие пути:

А как вы поступите если вам для некоторой сборки нужно будет отключить определенный модуль? Для больших проектов жизненно необходимо разбивать именно по модулям.
перестать беспокоиться о подключении скриптов в правильном порядке;

Об этом можно и так не беспокоиться, если вынести создание всех модулей в один файл, скажем bootstrap.js, и подключать его первым.
загружать javascript код асинхронно;

А зачем? в продакшен сборке вы все равно будете использовать сконкатеннированную и заминифицированную версию.
иметь возможность скомпилировать код в один минифицированный JS-файл;

и Grunt и Gulp это легко делают.

Зато из минусов у Вас теперь:
1. Необходомость заворочивать в define создание любой сущности ангуляра, которая и так создается в модуле ангуляра.
2. Дополнительный файл конфигурации RequireJS.
А зачем? в продакшен сборке вы все равно будете использовать сконкатеннированную и заминифицированную версию.

Если это труЪ SPA и он большой, то не каждый пользователь будет ждать когда загрузится один большой файл, особенно если ему из него нужно только 30 строчек, которые в отдельном файле намного быстрее загрузятся.
А require.js все равно не обеспечивает «ленивой загрузки».
И что я не так сказал?)
А require.js все равно не обеспечивает «ленивой загрузки».

Именно это он и обеспечивает при необходимости. Ну это так, к слову :)
Есть свои хакерские способы, и даже работают, но я бы в большинстве случаев сам бы не советовал так усложнять.

Другой способ — когда мы изначально подключаем все компоненты Angular, но в каждом из них только по необходимости запускаем загрузку модуля, который непосредственно исполняет код. То есть, к примеру, в качестве контроллера создаем функцию, которая сначала все загружает, и только по факту загрузки выполняет нужную работу. Таким образом, приложение изначально «знает» обо всех своих компонентах, но не в курсе, что они на самом деле делают. Не самый гибкий способ, но также вполне работоспособный. Только надо аккуратно запускать $digest-цикл вручную.
Обе ссылки имеют довольно слабое отношение к require.js.
То, что уже в загруженном приложении можно лениво инициализировать angular-сущности (или любые другие) — понятно и ежам.

Речь-то шла как раз о ленивой подгрузке именно AMD-модулей, т.е. отдельных файлов. Популярный миф, что в require.js это есть.
Популярный миф, что в require.js это есть.

Когда люди говорят с такой уверенностью, кажется что они говорят незыблемую истину. В require.js это есть.
Если в ответ на какое либо событие подгрузить файл (или набор файлов) модуля при помощи функции require() это не является ленивой загрузкой?
Если в одном из контроллеров будет вызов require():
$scope.getSomeModule = function(){
    require(['lazyLoaded.js'], function(lazyLoaded){
      ...
    });
}

Файл lazyLoaded.js загрузится только после выполнения метода $scope.getSomeModule() (например в ответ на клик или другое событие). Если же метод не выполнится, файл так и останется не загруженным. Поправьте, если я не прав, но это как раз и есть отложенная загрузка.
Попробуйте и посмотрите в инспекторе во вкладке Network, когда загрузится lazyLoaded.js.
Попробуйте и посмотрите в инспекторе во вкладке Network, когда

Пример
Ну, я рад, что оказался не прав. Спасибо.
AMD-модули асинхронны по-определению. Никто не мешает вызвать «require» внутри какой-нибудь функции, и загрузка модуля начнется только после этого (если не был ранее затребован и загружен каким-нибудь другим вызовом «require»).

Пример подхода можно посмотреть здесь. Особое внимание нужно уделить предотвращению повторных загрузок и параллельных загрузок одного и того же модуля.
При таком способе requirejs парсит то, что ему передано в качестве модуля регулярками (поэтому всякая условная и динамическая загрузка тоже не работает), находит вызовы require и подгружает все — а потом, при вызове, возвращает.

Асинхронна в AMD только подгрузка модулей, а не их определение. Люди поумнее меня пишут свои модульные системы, чтобы обойти это ограничение.
А в чем проблема? Lazy-loading то можно реализовать. Я понимаю, как работает require, если ему передать строку — require('some-module') — он будет искать этот модуль, причем еще до загрузки основного модуля, который содержит такой вызов (парсит все содержимое функции в виде строки). А что насчет require(['module-name'], function (module) {… })? То, что возвращает загруженные модули — это понятно, более того, поддерживает их состояние.

Насчет собственных модульных систем — ни к чему, как по мне. Сам не особо рад оберткам в виду «define» для всех AMD-модулей (отчасти решается build-процессом), но тут еще вопрос совместимости с другими разработками и сторонними библиотеками. Ну и сейчас уже, похоже, время переходить на ES6-модули, где, наконец-то, определение модуля не зависит от того, как он будет загружаться.
Насчет ES6-модулей согласен.
К слову, сейчас транс-компиляция все равно идет в один из существующих форматов модулей в зависимости от того, каким образом нужно модули использовать. И если нужно что-то лениво подгружать — в итоге технически используются именно AMD-модули для этого.
Асинхронна в AMD только подгрузка модулей, а не их определение.

не совсем так. в AMD определение модулей асинхронно, что следует прямо из названия. именно эта концептуальная особенность и позволяет проделывать всякие технические трюки, включая асинхронную подгрузку кода.

в YM же надстраивали возможность для асинхронного инстанцирования модулей. по смыслу это аналогично тому, что делает плагин requirejs-promise.
В контексте angular — не обеспечивает, так как для запуска приложения нужно что бы оно было загружено все и целиком, к моменту когда будет запущен этап конфигурации. То есть описанный в вашей статье метод этой проблемы не решает, а без него толку особо и нету.
Простите, статья не ваша.
Если это настолько большой SPA, то я бы сделал прогрес бар при загрузке приложения, куда лучше чем дополнительный запрос на сервер при каждом изменении его состояния.
http2 это конечно хорошо, но особо разницы в производительности не будет (если сравнивать собранную версию и набор файликов). Вот Angular2 с ленивой подгрузкой модулей грядет, это да.
про это, кстати, был веселый доклад на ng-conf 2014 www.youtube.com/watch?v=4yulGISBF8w и эпичный тред stackoverflow.com/questions/12529083/does-it-make-sense-to-use-require-js-with-angular-js — с аргументами за то, когда и зачем такое может быть нужно. и про r.js/almond тоже. если вкратце:

1. сокрытие внутренней файловой структуры компонентов.
2. подключение внешних библиотек.
3. поддержание общего порядка.

где-то тут же — загрузка тестов, сборка разных бандлов и прочее подобное счастье. понятно, что в сыром виде, как описывалось в этой статье, использование параллельно двух различных систем контроля различных зависимостей превращается в полное непотребство. но оригинал был написан осенью 2013 (ага, я сходил по ссылке). но в начале этого года tuchk4@ предлагал здесь уже гораздо более интересное решение: habrahabr.ru/post/216469/. любопытно, есть-ли какие-то новости про использование плагина в продакшене?
Скажите, а что заставляет художников рисовать такие тени, как на картинке?
Звонили из нулевых, просили отдать такие тени обратно.
… художников рисовать такие тени,...

Вы так говорите, как будто я орудовал кистью по холсту, и вышло у меня, мягко говоря, хреново.
Хотя последнего я не исключаю…

Это мое проявление нонконформизма по отношению к современному плоскому дизайну.
Пришел к выводу, что использование RequireJS в связке с Angular — не самый удобный вариант. Двойное описание dependencies в define и затем практически то же самое в модуле создает огромное количество лишнего кода. Использую конкатенацию в gulp/grunt для небольших проектов без dependency hell и browserify для проектов посложнее.
Для джанги есть отличный django-compressor, который позволяет писать так

        {% compress js %}
            <script src="{{ STATIC_URL }}bower_components/angular/angular.js"></script>
            <script src='{{ STATIC_URL }}bower_components/angular-ui-router/release/angular-ui-router.js'></script>
            <script src="{{ STATIC_URL }}bower_components/angular-resource/angular-resource.js"></script>
            <script src="{{ STATIC_URL }}bower_components/angular-sanitize/angular-sanitize.js"></script>
            <script src='{{ STATIC_URL }}bower_components/underscore/underscore.js'></script>
            <script src='{{ STATIC_URL }}bower_components/angular-bootstrap/ui-bootstrap.min.js'></script>
            <script src='{{ STATIC_URL }}bower_components/angular-bootstrap/ui-bootstrap-tpls.js'></script>
            <script src='{{ STATIC_URL }}bower_components/angular-gettext/dist/angular-gettext.min.js'></script>
        {% endcompress %}

На деве — все грузится как обычно. На проде — автоматом выдает собранную версию.
Dmitry Evseev, а как в вашем примере связать все с angular-route?
Dmitry Evseev — это автор статьи, которая является переводом!
… а как в вашем примере связать все с angular-route?

Опишите, пожалуйста, подробнее в чем проблема? Вы хотите запрашивать JS фалы в зависимости от маршрута или не знаете как подключить файл модуля (angular-route.js)?
Извинитие, протупил с автором ))

Ну вот в примере с angular-seed используется angular-route, а в статье это не упоминается. Я попробовал по аналогии — не получилось.

Я добавлял angular-route в файле app.js как зависимость requirejs и как зависимость при создании модуля angular.
Обычно я делаю следующим образом:
app.js:
define(
    [
        'angular',
        './angular-modules',
        './controllers/index',
        './directives/index',
        './filters/index',
        './services/index'
    ], 
    function (ng) {
        
        return ng.module('app', [
            'app.services',
            'app.controllers',
            'app.filters',
            'app.directives',
            'ngRoute',
            //... другие модули
        ]); 
    }
);


Обратите внимание, что в зависимости добавлен файл angular-modules.js

angular-modules.js:
define(
    [
        'angular',
        './../ngmodules/angular-route.min', 
        //... другие модули
    ], 
    function () {}
);
Спасибо за ответ, буду пробовать.
Sign up to leave a comment.

Articles

Change theme settings