# Overview URL: /docs/2.x.x/overview *** id: docs-overview title: Overview slug: / ------- Congratulations on deciding to use Eta! These docs will be your guide as you learn how to use this tool. :::tip These docs aren't 100% complete. If you just want to learn how to use Eta, go to [Learn](./learn) ::: ## For people who learn by example We recommend going [here](./learn) to find tutorials and lots of example code! ## Check out the source code Go to the [GitHub Repository](https://github.com/eta-dev/eta) # FAQ URL: /docs/2.x.x/about/FAQ *** id: FAQ title: FAQ ---------- ## Does it only work with HTML? It works with HTML, but you can also use it to generate templates of any language, like Markdown. ## How big is Eta? Eta is about 2KB gzipped, which is quite lightweight for a template engine library. ## Why should I use Eta instead of another template engine like Handlebars or Pug? Eta has a number of features that set it apart from the competition: * It's significantly faster than most templating engines * It supports plugins * It has great file handling * It's incredibly lightweight: in comparison: the full version only weighs about **2 KB gzipped**, compared to Pug's **237 KB** and Handlebars' **21.5 KB** * It works with other languages than HTML * It's not whitespace sensitive For more information, see [Why Eta?](about/why-eta.md) # Eta vs EJS URL: /docs/2.x.x/about/eta-vs-ejs *** ## title: Eta vs EJS Eta's syntax is very similar to EJS' (most templates should work with either engine), Eta has a similar API, and Eta and EJS share the same file-handling logic. Here are the differences between Eta and EJS: * Eta is more lightweight. Eta weighs around **2KB gzipped**, while EJS is **4.4KB gzipped** * Eta compiles and renders templates ***much* faster than EJS**. Check out these benchmarks: [https://rawcdn.githack.com/eta-dev/eta/main/browser-tests/benchmark.html](https://rawcdn.githack.com/eta-dev/eta/main/browser-tests/benchmark.html) * Eta allows left whitespace control (with `-`), something that doesn't work in EJS because EJS uses `-` on the left side to indicate that the value shouldn't be escaped. Instead, Eta uses `~` to output a raw value * Eta gives you more flexibility with delimeters -- you could set them to `{{` and `}}`, for example, while with EJS this isn't possible * Eta adds plugin support * Comments in Eta use `/* ... */` which allows commenting around template tags and is more consistent * Eta parses strings correctly. *Example: `<%= "%>" %>` works in Eta, while it breaks in EJS* * Eta exposes Typescript types and distributes a UMD build * Custom tag-type prefixes. *Example: you could change `<%=` to `<%*`* # How Eta Works URL: /docs/2.x.x/about/how-eta-works *** id: how-eta-works title: How Eta Works -------------------- Unlike many pieces of software, we like our users to understand what their programs are doing behind the scenes. ## TL;DR Eta uses Regular Expressions to turn a template into a function which can be called with a specific set of options. Since all of the parsing is done beforehand, the function (called a "Precompiled" function) just does string interpolation and is incredibly fast. ## Long Version: 1. Eta uses a big RegExp with inline tokenization to **parse** the template by looping through each valid tag (ex. `<%...%>`) in the template. It creates a simple syntax tree which it passes to `compileToString` 2. During compilation, Eta creates a function string from the syntax tree, then uses `Function` to bring it to life. 3. This explanation is really lacking. Just read the source code :) # About URL: /docs/2.x.x/about/overview *** id: overview title: About slug: /about ------------ Eta is an embedded JS template engine, created by the team who made Squirrelly. With Eta, you can write templates that are blazing fast and can be rendered in milliseconds, server-side or client-side. Eta doesn't just limit you to HTML--you can use it with any language, and custom delimeters make it so there aren't parsing errors. It's also tiny (**\~2 KB gzipped**), has **0 dependencies**, and is **blazing fast**. ![](https://img.shields.io/bundlephobia/minzip/eta@latest.svg) :::note Eta is consistently faster than most other template engines, according to benchmarks ::: # Performance URL: /docs/2.x.x/about/performance *** id: performance title: Performance ------------------ ## TL;DR Eta's faster than virtually all other template engines out there. ## Run tests in your browser! [https://rawcdn.githack.com/eta-dev/eta/main/browser-tests/benchmark.html](https://rawcdn.githack.com/eta-dev/eta/main/browser-tests/benchmark.html) # Why Pick Eta? URL: /docs/2.x.x/about/why-eta *** id: why-eta title: Why Pick Eta? -------------------- ## Features Eta has a number of features that set it apart from the competition: * Faster than most templating engines * Supports partials * Supports custom tags (delimeters) * Incredibly lightweight: in comparison: the full version only weighs about **2 KB gzipped**, compared to Pug's **237 KB** and Handlebars' **21.5 KB** * Works with other languages than HTML * Not white-space sensitive, but white-space-trimming configurable * Syntax accessible to non-JavaScript programmers * Supports comments and quotes containing ending delimeter (e.g. `<% /* commented out <% something %> */ %>`) | **Feature** | **Eta** | Handlebars | Pug | Marko | Dust | Swig | | :------------------- | :------ | :--------- | :--- | :---- | :--- | :--- | | Auto Escape | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Whitespace sensitive | No | No | Yes | No | No | No | | Content Type | All | All | HTML | HTML | All | All | # Compilation URL: /docs/2.x.x/api/compilation *** id: compilation title: Compilation ------------------ ## `Eta.compile` Compiles a string into a template function. [TypeDoc doc page](https://eta-dev.github.io/eta/modules/_compile_.html#compile) **Syntax** ```js Eta.compile(str, config) // returns a function that can be called with (data, config, [cb]) // note: config must be a valid configuration object ``` See the page on [config](./configuration) **Example** ```js var myTemplate = "Hi, my name is <%= it.name %>" var compiled = Eta.compile(myTemplate) // Returns a function: // function anonymous(it,c,cb ) { var tR='';tR+='Hi, my name is ';tR+=E.e(it.name);if(cb){cb(null,tR)} return tR } compiled({ name: "Johnny Appleseed" }, Eta.config) //Returns "Hi, my name is Johnny Appleseed" ``` :::note Many template engines offer you the option to Compile (which just renders your template) or Precompile (which turns your template into a function ahead of time). Eta precompiles automatically, but is still faster than other engines. ::: # Configuration URL: /docs/2.x.x/api/configuration *** id: configuration title: Configuration -------------------- Similarly to many other libraries, Eta allows you to customize its behavior via options. [TypeDoc doc page](https://eta-dev.github.io/eta/interfaces/_config_.etaconfig.html) ## List of options | Option | Description | Type | Default | Required? | | ------------ | :------------------------------------------ | :------------------------: | :----------------: | :-------: | | `async` | Whether to generate async templates | `boolean` | `false` | Yes | | `autoEscape` | Whether to automatically XML-escape | `boolean` | | Yes | | `autoTrim` | Configure automatic whitespace trimming | [autoTrim](#autotrim) | `[false, "nl"]` | Yes | | `cache` | Cache templates by `name` or `filename` | `boolean` | | Yes | | `e` | XML-escaping function | `Function` | `config.e` | Yes | | `filename` | Absolute filepath of template (for caching) | `string` | `undefined` | No | | `name` | Template name (for caching) | `string` | `undefined` | No | | `plugins` | Plugins array | [plugins](#plugins) | `config.plugins` | Yes | | `root` | Base filepath. Defaults to `"\"` internally | `string` | `undefined` | No | | `templates` | Object containing templates | `Cacher` | `config.templates` | Yes | | `tags` | Template delimiters | `[string, string]` | `["<%", "%>"]` | Yes | | `useWith` | Use `with(){}` to have data scope as global | `boolean` | `undefined` | No | | `varName` | Name of data object | `string` | `"it"` | Yes | | `view cache` | Overrides `cache` | `boolean` | `undefined` | No | | `views` | Absolute filepath to views directory | `string` | `undefined` | No | ### Delimiter Caveats Delimeters must be RegExp-escaped. ### `autoTrim` `autoTrim` controls whitespace trimming. **Signature** `"nl" | "slurp" | false | ["nl" | "slurp" | false, "nl" | "slurp" | false]` **Options** * `"nl"` trims a leading or trailing newline * `"slurp"` trims all leading/trailing whitespace * `true` is equivalent to `"slurp"` When an array is passed, Eta uses the equivalent options on the left or right side of the string ### `plugins` `plugins` is an array of objects, each with the following properties: | Property | Description | Type | | ----------------- | :------------------------------------------------ | :--------: | | `processAST` | Function that manipulates *Eta* syntax tree | `Function` | | `processFnString` | Function that manipulates *Eta* template function | `Function` | ## `config` `Eta.config` returns Eta's base ("global") configuration. See above. ## `getConfig` `getConfig` takes some config options and merges them with the default. It optionally takes a third parameter, which it merges with the default first. High-level APIs like `render` and `compile` call `getConfig` internally, but you should call lower-level APIs (like `compileToString`) with a valid config object, which you can get from this function. ### Syntax [TypeDoc doc page](https://eta-dev.github.io/eta/modules/_config_.html#getconfig) ### Example ```js Eta.compileToString(myTemplate, Eta.getConfig({ tags: ["{{", "}}"] })) ``` # Containers URL: /docs/2.x.x/api/containers *** id: containers title: Containers ----------------- Templates are stored in a storage object (internally exposed as `Cacher`). ## Syntax [TypeDoc doc page](https://eta-dev.github.io/dev/classes/_storage_.cacher.html) ## TL;DR To get a cache item, call `[cache].get('name')`. To define a cache item, run `[cache].define('name', value)`. To load an entire cache object, run `[cache].load(cacheObj)`. To reset a cache, run `[cache].clear()`. ## Examples ```js Eta.templates.define('my-partial', Eta.compile('This is a partial speaking')) console.log(Eta.templates.get('my-partial')) Eta.templates.clear() ``` # File Handling URL: /docs/2.x.x/api/file-handling *** id: file-handling title: File Handling -------------------- It is really easy to use Eta with Express. First, you must [install Eta](https://eta.js.org/docs/learn/install), then you must add an `app.engine()` line with the file extension (here, we use `.eta`) as the first parameter and the Eta module as the second parameter (`app.set('view engine', 'eta')`): :::note Eta works out-of-the-box with Express.js. ```js app.engine("html", require("eta").renderFile) // Or, if you want to use the .eta file extension app.set("view engine", "eta") ``` ::: # API Overview URL: /docs/2.x.x/api/overview *** id: overview title: API Overview slug: /api ---------- ## Big list of API options * `__express` (alias for `renderFile`) * `compile` * `compileToString` * `config` * `configure` * `defaultConfig` (alias for `config`) * `getConfig` * `loadFile` * `parse` (see [Parsing](api/parsing)) * `render` (see [Rendering](api/rendering)) * `renderFile` * `templates` # Parsing URL: /docs/2.x.x/api/parsing *** id: parsing title: Parsing -------------- :::note You won't need to use or understand Parsing unless you're writing native helpers or plugins. ::: ## Syntax [TypeDoc doc page](https://eta-dev.github.io/eta/modules/_parse_.html#parse) ## Examples ```js var myTemplate = 'Hi, my name is <%= it.name %>' var compiled = Eta.parse(myTemplate) //Returns an Eta syntax tree (like an AST): // ['Hi, my name is ', { t: 'i', val: 'it.name' }] /* val contains the content of the template object. t can be: - 'i': interpolate - 'r': raw - 'e': evaluate/exec */ ``` # Plugin Hooks URL: /docs/2.x.x/api/plugin-hooks *** id: plugin-hooks title: Plugin Hooks ------------------- Plugin hooks allow plugins to modify the template during the various lifecycle events. ## `processAST(buffer, config)` The `processAST` hook allows you to modify the AST. ```js var layoutRegExp = /^@\s*layout\s*\(\s*"([^]*)"\)$/ module.exports = { processAST: function (buffer, config) { var firstItem = buffer[0] if (firstItem.t === "e") { var val = firstItem.val.trim() if (layoutRegExp.test(val)) { buffer.shift() var layoutMatch = layoutRegExp.exec(val) var filePath = layoutMatch[1] var useLayoutCode = "tR=" + (env.async ? "await " : "") + 'E.includeFile("' + filePath + '",{content:tR})' buffer.push({ t: "e", val: useLayoutCode }) } } return buffer } } ``` ## `processFnString(fnString, config)` The `processFnString` hook allows you to modified the compiled function string which is used for prerendering. ```js module.exports = { processFnString: function (fnString, config) { return `var add=(a,b)=>a+b;${fnString}` } } ``` # Rendering URL: /docs/2.x.x/api/rendering *** id: rendering title: Rendering ---------------- Rendering a template compiles a template and then calls it with the data you pass to it. ## Syntax [TypeDoc doc page](https://eta-dev.github.io/eta/modules/_render_.html#render) ## Example ```js var myTemplate = 'Hi, my name is <%= it.name %>' Eta.render(myTemplate, { name: 'Johnny Appleseed' }) // Returns "Hi, my name is Johnny Appleseed" ``` # Templates & Partials URL: /docs/2.x.x/api/templates *** id: templates-partials title: Templates & Partials --------------------------- Templates and partials are both stored in one object (exposed as `Eta.templates`). ## Loading templates / partials If you just call `render` or `compile` with `name` or `filename` in options, Eta will load your template. ## Defining Partials ```js Eta.templates.define("my-partial", Eta.compile("This is a partial speaking")) Eta.render('... <%~ include("my-partial") %>', {}) // ... This is a partial speaking // To call a partial w/ data: Eta.templates.define("my-partial-2", Eta.compile("Name: <%= it.name %>")) Eta.render( '... <%~ include("my-partial-2", {name: it.name}) %>', // The 2nd argument passed to `include` is the data. You could also pass `it` to forward all data { name: "Ben" } ) // ... Name: Ben ``` # Eta with Bun URL: /docs/2.x.x/examples/bun *** id: bun title: Eta with Bun ------------------- To use Eta within Bun, follow the same patterns as you would with [Node.js](./node). # Eta with Deno URL: /docs/2.x.x/examples/deno *** id: deno title: Eta with Deno -------------------- ```js title="views/template.eta" My favorite food is <%= it.food %> <%~ includeFile('./footer') %> ``` ```js title="views/footer.eta" ``` ```js title="app.ts" import { renderFile, configure } from "https://deno.land/x/eta@v1.11.0/mod.ts" const viewPath = `${Deno.cwd()}/views/` // Set Eta's configuration configure({ // This tells Eta to look for templates // In the /views directory views: viewPath }) // Eta assumes the .eta extension if you don't specify an extension // You could also write renderFile("template.eta"), // renderFile("/template"), etc. let templateResult = await renderFile("./template", { food: "cake" }) console.log(templateResult) /* My favorite food is cake */ ``` # Eta with Express.js URL: /docs/2.x.x/examples/express *** id: express title: Eta with Express.js -------------------------- ```js title="views/template.eta" My favorite template engine is <%= it.favorite %> because it is: <%= it.reasons.join(', ') %> <%~ includeFile('./footer', it) %> ``` ```js title="views/footer.eta" ``` :::danger Never put objects on the `req` object straight in as the data, this can allow hackers to run XSS attacks. Always make sure you are destructuring the values on objects like `req.query` and `req.params`. ::: ```js title="index.js" var express = require("express") var app = express() var eta = require("eta") // Note: as of Eta version 2.0.0, you must configure the "cache" and "views" option separately for both Express and Eta. Eta will not use the values set in Express, in order to prevent vulnerabilities app.engine("eta", eta.renderFile) eta.configure({ views: "./views", cache: true }) app.set("views", "./views") app.set("view cache", true) app.set("view engine", "eta") app.get("/", function (req, res) { res.render("template", { favorite: "Eta", name: "Ben", reasons: ["fast", "lightweight", "simple"] }) }) app.listen(8000, function () { console.log("listening to requests on port 8000") }) ``` # Eta with Node.js URL: /docs/2.x.x/examples/node *** id: node title: Eta with Node.js ----------------------- ```js title="views/template.eta" My favorite food is <%= it.food %> <%~ includeFile('./footer') %> ``` ```js title="views/footer.eta" ``` ```js title="index.js" var eta = require("eta") var path = require("path") // Set Eta's configuration eta.configure({ // This tells Eta to look for templates // In the /views directory views: path.join(__dirname, "views") }) // Eta assumes the .eta extension if you don't specify an extension // You could also write renderFile("template.eta"), renderFile(path.join(__dirname, "views/template.eta"), // renderFile("/template"), etc. await eta.renderFile("./template", { food: "cake" }) /* My favorite food is cake */ ``` # Overview URL: /docs/2.x.x/examples/overview *** id: overview title: Overview slug: /examples --------------- Here are a few examples of using Eta. Also check out: * [https://github.com/alosaur/alosaur/tree/master/examples/eta](https://github.com/alosaur/alosaur/tree/master/examples/eta) * [https://github.com/asos-craigmorten/opine/tree/main/examples/eta](https://github.com/asos-craigmorten/opine/tree/main/examples/eta) # Async Support URL: /docs/2.x.x/learn/async *** id: async title: Async Support -------------------- Basically, you can use `async` and `await` in your templates as long as you configure Eta to be in async mode. Remember: if you're in async mode and are working with partials, you need to `await include(...)`! ## Example ```js function asyncFunc() { return new Promise((resolve) => { setTimeout(() => { resolve("HI FROM ASYNC") }, 20) }) } let result = await Eta.render( "<%= it.name %>: <%= await it.asyncFunc() %>", { name: "Ada Lovelace", asyncFunc: asyncFunc }, { async: true } ) // 'Ada Lovelace: HI FROM ASYNC' ``` ## Special Functions There are also two functions, `renderAsync` and `renderFileAsync` which are the equivalents of their respective functions with builtin `async` support. ### Example ```js function asyncFunc() { return new Promise((resolve) => { setTimeout(() => { resolve("HI FROM ASYNC") }, 20) }) } let result = await Eta.renderAsync( "<%= it.name %>: <%= await it.asyncFunc() %>", { name: "Ada Lovelace", asyncFunc } ) ``` # Configuring Eta URL: /docs/2.x.x/learn/configuration *** id: configuration title: Configuring Eta description: Setting custom delimiters, controlling caching, etc. ----------------------------------------------------------------- You can configure Eta using the `configure()` command, which merges the options you pass in with the current configuration. **Example** ```js Eta.configure({ cache: true // Make Eta cache templates }) ``` Eta's current configuration is stored in the `config` variable (which is aliased to `defaultConfig` for backwards compatibility) ```js Eta.config.tags // ["<%", "%>"] ``` ## Big list of configuration options Here's the TypeScript interface describing Eta's config (taken from the source code) ```ts interface EtaConfig { /** Whether or not to automatically XML-escape interpolations. Default true */ autoEscape: boolean /** Configure automatic whitespace trimming. Default `[false, 'nl']` */ autoTrim: trimConfig | [trimConfig, trimConfig] /** Compile to async function */ async: boolean /** Whether or not to cache templates if `name` or `filename` is passed */ cache: boolean /** XML-escaping function */ e: (str: string) => string /** Parsing options. NOTE: "-" and "_" may not be used, since they are reserved for whitespace trimming. */ parse: { /** Which prefix to use for evaluation. Default `""` */ exec: string /** Which prefix to use for interpolation. Default `"="` */ interpolate: string /** Which prefix to use for raw interpolation. Default `"~"` */ raw: string } /** Array of plugins */ plugins: Array<{ processFnString?: Function processAST?: Function processTemplate?: Function }> /** Remove empty lines and whitespace between lines */ rmWhitespace: boolean /** Delimiters: by default `['<%', '%>']` */ tags: [string, string] /** Holds template cache */ templates: Cacher /** Name of the data object. Default `it` */ varName: string /** Absolute path to template file */ filename?: string /** Holds cache of resolved filepaths. Set to `false` to disable */ filepathCache?: Record | false /** A filter function applied to every interpolation or raw interpolation */ filter?: Function /** Function to include templates by name */ include?: Function /** Function to include templates by filepath */ includeFile?: Function /** Name of template */ name?: string /** Where should absolute paths begin? Default '/' */ root?: string /** Make data available on the global object instead of varName */ useWith?: boolean /** Whether or not to cache templates if `name` or `filename` is passed: duplicate of `cache` */ "view cache"?: boolean /** Directory or directories that contain templates */ views?: string | Array /** The config object can also have other properties, potentially added by plugins */ [index: string]: any } ``` ## Default configuration ```ts var config: EtaConfig = { async: false, autoEscape: true, autoTrim: [false, "nl"], cache: false, e: XMLEscape, // function defined elsewhere include: includeHelper, // function defined elsewhere includeFile: includeFileHelper, // function defined elsewhere parse: { exec: "", interpolate: "=", raw: "~" }, plugins: [], rmWhitespace: false, tags: ["<%", "%>"], templates: templates, useWith: false, varName: "it" } ``` # How does Eta resolve template files? URL: /docs/2.x.x/learn/file-handling *** id: file-handling title: How does Eta resolve template files? ------------------------------------------- What happens when you call `renderFile(path, ...)` or `<%~ includeFile(path, ...) %>`? 1. If `path` is an absolute path: * First, look in `config.views`: if `config.views` is a path to a directory, look in it. If it is an array of directory paths, look in each * If Eta fails to find the template, look in `config.root` (by default `/`, the file-system base) 2. If `path` is a relative path: * If `includeFile()` was called from another template file, try to resolve the new template based on that template's file path * If that fails, fall back to searching `config.views` # Your First Template URL: /docs/2.x.x/learn/first-template *** id: first-template title: Your First Template -------------------------- This is about as simple as you can get. ```js var Eta = require("eta") Eta.render("The answer to everything is <%= it.answer %>", { answer: 42 }) ``` # Installation URL: /docs/2.x.x/learn/install *** id: install title: Installation sidebar\_label: Installation description: How to install Eta for use in Node.js or the browser ----------------------------------------------------------------- Eta tries to follow best practices, and provides a UMD build to support most JS loading options, like ES modules, CommonJS, and AMD. ## Install Eta ```sh npm install eta --save ``` Or if you prefer Yarn: ```sh yarn add eta ``` ### Importing / Requiring Eta is packaged as a UMD module, so you can require with CommonJS, import using ES Modules, or use AMD. ```js import * as Eta from "eta" // or var Eta = require("eta") ``` ```js import * as eta from "https://deno.land/x/eta@v1.6.0/mod.ts" ``` *Note: replace `1.6.0` with the current version* ### Unpkg ```html ``` ### JSDelivr ```html ``` This makes Eta available through the global `eta` variable, and importable using ES modules, CommonJS, and AMD. # Layouts URL: /docs/2.x.x/learn/layouts *** id: layouts title: Layouts description: Use layouts with Eta --------------------------------- Layouts are one of Eta's most convenient features -- along with partials, they allow you to separate your templates into clean and maintainable parts. **TL;DR** You can call ```js <% layout(filepath) %> ``` Inside your template. This will render the `filepath` template with the current template body stored in `it.body`. Your layout file will automatically have access to `it`. `it` can be overriden by passing data overrides: ```js <% layout(filepath, options) %> ``` **More advanced** Eta defines a local function called `layout` which stores a filepath (or template name) and parameters in inner template variables. Before a template returns, it checks to see whether the filepath is defined. If so, it returns the result of the following: ```js includeFile( filepath, Object.assign(it, { body: templateResult }, layoutParameters) ) ``` Alternatively, if `includeFile` is not defined, it will fall back to `include`: ```js include( templateName, Object.assign(it, { body: templateResult }, layoutParameters) ) ``` ## Examples ### Simple Layout ```js title="layout.eta" <%= it.title %> <%~ it.body %>
Copyright SomeCo, Inc.
``` ```js title="index.eta" <% layout('./layout') %>

