Angular boilerplate. Простота — тренд молодежи

  • Tutorial
Любая физическая система стремится к состоянию с наименьшей потенциальной энергией. И программисты не исключение. Поэтому речь пойдет о том, как упростить себе жизнь при разработке на angular.js, используя при этом сервисы, которые сейчас в тренде. Главным образом, я буду ненавязчиво пиарить свое архитектурное решение angular-boilerplate, а на закуску предложу поделиться своим опытом и идеями в комментариях.

Мотивация


Свести рутину к минимуму, создать интуитивно понятную архитектуру и собрать вместе то, что называется best practices.

Сразу оговорюсь, что angular-boilerplate — результат создания с нуля нескольких проектов разной степени сложности, в процессе работы над которыми я всячески старался избавиться от ненужного усложнения, в первую очередь, собственного рабочего процесса. Поэтому в чем-то мои решения вызваны субъективным взглядом на разработку, но тем не менее все они прошли проверку временем и показали практическую пользу.

Особенности



Grunt


Начнем с того, что нам понадобится grunt.js. Grunt нужен будет, в первую очередь, для того, чтобы собирать и сжимать файлы кода и стилей. В проекте уже есть две задачи в Gruntfile.js: grunt install и grunt build — первая позволяет выполнять различные операции при установке и в начальном варианте производит установку необходимых библиотек через bower, вторая, как я уже писал выше собирает и минифицирует файлы с помощью require.js.

Bower


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

Require.js


Практика, когда библиотеки и код подлючаются в html странице, лично у меня вызывает когнитивный диссонанс. И я всегда хотел иметь в javascipt привычную для других языков возможность програмно подключать код и определять зависимости. Поэтому я с большим энтузиазмом отношусь к Require.js и применению его в качестве каркаса для приложения. В этом месте я, пожалуй, опущу детальное введение в Require.js, а просто укажу на то, как он используется в проекте.

Итак, главное, что нам дает require.js, помимо модульности — возможность свести всё приложение в один файл и затем сжать. Но, разумеется, при разработке хотелось бы иметь дело с нормальной версией файлов, поэтому в индексе есть два варианта подключения логики и стилей:
<!--Development-->
<link rel="stylesheet" href="app/styles/styles.css">
<!--Production-->
<link rel="stylesheet" href="app/styles/styles.min.css">

<!--Development-->
<script data-main="app/js/app" src="app/lib/requirejs/require.js"></script>
<!--Production-->
<script src="app/js/app.min.js"></script>

Которые позволяют переключаться между development и production.

Все библиотеки, модули и прочее при этом подключаются при инициализации приложения, и их настройки определяются в файле app.js:
require.config({
    baseUrl: 'app',
    paths: {
        'jquery': 'lib/jquery/dist/jquery',
        'angular': 'lib/angular/angular',
        'text': 'lib/requirejs-text/text'
    },
    shim: {
        'jquery': {
            exports: 'jQuery'
        },
        'angular': {
            exports: 'angular',
            'deps': ['jquery']
        },
        'lib/angular-route/angular-route': {
            'deps': ['angular']
        }
    },
    config: {
        'js/services': {
            apiUrl: 'http://localhost/api'
        }
    }
});

Далее пройдемся по файлам.

Файлы


/app
     /js
          app.js 
          app.min.js 
          controllers.js 
          directives.js 
          filters.js 
          services.js 
     /styles
          styles.js 
          styles.min.js 
     /templates
          someone-template.html
index.html
bower.json
package.json
.bowerrc
Gruntfile.js 

Общая структура проекта не должна вызывать особых вопросов. Сервисы, директивы, контроллеры, фильтры задаются в соответсвующих файлах, которые потом автоматически подключаются при загрузке приложения:
var angular = require('angular'),
        controllers = require('js/controllers'),
        services = require('js/services'),
        directives = require('js/directives'),
        filters = require('js/filters');
...
angular.forEach(services, function (service, name) {
        $provide.factory(name, service);
});
...

angular.forEach(directives, function (directive, name) {
        app.directive(name, directive);
});

angular.forEach(filters, function (filter, name) {
        app.filter(name, filter);
});

angular.forEach(controllers, function (controller, name) {
        app.controller(name, controller);
});


