Привет, это я, @shadowtime2000, один из сопровождающих Eta, быстрого встраиваемого шаблонизатора. В этом уроке я покажу, как создать изоморфный (браузер/узел) механизм шаблонов JavaScript.
Дизайн
Первоначальный дизайн механизма шаблонов будет довольно простым. Он просто интерполирует значения из объекта data. Он будет использовать{{valueName}}для интерполяции значений.
Простой рендеринг
Во-первых, давайте создадим простую функцию рендеринга, которая берет шаблон и данные и будет отображать значение.
var render = (template, data) => {
return template.replace(/{{(.*?)}}/g, (match) => {
return data[match.split(/{{|}}/).filter(Boolean)[0]]
})
}
По сути, все, что нужно сделать, это найти все, что заключено в скобки, и заменить его именем внутри data. Вы можете написать свои шаблоны так, и он возьмет их из объекта данных.
Hi, my name is {{name}}!render("Hi, my name is {{name}}!", { name: "shadowtime2000" });
Но есть проблема, в интерполяциях нельзя использовать пробелы.
render("Hi, my name is {{ name }}!", { name: "shadowtime2000" }) /* Hi, my name is undefined! */
Это требует, чтобы у вас были пробелы внутри объекта данных, что не так уж и чисто. Мы можем разрешить пробелы, обрезав начальные и конечные пробелы имени данных перед интерполяцией.
Это довольно хорошо, но для больших шаблонов это будет не так быстро, потому что ему приходится каждый раз анализировать его. Вот почему многие механизмы шаблонов поддерживают компиляцию, когда шаблон компилируется в более быструю функцию JS, которая может принимать данные и интерполировать их. Давайте добавим компиляцию в наш шаблонизатор, но прежде нам нужно добавить специальную функцию парсинга.
Разбор
Поскольку синтаксический анализ может быть немного скучным, давайте просто повторно используем код из другого механизма шаблонов JS. Я бы использовал механизм парсинга Eta, но он чрезвычайно оптимизирован и может сбить людей с толку. Итак, давайте воспользуемся другим популярным кодом парсинга шаблонизатора JS, mde/ejs. Не забудьте указать их для механизма синтаксического анализа.
var parse = (template) => {
let result = /{{(.*?)}}/g.exec(template);
const arr = [];
let firstPos;
while (result) {
firstPos = result.index;
if (firstPos !== 0) {
arr.push(template.substring(0, firstPos));
template = template.slice(firstPos);
}
arr.push(result[0]);
template = template.slice(result[0].length);
result = /{{(.*?)}}/g.exec(template);
}
if (template) arr.push(template);
return arr;
}
В основном это делает цикл по выполнению шаблона регулярного выражения в шаблоне и добавлению материала в структуру данных. Вот как будет выглядеть эта структура данных:
["Hi my name is ", "{{ name }}", "!"]
Теперь, когда мы закончили синтаксический анализ, давайте перейдем к компиляции.
Сборник
Давайте кратко рассмотрим, что выдаст компиляция. Представьте, что вы вводите этот шаблон:
Hi my name is {{ name }}!
Это даст вам эту функцию:
function (data) {
return "Hi my name is "+data.name+"!";
}
Давайте сначала создадим функцию для анализа, а затем создадим строку, которую можно использовать. Сначала нам нужно разобрать шаблон.
const compileToString = (template) => {
const ast = template;
}
Мы также должны создать строку, которая будет использоваться в качестве функции.
const compileToString = (template) => {
const ast = template;
let fnStr = `""`;
}
Причина, по которой мы используем кавычки в начале, заключается в том, что при компиляции шаблонов и т. д. все они начинаются с +. Теперь нам нужно перебрать AST.
const compileToString = (template) => {
const ast = template;
let fnStr = `""`;
ast.map(t => {
// checking to see if it is an interpolation
if (t.startsWith("{{") && t.endsWith("}}")) {
// append it to fnStr
fnStr += `+data.${t.split(/{{|}}/).filter(Boolean)[0].trim()}`;
} else {
// append the string to the fnStr
fnStr += `+"${t}"`;
}
});
}
Последняя часть этой функции — возврат строки функции.
const compileToString = (template) => {
const ast = template;
let fnStr = `""`;
ast.map(t => {
// checking to see if it is an interpolation
if (t.startsWith("{{") && t.endsWith("}}")) {
// append it to fnStr
fnStr += `+data.${t.split(/{{|}}/).filter(Boolean)[0].trim()}`;
} else {
// append the string to the fnStr
fnStr += `+"${t}"`;
}
});
return fnStr;
}
Итак, если он принимает этот шаблон:
Hi my name is {{ name }}!
Он вернет это:
""+"Hello my name is "+data.name+"!"
Теперь это сделано, создать функцию компиляции относительно просто.
const compile = (template) => {
return new Function("data", "return " + compileToString(template))
}
Теперь мы завершили компиляцию для нашего шаблонизатора.
Подведение итогов
В этом уроке я показал, как:
- Реализовать простую функцию рендеринга
- Понимание механизма синтаксического анализа, адаптированного из EJS.
- Повторите AST, чтобы создать быстро скомпилированные шаблонные функции.
Первоначально опубликовано на https://hackernoon.com 20 декабря 2020 г.