<%= it.message %>

``` ### Conditional Layouts One of the advantages of our layout function is that we can modify the layout as many times as we want. Eta just cares what it is set to by the time a template finishes rendering. ```js title="index.eta" <% if (user.type === 'admin') { %> <% layout('./admin') %> <% } else { %> <% layout('./user') %> <% } %> This is the template content ``` Of course, since Eta supports multi-line tag content, we could rewrite that as: ```js title="index.eta" <% if (user.type === "admin") { layout("./admin") } else { layout("./user") } %> This is the template content ``` # Overview URL: /docs/2.x.x/learn/overview *** id: overview title: Overview slug: /learn ------------ Congratulations on deciding to use Eta! These docs will be your guide as you learn how to use this tool. # Partials URL: /docs/2.x.x/learn/partials *** id: partials title: Partials --------------- There are two kinds of partials: *named partials* and *file partials*. :::tip *By the way, you can overwrite the `include` and `includeFile` functions using `eta.configure`. They are just functions defined on the config object: `eta.config.include` and `eta.config.includeFile`* ::: Named partials have to be defined ahead of time as template functions. They are included using `<%~ include(partialName, data) %>` and work in the browser, Node, and Deno. ```js Eta.templates.define("mypartial", Eta.compile("PARTIAL SPEAKING")) Eta.render('This is a partial: <%~ include("mypartial") %>', { name: "Person" }) ``` File partials, on the other hand, do not need to be defined ahead of time. They are included using `<%~ includeFile(path, data) %>` and do not work in the browser. Eta looks in `config.views` for the templates you reference (*note: it's actually a little bit more complicated, check out [file-handling](./file-handling))*. ```js Eta.configure({ views: path.join(__dirname, views) }) let template = "<%~ includeFile('./footer.eta', data) %>" Eta.render(template, data) ``` # Plugins URL: /docs/2.x.x/learn/plugins *** id: plugins title: Plugins description: Learn the basics of Eta plugins by creating a simple plugin ------------------------------------------------------------------------ Here you'll learn how to create a plugin for Eta by creating a plugin that enables layout support. *Note: this tutorial was created before Eta got [built-in layout support](./layouts). This plugin is no longer necessary if you want to use layouts, but we're leaving this tutorial up because it is a helpful example* :::tip Feel free to publish NPM/Deno packages based on this code! A good rule of thumb is to begin plugin packages with **eta\_plugin\_**. For example, you could create a plugin called **eta\_plugin\_layouts**. ::: ### Plugin Code ```js title="plugin-inheritance.js" var layoutRegExp = /^@\s*layout\s*\(\s*"([^]*)"\)$/ module.exports = { processAST: function (buffer, env) { var firstItem = buffer[0] if (firstItem.t === "e") { var val = firstItem.val.trim() if (layoutRegExp.test(val)) { buffer.shift() var layoutMatch = layoutRegExp.exec(val) var filePath = layoutMatch[1] var useLayoutCode = "tR=" + (env.async ? "await " : "") + 'E.includeFile("' + filePath + '",{content:tR})' buffer.push({ t: "e", val: useLayoutCode }) } } return buffer } } ``` ### Server Code ```js title="index.js" var express = require("express") var app = express() var Eta = require("eta") var EtaInherit = require("./plugin-inheritance") Eta.configure({ plugins: [EtaInherit], cache: false }) app.engine("eta", Eta.renderFile) app.set("view engine", "eta") app.set("views", "./views") app.get("/", function (req, res) { res.render("index", { favorite: "Eta" }) }) app.listen(3000, function () { console.log("listening to requests on port 3000") }) ``` ### Final Result ```js title="views/index.eta" <% @layout("./layout") %>

