esbuild-ejs
v0.1.0
Published
esbuild plugin to compile .ejs templates into JS modules
Readme
esbuild-ejs
An esbuild plugin that compiles .ejs templates into JavaScript modules. Each template exports a function that returns an array of DOM elements built with dolla's createElement.
Installation
npm install esbuild-ejsRequires esbuild as a peer dependency.
Usage
Build configuration
import esbuild from 'esbuild';
import ejsPlugin from 'esbuild-ejs';
await esbuild.build({
entryPoints: ['app.js'],
bundle: true,
outfile: 'out.js',
plugins: [ejsPlugin()],
});Importing templates
import layout from './layouts/application.html.ejs';
const elements = layout();
for (const el of elements) {
document.body.appendChild(el);
}Template syntax
A template like this:
<div><span><%= 'Hello World' %></span></div>Compiles to:
import createElement from 'dolla/createElement';
export default function application() {
return [createElement("div", {content: createElement("span", {content: 'Hello World'})})];
}Tags
| Tag | Purpose |
|-----|---------|
| <%= expr %> | Evaluate expression and output the result |
| <%- expr %> | Evaluate expression and output without escaping |
| <% code %> | Execute JavaScript without outputting |
| <%# comment %> | Comment (omitted from output) |
Features
Parameter extraction
Free variables used in template expressions are automatically extracted into destructured function parameters. Locally declared variables, function params, arrow params, and JS builtins are excluded.
<div><%= user.name %></div>
<div><%= avatarTemplate({ account: account }) %></div>Compiles to:
export default function template({user, avatarTemplate, account}) {
return [createElement("div", {content: user.name}), createElement("div", {content: avatarTemplate({ account: account })})];
}Subtemplates
Define reusable functions within templates. Function and arrow subtemplates return their content and can be called with <%= %>.
<% function renderItem(x) { %>
<span><%= x %></span>
<% } %>
<div><%= renderItem(name) %></div>Callback subtemplates work with iterators and higher-order functions:
<% formTag(function () { %>
<input type="text">
<input type="submit" />
<% }) %>Iterators
Use forEach or map to loop over data:
<ul>
<% items.forEach((item) => { %>
<li><%= item %></li>
<% }) %>
</ul>map with <%= %> inlines the results:
<table>
<%= rows.map(row => { %>
<tr><%= row %></tr>
<% }) %>
</table>Conditional statements
<% if (show) { %>
<div class="visible">yes</div>
<% } else { %>
<div class="hidden">no</div>
<% } %>Attribute interpolation
Embed expressions inside HTML attributes:
<div class="uniformLabel [[= foo ? 'disabled' : 'bold' ]] -yellow">
Hello World
</div>Comment scrubbing
Both EJS comments and HTML comments are stripped from output:
<%# This EJS comment is removed %>
<!-- This HTML comment is also removed -->Import hoisting
Import statements in <% %> tags are hoisted to the module level:
<% import listenerElement from 'dolla/listenerElement' %>
<%= listenerElement('div', {content: 'click me'}, 'click', handler) %>Compiles to:
import createElement from 'dolla/createElement';
import listenerElement from 'dolla/listenerElement';
export default function template({handler}) {
return [listenerElement('div', {content: 'click me'}, 'click', handler)];
}If a template imports createElement from another package, the built-in dolla import is automatically aliased to __createElement to avoid collisions.
Promise rendering
Template expressions can contain Promises. The promise passes through in the result array for the caller to await:
<div><%= fetchData() %></div>const result = template({fetchData: () => fetch('/api').then(r => 'loaded')});
document.body.append(result)starts as
<body><div></div></body>then when loaded
Options
ejsPlugin({
filter: /\.ejs$/i, // regex to match files (default: /\.ejs$/i)
open: '<%', // opening delimiter (default: '<%')
close: '%>', // closing delimiter (default: '%>')
})Custom delimiters
ejsPlugin({ open: '[[', close: ']]' })Then in your template:
<div><span>[[= 'Hello World' ]]</span></div>Development
git clone https://github.com/laserkats/esbuild-ejs.git
cd esbuild-ejs
npm installRunning tests
npm testProject structure
src/
index.js - esbuild plugin entry point
template.js - state-machine parser and code generator
template/
balance-scanner.js - bracket/brace balance tracking
html-tag.js - HTML element code generation
js.js - JavaScript expression node
string.js - text content node
subtemplate.js - block construct (loops, conditionals, callbacks)
var-generator.js - unique variable name generator
test/
compilation_test.js - compiled output assertions
dom_test.js - runtime DOM assertions via linkedom
integration_test.js - esbuild plugin integration tests
setup.js - linkedom DOM globals setup
fixtures/ - template fixtures used by testsLicense
MIT
