Пишем свой JavaScript шаблонизатор

3.2 из 5

Mustache — шаблонизатор, который содержит минимум управляющей логики и доступен для разных языков программирования. Его можно использовать и на сервере (PHP, Ruby и т.д.), и на клиенте (Javascript).

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

Шаблон пишется на простом языке, состоящем из нескольких типов тегов. Теги обрамляются двумя или тремя фигурными скобками с каждой стороны. Можно использовать вложенные шаблоны.

Рассмотрим простой пример шаблона:

{{header}}

{{content}}



    {{#authors}}
  • {{#accent}} {{.}} {{/accent}}

  • {{/authors}}
    {{^authors}}
  • anonymous

  • {{/authors}}

Данные, с которыми работает шаблон, называются контекстом. Имя тега указывает, к какому полю контекста необходимо обратиться. Пример данных, которые могут послужить контекстом для нашего шаблона:

Var data = {
header: "Новый пост",
content: "Первая строка
Вторая",
authors: ["alex", "daemon", "john"],
accent: function () {
return function (text, render) {
text = render(text);
return "" + text + "";
}
}
};

Чтобы «запустить» шаблонизатор и отрисовать с помощью шаблона данные, необходимо подключить библиотеку:

И вызывать рендеринг методом to_html:

Mustache.to_html(template, data);

Здесь первым параметром передается шаблон, а вторым — данные. Так же можно использовать третий параметр —список дополнительных шаблонов, и четвертый — функцию callback, которая вызывается после обработки шаблона.

Более подробно о тегах

Всего в Mustache четыре основных типа тегов: переменная , секция , комментарий и подключение дополнительного шаблона .

Переменная выводит данные с экранированием HTML-сущностей {{header}} и без экранирования {{{content}}} . Отличаются они количеством скобок. В нашем случае, вместо {{header}} подставится строчка «Новый пост».

Секция представляет собой парный тег. Принцип ее действия зависит от типа данных, с которыми она работает. Если в контексте имени секции соответствует поле со списком, то шаблонизатор проходит по его элементам и текст внутри парного тега обрабатывается по одному разу для каждого элемента списка. Элемент списка подставляется заместо тега-точки. Так, например, секция {{#authors}}

  • {{.}}
  • {{/authors}} превратится в
  • alex
  • daemon
  • john
  • . Если список пуст, то обрабатывается содержимое «тега с крышечкой», в нашем случае — это {{^authors}} … {{/authors}} .

    Если имени секции соответствует функция, то для подстановки будет использован результат ее выполнения.

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

    Комментарий оформляется в виде тега с восклицательным знаком, например, {{! comment content}} .

    Подключение дополнительного шаблона вызывается с помощью тега с угловой скобкой. Например, {{>copyright}} . Если в текущем контексте присутствует поле с таким названием, то оно будет передано в качестве контекста для подключаемого шаблона.

    Производительность

    Вопросы производительности Javascript шаблонизаторов рассматриваются в этой статье . Автор провел тестирование восьми шаблонизаторов и выяснил, что на простых шаблонах Mustache показывается лучшую производительность.

    Но так как я частенько увлекаюсь JavaScript"ом, то решено было найти подобный шаблонизатор для этого языка, но серверного (node.js, ага). Первые ссылки в поисковике выдают такие шаблонизаторы, как: ECT , JUST , . Все они, естественно, не подходили для меня, т.к. слишком далеко были от синтаксиса Smarty.

    Чтож, раз хочется, а решений нет - то сделай. Выискивая время на работе и иногда получая по «шапке», родился проект NodeSmarty , который, в будущем, будет частично копировать синтаксис Smarty .

    Итак, свои корни проект пустил примерно 4 месяца назад, в сентябре 2012 года и постепенно обретал рабочий вид. По началу весь алгоритм компиляции шаблонов брался из Smarty (файл Smarty_Compiler.class.php), некоторые функции не были перенесены. В процессе написания кода я делал всевозможные сокращения кода в плане простоты, так как это «мой стиль». Поэтому были заменены все огромные регулярные выражения (да и в них, кстати, всё равно было полно ненужного мусора), часть логики в функциях.

    Так как проект необкатанный, то совершенно понятно, что в процессе будет найдено достаточное количество багов. Также уверен, что будут жалобы в сторону обработок ошибок в шаблонах (на данном этапе эта «функция» реализована плохо). Я буду только рад получать сообщения об улучшении кода, и по мере своих возможностей вносить как исправления, так и новые функции.

    Примеры работы шаблонизатора:

    compiler.js:
    var NodeSmarty = require("./NodeSmarty/"); var Template = new NodeSmarty(); Template .setTemplateDir("./templates/") .setCompileDir("./compile/") .setCacheDir("./cache/"); var Array = ["val1", "two"]; Template.assign({ "Value":"first", "number":10, "array":Array }); var Final = Template.fetch("template.tpl", function(data) { console.log(data); });

    template.tpl:
    {include file="header.tpl.html"} {$Value} {$number*10/20} {if !$Value} //... {elseif $number = $Value} //... {else} //... {/if} {* comment *} {literal} for(var di=0; di < 10; di++) { console.log(di); } {/literal} {foreach from=$array item=Foo key=Key} Item: {$Foo} key: {$Key} {/foreach}

    Скорость выполнения кода выше, чем у EJS и JUST. Происходит это из-за того, что при повторном запросе файла, вызывается не шаблон, а уже скомпилированный код. Но есть и еще один плюс (хотя его и нет, но будет в будущем) - повышение скорости выполнения также зависит от файловой системы (дада, сначала нужно проверить, не изменились ли шаблоны, перекомпилировать их). Если же компилировать файлы при первом запросе, а при последующих просто выгружать из памяти, то скорость соответственно увеличится, и время выполнения кода станет еще меньше!

    Сравнение скорости выполнения кода при 1000 итераций:

    Немножко о принципе работы.

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

    Первым делом парсится документ на наличие открывающего тега: { и закрывающего тега: } (хотя можно и изменить по желанию, см документацию).
    Потом каждый тег парсится на главный параметр и атрибуты. Главный параметр также проходит обработку и разбивается на 2 типа: команда или переменная. Переменная отличается от команды впередистоящим знаком доллара ($ ). И переменная и команда проходят обработку для преобразования себя в рабочий JavaScript код. Скомпилированный код записывается в файл и происходит eval текста (хотя там не eval, а new Function() ).

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

    ICanHaz.js – довольно простая и легкая библиотека, чтобы работать с шаблонами Mustache. С ним можно определять фрагменты шаблонов в тегах скриптов с type=”text/html” и вызывать их через id, с валидацией.

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

    – расширение для Mustache и полностью с ним совместимо. Имеет большую скорость исполнения, так как не перегружен лишней логикой. HTML по тому же принципу генерируется из данных в формате JSON. Существенный недостаток – большой вес библиотеки.

    Underscore.js – удобный и практичный инструмент для работы с jQuery. Имеет больше 60 функциональных утилит. Данный шаблонизатор усиляет функциональность jQuery при работе с массивами, функциями, объектами, коллекциями и т.д. 5 альтернатив jQuery UI .

    Hogan.js – шаблонизатор, который разработала компания Twitter. Его используют в качестве компоновщика промежуточных шаблонов для их быстрой динамической обработки веб-браузером. В основу разработки данного шаблонизатора был положен Mustache, но Hogan.js работает намного быстрее. Для доступа к функциям парсера предлагается API. Сканирование шаблонов и парсинг исполняются отдельными методами, в результате чего шаблоны могут заранее обрабатываться на сервере, а на клиентской стороне использоваться в Javascript-виде.

    – используют для работы с серверными шаблонами , но в общем применим и для других целей. Имеет высокую читабельность кода и хорошую безопасность. Функционирует из части клиента, есть возможность с помощью утилиты компилировать html-шаблоны из командной строки. Шаблонизатор переименован в Pug.js, но по старому названию можно найти большое количество документации и полезной информации для разработчика.

    Шаблонизатор ECT разработан для повышения скорости. В его основе шаблонизатор Coffeescript. Имеет хорошую совместимость с Node.js и довольно простой синтаксис. Можно также ознакомиться с предварительными тестами, которые показывают производительность данной библиотеки.

    Компания Hyper Host™ желает Вам приятной работы в мире JS и готова разместить Ваши замечательные проекты на нашем !

    4366 раз(а) 3 Сегодня просмотрено раз(а)

    Недавно пришлось столкнуться с уже готовым очень простым проектом, в котором было необходимо добавить вывод дерева категорий и товаров в них. Данные приходили в виде json через запросы к API. В арсенале на фронте был лишь jQuery, а привыкнув к шикарному шаблонизатору Angular’а, я не хотел вновь возвращаться к конкатенации строк, и подключать какой-то фреймворк естественно не имело смысла. Мне необходим был минимальный шаблонизатор, который мог бы повторять куски верстки подобно директивам angular, с условиями и переменными.

    Вооружившись статьями авторов John Resig и Krasimir Tsonev , я приступил к работе. В итоге я получил функцию, которая могла компилировать шаблон типа:

    min

    Начнем с банального:

    Var TemplateEngine = function(tpl, data) { // код шаблонизатора } var template = "

    Hello, my name is . I\"m years old.

    "; console.log(TemplateEngine(template, { name: "John", age: 23 }));

    И как вы догадались, хотелось бы, что бы функция возвращала:

    Hello, my name is John. I"m 23 years old.

    Для обнаружения js переменных в тексте верстки воспользуемся регулярным выражением:

    Var re = /]+)?%>/g

    С помощью данной регулярки мы сможем находить все что находится между тегами . Параметр /g обозначает, что нас интересует не одно совпадение, а все.

    Есть множество способов как использовать в js регулярное выражение, мы воспользуемся методом.exec():

    Var re = /]+)?%>/g; var match = re.exec(tpl);

    Если мы сделаем console.log переменной match, то получим следующее:

    [ "", " name ", index: 21, input: "

    Hello, my name is . I\"m years old.

    " ]

    Как вы видите, наш масcив содержит лишь один элемент, а обработать нам нужно все, поэтому обернем всю логику в цикл while.

    Var re = /]+)?%>/g, match; while(match = re.exec(tpl)) { console.log(match); }

    Запустив данный код мы найдем обе переменные и .

    Теперь самое интересное, нам необходимо заменить найденные переменные их значениями. Самое простое что приходит на ум это сделать простой.replace(). Но это бы работало с простыми json объектами с одним уровнем вложенности. На практике же мы имеем дело с объектами, которые имеют многоуровневую вложенность:

    { name: "John", profile: { age: 23 } }

    И tpl.replace(match, data) будет уже не достаточным решением. Потому что когда мы напишем , код заменится на data[«profile.age»] и будет undefined. Так как способ замены нам не подходит, было бы очень круто, если бы между тегами можно было выполнять реальный js код.

    Var template = "

    Hello, my name is . I\"m years old.

    ";

    Как избавиться от this я расскажу ниже.

    Как же это реализовать? В статье Джона Резига он использует new Function для создания функции из строки.

    Var fn = new Function("arg", "console.log(arg + 1);"); fn(2); // outputs 3

    Для понимания, данный код можно рассмотреть как:

    Var fn = function(arg) { console.log(arg + 1); } fn(2); // outputs 3

    fn — реальная функция, которая выполняет другую функцию, которая передана как текстовый параметр.

    Это именно то, что нам необходимо, нам необходимо шаблон преобразовывать в вид:

    Return "

    Hello, my name is " + this.name + ". I\"m " + this.profile.age + " years old.

    ";

    Но наш код будет работать только для вывода переменных между текстом верстки. Но нам ведь необходим шаблонизатор с циклами и условиями, и если мы будем иметь шаблон типа

    Return "My skills:" + for(var index in this.skills) { + "" + this.skills + "" + }

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

    Var r = ; r.push("My skills:"); for(var index in this.skills) { r.push(""); } return r.join("");

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

    Var TemplateEngine = function(tpl, data) { var re = /]+)?%>/g, code = "var r=;\n", cursor = 0, match; var add = function(line) { code += "r.push("" + line.replace(/"/g, "\\"") + "");\n"; } while(match = re.exec(tpl)) { add(tpl.slice(cursor, match.index)); add(match); cursor = match.index + match.length; } add(tpl.substr(cursor, tpl.length - cursor)); code += "return r.join("");"; // /g, reExp = /(^()?(var|if|for|else|switch|case|break|{|}|;))(?:(?=\()|(?=)|$)/g, code = "var r=;\n", cursor = 0, match; var add = function(line, js) { js? (code += line.match(reExp) ? line + "\n" : "r.push(" + line + ");\n") : (code += line != "" ? "r.push("" + line.replace(/"/g, "\\"") + "");\n" : ""); return add; };

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

    Var r=; r.push("My skills:"); for(var index in this.skills) { r.push(""); r.push(this.skills); r.push(""); } r.push(""); return r.join("");

    И конечно же все это будет успешно скомпилировано.

    Теперь для того, что бы шаблон не передавать строкой, а удобно его верстать в теле html страницы, мы поместим его между тегами:

    В данном случае mime тип text/html для браузера будет неизвестным и он пропустит выполнение его. Но содержимое этих тегов мы легко можем получить с помощью.innerHTML.

    Добавим теперь в начало нашего шаблонизатора проверку, если строка начинается с #, то это id нашего шаблона.

    Var html = tpl.charAt(0) === "#" ? document.getElementById(tpl.substring(1)).innerHTML: tpl;

    Если строка начинается не с #, значит мы передали сразу шаблон как строку.

    И завершающим этапом для нас будет избавление от обращения к this перед каждой переменной, а для этого мы передадим имена переменных scope в параметре new Function, а значения их мы присвоим с помощью метода.apply().

    Return new Function(name, code.replace(/[\r\t\n]/g, "")).apply(this,value);

    Теперь наш шаблонизатор может принять шаблон типа:

    min

    при этом js будет выглядеть так:

    Var scope = [ { name: "item 1", price: "10$", time: "30" }, { name: "item 1", price: "10$", time: "30" }, { name: "item 1", price: "10$", time: "30" } ]; var template = tpl("#tpl_id",scope);

    Теперь содержимое переменной template будет скомпилированной версткой, которую мы можем вставить в любой участок кода.

    В итоге мы получим финальную версию нашего шаблонизатора, который может получать шаблоны по их id и компилировать в верстку.

    Var tpl = function (str, data) { var name = , value = ; var html = str.charAt(0) === "#" ? document.getElementById(str.substring(1)).innerHTML: str; if (typeof(data) === "object") { for (var k in data) { name.push(k); value.push(data[k]); } } var re = /]+)?%>/g, reExp = /(^()?(var|if|for|else|switch|case|break|{|}|;))(?:(?=\()|(?=)|$)/g, code = "var r=;\n", cursor = 0, match; var add = function(line, js) { js? (code += line.match(reExp) ? line + "\n" : "r.push(" + line + ");\n") : (code += line != "" ? "r.push("" + line.replace(/"/g, "\\"") + "");\n" : ""); return add; }; while(match = re.exec(html)) { add(html.slice(cursor, match.index))(match, true); cursor = match.index + match.length; } add(html.substr(cursor, html.length - cursor)); code += "return r.join("");"; return new Function(name, code.replace(/[\r\t\n]/g, "")).apply(this,value); };

    Есть несколько случаев, когда вам может понадобиться шаблонизатор на JavaScript , среди них как необходимость формирования содержимого на клиенте, так и на сервере, если используется JavaScript среды, такие как NodeJS или Rhino . Сразу скажу, что для себя рассматривал многие имеющиеся шаблонизаторы, от простых до экзотических. Более всего интересовали простые, однако позволяющие использовать сложную логику в шаблонах, и одним из таковых оказался EJS . Однако, штука эта была написана несколько не так, как мне бы хотелось, с одной стороны было много лишнего, с другой основная функциональность уж слишком усложнена. Я увидел возможность реализовать компиляцию и рендеринг его шаблонов гораздо проще.

    Применение EJS

    То, что делает шаблонизатор : беря шаблон и данные , возвращает экземпляр содержимого , полученный путём обработки шаблона на основе этого конкретного экземпляра данных .

    В случае EJS суть проста: в составе шаблона мы можем совмещать выводимое как есть содержимое с кусочками встроенного JavaScript , аналогично тому, как в файлах сценариев PHP объединяется HTML с кодом. В коде видны как глобальные переменные, так и те, что мы позаботимся передать рендереру при вызове. Ещё там видны так называемые функции-помощники, которые упрощают формирование часто используемых конструкций, например, тегов ссылок или картинок.

    Код обрамляется в тег или в тег .

    Вот как это может выглядеть:

    Я намеренно написал конструкции в разных стилях, чтобы показать, что наличие пробелов значения не имеет. Формируя вывод с использованием шаблона, мы передаём объект со свойствами, выступающими в качестве переменных, с которыми оперирует код внутри нашего шаблона. Смысл в том, что с помощью логических конструкций на самом же JavaScript можно гибко менять вид формируемого в результате содержимого. Как вариант возможно использование тегов вида [% %] , если шаблон предполагается рендерить уже после того, как он включён в дерево DOM . В свете моих паттернов использования, оно мне показалось не очень полезным.

    Улучшаем EJS

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

    Разбор исходных шаблонов

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

    /(?:\n\s*)?(])+)%>/gm

    Итак, что мы здесь делаем:

    • (?:\n\s*)? - убираем перевод строки и все пробелы от него до тега кода, если таковое вообще присутствует. Это, конечно, делать было вовсе не обязательно, но мы же эстеты, так предотвратим появление множества пустых строк в формируемом содержимом. Спецификатор ?: в начале подпаттерна означает, что его содержимое не захватывается, а просто пропускается.
    • (] - наш код - это любые символы, не являющиеся % , или символ % , за которым не следует символ > .
    • (?:[^%]|[%][^>])+ - наш код - эти последовательности могут присутствовать более одного раза, но не должны захватываться.
    • ((?:[^%]|[%][^>])+) - наш код - а захватываем мы всю последовательность целиком.
    • %> - закрывающий тег не захватываем.
    • gm - включаем глобальную полнотекстовую (многострочную) обработку.

    Как можно видеть, всё достаточно тривиально, осталось разобраться, что делать с этим регулярным выражением. А здесь нам поможет очень полезный метод String.split , который разбивает строку с использованием указанного разделителя, возвращая массив получившихся подстрок. Особенность данного метода в том, что в качестве разделителя он может принимать регулярное выражение, причём, если в нём присутствуют захватываемые подпаттерны, подстроки, которые им соответствуют, также попадут в результирующий массив в обычном порядке. Это и даёт нам идею нативной оптимизации компилятора шаблонов: разбиваем строку регулярным выражением, получаем последовательность из кусочков строчного контента и спецификаторов кода со следующими за ними кусочками этого кода, далее достаточно просто обработать получившийся массив.

    Формирование исполняемого кода

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

    Организуем это следующим нехитрым образом:

    • функцию будем формировать в виде строки, пригодной для вызова eval
    • в теле функции организуем последовательное присоединение кусочков содержимого к некой переменной результата
    • обычный код на JS вставим как есть

    Однако нам потребуется решить следующие проблемы:

    • передача переменных и функций-помощников в тело исполняемой функции шаблона
    • организация переменной результата так, чтобы это не пересекалось с переменными, используемыми в шаблоне
    Решение первой проблемы

    Для того, чтобы решить первую проблему, проще всего использовать конструкцию with . Да, это именно та вещь, которая является извечной темой бурных дискуссий разработчиков и головной болью стандартизаторов w3c, именно её так ненавидят всякие валидаторы типа JSLINT и компиляторы вроде Closure Compiler . То, что делает данная конструкция, проще всего представить как предоставление объекта в качестве уровня видимости локальных переменных, то есть вы сможете обратиться к свойствам объекта по имени как к обычным переменным, то есть без префикса имени объекта с точкой, вот, как это выглядит:

    Var obj = { foo: "It is foo.", bar: 42 }; with(obj){ foo += " And bar is " + bar; }

    При чтении значений всё довольно просто, не так тривиально при записи, особенно при использовании вложенных конструкций with , однако в шаблонах нам потребуется в основном читать значения, поэтому использование нативного подхода вместо того, чтобы городить очередной костыль, более чем оправданно. Если быть точным, то на самом деле создаются два взаимовложенных уровня видимости локальных переменных: один из объекта, другой внутри тела with, то есть при создании локальных переменных там, они не попадают в исходный объект.

    Решение второй проблемы

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

    Посмотрим, что у нас есть, а вариантов в общем то не много: как-то использовать объект this или объект arguments , которые, ввиду особенностей языка, пользователь всё равно не должен использовать в шаблонах, а если и может, то лишь неким особым образом. Рассмотрев все за и против, я решил использовать arguments , массив неименованных аргументов, переданных функции, в один из них и будем класть всё, что нужно для корректного формирования содержимого.

    Особенности реализации

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

    Самое время перейти непосредственно к коду, рассмотрим всё по частям, начиная с главного и основного, а именно, с объекта шаблона:

    // Конструктор var EJS = function(src){ if(typeof src == "string"){ // Если передан шаблон this.compile(src); // Сразу компилируем его } }; // Прототип EJS.prototype = { regexp: /(?:\n\s*)?(])+)%>/gm, helper: {} // Функции-помощники };

    EJS.prototype.compile = function(src){ delete this.method; delete this.error; // удаляем следы предыдущего вызова компилятора var p = src.split(this.regexp), // Результат разбора r = , // Результат сборки i, o; this.parsed = p; // Сразу же сохраняем результат разбора // Выполняем сборку функции генерации содержимого for(i = 0; i < p.length; i++){ if(p[i] == "