This page was rendered with Eta

My favorite template engine is <%= it.favorite %> ``` ```js title="views/footer.eta" This is the footer ``` ```js title="views/layout.eta" Page about Eta

Eta Rendered Page

Here's the content:
===========================================

<%~it.content%>

===========================================

<%~ includeFile('./footer') %> ``` # Security URL: /docs/2.x.x/learn/security *** id: security title: Security --------------- ## Templates are code, not user input Eta templates compile to JavaScript functions. Rendering a template is equivalent to executing JavaScript code. This means you should **never pass untrusted or user-controlled strings** as templates to `render()`, `renderString()`, or any other Eta method that accepts a template string. ```js // DANGEROUS — equivalent to eval() on user input const userInput = req.body.template eta.renderString(userInput, data) // SAFE — user data is passed through the data object eta.renderString("Hello <%= it.name %>!", { name: req.body.name }) ``` This is the standard security model for embedded JS template engines (EJS, lodash templates, doT, etc.) and template engines in other languages (Jinja2, ERB, Blade). Templates are authored by developers, not end users. ## Sandboxing Eta does not sandbox template execution and this is not a goal of the project. If you need to render untrusted templates, use a logic-less template engine (like Mustache) or run templates in a sandboxed environment (like an isolated VM or Web Worker). # Integrations URL: /docs/2.x.x/resources/integrations *** id: integrations title: Integrations ------------------- :::warn None of these are: * Officially supported * Vetted for security * Guaranteed to work Use at your own risk! ::: ## Integrations * Opine (see [https://github.com/asos-craigmorten/opine/tree/main/examples/eta](https://github.com/asos-craigmorten/opine/tree/main/examples/eta)) * Alosaur (see [https://github.com/alosaur/alosaur/tree/master/examples/eta](https://github.com/alosaur/alosaur/tree/master/examples/eta)) * Rollup Plugin (see [https://github.com/stateful/rollup-plugin-eta](https://github.com/stateful/rollup-plugin-eta)) * Fastify (see [https://github.com/fastify/point-of-view](https://github.com/fastify/point-of-view)) # Overview URL: /docs/2.x.x/resources/overview *** id: overview title: Overview slug: /resources ---------------- Here are some resources for learning/using Eta. ## Plugins * [eta\_plugin\_mixins](https://github.com/nebrelbug/eta_plugin_mixins) # Tutorials and Articles URL: /docs/2.x.x/resources/tutorials-and-articles *** id: tutorials-and-articles title: Tutorials and Articles ----------------------------- * [https://dev.to/nebrelbug/i-built-a-js-template-engine-3x-faster-than-ejs-lj8](https://dev.to/nebrelbug/i-built-a-js-template-engine-3x-faster-than-ejs-lj8) # Async Templates URL: /docs/2.x.x/syntax/async *** id: async title: Async Templates ---------------------- Eta supports optional async support using the `async` and `await` keywords (to support ES5 and lower, use a plugin or transpiler). ## Basic Syntax Essentially, you can use `await` just like in regular JavaScript. ## Example ```js function asyncFunc() { return new Promise(resolve => { setTimeout(() => { resolve('HI FROM ASYNC') }, 20) }) } let result = await Eta.render( '<%= it.name %>: <%= await it.asyncFunc() %>', { name: 'Ada Lovelace', asyncFunc: asyncFunc }, { async: true } ) // 'Ada Lovelace: HI FROM ASYNC' ``` # Auto XML-Escaping URL: /docs/2.x.x/syntax/auto-escaping *** id: auto-escaping title: Auto XML-Escaping ------------------------ Auto-escaping is an important feature of Eta. When it's enabled, every reference without the `~` prefix will be HTML-escaped, to provide some protection against XSS. :::warn Eta has **not** been vetted for security, and autoEscaping is probably not completely foolproof. We use the same function as many other template engines, like Mustache and Handlebars, but there's still the possibility that there's some vulnerability. ::: ```js Eta.configure({ autoEscape: true }) // Turns autoEscaping on Eta.configure({ autoEscape: false }) // Turns autoEscaping off // autoEscaping is on by default ``` ## Disabling To avoid escaping a specific reference, you can use the raw prefix: *Examples*: `<%~ someval %>` :::note Auto-escaping can be helpful, but it also negatively impacts performance. For best results, XML-Escape data before you store it or attempt to render it in a template. ::: # Caveats URL: /docs/2.x.x/syntax/caveats *** id: caveats title: Caveats -------------- ## Reserved variable names *Don't use these variables in your templates* * `it` * `tR` * `cb` * `E` ## Parsing * Using RegExp literals inside your templates has a high likelihood of making them fail. Please, put that logic in a helper or something. If you really must, use `new RegExp('a|b')` instead. ## Delimiters * Your closing delimeter can't contain `'`, `"`, or \`\`\` (*probably: I haven't actually tried it*) # Cheatsheet URL: /docs/2.x.x/syntax/cheatsheet *** id: cheatsheet title: Cheatsheet ----------------- ## Conditionals ```js <% if (it.someval === "someothervalue") { %> Display this! <% } else { %> They're not equal <% } %> ``` ## Looping over arrays ```js <% users.forEach(function(user){ %> <%= user.first %> <%= user.last %> <% }) %> ``` ## Looping over objects ```js <% Object.keys(someObject).forEach(function(prop) { %> <%= someObject[prop] %> <% }) %> ``` ## Logging to the console ```js <% console.log("The value of it.num is: " + it.num) %> ``` # Native Code (Evaluate) URL: /docs/2.x.x/syntax/evaluate *** id: evaluate title: Native Code (Evaluate) ----------------------------- An evaluate tag inserts its contents into the template function. By default, evaluate tags don't start with a prefix. ## Overview Put valid JavaScript code between the tag delimiters. ## Comments Comments are written inside evaluate tags. *Example*: ```js <% /* this is a comment */ %> ``` ## Console.log You can log to the console with evaluate syntax. *Example*: ```js <% console.log("Hi") %> ``` # Interpolate URL: /docs/2.x.x/syntax/interpolate *** id: interpolate title: Interpolate ------------------ An interpolation outputs data into the template. ## Basic Syntax ```js <%= reference %> ``` ## Overview Put a reference between the opening and closing delimeters (by default `<%`and `%>`), followed by the interpolate prefix (`=` by default). **The data you call a template with is stored in an object named `it` by default.** Since Eta templates parse into JavaScript, you can write a reference using dot notation: `

