Последние изменения в cl-closure-template
Тут незадолго до нового года вышел новый релиз Google Closure Templates и я решил посмотреть, что же изменилось в библиотеке пока я занимался разными другими делами. Оказалось, что разрыв в возможностях с cl-closure-template уже довольно внушительный, так что я решил его немного сократить. Правда, некоторые решения оригинальной реализации мне не понравились и я решил от них отклониться. Итак, в последнее время в cl-closure-template были добавлены:
Поддержка литералов для списков: [<expr1>, <expr2>, ...]. Например: [1, 'two', [3, false]]. [] - пустой список.
Поддержка литералов для словарей: {<keyExpr1>: <valueExpr1>, <keyExpr2>: <valueExpr2>, ...}. Например: {'aaa': 42, 'bbb': 'hello'}. {:} - пустой словарь. В оригинальной версии вместо фигурных скобок используются квадратные и мотивируется это тем, что фигурные уже используются для команд. И в начале я тоже сделал на квадратных. Но потом решил что это не нормально, что гораздо естественнее и приятнее использовать всё таки фигурные скобки. Скорей всего такое решение в оригинальной реализации обусловлено особенностями парсера, но у меня таких особенностей нет, поэтому в итоге переделал на фигурные скобки.
Добавлена функция keys. keys(map) - возвращает ключи словаря map в виде списка.
Добавлена функция augmentMap. augmentMap(baseMap, additionalMap) - создаёт новый словарь (ну, реализация несколько сложнее, не просто словарь), содержащий записи как из additionalMap, так и из baseMap. При поиске ключа в таком словаре он сначала ищется в additionalMap, а если не найден, то поиск продолжается в baseMap. Для Common Lisp получилась хорошая реализация, без лишних движений, а вот в случае JavaScript приходиться заниматься копированием additionalMap.
Добавлена функция strContains. strContains(str, subStr) - проверят является ли subStr частью str.
Добавлена функция isNonnull. isNonnull(value) - возвращает true если value определенна и не равна null.
Реализована возможность вызывать (call) шаблоны из других namespace. Например:
{namespace mytemplate.test1} {template helloWorld} Hello world! {/template}{namespace mytemplate.test2} {template callHelloWorld} {call mytemplate.test1.helloWorld /} {/template}В случае Common Lisp вызвать таким образом можно только зарегистрированный шаблон, а в JavaScript всё что угодно.
Добавлена команда let. Мне не понравилось как она сделана в Google Closure Templates - не ясно как определяется область видимости создаваемых переменных. При чём, не ясно как в спецификации, там и при просмотре кода шаблоны так же могут быть трудности. Поэтому я решил сделать вариант с более чёткой структурой: {let $<identifier1>="<expression1>" $<identifierN>="<expressionN>"}...{/let}
Пример:
{let $x="1" $y="$foo.bar + 10"} // Здесь код, использующий $x и $y {/let}Честно говоря, ранее у меня уже была команда with, которая делала то же самое. Но let, конечно, куда лучше подходит по названию, плюс я немного поправил синтаксис. Правда, при этом была потеряна одна возможность (rendering to string) оригинальной реализации, но я считаю её совершенно несущественной.
В Google Closure Templates сейчас есть такая штука, как "delegates", с которой связанно несколько команд: delpackage, deltemplate и delcall. Я считаю данный функционал исключительно мутным и реализовывать что-либо подобное не хочу. А вместо это предлагаю, как мне представляется, куда более простой и понятный механизм - прототипы пространств имён.
Рассмотрим Common Lisp backend. При компиляции namespace создаётся package с таким же именем, а в нём функции для каждого шаблона из namespace. Но эти функции на самом деле никакой реализации не содержат, а просто обращаются к объекту *ttable*, находящемся в том же пакете. *ttable* это объект класса closure-template:ttable, который содержит в себе словарь с реальными обработчикам. Так вот, при создании этого объекта ему можно указать prototype - другой объект класса ttable. Вот небольшой пример, скажем у нас есть такой незамысловатый namespace:
{namespace myapp.view.base}
{template helloWorld}
Hello world!
{/template}
{template mainPage}
<h1>{call helloWorld /}</h1>
{/template}
Если теперь выполнить:
'#:myapp.view.mytheme
:prototype (closure-template:package-ttable '#:myapp.view.base))
И скомпилировать файл с шаблонами:
{namespace myapp.view.mytheme}
{template helloWorld}
Привет мир!
{/template}
То станет возможным выполнить следующий код:
"<h1>Hello world!</h1>"
CL-USER> (myapp.view.mytheme:main-page)
"<h1>Привет мир!</h1>"
Надеюсь демонстрация получилась достаточно наглядной. Для JavaScript никакой специальной поддержки нет, достаточно просто реализовать такое поведение написав несколько строк на
myapp.view.mytheme = (function () {
function C () {}
C.prototype = myapp.view.base;
return new C;
})();
Вообще, из-за накопления различий и появлением не вполне очевидных возможностей задумался про написание документации.