Рубрики
Без рубрики

Оптимизация HTML и кэширование угловых партиалов с помощью Python

Если вы хотите найти способ сократить преждевременные миллисекунды ваших запросов на страницы/шаблоны, вы находитесь в правильном месте. Этот учебник предоставит вам рекомендации по оптимизации HTML и кэшированию угловых партиалов с помощью Python.

Автор оригинала: Kyle J. Roux.

Преждевременная Оптимизация

это корень всего зла.

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

Просто чтобы быть ясным, эта техника предназначена исключительно для производственного использования. Единственный раз, когда он должен быть использован в разработке, – это проверить, не сломает ли это производственную площадку. В противном случае, если все без ошибок и вы ищете какой-либо способ сократить преждевременные миллисекунды от ваших запросов страницы/шаблона, то вы находитесь в правильном месте.

План

Так что на самом деле есть только несколько вещей, которые нам нужно сделать здесь:

  1. Соберите наши частичные файлы шаблонов
  2. Минимизация HTML-кода
  3. Создайте файл JavaScript, который добавит все наши шаблоны в наш сервис Angular $templateCache .

Таким образом, наш сгенерированный файл JavaScript с шага 3 будет загружен один раз при начальной загрузке страницы вместе со всеми другими нашими ресурсами JavaScript. С этого момента служба $templateCache будет использовать уменьшенный HTML-код в этом файле для обработки любых запросов на частичные файлы, которые ваше приложение отправляет на сервер. Хотя angular асинхронно загружает частичную часть с помощью запроса xhr, в зависимости от времени отклика сервера иногда все может показаться вялым, особенно если директива ngView составляет большую часть вашего основного шаблона.

Наши Инструменты

Мне нравится писать свои помощники по программированию на Python, так как каждая операционная система, стоящая своего веса, поставляется с предустановленным Python (за исключением Windows, но я не уверен, что это для пользователей Windows). Кажется, у меня никогда не возникало проблем с использованием Python, где бы я ни находился.

В основном мы можем использовать потрясающую стандартную библиотеку Python для того, что нам нужно, за исключением случаев, когда мы сокращаем HTML, потому что встроенный HTML-парсер Python, html.parser , как известно, сложен в использовании и подвержен ошибкам. Поэтому вместо этого мы просто будем использовать стороннюю библиотеку: htmlmin . Он имеет легкий след и небольшой, простой API, и это именно та библиотека, которая нам нужна.

Чтобы убедиться, что мы можем использовать его в нашей программе, давайте установим его (надеюсь, в virtualenv вашего проекта, но это не лекция о структуре проекта, поэтому, если его там нет, это не повлияет на нас сейчас. Однако я бы посоветовал заглянуть в эту тему).

$ pip install -U htmlmin

После этого минимизация HTML будет самой простой частью того, что нам нужно сделать, то есть мы можем просто использовать следующие строки:

from htmlmin import minify
minified_html = minify(html)

Ничего не может быть проще.

Теперь перейдем к остальному, что нам нужно.

Генерация JavaScript в Python

Чтобы сгенерировать файл JavaScript, который добавляет наши шаблоны в наш кэш AngularJS, мы можем просто использовать простые методы интерполяции строк.

Самое смешное, что Angular имеет свою собственную систему интерполяции, и иногда это может вызвать конфликты с альтернативными механизмами интерполяции. Наиболее известными примерами являются Jinja2 и Django, так как они оба имеют конфликты из-за совместного использования их стиля интерполяции переменных с двойной фигурной скобкой {{ var }} с Angular. К сожалению, это может запутать стандартный стиль интерполяции строк Python, который использует одинарные фигурные скобки { var } .

К счастью для нас, Python поддерживает другой синтаксис интерполяции строк, и на самом деле это оригинальный способ Python. Он использует стиль c "%TYPE CODE" % VAR , т. е.:

>>> print 'this text was --> %s added' % ('just')
this was --> just added

Итак, поскольку наши угловые партиалы будут завалены {{ и }} , нам нужно будет использовать более старый синтаксис интерполяции Python, чтобы обойти это. Поскольку текущий синтаксис интерполяции рекомендуется, это не то, что вы должны делать регулярно без рассмотрения, так как это может быть источником нечетных ошибок, если сделано неправильно, и это также делает код менее читаемым.

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

Продолжая Идти Дальше

Вот код для интерполяции функций JavaScript, которые добавят шаблоны в службу $templateCache .

import os
from htmlmin import minify

CACHE_FUNC_TEMPLATE = (
"angular.module('%s').run(['$templateCache',function($templateCache){"
"    $templateCache.put('%s',"
"'%s');\n}]);"
)

cache_template =\
    lambda template,name,module:\
            (CACHE_FUNC_TEMPLATE % (module,name,(minify(open(template,'r').read()))))

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

В этих нескольких строчках мы достигли довольно многого. Для начала в первые 2 строки мы импортируем модуль os , единственную часть стандартной библиотеки, которая нам понадобится, и функцию minify из библиотеки HTMLMinifier , которую мы установили ранее. Следующие 3 строки определяют строку, которую мы будем использовать для генерации JavaScript. В нем разбросано 3 %s ‘s, и это заполнители для частей, которые будут меняться: угловой модуль, который нужно объявить при добавлении шаблона в кэш, имя, которое нужно дать шаблону, и содержимое шаблона. Следующие 4 строки определяют анонимную функцию, которая инкапсулирует всю необходимую нам интерполяцию строк.

Сбор Файлов

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