Я думаю, что многие, да и я сам по началу задавались вопросом, как правильно оформлять логику, которая не привязана к конкретному отображению, а выполняется глобально. Часто ответом на этот вопрос служит контроллер, который вызывается где-то в index.html с помощью ng-controller. Конечно это не смертельно, но я лично считаю такой вариант более правильным:
app.run(controllers.GlobalCtrl);

То есть данный контроллер вызывается единожды при запуске приложения и формально не связан ни с одним шаблоном.

Одним из приятных дополнений requirejs является модуль text, который позволяет подгружать через require() не только AMD модули, но и обычные текстовые файлы, к примеру, html, которые потом могут быть включены при компиляции. Это позволяет нам добавлять в общий билд файлы шаблонов.

...
.when('/', {
       controller: controllers.MainCtrl,
       template: require('text!templates/main.html')
});
...


И напоследок, что касается:
$httpProvider.interceptors.push(['$q', '$error', function ($q, $error) {
            return {
                'request': function (config) {
                    return config;
                },
                'response': function (response) {
                    return response;
                },
                'responseError': function (rejection) {
                    $error('Connection error!');
                    return $q.reject(rejection);
                }

            }
        }]);

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

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

Ну. И что?
Реклама
Комментарии 38
  • +1
    Эм… angular-seed + require.js? А где все что нужно для прогона unit/e2e? где протрактор? Мне больше нравится ngboilerplate. Правда в итоге я все-равно собрал для себя свой скелет проекта.

    p.s. require.js на самом деле не так уж и нужен для проектов на angular.js. Зависимости разруливаются внутри фреймворка, так что главное правильно собрать проект, а для этого хватит и связки concat+ngmin+uglify.
    • +1
      Правда в итоге я все-равно собрал для себя свой скелет проекта.

      Так не стесняйтесь же, поделитесь.
      А про тестирование — тут каждый по-своему решает, хотя, конечно, можно и протрактор предложить.
      • 0
        Я сейчас перевожу проекты с grunt на gulp, вот когда отработаю еще хотя бы один два проекта, тогда можно будет думать о том что бы оформить все как готовый bootstarp для проекта, и, в идеале, сделать генераторы для yoman.
      • 0
        Расскажите как смогли с ng-min подружиться? Он же ни черта не оптимизирует в сложных ситуациях, например, контроллеры директив и т.п.
        • 0
          Вроде как это дело было пофикшено с пол года как, нет?
      • +1
        По моему опыту надо выносить все составные части проекта в отдельные файлы. Часто приходится использовать еще и вложенные папки, для лучшей структуры. Проект при этом билдится с помощью grunt в реальном времени.
        • 0
          Не то что в отдельные файлы, но и в отдельные модули.
          • 0
            Если это модули или какие-то самописные плагины, то я лично для этого использую ту же lib папку.
            • +1
              Каждый отдельный контроллер, сервис и т.д. следует выносить в отдельный файл. При этом, еще лучше, как написал Fesor разносить архитектуру на отдельные модули.
              • –1
                Почему следует? Сам так делал и кроме головной боли при постоянном переключении между файлами ничего из такого подхода не вынес.
                • 0
                  Я думаю что если у вас часто возникает необходимость вносить изменения за раз сразу в паре десятков файлов, то что-то явно пошло не так. Либо же вы не работали с большими приложениями.
                  • –1
                    в паре десятков? аппокалипсис, определенно. Мне лично достаточно попрыгать хотя бы между двумя файлами, чтобы предпочесть это скролу в одном.
                    • 0
                      Каждому свое. Я обычно разве что фильтры пишу в одном файле, и то не все а скажем, для модуля. Или небольшие директивы, которые можно объединить по смыслу. Одно дело переключаться между файлами в период разработки (у меня редко бывают случае когда нужно переключаться больше чем между тремя файлами), и искать какую-то директиву при новой итерации, особенно когда ее писал кто-то другой, и это было год назад.
                  • 0
                    Почему следует?

                    Потому что это хрестоматийная истина, 90% файлов должно умещаться на один экран. Попробую объяснить почему:
                    1. если вы работаете в команде, то все будут работать в одном огромном файле, что плохо скажется на сливании и комитах.
                    2. если вам нужно внести правки в код, то не придется скролить в 2600 строке. Достаточно набрать в IDE имя нужного файла.
                    3. нет структуры кода, невозможно понять какой класс/функция какому модулю принадлежат.
                    • 0
                      Да вот практика показала, что ничего страшного в этом нет. Раньше рассуждал так же, как и вы.

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

                      Конфликт при мердже будет конфликтом вне зависимости от того, в каком файле он произойдет.
                      2. если вам нужно внести правки в код, то не придется скролить в 2600 строке. Достаточно набрать в IDE имя нужного файла.

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

                      По-моему это немного не в тему. Мы ведь не про структурирование на модули говорим, а про то, как писать компоненты вгутри одного.

                      Давайте я закрою этот вопрос. Я лично пробовал делать обоими способами. Причем изначально был сторонником вашей позиции. И существенной разницы не почувствовал. Меня смутил безаппеляционный тон, поэтому я захотел услышать аргументы. Аргументы оказались чисто субъективными и объективных причин говорить, как «следует» делать, я так и не увидел.
                      • 0
                        Посмотрите на структуру крупных open source проектов и подумайте почему «бородатые дяди» не пишут все в одном файле, пусть даже в рамках одного модуля.
                        И существенной разницы не почувствовал.

                        Если для вас это удобно, то пользуйтесь огромными файлами. Но когда вы будете работать над крупными проектами вы поменяете свою точку зрения, или ваши коллеги вам ее поменяют.
                        • 0
                          Это все на самом деле довольно субъективно. Мне к примеру проще переключить в IDE файл, чем скролить. Ну и позиция курсора в открытых файлах сохраняется, меньше перемещать. Опять же можно держать на одном экране открытыми несолько файлов, что бы перед глазами было все что нужно.
              • 0
                кто-нибудь может объяснить какое преимущество в angular.js?
                и чем он лучше knockout.js?
                • 0
                  Иногда мне кажется, что, главным образом, в том, что angular получил серьезную пиар поддержку на старте. Сайты успешно делают и на том, и на другом. Каждый выбирает либо то, чем он лучше владеет, либо если не владеет ни тем, ни другим, то, о чем больше всего говорят. Сейчас мне кажется, что в Angular более четкая логика разделения модуля на контроллеры, сервисы и директивы, но не исключаю, что это только потому, что все последние проекты я делал на Angular, а не Knockout
                  • 0
                    knockout.js — библиотека для data binding и все, а angular это MVVM фреймворк. Только на knockout сложное приложение не сделаешь, всеравно нужны будут jquery, underscore/lodash, для разделения логики и представления backbone или что-то еще… А angular самодостаточен, хорошо структурирован и с ним ваш код легко покрывать тестами.
                  • 0
                    • 0
                      А в чем фишка yoman? Просто в свое время, когда я для каждого контроллера, сервиса и т.д. создавал отдельный файл, мне было достаточно шаблонов в Webstorm для соответствующих элементов. Я правильно понимаю, что по функционалу это тоже самое?
                      • 0
                        Да, вот только в команде разработчики могут сидеть хоть на Webstorm, хоть на vim.
                        • 0
                          Здесь согласен, хотя и не уверен, что это нужно выносить в общий репозиторий, а не иметь в форме индивидуальной дев надстройки.
                          • 0
                            генераторы для yoman конечно же хранятся отдельно, да и сам yoman ставят глобально а не для проекта, и выбор использовать его или нет остается на совести разработчика.
                        • +1
                          Суб-генераторы для контроллеров, views, сервисов, factories. Заготовки тестов для всего вышеперечисленного. LiveReload из коробки. По необходимости можно включить поддержку bootstrap, SASS. Вебсервер на Node.js из коробки. Bower из коробки. Настроеный gitignore чтобы не тащить в репо, что не надо.

                          Сегодня разворачивали новый проект на машине второго разработчика — git clone, npm install, bower install, grunt serve. Через 2 минуты имеем полностью настроенное рабочее окружение.

                          Мне для того чтобы всё сделать руками — понадобится намного больше времени, но кроме всего прочего, лучшие профессионалы уже подумали и сделали — только пользуйся. А если захочется большего — есть генератор генераторов.
                      • +4
                        У меня другой подход, реализующийся через несколько самописных тасков для grunt`a

                        Весь проект делиться на маленькие модули, которые называются компоненты. Компоненты могут состоять как из js, так и из css или html шаблонов.
                        Структура компонента выглядит так:

                        имя папки(есть имя компонента) /
                        — /js — весь js код
                        — /style — стили
                        — /img — картинки
                        — /mock — моки
                        — /test — e2e и unti тесты

                        В дополнение к этому в корне компонента может быть файл c.json.
                        В котором могут быть указаны зависимости от других компонентов и/или структура компонента.

                        Имена компонентов «разруливаются» относительно директории исходников. Например:
                        Исходники у нас в папке src/client/
                        С такой структурой:
                        lib
                        — /utils
                        — /popup
                        — /button
                        — /icons

                        То " компонент утилиты" у меня будет называться lib.utils

                        Далее при разработке какого либо компонента который зависит от utils, я в файле c.json указываю «depends»: ['lib.utils']

                        Какие плюшки еще я получаю.
                        Я могу разрабатывать и тестировать компоненты отдельно от самого приложения, главное написать нужные зависимости, причем не важно, js это код или я пишу стили для кнопок.
                        Очень просто разруливаются зависимости.
                        Можно собирать несколько клиентов на одной кодобазе.
                        • 0
                          Спасибо, буду признателен и, думаю, люди тоже, если вы поделитесь своими тасками =) Еще такой вопрос — как при такой системе подключаются сторонние библиотеки?
                          • +1
                            сторонние библиотеки устанавливаются через bower и указываются в c.json. Например для указания зависимости от angulara в c.json добавляем «bower»: ['@ angular'] (пробел для хаьрапарсера)

                            если, перед именем стоит знак @ то скрипт читает файл .bowerrc находит директорию где лежат модули, после знака @ идет имя модуля, и мы пытаемся прочитать файл bower.json в этом каталоге поле main.

                            Так же можно указать прямой путь к js файлу относительно корня проекта.
                            Единственное ограничение сторонние модули сейчас могут подключаться только в виде js файлов.
                            • 0
                              У меня была как-то идея организовать схожую систему сборки, но без json с зависимостями, но увы руки так и не дошли. Точнее я думал вооружиться astral (по аналогии с ngmin) и таким образом разруливать зависимости, а все внешние зависимости, типа underscore и jquery забирать уже из index.html и добавлять туда при сборке правильные ссылки (с указанием CDN при сборке в релиз).
                        • 0
                          Хочу поделиться своей наработкой: AngularJS + gulp
                          В пакет входит: gulp-uglify, gulp-jshint, gulp-concat, gulp-sass, gulp-jade, livereload и еще пара модулей.

                          github.com/tundrax/angular-gulp/
                          • 0
                            Запуск сервера разработки
                            gulp --env=development или gulp --env=staging

                            Билд
                            gulp build --env=production

                            Файлы конфигурации среды (development, staging, production) находятся в папке src/scripts/config

                            Тест
                            карма старт
                            • 0
                              Спасибо, а какая практическая польза от отдельных модулей для контроллеров, сервисов, моделей?
                              • 0
                                При тестировании. Если тестируются модели, загружаем «MyApp.models», а остальные зависимости можно просто mockнуть.
                            • 0
                              А как вы решили проблему, при которой, допустим при выполнении gulp-sass, в случае если у нас ватчер запустил сборку и она упала (синтаскическая ошибка или еще чего), падает и ватчер?
                              • 0
                                Нужно передать опцию в sass()

                                .pipe(sass({errLogToConsole: true}))

                                И тогда watcher не падает
                            • +1
                              Итак, главное, что нам дает require.js, помимо модульности

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

                              — возможность свести всё приложение в один файл и затем сжать.

                              Это к гранту вопросы (Вопрос №1, Вопрос №2).

                              Общая структура проекта не должна вызывать особых вопросов.

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

                              app.run(controllers.GlobalCtrl);

                              Зачем контроллер-то в .run запускать? Для это есть сервисы (нативные, без извращения require.js).
                              Или <html ng-app="App" ng-controller="indexCtrl"> — будет запущен только раз, когда приложение загрузилось.

                              Советую внимательно посмотреть на ngbp и все будет хорошо.
                              • 0
                                Я думаю, вы могли бы оформить ваш код в виде yeoman генератора, это позволит разворачивать «скелет» приложения в 2 команды.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое
                                Интересные публикации