Окружение для разработки на aiorest (asyncio) + angular.js

в 8:46, , рубрики: AngularJS, bower, grunt, gulp, python

В этой статье мы соберём вместе 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 Annotation
  • uglify — минификация
  • 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js