User's last name: <%= it.user.lastName %>` or bracket notation: `

User's last name: <%= it.user['lastName'] %>`. You can also use ternary operators because of this: `

User's last name: <%= it.loggedIn ? it.user.lastName : "N/A" %>

`. :::note You can output a raw value by putting `~` instead of `=` after the opening delimeters (ex. `<%~ unescaped %>`) ::: # Syntax Overview URL: /docs/2.x.x/syntax/overview *** id: overview title: Syntax Overview slug: /syntax ------------- ## Definitions A template tag has the structure: `DELIMITER [WSCONTROL] [PREFIX] CONTENT [WSCONTROL] DELIMITER`. * By default the opening delimiter is `<%` and the closing is `%>` * `WSCONTROL` stands for whitespace control. Optionally, immediately after the opening delimiter or before the closing delimiter, you can put a `-` or a `_`. [Read more](syntax/whitespace-control) * The prefix of a tag lets Eta know its type. `<% = something %>`, has `=` as the prefix, which tells Eta it's an interpolate tag. ## Syntax Overview The data you call a template with is stored in a variable called `it`, similarly to doT.js. * To output a value into your template, use [interpolation tags](syntax/interpolate). Put the value you want to output between (by default) your opening and closing delimeters, prefixed by a `=`. * Example: `<%= it.value %>` * Example: `<%= 2 + 4 %>` * To output an unescaped value into your template, use [raw interpolate tags](syntax/auto-escaping). Put the value you want to output between (by default) your opening and closing delimeters, prefixed by a `~`. *Note: Eta uses `~` instead of `-` (which EJS uses) so it can support left newline trimming.* * Example: `<%~ it.value %>` * Example: `<%~ "

HTML

