Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Modern Template Consolidation Engine for EJS, Markdown, Pug, Nunjucks, Liquid, and Handlebars
Ecto is a modern template consolidation engine that enables the best template engines: EJS, Markdown, Pug, Nunjucks, Mustache, Handlebars, and Liquid. It consolidates these template engines to a single library, allowing you to use any of them with ease.
render
and renderFromFile
functions for ES6 and Typescript.The Node.js package manager documentation provides the commands needed to complete the install on Windows and other operating systems.
npm install
to ensure all project dependencies are correctly installed.npm install ecto
import { Ecto } from `ecto`;
const ecto = new Ecto();
const source = "<h1>Hello <%= firstName%> <%= lastName %>!</h1>";
const data = {firstName: "John", lastName: "Doe"}
ecto.render(source, data).then((output) => {
console.log(output);
});
After running your program you should see the following output:
<h1>Hello John Doe!</h1>
You can easily set a different defaultEngine, here we use Handlebars.
import { Ecto } from `ecto`;
const ecto = new Ecto({defaultEngine: "handlebars"});
let source = "<h1>Hello {{ firstName }} {{ lastName }}!</h1>";
let data = {firstName: "John", lastName: "Doe"};
await ecto.render(source, data); //returns <h1>Hello John Doe!</h1>
To render from a template file, Ecto uses the renderFromFile
function. This performs an automatic selection of the engine based on the file extension.
import { Ecto } from `ecto`;
const ecto = new Ecto();
let data = { firstName: "John", lastName: "Doe"};
//async renderFromFile(filePath:string, data?:object, rootTemplatePath?:string, filePathOutput?:string, engineName?:string): Promise<string>
await ecto.renderFromFile("./path/to/template.ejs", data); // returns <h1>Hello John Doe!</h1>
Next Steps:
We focus on the most popular and well-maintained consolidation engines. Unfortunately other engines suffered from packages that were unsupported, making it difficult to validate them as working fully. Some engines also had limited types and lacked ease of use.
Our goal is to support the top engines, handling the vast majority of use cases. Here are the top engines that we support:
Engine | Monthly Downloads | Extensions |
---|---|---|
EJS | .ejs | |
Markdown | .markdown, .md | |
Pug | .pug, .jade | |
Nunjucks | .njk | |
Mustache | .mustache | |
Handlebars | .handlebars, .hbs, .hjs | |
Liquid | .liquid |
The Extensions
are listed above for when we Render from File.
The API is focused on using the main Ecto class:
const ecto = new Ecto();
//ecto.<API> -- functions and parameters
When looking at the API there are two main methods to make note of: render (async) - Render from a string. renderFromFile (async) - Renders from a file path and will auto-select what engine to use based on the file extension. It will return a Promise of the rendered output.
Two key parameters to know are: defaultEngine:string - the default engine to use and set by default to ejs. mappings:EngineMap - Mapping class of all the engines registered in the system.
As we have shown in Getting Started -- It's that Easy! You can render in only a couple of lines of code.
render and renderSync (async
) - Render from a string. Here is the render function with all possible arguments shown:
Name | Type | Description |
---|---|---|
source | string | The markup/template source to be rendered. |
data | object | The data to be rendered by the file. |
engineName | string | Used to override the Ecto.defaultEngine parameter. |
rootTemplatePath | string | The root template path that is used for partials and layouts . |
filePathOutput | string | Used to specify the file path, if you want to write the rendered output to a file. |
Here is the simplest example of a render function. We are also showing the required steps you need to take beforehand such as setting up Ecto.
const ecto = new Ecto();
const source = "<h1>Hello <%= firstName%> <%= lastName %>!</h1>";
const data = {firstName: "John", lastName: "Doe"};
await ecto.render(source, data); //returns <h1>Hello John Doe!</h1>
If you want to do synchronous rendering, you can do so by using the renderSync
function. This function takes the same parameters as the render
function.
const ecto = new Ecto();
const source = "<h1>Hello <%= firstName%> <%= lastName %>!</h1>";
const data = {firstName: "John", lastName: "Doe"};
ecto.renderSync(source, data); //returns <h1>Hello John Doe!</h1>
Now let's say your desired engine is not EJS, you will need to specify it explicitly. You can either set the defaultEngine parameter, or simply pass it in the render
function. In this case with the popular engine, Handlebars:
const ecto = new Ecto();
const source = "<h1>Hello {{firstName}} {{lastName}}!</h1>";
const data = {firstName: "John", lastName: "Doe"}
ecto.render(source, data, "handlebars").then((output) => {
console.log(output);
});
The render
and renderSync
function also can handle partial files for standard engines (markdown excluded) by simply adding the rootTemplatePath
:
const ecto = new Ecto();
const source = "<h1>Hello <%= firstName%> <%= lastName %>!</h1><%- include('/relative/path/to/partial'); %>";
const data = {firstName: "John", lastName: "Doe"};
ecto.render(source, data, undefined, "./path/to/templates").then((output) => {
console.log(output);
});
With render
and renderSync
you can also write to a file. This is accomplished by specifying the filePathOutput
parameter as below. It will still return the output as a string
:
const ecto = new Ecto();
const source = "<h1>Hello <%= firstName%> <%= lastName %>!</h1>";
const data = {firstName: "John", lastName: "Doe"};
ecto.render(source, data, undefined, undefined, "./path/to/output/file.html").then((output) => {
console.log(output);
});
Notice the undefined
value passed into the engineName
parameter. This is done because we already have the defaultEngine set to EJS. If you want you can easily add it in here too.
To render via a template file, it is as simple as calling the renderFromFile
or renderFromFileSync
function with a couple of simple parameters passed in.
renderFromFile (async
) or renderFromFileSync (synchronous
) - Renders from a file path and will auto-select what engine to use based on the file extension. It will return a Promise<string>
of the rendered output. One of the main benefits is that it will automatically select the correct engine based on the file extension. The renderFromFile function takes the following parameters:
Name | Type | Description |
---|---|---|
filePath | string | The file that you would like to render. |
data | object | The data to be rendered by the file. |
rootTemplatePath | string | The root template path that is used for partials and layouts . |
filePathOutput | string | Used to specify the file path if you want to write the rendered output to a file. |
engineName | string | Used to to override the auto-selection of the engineName . |
This simple example showing the renderFromFile
or renderFromFileSync
function shows you the bare minimum required to execute this function successfully, we are passing in the template and it will return a string
.
One of the main benefits is that it will automatically select the correct engine based on the file extension.
const ecto = new Ecto();
const data = { firstName: "John", lastName: "Doe"};
await ecto.renderFromFile("./path/to/template.ejs", data); // returns <h1>Hello John Doe!</h1>
To do this synchronously, you can use the renderFromFileSync
function. This function takes the same parameters as the renderFromFile
function.
const ecto = new Ecto();
const data = { firstName: "John", lastName: "Doe"};
await ecto.renderFromFileSync("./path/to/template.ejs", data); // returns <h1>Hello John Doe!</h1>
In this example, we are writing the output to a HTML file:
const ecto = new Ecto();
const data = { firstName: "John", lastName: "Doe"};
await ecto.renderFromFile("./path/to/template.ejs", data, undefined, "./path/to/output/yourname.html")
Notice that in these examples it is using the ./path/to/template.ejs
to specify EJS for the rendering.
You can override the auto-selected engine by passing in the string value of a template engine as a parameter into the renderFromFile
function. We pass in pug
, which states we want to render the template using the Pug engine.
const ecto = new Ecto();
const data = { firstName: "John", lastName: "Doe"};
ecto.renderFromFile("./path/to/template.ejs", data, undefined, "./path/to/output/yourname.html", "pug");
This string parameter can be used to set the default template engine for an instance of the Ecto class. Ecto has been designed to support all major engines.
Ecto.defaultEngine
is set by default to EJS.If you would like to change the template engine there are several options available to users when setting the value of defaultEngine
:
One option for setting the default engine, is to do so in the Ecto constructor:
const ecto = new Ecto({defaultEngine: "ejs"});
const ecto = new Ecto({defaultEngine: "markdown"});
const ecto = new Ecto({defaultEngine: "pug"});
const ecto = new Ecto({defaultEngine: "nunjucks"});
const ecto = new Ecto({defaultEngine: "mustache"});
const ecto = new Ecto({defaultEngine: "handlebars"});
const ecto = new Ecto({defaultEngine: "liquid"});
We can also set the default engine as a parameter, like so:
const ecto = new Ecto();
ecto.defaultEngine = "mustache";
Alternatively, we can set the engine directly on the constructor. This would make our previous example:
const ecto = new Ecto({defaultEngine: "liquid"});
ecto.defaultEngine = "mustache";
You can explicitly override the Ecto.defaultEngine parameter in the render function:
const ecto = new Ecto();
const source = "<h1>Hello {{firstName}} {{lastName}}!</h1>";
const data = {firstName: "John", lastName: "Doe"};
await ecto.render(source, data, "handlebars"); //returns <h1>Hello John Doe!</h1>
The renderFromFile
function automatically decides on the template engine, based on the file extension in the file path you specify. However, you can also explicitly set the engine you would like to use. Here we set the engine to be Pug.
const ecto = new Ecto();
const data = { firstName: "John", lastName: "Doe"};
await ecto.renderFromFile("./path/to/template.ejs", data, undefined, "./path/to/output/yourname.html", "pug"); // returns <h1>Hello John Doe!</h1>
To make it easier to access and change between engines, all supported engines are provided as parameters on the Ecto
class as Ecto.<EngineFullName>
const ecto = Ecto();
console.log(ecto.Handlebars.name); // will return "handlebars"
console.log(ecto.Handlebars.opts); // will return "handlebars" options object
To access a specific engine you can do so by going to ecto.<engine_name>.engine
and setting the SafeString:
const ecto = Ecto();
ecto.Handlebars.engine.SafeString("<div>HTML Content!</div>");
We have added in a couple of helper methods to make it easier to find a template without the extension. This is useful when you want to find a template without the extension. For example, if you have a template called template.ejs
and you want to find it without the extension, you can use the findTemplateWithoutExtension
for async or findTemplateWithoutExtensionSync
function. This function takes two parameters:
Name | Type | Description |
---|---|---|
filePath | string | The file that you would like to find without the extension. |
templateName | string | The name of the template you would like to find. |
const ecto = new Ecto();
const templateFilePath = ecto.findTemplateWithoutExtensionSync("./path/to/", "index");
This will return the full path of the template based on what extension is there such as ./path/to/index.ejs
.
Typescript is the language Ecto is already written in and while many of these examples are in javascript
they should be compatible with typescript
. To use Ecto just do an import:
import { Ecto } from "ecto";
and then simply call it like you do with standard javascript:
const ecto = Ecto();
const source = "# markdown rulezz!";
const output = await ecto.render(source, undefined, "markdown");
console.log(output) //should be <h1 id="markdown-rulezz">markdown rulezz!</h1>
NOTE: this example would be in an async
function.
Markdown does not contain complexities such as data objects, or partials and layouts. To render markdown its as simple as:
const ecto = Ecto();
const source = "# markdown rulezz!";
await ecto.render(source, undefined, "markdown"); //should be <h1 id="markdown-rulezz">markdown rulezz!</h1>
Render by Markdown file:
const ecto = Ecto();
await ecto.renderByFile("/path/to/file.md");
We are using Markdoc which has a ton of powerful features.
In Ecto we use the handlebars engine to render mustache
related templates. This is because handlebars is based on mustache with just more additional features.
Handlebars is a fantastic template engine, and we've incorporated helpers to make it even better. We added in helpers-for-handlebars so you can format dates, and more. Here is an example using Handlebars Helpers
in your template:
const ecto = Ecto();
const source = "{{year}}";
ecto.render(source, undefined, "handlebars").then((output) => {
console.log(output)
});
Ecto contains a mapping class containing all of the engines registered in the system. The class has been designed to allow users to edit existing engine mappings. Let us explore this class in detail.
The Map
object has the following structure: Map<string, Array<string>>
From here you can start setting and editing engine mappings.
To set an engine mapping with extensions simply write:
mappings.set("handlebars", ["handlebars","hbs","hjs"]);
This sets the handlebars engine to accept files with the following file extensions:
To delete a mapping entirely you can use the delete
method.
mappings.delete("handlebars");
This will remove this engine mapping entirely.
To delete an extension for a particular engine mapping, you can use the deleteExtension
method. This method takes two arguments:
name:string
- the name of the engine you would like to delete the extension for.extension:string
- the extension you would like to delete.The following code deletes two of the extensions for our handlebars
engine mapping.
mappings.deleteExtension("handlebars", "hbs","hjs");
After executing this code the only accepted file extension for the Handlebars engine will be handlebars
.
Other useful methods include get
and getName
.
The get
method takes one argument, name:string
, and will return the extensions for the name you specify. If extensions are found, they are returned as an Array<string>
. If no engine mapping can be found for the name you specify, undefined
is returned. To use the get
method simply write:
mappings.get("handlebars")
This will retrieve the array of extensions assigned to the Handlebars engine.
The getName
method takes a single argument, extension:string
. If a valid extension is given, this method will return the name of the engine mapping that the extension belongs to. For example:
mappings.getName("hjs")
This will return the string “handlebars”, which is the corresponding engine for this extension. If no match can be found, this method will return undefined
.
Gaining an understanding of this class will provide you with more options and possibilities when using Ecto.
This is an open-source project under MIT License. If you would like to get involved and contribute to this project, simply follow these steps:
Pull requests are used in open-source projects or in some corporate workflows to manage changes from contributors and to initiate code review before such changes are merged.
By creating a pull request, you tell others about the changes you've pushed to your fork of a GitHub repository, so that the maintainers of the original repository can review your changes, discuss them, and integrate them into the base branch.
Issues can be used to keep track of bugs, enhancements, or other requests. Issues can be created based on code from pull requests, comments, or created from the main repository page.
A template engine is a tool that allows developers to write HTML markup that contains the template engine’s defined tags and syntax. These tags are used to insert variables into the final output of the template, or run some programming logic at run-time before sending the final HTML to the browser for display.
EJS stands for Embedded JavaScript. It is a templating engine that allows users to generate HTML using plain JavaScript.
You define HTML pages in the EJS syntax and specify where various data will be shown on the page. Then, your application combines data with the template and "renders" a complete HTML page where EJS takes your data and inserts it into the web page according to how you've defined the template. For example, you could have a table of dynamic data from a database and you want EJS to generate the table of data according to your display rules. It saves you from the writing code and logic to dynamically generate HTML based on data.
It is a tool for generating web pages that can include dynamic data and can share templated pieces with other web pages. It is not a front-end framework. While EJS can be used by client-side Javascript to generate HTML on the client-side, it is typically used by your back-end to generate web pages in response to some URL request. EJS is not a client-side framework like Angular or React.
Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents. Markdown is now one of the world’s most popular markup languages.
Using Markdown is different from using a word and text editor. In an application like Microsoft Word, you click buttons to format words and phrases, and the changes are visible immediately. Markdown isn’t like that. When you create a Markdown-formatted file, you add Markdown syntax to the text to indicate which words and phrases should look different.
There are several reasons why people might choose Markdown instead of standard text editors.
Pug.js is an HTML templating engine that takes simple Pug code, which the Pug compiler will compile into HTML code that browsers can understand. Some features and advantages of the Pug template engine are as follows:
Nunjucks is a rich and powerful template engine for JavaScript. Nunjucks is developed by Mozilla and maintained by the Node JS Foundation. Nunjucks can be used in both Node and the browser.
In Node, Nunjucks is installed using npm. It is rich, fast, extensible, and available everywhere. It's highly optimized at just 8kb gzipped.
Some of the advantages of using Nunjucks for your project are:
Mustache is a logic-less template syntax. It can be used for HTML, config files, source code, and more. It is often referred to as “logic-less” as there are no if statements, else clauses, or for loops. There are only tags. Tags are replaced with actual values at runtime.
Mustache.js is an implementation of the mustache template system in JavaScript. It is often considered the base for JavaScript templating. Since mustache supports various languages, we don’t need a separate templating system on the server side.
Mustache.render("Hello, {{name}}", { name: "John" });
// returns: Hello, John
We see two braces around {{ name }}
. This is Mustache syntax to show that it is a placeholder. When Mustache compiles this, it will look for the “name” property in the object we pass in, and replace {{ name }}
with the actual value, in this case, “John”.
Mustache is not actually a templating engine. Mustache is a specification for a templating language. In general, we would write templates according to the Mustache specification, and they can then be compiled by a templating engine to be rendered, eventually creating an output.
Handlebars is a simple templating language. It uses a template and an input object to generate HTML or other text formats. Handlebars templates look like regular text with embedded Handlebars expressions. Handlebars expressions are wrapped in double curly braces, like this: {{expression}}
. We use @jaredwray/fumanchu
as it contains handbars and helpers.
const ecto = Ecto();
const source = "{{year}}";
await ecto.render(source, undefined, "handlebars"); //returns current year as a number
Some refer to Liquid as a template language, while others may call it a template engine. It doesn't really matter which label you apply, in many ways both are right. It has a syntax (like traditional programming languages), has concepts such as output, logic, and loops, and it interacts with variables and data, just as you would with a language such as PHP.
Liquid, like the previous template engines, creates a bridge between an HTML file and a data store. It does this by allowing us to access variables from within a template, or the Liquid file, with a simple and readable syntax.
Liquid files have the extension of .liquid
. A liquid file is a mix of standard HTML code and Liquid constructs. Its clear syntax is easy to distinguish from HTML when working with a Liquid file. This is made even easier thanks to the use of two sets of delimiters.
The double curly brace delimiters {{ }}
denote output, and the curly brace percentage delimiters {% %}
denote logic. You'll become very familiar with these as every Liquid construct begins with one, or the other. Another way of thinking of delimiters is as placeholders. A placeholder can be viewed as a piece of code that will ultimately be replaced by data when the compiled file is sent to the browser.
FAQs
Modern Template Consolidation Engine for EJS, Markdown, Pug, Nunjucks, Liquid, and Handlebars
We found that ecto demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.