Skip to main content

TemplateEngine

In most cases, if you only use browser rendering and inject data, there is no need to override the TemplateEngine. EasyEmailPro uses liquidjs, which already supports many syntaxes. Only when you need server-side rendering and your template engine is not SolidJS, you need it. For new projects, we still highly recommend liquidjs because of its excellent performance.

Below are the steps to override the TemplateEngine.

  1. Customize your TemplateEngine by implementing the methods used inside it.
VelocityTemplateEngine.tsx
import {
ConditionOperator,
ConditionOperatorSymbol,
LogicCondition,
LogicIteration,
TemplateEnginePlugin,
components,
} from "easy-email-pro-core";

const { Raw } = components;
import React from "react";
import he from "he";
import Velocity from "velocityjs";
import { set } from "lodash";

const symbolMap = {
[ConditionOperatorSymbol.AND]: "&&",
[ConditionOperatorSymbol.OR]: "||",
};

function isNumeric(x: string | number): x is number {
return (typeof x === "number" || typeof x === "string") && !isNaN(Number(x));
}

export class VelocityTemplateEngine extends TemplateEnginePlugin {
public generateIterationTemplate(
option: LogicIteration,
content: React.ReactNode
): React.ReactNode {
return (
<>
<Raw>
{`
<!-- htmlmin:ignore -->
#foreach( $${option.itemName} in $${option.dataSource} )
${option.limit ? `#if( $foreach.count <= ${option.limit} )` : ""}
<!-- htmlmin:ignore -->
`}
</Raw>
{content}
<Raw>{`
<!-- htmlmin:ignore --> ${
option.limit ? `#end` : ""
}#end <!-- htmlmin:ignore -->
`}</Raw>
</>
);
}

public generateConditionTemplate(
option: LogicCondition | string,
content: React.ReactNode,
fallback?: React.ReactNode
): React.ReactNode {
if (typeof option === "string") {
return (
<>
<Raw>
{`
<!-- htmlmin:ignore -->#if (${option})<!-- htmlmin:ignore -->
`}
</Raw>
{content}
<Raw>{"<!-- htmlmin:ignore -->#else<!-- htmlmin:ignore -->"}</Raw>
{fallback}
<Raw>{"<!-- htmlmin:ignore --> #end <!-- htmlmin:ignore -->"}</Raw>
</>
);
}

const { symbol, groups } = option;

const generateExpression = (condition: {
left: string | number;
operator: ConditionOperator;
right: string | number;
}) => {
if (condition.operator === ConditionOperator.TRUTHY) {
return condition.left;
}
if (condition.operator === ConditionOperator.FALSY) {
return condition.left + " == nil" || condition.left + " == false";
}
return (
"$" +
condition.left +
" " +
condition.operator +
" " +
(isNumeric(condition.right)
? condition.right
: `"${he.encode(condition.right)}"`)
);
};
const uuid = +new Date();
const variables = groups.map((_, index) => `$con_${index}_${uuid}`);

const assignExpression = groups
.map((item, index) => {
return `#set( ${variables[index]} = ${item.groups
.map(generateExpression)
.join(` ${symbolMap[item.symbol]} `)} )`;
})
.join("\n");
const conditionExpression = variables.join(` ${symbolMap[symbol]} `);

return (
<>
<Raw>
{`
<!-- htmlmin:ignore -->
${assignExpression}
#if( ${conditionExpression} )
<!-- htmlmin:ignore -->
`}
</Raw>
{content}
<Raw>
{`
<!-- htmlmin:ignore -->
#end
<!-- htmlmin:ignore -->
`}
</Raw>
</>
);
}

public generateVariable(variable: string, defaultValue?: string): string {
if (defaultValue) {
return `#if ( !$${variable} )\n#set ( $${variable} = "${defaultValue}" )\n#end`;
}
return `$${variable}`;
}

public isVariable(value: string): boolean {
return /\$[^$]+/.test(value);
}

public renderWithData(html: string, data: Record<string, any>): string {
return Velocity.render(html, data);
}
}
  1. Registering your TemplateEngine will override the default TemplateEngine.
PluginManager.registerPlugin(VelocityTemplateEngine);