" %>` * [Evaluation tags](syntax/evaluate) don't have a prefix (by default) and place the code inside them into the template function. * Example: **comments** are written using evaluation tags (`<% /*comment */ %>`) * Example: conditionals are written using evaluation tags: ```js <% if (num === 3) { %> Display this <% } else { %> Display this instead <% } %> ``` * [Whitespace trimming](syntax/whitespace-control) is the same as in EJS (but supports trimming left newlines). Follow the opening delimiter or precede the closing delimiter with `_` or `-`. ## Helpful Tips: * `=` and `~` don't have to come immediately following your opening delimiter. For example, `<% = 2 + 4 %>` is still valid * Eta's configuration is stored in the variable `E`. That's why, for partials, you write `<%~ E.include("mypartial") %>` ## Inspiration Eta takes inspiration from EJS, doT.js, Mustache, Handlebars, Nunjucks, and many other great template engines. Significant chunks of its code are borrowed from [Squirrelly](https://squirrelly.js.org) # Partials URL: /docs/2.x.x/syntax/partials *** id: partials title: Partials --------------- ## Basic Syntax A template function is always called with [an Eta config object](../api/configuration.md), which is stored in a variable called `E`. `E` has two functions for including partials: `include` and `includeFile`. As of Eta 1.6.0, `E.include` and `E.includeFile` are aliased as `include` and `includeFile`. Either method works. ```js <%~ E.include(name, options) %> <% /* or */ %> <%~ include(name, options) %> ``` ```js <%~ E.includeFile(path, options) %> <% /* or */ %> <%~ includeFile(path, options) %> ``` ## Example ```js <%~ E.include('my-partial') %> ``` ```js <%~ E.include('my-partial', {users: it.users}) %> ``` ``` <%~ E.includeFile('../partials/footer', {description: "Footer" }) %> ``` # Whitespace Control URL: /docs/2.x.x/syntax/whitespace-trimming *** id: whitespace-control title: Whitespace Control ------------------------- Eta allows you to control the whitespace before or after tags. :::note Eta borrows its whitespace control syntax from EJS ::: ## Basic Syntax Opening delimiters can be followed with `-` or `_`, and closing delimiters can be prefixed with `-` or `_` `_` at the beginning of a tag will trim all whitespace before it, and `_` at the end of a tag will trim all whitespace after it. `-` at the beginning of a tag will trim 1 newline before it, and `-` at the end of a tag will trim 1 newline after it. ## Examples ```js Hi <%- = it.myname %> ``` By default, Eta removes the first newline character after each tag. This can be [configured](../api/configuration) # Configuration Options URL: /docs/3.x.x/api/configuration *** id: configuration title: Configuration Options ---------------------------- ```ts type config = { /** Whether or not to automatically XML-escape interpolations. Default true */ autoEscape: boolean /** Apply a filter function defined on the class to every interpolation or raw interpolation */ autoFilter: boolean /** Configure automatic whitespace trimming. Default `[false, 'nl']` */ autoTrim: trimConfig | [trimConfig, trimConfig] /** Whether or not to cache templates if `name` or `filename` is passed */ cache: boolean /** Holds cache of resolved filepaths. Set to `false` to disable. */ cacheFilepaths: boolean /** Whether to pretty-format error messages (introduces runtime penalties) */ debug: boolean /** Function to XML-sanitize interpolations */ escapeFunction: (str: unknown) => string /** Function applied to all interpolations when autoFilter is true */ filterFunction: (val: unknown) => string /** Raw JS code inserted in the template function. Useful for declaring global variables for user templates */ functionHeader: string /** Parsing options */ parse: { /** Which prefix to use for evaluation. Default `""`, does not support `"-"` or `"_"` */ exec: string /** Which prefix to use for interpolation. Default `"="`, does not support `"-"` or `"_"` */ interpolate: string /** Which prefix to use for raw interpolation. Default `"~"`, does not support `"-"` or `"_"` */ raw: string } /** Array of plugins */ plugins: Array<{ processFnString?: Function processAST?: Function processTemplate?: Function }> /** Remove empty lines and whitespace between lines */ rmWhitespace: boolean /** Delimiters: by default `['<%', '%>']` */ tags: [string, string] /** Make data available on the global object instead of varName */ useWith: boolean /** Name of the data object. Default `it` */ varName: string /** Directory that contains templates */ views?: string /** Control template file extension defaults. Default `.eta` */ defaultExtension?: string; } ``` # API Overview URL: /docs/3.x.x/api/overview *** id: overview title: API Overview slug: /api ---------- ## Setting up Eta Eta is exported as a class, so you must instantiate it before using it: ```js import { Eta } from "eta" const eta = new Eta(options) ``` Passing in options is optional. You can find a list of all options [here](api/configuration). Most users will need to pass in the `views` option, which is the path to your templates directory. ```js const eta = new Eta({ views: path.join(__dirname, "templates") }) ``` Other common options include: * `debug`: Enables pretty-printing of runtime errors. Defaults to `false`. * `cache`: Whether to cache templates. Defaults to `false`. * `autoEscape`: Whether to automatically escape HTML in templates. Defaults to `true`. ## Rendering Template Files ### Synchronously To render a template, use the `render` method: ```js const res = eta.render("templateName", { name: "Ben" }) ``` The first argument is the name of the template, and the second argument is the data to pass to the template. The template name is relative to the `views` option passed in when instantiating Eta. If you want to used named templates without resolving from the filesystem, name your templates with a leading `@`. Eta won't attempt to resolve those templates from the filesystem, and will instead look for them in the cache. ### Asynchronously To render a template asynchronously, use the `renderAsync` method: ```js const res = await eta.renderAsync("templateName", { name: "Ben" }) ``` The `renderAsync` method returns a promise, so you must use `await` or `.then` to get the result. ## Rendering Strings You can render a string as a template using the `renderString` method: ```js const res = eta.renderString("Hello <%= it.name %>", { name: "Ben" }) ``` Or render a string asynchronously using the `renderStringAsync` method: ```js const res = eta.renderStringAsync("Hello <%= await it.someFunction() %>", { someFunction: () => Promise.resolve("Ben") }) ``` ## Defining Templates Programmatically To define a template programmatically, use `loadTemplate`: ```js const headerPartial = `

<%= it.title %>

