Pesquisar aqui

sexta-feira, 8 de janeiro de 2021

Como criar um mecanismo de modelo usando JavaScript

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.