def gather_templates(dirname=None):
    rtn = []
    if dirname is None:
        dirname = os.path.realpath(os.getcwd())
    for fle in os.listdir(dirname):
        if fle.endswith('.html'):
            rtn.append(
                (os.path.join(dirname,fle))
            )
    return rtn

Последнее, что нам нужно, – это функция, которая будет проходить через собранные шаблоны и передавать их имя и содержимое нашей функции cache_template из предыдущей версии.

def minify_templates(dirname,module):
    '''
        add all templates in :dirname to :modules templates cache
    '''
    html = gather_templates(dirname)
    rtn = ''
    brk = '\n'
    for itm in html:
        if not itm.endswith('.min.html'):
            rtn +=\
                cache_template(
                    itm,
                    os.path.basename(itm),
                    module
                ) + brk
    return rtn

Заканчиваю

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

DEFAULT_OUT_FILE = 'all.min.js'

def do_min(module,dirname=None,outfile=None):
    outfile = outfile or DEFAULT_OUT_FILE
    with open(outfile,'w') as out:
        out.write(minify_templates(dirname or os.getcwd(),module))
    print 'all done'

def main():
    module,outfile = None,None
    args = (len(sys.argv) > 1) and sys.argv[1:]
    if args:
        if len(args) == 1:
            module = args[0]
        elif len(args) == 2:
            module,outfile = args
        do_min(module,None,outfile)
        return
    print 'Usage: %s MODULE [OUTFILE][default: %s]' %\
                    (sys.argv[0],DEFAULT_OUT_FILE)

if __name__ == "__main__":
    main()

Вот пример запуска этого в каталоге с этим в файле test.html

{{ angularvar }}
$ Python cache_templates.py test.app test.min.js
$ cat test.min.js

angular.module('test.app').run(['$templateCache',function($templateCache){    $templateCache.put('test.html','
{{ angularvar }}
'); }]);

Для справки, вот полный файл, который мы создавали

import os
import sys
from htmlmin import minify

CACHE_FUNC_TEMPLATE = (
"angular.module('%s').run(['$templateCache',function($templateCache){"
"    $templateCache.put('%s',"
"'%s');\n}]);"
)

cache_template =\
    lambda template,name,module:\
            (CACHE_FUNC_TEMPLATE % (module,name,(minify(open(template,'r').read()))))


def gather_templates(dirname=None):
    html = []
    if dirname is None:
        dirname = os.path.realpath(os.getcwd())
    for fle in os.listdir(dirname):
        if fle.endswith('.html'):
            html.append(
                (os.path.join(dirname,fle))
            )
    return html

def minify_templates(dirname,module):
    '''
        add all templates in :dirname to :modules templates cache
    '''
    html = gather_templates(dirname)
    rtn = ''
    brk = '\n'
    for itm in html:
        if not itm.endswith('.min.html'):
            rtn +=\
                cache_template(
                    itm,
                    os.path.basename(itm),
                    module
                ) + brk
    return rtn

DEFAULT_OUT_FILE = 'all.min.js'

def do_min(module,dirname=None,outfile=None):
    outfile = outfile or DEFAULT_OUT_FILE
    with open(outfile,'w') as out:
        out.write(minify_templates(dirname or os.getcwd(),module))
    print 'all done'

def main():
    module,outfile = None,None
    args = (len(sys.argv) > 1) and sys.argv[1:]
    if args:
        if len(args) == 1:
            module = args[0]
        elif len(args) == 2:
            module,outfile = args
        do_min(module,None,outfile)
        return
    print 'Usage: %s MODULE [OUTFILE][default: %s]' %\
                    (sys.argv[0],DEFAULT_OUT_FILE)

if __name__ == "__main__":
    main()

Обновление

В итоге у меня возникли проблемы в некоторых партиалах при передаче содержимого шаблона в angular. Поэтому я попробовал несколько вещей и нашел лучшее решение-сбросить партиалы в виде большой строки JSON, а затем повторить эту строку в angular, сделав это, я также смог значительно упростить/минимизировать фактическое количество javascript, генерируемого этим скриптом. Вот моя обновленная версия вышеприведенного скрипта:

import os
import sys
from htmlmin import minify

FUNC_TEMPLATE = (
"var t = %s;"
"angular.module('%s').run(['$templateCache',function($templateCache){"
"    var templates = JSON.parse(t).templates;"
"    angular.forEach(templates,function(val,key){"
"        $templateCache.put(key,val);"
"    });"
"}]);"
)

gather_html = lambda name: {x[0]:minify(x[1]) for x in ( lambda name:
{x:open(os.path.join(name , x),'r').read().decode('utf8') for x in ( lambda name: 
[x for x in os.listdir(name) if x.endswith('.html')])(name)})(name).items()}

make_template_cache = lambda dirname,module: 
FUNC_TEMPLATE  % (repr(json.dumps(dict(templates=gather_html(dirname)))),module)

def do_min(module,dirname=None,outfile=None):
    outfile = outfile or DEFAULT_OUT_FILE
    with open(outfile,'w') as out:
        out.write(make_template_cache(dirname or os.getcwd(),module))
    print 'all done'

def main():
    module,outfile = None,None
    args = (len(sys.argv) > 1) and sys.argv[1:]
    if args:
        if len(args) == 1:
            module = args[0]
        elif len(args) == 2:
            module,outfile = args
        do_min(module,None,outfile)
        return
    print 'Usage: %s MODULE [OUTFILE][default: %s]' %\
                    (sys.argv[0],DEFAULT_OUT_FILE)