O design
O design inicial do mecanismo de template será bastante simples. Ele simplesmente interpola valores de um
data
objeto. Vai usar{{valueName}}
para interpolar valores.Renderização Simples
Primeiro, vamos criar uma função de renderização simples que pega o modelo e os dados e renderiza o valor.
var render = (template, data) => {
return template.replace(/{{(.*?)}}/g, (match) => {
return data[match.split(/{{|}}/).filter(Boolean)[0]]
})
}
Basicamente, tudo o que faz é pesquisar qualquer coisa que esteja entre colchetes e substitui-o pelo nome dentro
data
. Você pode escrever seus modelos como este e irá retirá-los do objeto de dados.Hi, my name is {{name}}!
render("Hi, my name is {{name}}!", {
name: "shadowtime2000"
});
Mas há um problema, você não pode ter espaços nas interpolações.
render("Hi, my name is {{ name }}!", {
name: "shadowtime2000"
})
/*
Hi, my name is undefined!
*/
Isso requer que você tenha espaços dentro do objeto de dados, o que não é tão limpo. Podemos fazer com que permita espaços cortando os espaços em branco iniciais e finais do nome dos dados antes da interpolação.
var render = (template, data) => {
return template.replace(/{{(.*?)}}/g, (match) => {
return data[match.split(/{{|}}/).filter(Boolean)[0].trim()]
})
}
Isso é muito bom, mas para modelos maiores não seria tão rápido porque tem que analisá-lo o tempo todo. É por isso que muitos motores de template suportam compilação, onde o template é compilado em uma função JS mais rápida que pode pegar os dados e interpolar. Vamos adicionar compilação ao nosso mecanismo de template, mas antes disso, precisamos adicionar uma função de análise especial.
Análise
Já que a análise pode ser um pouco entediante, vamos apenas reutilizar algum código de outro mecanismo de modelo JS. Eu teria usado o mecanismo de análise Eta, mas ele foi extremamente otimizado e pode ser muito confuso para as pessoas. Portanto, vamos usar outro código de análise de mecanismo de modelo JS popular, mde / ejs . Lembre-se de atribuí-los ao mecanismo de análise.
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;
}
O que isso basicamente faz é repetir a execução do padrão regex no modelo e adicionar as coisas a uma estrutura de dados. Esta é a aparência dessa estrutura de dados:
["Hi my name is ", "{{ name }}", "!"]
Agora que a análise foi concluída, vamos prosseguir para a compilação.
Compilação
Vamos dar uma rápida visão geral do resultado da compilação. Imagine que você insira este modelo:
Hi my name is {{ name }}!
Ele lhe dará esta função:
function (data) {
return "Hi my name is "+data.name+"!";
}
Vamos primeiro criar uma função para analisar e, em seguida, criar uma string que pode ser usada. Primeiro, temos que analisar o modelo.
const compileToString = (template) => {
const ast = template;
}
Também temos que criar uma string que será usada como função.
const compileToString = (template) => {
const ast = template;
let fnStr = `""`;
}
O motivo pelo qual estamos usando aspas no início é porque quando ele está compilando os modelos e outros, todos eles começarão com um
+
. Agora temos que iterar no 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}"`;
}
});
}
A parte final desta função é retornar a string da função.
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;
}
Então, se for este o modelo:
Hi my name is {{ name }}!
Ele retornará este:
""+"Hello my name is "+data.name+"!"
Agora que isso está feito, criar uma função de compilação é relativamente simples.
const compile = (template) => {
return new Function("data", "return " + compileToString(template))
}
Agora concluímos a compilação de nosso mecanismo de modelo.
Sem comentários:
Enviar um comentário
Comente de forma construtiva...
Nota: só um membro deste blogue pode publicar um comentário.