` eta.loadTemplate("@header", headerPartial) ``` If your template isn't a file in the views directory, you must name it with a leading `@` so that Eta knows not to resolve it from the filesystem. The third argument to `loadTemplate` is an object of type `{async: boolean}` describing whether the template is async or not. By default, Eta will assume that the template is synchronous. ## Common Use Cases ### Custom Tags You can change Eta's default tags by using the `tags` option: ```js const eta = new Eta({ tags: ["{{", "}}"] }) ``` ### Auto-filtering Data You can automatically filter all values by passing them through your own filter function: ```js const eta = new Eta({ autoFilter: true, filterFunction: (val) => { if (typeof val === "string") { return val.toUpperCase() } return val } }) ``` ### Getting rid of `it` By default, Eta will store all data in the `it` variable. You can customize the name of this variable by using the `varName` option: ```js const eta = new Eta({ varName: "data" }) // "Hi <%= data.name %>" ``` If you want to get rid of `it` entirely, you can use the `useWith` option: ```js const eta = new Eta({ useWith: true }) // "Hi <%= name %>" ``` This is generally considered to be bad practice, as it can lead to naming collisions / poor performance. A better approach is to use the `functionHeader` configuration option: ```js const eta = new Eta({ functionHeader: "const name=it.name, age=it.age" }) // "Hi <%= name %>, our records show you are <%= age %> years old" ``` ### Customizing file handling You can customize how Eta reads files by extending the Eta class and overriding the `readFile` and `resolvePath` methods: ```js class CustomEta extends Eta { readFile = function (...) {...} resolvePath = function (...) {...} } ``` # Quickstart URL: /docs/3.x.x/intro/quickstart *** id: quickstart title: Quickstart slug: / ------- Install Eta ```bash npm install eta ``` In the root of your project, create `templates/simple.eta` ```js Hi <%= it.name %>! ``` Then, in your JS file: ```js import { Eta } from "eta" const eta = new Eta({ views: path.join(__dirname, "templates") }) // Render a template const res = eta.render("./simple", { name: "Ben" }) console.log(res) // Hi Ben! ``` # Security URL: /docs/3.x.x/intro/security *** id: security title: Security --------------- ## Templates are code, not user input Eta templates compile to JavaScript functions. Rendering a template is equivalent to executing JavaScript code. This means you should **never pass untrusted or user-controlled strings** as templates to `render()`, `renderString()`, or any other Eta method that accepts a template string. ```js // DANGEROUS — equivalent to eval() on user input const userInput = req.body.template eta.renderString(userInput, data) // SAFE — user data is passed through the data object eta.renderString("Hello <%= it.name %>!", { name: req.body.name }) ``` This is the standard security model for embedded JS template engines (EJS, lodash templates, doT, etc.) and template engines in other languages (Jinja2, ERB, Blade). Templates are authored by developers, not end users. ## Sandboxing Eta does not sandbox template execution and this is not a goal of the project. If you need to render untrusted templates, use a logic-less template engine (like Mustache) or run templates in a sandboxed environment (like an isolated VM or Web Worker). # Syntax Cheatsheet URL: /docs/3.x.x/intro/syntax-cheatsheet *** id: syntax-cheatsheet title: Syntax Cheatsheet ------------------------ ## Conditionals ```js <% if (it.someval === "someothervalue") { %> Display this! <% } else { %> They're not equal <% } %> ``` ## Looping over arrays ```js <% users.forEach(function(user){ %> <%= user.first %> <%= user.last %> <% }) %> ``` ## Looping over objects ```js <% Object.keys(someObject).forEach(function(prop) { %> <%= someObject[prop] %> <% }) %> ``` ## Logging to the console ```js <% console.log("The value of it.num is: " + it.num) %> ``` ## Async Partials ```js <%~ await includeAsync("./path-to-partial") %> ``` # Template Syntax URL: /docs/3.x.x/intro/template-syntax *** id: template-syntax title: Template Syntax ---------------------- Eta's syntax will be familiar if you've ever used EJS. You'll get the hang of it in no time! ## Basic Syntax The data you pass in is available in the `it` variable. **To output data**, use the `<%=` opening tag. ```js Hi <%= it.name %> ``` By default, Eta will automatically XML-escape the data you output. **To allow raw HTML**, use the `<%~` opening tag. ```js Hi <%~ it.contentContainingHTML %> ``` **To evaluate JavaScript**, use the `<%` opening tag. ```js <% let myVar = 3 %> ``` **Comments** are just like regular JavaScript multiline comments! ```js <% /* this is a comment */ %> ``` ## Partials and Layouts Partials are just like regular templates, except they are rendered inside other templates. **To render a partial**, use the `<%~` opening tag + the `include()` function. ```js <%~ include("./path-to-partial") %> <% /* we can also pass in data that will be merged with `it` and passed to the partial */ %> <%~ include("./path-to-partial", { option: true }) %> ``` **To render an async partial**, use the `<%~` opening tag + the `includeAsync()` function. ```js <%~ await includeAsync("./path-to-partial") %> ``` A template file can only have one parent layout (though layouts themselves can have parents). **To set the parent layout**, use the `layout()` function. ```js <% layout("./path-to-layout") %> ``` To render child content in the layout, use `it.body`. ``` <%~ it.body %> ``` ### Name Resolution of Partials and Layouts If you're running Eta in Node.js or Deno, Eta will automatically try to resolve partials and layouts from inside the filesystem. Ex. `<%~ include("/header.eta") %>` will look for a file called `header.eta` in the `views` directory of your project. But what if you want to include a partial/layout that doesn't exist on the filesystem? Maybe you programatically defined it as a string or loaded it from the internet. There's a solution for that. If you name your template starting with an `@` symbol, Eta will know to look in the internal template storage rather than on the filesystem. ```js <%~ include("@header") %> ``` ## Whitespace Control *Note: a "delimiter" means the opening or closing tag.* Opening delimiters can be followed with `-` or `_`, and closing delimiters can be prefixed with `-` or `_` `_` at the beginning of a tag will trim all whitespace before it, and `_` at the end of a tag will trim all whitespace after it. `-` at the beginning of a tag will trim 1 newline before it, and `-` at the end of a tag will trim 1 newline after it. ```js Hi <%- = it.myname %> <% /* %The newline after "Hi" will be stripped */ %> ``` # Deno URL: /docs/3.x.x/resources/deno *** id: deno title: Deno ----------- Eta should work out-of-the-box with Deno! You can import it from `deno.land/x`: ```ts import { Eta } from "https://deno.land/x/eta@v3.0.3/src/index.ts" ``` configured this way: ```ts let viewpath = Deno.cwd()+'/views/' let eta = new Eta({ views: viewpath, cache: true }) ``` and used like this: ```ts res.send(eta.render('home',{title:"that's my title"})); ``` # Express.js URL: /docs/3.x.x/resources/express *** id: express title: Express.js ----------------- Eta no longer supports the Express.js `app.engine()` function, but it's still completely possible to use with Express.js! Here's a quick example. ```js const express = require("express") const path = require("node:path") const { Eta } = require("eta") const app = express() const eta = new Eta({ // Views directory path views: path.join(__dirname, "views"), // on Deno : `${Deno.cwd()}/views/` // Any other option... cache: true }) app.get("/", (req, res) => { const renderedTemplate = eta.render("index", { title: "Hello", place: "there!" }) // create `index.eta` in the `views` folder res.status(200).send(renderedTemplate) }) app.listen(3000, () => { console.log("Server listening on port 3000") }) ``` ### Alternatively, define an Express app engine using ETA Missing the good'old `res.render` to render computed templates ? Here is a quick walk around to achieve the same behaviour throughout the whole application. Note : `app.engine("eta", eta.render)` is no longer supported on `v3.x.x` for Node.js and Deno ```js const express = require("express") const path = require("node:path") const { Eta } = require("eta") // Create app const app = express() // Setup eta const eta = new Eta({ views: path.join(__dirname, "views") }) app.engine("eta", buildEtaEngine()) app.set("view engine", "eta") // Home route app.get("/", (req, res) => { res.render("home", { message: "Hello world !" }) // create `home.eta` in the `views` folder }); // Start server app.listen(3000, () => { console.log("Server listening on port 3000") }); function buildEtaEngine() { return (path, opts, callback) => { try { const fileContent = eta.readFile(path); const renderedTemplate = eta.renderString(fileContent, opts); callback(null, renderedTemplate); } catch (error) { callback(error); }; }; } ``` # Fastify URL: /docs/3.x.x/resources/fastify *** id: fastify title: Fastify -------------- Fastify can use `eta-js` through `@fastify/view` plugin. ```js import fastify from "fastify"; import fastifyView from "@fastify/view"; import { Eta } from "eta"; import path from "path"; const eta = new Eta(); const server = fastify(); server.register(fastifyView, { engine: { eta, }, templates: path.join(__dirname, "my-views"), }); server.get("/", (req,res) => { // home route }); server.ready().then(() => { server.listen({ port: 8888 }, async (err, address) => { console.log(`Example app listening on port 8888`) }); }); ``` # Integrations URL: /docs/3.x.x/resources/integrations *** id: integrations title: Integrations ------------------- :::warn None of these are: * Officially supported * Vetted for security * Guaranteed to work Use at your own risk! They may not support Eta v3 yet either. ::: ## Frameworks that support Eta * Opine (see [https://github.com/asos-craigmorten/opine/tree/main/examples/eta](https://github.com/asos-craigmorten/opine/tree/main/examples/eta)) * Alosaur (see [https://github.com/alosaur/alosaur/tree/master/examples/eta](https://github.com/alosaur/alosaur/tree/master/examples/eta)) * Fastify (see [https://github.com/fastify/point-of-view](https://github.com/fastify/point-of-view)) ## Tools for Eta development * Rollup Plugin (see [https://github.com/stateful/rollup-plugin-eta](https://github.com/stateful/rollup-plugin-eta)) * VSCode Extension (see [https://marketplace.visualstudio.com/items?itemName=shadowtime2000.eta-vscode](https://marketplace.visualstudio.com/items?itemName=shadowtime2000.eta-vscode)) * ESLint Plugin (see [https://github.com/eta-dev/eslint-plugin-eta](https://github.com/eta-dev/eslint-plugin-eta)) * NodeRED Flow (see [https://flows.nodered.org/node/@ralphwetzel/node-red-contrib-eta](https://flows.nodered.org/node/@ralphwetzel/node-red-contrib-eta)) * Koa Middleware (see [https://github.com/cedx/koa-eta/wiki](https://github.com/cedx/koa-eta/wiki)) # Tutorials and Articles URL: /docs/3.x.x/resources/tutorials-and-articles *** id: tutorials-and-articles title: Tutorials and Articles ----------------------------- * [https://dev.to/nebrelbug/i-built-a-js-template-engine-3x-faster-than-ejs-lj8](https://dev.to/nebrelbug/i-built-a-js-template-engine-3x-faster-than-ejs-lj8) * [2.x.x tutorial on creating plugins](/docs/2.x.x/learn/plugins) (code needs to be slightly tweaked, but still relevant) # Configuration Options URL: /docs/4.x.x/api/configuration *** id: configuration title: Configuration Options ---------------------------- ```ts type config = { /** Whether or not to automatically XML-escape interpolations. Default true */ autoEscape: boolean /** Apply a filter function defined on the class to every interpolation or raw interpolation */ autoFilter: boolean /** Configure automatic whitespace trimming. Default `[false, 'nl']` */ autoTrim: trimConfig | [trimConfig, trimConfig] /** Whether or not to cache templates if `name` or `filename` is passed */ cache: boolean /** Holds cache of resolved filepaths. Set to `false` to disable. */ cacheFilepaths: boolean /** Custom tag prefixes. Keys are prefixes, values are handler functions. Default {} */ customTags: Record string> /** Whether to pretty-format error messages (introduces runtime penalties) */ debug: boolean /** Function to XML-sanitize interpolations */ escapeFunction: (str: unknown) => string /** Function applied to all interpolations when autoFilter is true */ filterFunction: (val: unknown) => string /** Raw JS code inserted in the template function. Useful for declaring global variables for user templates */ functionHeader: string /** Parsing options */ parse: { /** Which prefix to use for evaluation. Default `""`, does not support `"-"` or `"_"` */ exec: string /** Which prefix to use for interpolation. Default `"="`, does not support `"-"` or `"_"` */ interpolate: string /** Which prefix to use for raw interpolation. Default `"~"`, does not support `"-"` or `"_"` */ raw: string } /** Array of plugins */ plugins: Array<{ processFnString?: Function processAST?: Function processTemplate?: Function }> /** Remove empty lines and whitespace between lines */ rmWhitespace: boolean /** Delimiters: by default `['<%', '%>']` */ tags: [string, string] /** Make data available on the global object instead of varName */ useWith: boolean /** Name of the data object. Default `it` */ varName: string /** Directory that contains templates */ views?: string /** Control template file extension defaults. Default `.eta` */ defaultExtension?: string; } ``` # API Overview URL: /docs/4.x.x/api/overview *** id: overview title: API Overview slug: /api ---------- ## Setting up Eta Eta is exported as a class, so you must instantiate it before using it: ```js import { Eta } from "eta" const eta = new Eta(options) ``` In Node ESM, resolve paths with `import.meta.dirname` (Node 20.11+): ```js import path from "node:path" const eta = new Eta({ views: path.join(import.meta.dirname, "templates") }) ``` Note: `import.meta.dirname` requires Node 20.11+. Passing in options is optional. You can find a list of all options [here](api/configuration). Most users will need to pass in the `views` option, which is the path to your templates directory. ```js const eta = new Eta({ views: path.join(import.meta.dirname, "templates") }) ``` Other common options include: * `debug`: Enables pretty-printing of runtime errors. Defaults to `false`. * `cache`: Whether to cache templates. Defaults to `false`. * `autoEscape`: Whether to automatically escape HTML in templates. Defaults to `true`. ## Rendering Template Files ### Synchronously To render a template, use the `render` method: ```js const res = eta.render("templateName", { name: "Ben" }) ``` The first argument is the name of the template, and the second argument is the data to pass to the template. The template name is relative to the `views` option passed in when instantiating Eta. If you want to used named templates without resolving from the filesystem, name your templates with a leading `@`. Eta won't attempt to resolve those templates from the filesystem, and will instead look for them in the cache. ## Using Eta in the browser Import the browser-friendly core build: ```html ``` ### Asynchronously To render a template asynchronously, use the `renderAsync` method: ```js const res = await eta.renderAsync("templateName", { name: "Ben" }) ``` The `renderAsync` method returns a promise, so you must use `await` or `.then` to get the result. ## Rendering Strings You can render a string as a template using the `renderString` method: ```js const res = eta.renderString("Hello <%= it.name %>", { name: "Ben" }) ``` Or render a string asynchronously using the `renderStringAsync` method: ```js const res = eta.renderStringAsync("Hello <%= await it.someFunction() %>", { someFunction: () => Promise.resolve("Ben") }) ``` ## Defining Templates Programmatically To define a template programmatically, use `loadTemplate`: ```js const headerPartial = `

