В этой статье мы соберём вместе aiorest + jinja2 + angular.js + gulp.js + bower.js + nginx. В результате мы получим:
- авто-перезапуск сервера при изменении python-кода и jinja-шаблонов
- сборка, минификация и автоматическая пересборка при изменении клиентского js-кода
Начнём с главного — как пользоваться, а затем я подробнее опишу некоторые моменты реализации. Если вам легче читать код — вот ссылка на репу.
Установка
$ git clone git@github.com:imbolc/aiorest-angular-template.git
$ cd aiorest-angular-template
$ sudo npm install -g gulp bower
$ npm install
$ bower install
$ ./bin/buildenv.py
$ sudo ./bin/configure_nginx.py
Добавляем в /etc/hosts
:
127.0.1.1 aio-angular.l.com
И запускаем сервер разработчика:
$ gulp
Готово, теперь можно открывать в браузере: http://aio-angular.l.com/
Использование
Просто добавляйте обработчики в handlers.py
, урлы — в urls.py
, а клиентский код раскладывайте в папке client
в удобной для вас структуре.
Конфигурация
aiorest не умеет ничего, кроме как отдавать json, поэтому, для отдачи статики мы будем использовать nginx, шаблон его конфига как и другие настройки находится в папке cfg
. Там же находится файлик с питон-зависимостями pipreq.txt
и конфиг для питон-части приложения __init__.py
+ local.py
(последний в джанга-стайл перезаписывает специфичную для текущего сервера конфигурацию первого).
Хранить конфигурацию лучше в одном месте, поэтому клиентский конфиг мы так же генерируем на сервере, а потом подсовываем ангуляру в виде value
(templates/client_config.js
):
angular.module('app').value('cfg', << config|tojson|safe >>);
Сборка js
Мы будем пользоваться мудростью этой статьи, которую я всячески рекомендую к прочтению: Real-World Best Practices for Building Angular.js Apps without Browserify or Require.js. Вкратце: require.js для ангуляра не подходит, browserify — подходит, но нам не нужна большая часть его функционала потому, что в ангуляре уже есть модули.
Итак, будем использовать gulp.js — современную замену bower.js, он не использует промежуточных файлов, имеет более приятный синтаксис описания задач и уже аналогичную кучу готовых плагинов. Смотрим в gulpfile.js
:
gulp.task('js', function () {
gulp.src(['client/app.js', 'client/**/*.js'])
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(concat('app.js'))
.pipe(ngAnnotate())
.pipe(uglify())
.pipe(sourcemaps.write())
.pipe(gulp.dest('./static/build'));
});
sourcemaps.init
+sourcemaps.write
добавляют в сборку source map который позволяет в консоли браузера видеть ошибки и лог-сообщения с нумерацией строк исходных файловconcat
— копирует все файлы в одинngAnnotate
— нельзя просто так взять и минифицировать ангуляр-код: Dependency Annotationuglify
— минификацияplumber
— перехватывает ошибки в ходе всего этого, чем не даёт падать авто-персборке
Авто-перезапуск сервера
gulp.task('dev_server', shell.task([
('nodemon ./app.py --exec "var/env/bin/python"' +
' --ext "py html" --ignore "static"')
]));
Дада, nodemon умеет перезапускать не только node.js :)
Серверный код
Приложение начинается в app.py
:
import asyncio
import aiorest
import cfg
import lib.logging
import prepare_static
import urls
lib.logging.setup(cfg.LOG_FILE)
prepare_static.client_config(urls=urls.CLIENT_URLS)
prepare_static.render_html('index.html')
server = aiorest.RESTServer(hostname='127.0.0.1')
for url in urls.SERVER_URLS:
server.add_url(*url)
loop = asyncio.get_event_loop()
loop.run_until_complete(loop.create_server(
server.make_handler, '127.0.0.1', cfg.PORT))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
Здесь мы настраиваем логирование, подготавливаем клиентский конфиг описанный выше. Затем рендерим пока единственный шаблон т.к. в нём есть некоторая логика:
<% if cfg.DEBUG %>
<script src="/static/bower/angular/angular.js"></script>
<script src="/static/bower/angular-resource/angular-resource.js"></script>
<% else %>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.14/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.14/angular-resource.min.js"></script>
<% endif %>
И да, мы переопределили обозначения управляющих конструкций в jinja2 ({{ }}
=> << >>
, {% %}
=> <% %>
), т.к. ангуляр использует такие же.
Далее мы создаём aiorest-сервер, добавляем урлы из файлика urls.py
:
import handlers
SERVER_URLS = [
('GET', '/hello', handlers.hello),
('GET', '/hello/{name}', handlers.hello),
]
CLIENT_URLS = {
'hello': '/api/hello/:name',
}
CLIENT_URLS
— это аякс урлы которые мы будем использовать в браузерном кодe. А хендлеры для серверных урлов находятся в файлике handlers.py
:
def hello(name='world'):
message = 'Hello, {}!'.format(name)
return {'message': message}
Angular-код
Описывать hello world на ангуляре пожалуй не буду :) Единственное, обращу внимание на использование клиентского конфига, который мы готовили выше (client/hello/hello.svc.js
):
angular.module('app')
.service('HelloSvc', function ($resource, cfg) {
this.fetchGreeting = function () {
return $resource(cfg.urls.hello).get.apply(this, arguments);
};
});
Автор: Imbolc