<%= it.title %>

` eta.loadTemplate("@header", headerPartial) ``` If your template isn't a file in the views directory, you must name it with a leading `@` so that Eta knows not to resolve it from the filesystem. The third argument to `loadTemplate` is an object of type `{async: boolean}` describing whether the template is async or not. By default, Eta will assume that the template is synchronous. ## Common Use Cases ### Custom Tags You can change Eta's default tags by using the `tags` option: ```js const eta = new Eta({ tags: ["{{", "}}"] }) ``` ### Auto-filtering Data You can automatically filter all values by passing them through your own filter function: ```js const eta = new Eta({ autoFilter: true, filterFunction: (val) => { if (typeof val === "string") { return val.toUpperCase() } return val } }) ``` ### Getting rid of `it` By default, Eta will store all data in the `it` variable. You can customize the name of this variable by using the `varName` option: ```js const eta = new Eta({ varName: "data" }) // "Hi <%= data.name %>" ``` If you want to get rid of `it` entirely, you can use the `useWith` option: ```js const eta = new Eta({ useWith: true }) // "Hi <%= name %>" ``` This is generally considered to be bad practice, as it can lead to naming collisions / poor performance. A better approach is to use the `functionHeader` configuration option: ```js const eta = new Eta({ functionHeader: "const name=it.name, age=it.age" }) // "Hi <%= name %>, our records show you are <%= age %> years old" ``` ### Customizing file handling You can customize how Eta reads files by extending the Eta class and overriding the `readFile` and `resolvePath` methods: ```js class CustomEta extends Eta { readFile = function (...) {...} resolvePath = function (...) {...} } ``` # Quickstart URL: /docs/4.x.x/intro/quickstart *** id: quickstart title: Quickstart slug: / ------- Eta is a lightweight and blazing fast embedded JS templating engine by [bgub (Ben Gubler)](https://github.com/bgub). It works inside Node, Deno, and the browser. Install Eta ```bash npm install eta ``` In the root of your project, create `templates/simple.eta` ```js Hi <%= it.name %>! ``` Then, in your JS file: ```js import { Eta } from "eta" import path from "node:path" const eta = new Eta({ views: path.join(import.meta.dirname, "templates") }) // Render a template const res = eta.render("./simple", { name: "Ben" }) console.log(res) // Hi Ben! ``` Note: `import.meta.dirname` requires Node 20.11+. Eta v4 is ESM-only. In browsers, import the core build: ```html ``` # Security URL: /docs/4.x.x/intro/security *** id: security title: Security --------------- ## Templates are code, not user input Eta templates compile to JavaScript functions. Rendering a template is equivalent to executing JavaScript code. This means you should **never pass untrusted or user-controlled strings** as templates to `render()`, `renderString()`, or any other Eta method that accepts a template string. ```js // DANGEROUS — equivalent to eval() on user input const userInput = req.body.template eta.renderString(userInput, data) // SAFE — user data is passed through the data object eta.renderString("Hello <%= it.name %>!", { name: req.body.name }) ``` This is the standard security model for embedded JS template engines (EJS, lodash templates, doT, etc.) and template engines in other languages (Jinja2, ERB, Blade). Templates are authored by developers, not end users. ## Sandboxing Eta does not sandbox template execution and this is not a goal of the project. If you need to render untrusted templates, use a logic-less template engine (like Mustache) or run templates in a sandboxed environment (like an isolated VM or Web Worker). # Deno URL: /docs/4.x.x/resources/deno *** id: deno title: Deno ----------- Eta works out of the box with Deno. Prefer importing from JSR: ```ts import { Eta } from "jsr:@bgub/eta" ``` configured this way: ```ts const viewpath = `${Deno.cwd()}/views/` const eta = new Eta({ views: viewpath, cache: true }) ``` and used like this: ```ts res.send(eta.render('home',{title:"that's my title"})); ``` # Express.js URL: /docs/4.x.x/resources/express *** id: express title: Express.js ----------------- Eta no longer supports the Express.js `app.engine()` function, but it's still completely possible to use with Express.js! Here's a quick example. ```js import express from "express" import path from "node:path" import { Eta } from "eta" const app = express() const eta = new Eta({ // Views directory path views: path.join(import.meta.dirname, "views"), // on Deno : `${Deno.cwd()}/views/` // Any other option... cache: true }) app.get("/", (req, res) => { const renderedTemplate = eta.render("index", { title: "Hello", place: "there!" }) // create `index.eta` in the `views` folder res.status(200).send(renderedTemplate) }) app.listen(3000, () => { console.log("Server listening on port 3000") }) ``` Note: `import.meta.dirname` requires Node 20.11+. ### Alternatively, define an Express app engine using ETA Missing the good'old `res.render` to render computed templates ? Here is a quick walk around to achieve the same behaviour throughout the whole application. Note : `app.engine("eta", eta.render)` is no longer supported on `v3.x.x` for Node.js and Deno ```js import express from "express" import path from "node:path" import { Eta } from "eta" // Create app const app = express() // Setup eta const eta = new Eta({ views: path.join(import.meta.dirname, "views") }) app.engine("eta", buildEtaEngine()) app.set("view engine", "eta") // Home route app.get("/", (req, res) => { res.render("home", { message: "Hello world !" }) // create `home.eta` in the `views` folder }); // Start server app.listen(3000, () => { console.log("Server listening on port 3000") }); function buildEtaEngine() { return (path, opts, callback) => { try { const fileContent = eta.readFile(path); const renderedTemplate = eta.renderString(fileContent, opts); callback(null, renderedTemplate); } catch (error) { callback(error); }; }; } ``` Note: `import.meta.dirname` requires Node 20.11+. # Fastify URL: /docs/4.x.x/resources/fastify *** id: fastify title: Fastify -------------- Fastify can use `eta-js` through `@fastify/view` plugin. ```js import fastify from "fastify" import fastifyView from "@fastify/view" import { Eta } from "eta" import path from "node:path" const eta = new Eta() const server = fastify() server.register(fastifyView, { engine: { eta }, templates: path.join(import.meta.dirname, "my-views"), }) server.get("/", (req, res) => { // home route }) server.listen({ port: 8888 }).then(() => { console.log("Example app listening on port 8888") }) ``` Note: `import.meta.dirname` requires Node 20.11+. # Integrations URL: /docs/4.x.x/resources/integrations *** id: integrations title: Integrations ------------------- :::warn None of these are: * Officially supported * Vetted for security * Guaranteed to work Use at your own risk. They may lag behind Eta v4. ::: ## Frameworks that support Eta * Opine (see [https://github.com/asos-craigmorten/opine/tree/main/examples/eta](https://github.com/asos-craigmorten/opine/tree/main/examples/eta)) * Alosaur (see [https://github.com/alosaur/alosaur/tree/master/examples/eta](https://github.com/alosaur/alosaur/tree/master/examples/eta)) * Fastify (see [https://github.com/fastify/point-of-view](https://github.com/fastify/point-of-view)) ## Tools for Eta development * Rollup Plugin (see [https://github.com/stateful/rollup-plugin-eta](https://github.com/stateful/rollup-plugin-eta)) * VSCode Extension (see [https://marketplace.visualstudio.com/items?itemName=shadowtime2000.eta-vscode](https://marketplace.visualstudio.com/items?itemName=shadowtime2000.eta-vscode)) * ESLint Plugin (see [https://github.com/eta-dev/eslint-plugin-eta](https://github.com/eta-dev/eslint-plugin-eta)) * NodeRED Flow (see [https://flows.nodered.org/node/@ralphwetzel/node-red-contrib-eta](https://flows.nodered.org/node/@ralphwetzel/node-red-contrib-eta)) * Koa Middleware (see [https://github.com/cedx/koa-eta/wiki](https://github.com/cedx/koa-eta/wiki)) # Tutorials and Articles URL: /docs/4.x.x/resources/tutorials-and-articles *** id: tutorials-and-articles title: Tutorials and Articles ----------------------------- * [https://dev.to/nebrelbug/i-built-a-js-template-engine-3x-faster-than-ejs-lj8](https://dev.to/nebrelbug/i-built-a-js-template-engine-3x-faster-than-ejs-lj8) * [2.x.x tutorial on creating plugins](/docs/2.x.x/learn/plugins) (code needs to be slightly tweaked, but still relevant) # Cheatsheet URL: /docs/4.x.x/syntax/cheatsheet *** id: cheatsheet title: Cheatsheet ----------------- ## Output (escaped) ```js <%= it.name %> ``` ## Output (raw HTML) ```js <%~ it.htmlContent %> ``` ## Execute JavaScript ```js <% let x = 1 + 2 %> ``` ## Comments ```js <% /* this is a comment */ %> ``` ## Conditionals ```js <% if (it.show) { %> Visible! <% } else { %> Hidden <% } %> ``` ## Looping over arrays ```js <% it.users.forEach(function(user) { %> <%= user.first %> <%= user.last %> <% }) %> ``` ## Looping over objects ```js <% Object.keys(it.obj).forEach(function(key) { %> <%= it.obj[key] %> <% }) %> ``` ## Partials ```js <%~ include("./header") %> <%~ include("./header", { title: "Home" }) %> ``` ## Async partials ```js <%~ await includeAsync("./header") %> ``` ## Layout ```js <% layout("./base") %> <% layout("./base", { title: "Home" }) %> ``` ## Blocks ```js <% /* In child template: define a block */ %> <% block("sidebar", () => { %> <% }) %> <% /* In layout: render a block with optional fallback */ %> <%~ block("sidebar", () => { %> <% }) %> ``` ## Capture ```js <% const fragment = capture(() => { %>

Reusable content

<% }) %> <%= fragment %> ``` ## Custom tags ```js // Config: customTags: { "#": () => "", "*": (key, data) => data[key.trim()] } <%# comment %> <%* name %> ``` ## Logging ```js <% console.log("Debug: " + it.value) %> ``` # Custom Tags URL: /docs/4.x.x/syntax/custom-tags *** id: custom-tags title: Custom Tags ------------------ Custom tags let you define your own tag prefixes with handler functions. This is useful for translation keys, comments, or any domain-specific template syntax. ## Configuration Pass a `customTags` object when creating an Eta instance. Keys are tag prefixes, values are functions that receive the tag content (as a string) and the template data object: ```js const eta = new Eta({ customTags: { "#": () => "", // comment tag "*": (key, data) => translations[data.lang][key.trim()], }, }) ``` ## Usage Use your custom prefix after the opening delimiter, just like `=` or `~`: ```js // Comment tag — outputs nothing <%# This is a comment %> // Translation tag — looks up a key

<%* greeting %>

``` ## Example: translations ```js const translations = { en: { greeting: "Hello!", farewell: "Goodbye!" }, pl: { greeting: "Czesc!", farewell: "Do widzenia!" }, } const eta = new Eta({ customTags: { "*": (key, data) => translations[data.lang][key.trim()], }, }) eta.renderString("

<%* greeting %>

", { lang: "en" }) // => "

Hello!

" ``` ## How it works * Tag content is passed to the handler as a **static string**, not evaluated as JavaScript. `<%* greeting %>` passes `" greeting "` to the handler, not a variable lookup. * The handler receives the full template data object as its second argument, so it can do its own lookups. * The handler's return value is concatenated directly to the output — no auto-escaping is applied. Escape values yourself if needed. ## Restrictions Custom tag prefixes cannot conflict with: * Built-in prefixes (`=`, `~`, or the empty string) * Whitespace trim markers (`-`, `_`) Attempting to use a conflicting prefix will throw an error. # Helpers URL: /docs/4.x.x/syntax/helpers *** id: helpers title: Helpers -------------- Eta provides several built-in helper functions available inside templates. ## output() The `output()` function appends a string directly to the template output. This is useful inside loops or conditionals where you want to write output from JavaScript code: ```js <% for (const item of it.items) { output("
  • " + item + "
  • ") } %> ``` ## capture() The `capture()` function executes a block of template code and returns its output as a string, instead of writing it to the template output. This is useful for storing rendered content in a variable: ```js <% const greeting = capture(() => { %>

    Hello, <%= it.name %>!

    <% }) %> <%= greeting %> <%= greeting %> ``` This renders the greeting twice. Without `capture()`, there's no way to reuse a rendered fragment within the same template. ## captureAsync() The async version of `capture()`, for use with `renderAsync` or `renderStringAsync`: ```js <% const data = await captureAsync(async () => { %> <%= await it.fetchData() %> <% }) %> <%~ data %> ``` # Layouts and Blocks URL: /docs/4.x.x/syntax/layouts-and-blocks *** id: layouts-and-blocks title: Layouts and Blocks ------------------------- Eta has built-in support for layouts with named content blocks, giving you a powerful way to build page templates with overridable sections. ## Layouts A template file can have one parent layout (though layouts themselves can have parents). To set the parent layout, call the `layout()` function: ```js <% layout("./base") %>

    My Page

    This content will be available as `it.body` in the layout.

    ``` In the layout file (`base.eta`), render the child content with `it.body`: ```html <%= it.title %> <%~ it.body %> ``` You can also pass extra data to the layout: ```js <% layout("./base", { title: "My Page" }) %> ``` ## Blocks Blocks let you define named content sections that child templates can fill, and layouts can render. This is useful for things like page-specific scripts, styles, or sidebar content. ### Defining blocks in a child template Use the `block()` helper to define a named block: ```js <% layout("./base") %> <% block("title", () => { %> My Page Title <% }) %> <% block("sidebar", () => { %> <% }) %>

    Main content goes in it.body as usual.

    ``` ### Rendering blocks in a layout In the layout, call `block()` with just the name to render the block's content. You can provide a fallback by passing a function as the second argument: ```html <%~ block("title", () => { %>Default Title<% }) %>
    <%~ it.body %>
    ``` If the child template defines a `"sidebar"` block, its content is rendered. If not, the block renders nothing (or the fallback content if one is provided). ### Async blocks For blocks that need to await async operations, use `blockAsync()`: ```js <% layout("./base") %> <% blockAsync("data", async () => { %> <%= await fetchSomeData() %> <% }) %> ``` In the layout, render with `blockAsync()`: ```js <%~ await blockAsync("data") %> ``` ### How blocks work When a child template calls `block("name", fn)` and a layout is active, the block content is captured and stored. When the layout later calls `block("name")`, the stored content is returned. This means: * Blocks defined in the child are available to the parent layout * If no layout is active, `block()` renders its content inline (useful for reusable components) * Fallback content in the layout is only used when the child doesn't define that block ### Full example **`views/page.eta`**: ```js <% layout("./layout") %> <% block("head", () => { %> <% }) %> <% block("scripts", () => { %> <% }) %>

    <%= it.title %>

    <%= it.content %>

    ``` **`views/layout.eta`**: ```html <%~ block("head") %>
    <%~ it.body %>
    <%~ block("scripts", () => { %> <% }) %> ``` **Rendering**: ```js const html = eta.render("./page", { title: "Hello", content: "Welcome to my site" }) ``` **Output**: ```html

    Hello

    Welcome to my site

    ``` # Template Syntax URL: /docs/4.x.x/syntax/template-syntax *** id: template-syntax title: Template Syntax ---------------------- Eta's syntax will be familiar if you've ever used EJS. You'll get the hang of it in no time! ## Basic Syntax The data you pass in is available in the `it` variable. **To output data**, use the `<%=` opening tag. ```js Hi <%= it.name %> ``` By default, Eta will automatically XML-escape the data you output. **To allow raw HTML**, use the `<%~` opening tag. ```js Hi <%~ it.contentContainingHTML %> ``` **To evaluate JavaScript**, use the `<%` opening tag. ```js <% let myVar = 3 %> ``` **Comments** are just like regular JavaScript multiline comments! ```js <% /* this is a comment */ %> ``` ## Partials Partials are templates rendered inside other templates. **To render a partial**, use the `<%~` opening tag + the `include()` function. ```js <%~ include("./path-to-partial") %> <% /* we can also pass in data that will be merged with `it` and passed to the partial */ %> <%~ include("./path-to-partial", { option: true }) %> ``` **To render an async partial**, use `includeAsync()`. ```js <%~ await includeAsync("./path-to-partial") %> ``` ### Name Resolution If you're running Eta in Node.js or Deno, Eta will automatically try to resolve partials and layouts from the filesystem. Ex. `<%~ include("/header.eta") %>` will look for a file called `header.eta` in the `views` directory. If you want to include a partial that doesn't exist on the filesystem (e.g. one defined programmatically), name it starting with `@`: ```js <%~ include("@header") %> ``` ## Whitespace Control Opening delimiters can be followed with `-` or `_`, and closing delimiters can be prefixed with `-` or `_`. `_` at the beginning of a tag will trim all whitespace before it, and `_` at the end of a tag will trim all whitespace after it. `-` at the beginning of a tag will trim 1 newline before it, and `-` at the end of a tag will trim 1 newline after it. ```js Hi <%- = it.myname %> <% /* The newline after "Hi" will be stripped */ %> ```