fast-ejs-builder
v1.0.3
Published
Fast-EJS is a simple and fast tool to pre-render EJS templates into static HTML files with clean conventions and flexible data handling.
Maintainers
Readme
Fast EJS Builder

A fast and lightweight static site generator that combines EJS templating with Tailwind CSS for rapid web development.
Features
- Fast Builds: Optimized build process with configurable intervals
- Tailwind CSS Integration: Automatic CSS generation and processing
- Component System: Reusable EJS components for modular development
- Data Injection: Support for JavaScript and JSON data files
- Development Mode: Live reloading with watch functionality
- Flexible Configuration: JSON-based configuration with schema validation
- Index Routing: Automatic
index.htmlgeneration for clean URLs
Installation
Global Installation (Recommended)
npm install -g fast-ejs-builderLocal Installation
npm install fast-ejs-builder --save-devQuick Start
Initialize your project:
mkdir my-site && cd my-siteCreate your project structure: This is the recommended structure. You can use your own.
my-site/ ├── components/ # Reusable EJS components ├── data/ # Global and local data files └── pages/ # EJS templates │ └── public/ # Static assets └── fast.ejs.json # Configuration fileConfigure your site in
fast.ejs.json:{ "build": { "output": "build", "interval": 100, "useIndexRouting": true }, "components": { "dir": "components", "autoGenerate": false }, "data": { "dir": "data", "allow": "all" }, "pages": { "dir": "pages" }, "tailwind": { "output": "public/app.css", "imports": [] } }Start development:
fast-ejs devBuild for production:
fast-ejs build
With local Installation
- Add fast-ejs-builder to your dev dependencies
npm install fast-ejs-builder --save-dev- Add the build and dev scripts in your
package.json
{
"scripts:": {
"dev": "fast-ejs dev",
"build": "fast-ejs build"
}
}- Run your package
npm run dev
npm run buildConfiguration
The fast.ejs.json (occasionnally called the FEJ) file controls all aspects of your site generation. Here's a complete configuration example:
{
"build": {
"output": "build",
"interval": 100,
"useIndexRouting": true
},
"components": {
"dir": "components",
"autoGenerate": false
},
"data": {
"dir": "data",
"allow": "all"
},
"pages": {
"dir": "pages"
},
"tailwind": {
"output": "public/app.css",
"imports": [
"public/style.css",
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
]
}
}Configuration Options
build.output: Directory where generated HTML files are savedbuild.interval: Milliseconds between rebuilds in development modebuild.useIndexRouting: Generateroute/index.htmlinstead ofroute.htmlcomponents.dir: Directory containing reusable EJS componentscomponents.autoGenerate: Generate missing components. Default isfalsedata.dir: Directory for global and page-specific data filesdata.allow: Data file format ("js","json", or"all")pages.dir: Directory containing your EJS page templates. Here is where you should mainly work.tailwind.output: Path to generated Tailwind CSS filetailwind.imports: Array of external CSS URLs to include. Each@layerwill be detected if specified.
Usage Examples
Creating Pages
Create EJS templates in your pages.dir directory:
<!-- pages/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title>
<link rel="stylesheet" href="<%= tailwindCss %>" />
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<%- include('../components/header') %>
<!-- or <%- $('header') %> -->
<main>
<h1 class="text-4xl font-bold text-gray-900 mb-4"><%= title %></h1>
<p class="text-lg text-gray-700"><%= description %></p>
</main>
<%- $('footer') %>
</div>
</body>
</html>Using Components
Create reusable components in your components.dir:
<!-- components/header.ejs -->
<header class="bg-white shadow-sm">
<nav class="container mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<a href="/" class="text-xl font-bold text-gray-900"><%= siteName %> | <%= $0 %> </a>
<ul class="flex space-x-4">
<% navItems.forEach(item => { %>
<li>
<a
href="<%= item.url %>"
class="text-gray-600 hover:text-gray-900 <%= $cls($route==item.url && 'underline decoration-2 underline-offset-4') %>"
>
<%= $if($route === item.url,"»") %>
<%= item.label %>
</a>
</li>
<% }) %>
</ul>
</div>
</nav>
</header>And use it inside a page or another component by calling:
<%- $("header","Some arg") %>Here $0 has the value of "Some arg". You can pass many args and access them through $1,$2,...
Remember that only components can access args data $x.
Passing data to pages
1. Using base data
Fast EJS comes with default data that can't be overrided.
$: function
Imports a component by its name. Like a 'super include'.<%- $("users/avatar","https://placehold.co/400") %>$0,$1,... :
Return the args passed to a component. Can be accessed only inside a component, not a page.
In the previous example, we can access"https://placehold.co/400"by using$0(0for the first arg).<!--components/users/avatar.ejs--> <img src="<%= $0 %>" class="w-10 aspect-square rounded-full"/>$async: Promise function
Asynchronously imports a component.<%- await $async("dashboard") %>$route:
Returns the current route relative to thepages.dir. In this example, it will return/home<!--pages/home.ejs--> <%= $route %>$css:
Automatically imports the relative path of generated tailwind css fromtailwind.outputinside a page. No need to manually write the css path and change everytime.
For example, insidepages/users/profile.ejs, it can return something like../../public/app.css
while insidepages/index.ejs, it will return something like./public/app.css<%- $css %>$date:
Returns a new Date object.
Stop writing year manually.<%= $date.getFullYear() %>$env: function
Get a env variable fromprocess.env. Useful to build env based pages.<%= $env("NODE_ENV") %>$cls: function
Same behavior astailwind clsx. Useful to write conditions based classes.<%= $cls(isActive && "text-primary", "bg-gray-100") %>$if: function
Returns a value based on a condition or a default value if set. Can also works with components :=)<%- $if(isActive, $("active-bar")) %> <%= $if($env("NODE_ENV")=="prod", "Hello","World") %>$debug: function
Prints anything in the console during build. Use it to debug your pages or components<!--components/header.ejs--> <%- $debug("Header component says hi !") %>$upper,$lowerand$trim: functions Utils for strings.<%- $trim($upper(user.name)) %>
2. Using your own data
Fill data files in data.dir (generated automatically if missing).
Remember that using js data allow you to use function, async function and getter as data.
- Global data : Can be accessed inside every pages and components
If data.allow is all or js (recommended)
// data/global.js
module.exports = {
siteName: "My Awesome Site",
navItems: [
{ label: "Home", url: "/" },
{ label: "About", url: "/about" },
{ label: "Contact", url: "/contact" },
],
add: (a, b) => a + b,
getUsers: async () => await db.getUsers(),
get randomBool() {
return Math.random() > 0.5;
},
};If data.allow is all or json
// data/global.json
{
"siteName": "My Awesome Site",
"navItems": [
{ "label": "Home", "url": "/" },
{ "label": "About", "url": "/about" },
{ "label": "Contact", "url": "/contact" },
],
}- Local data : Can be accessed only in the target page
// data/local.js
module.exports = {
// for page "pages/users/profile.ejs"
"users/profile": {
title: "Welcome to My Site",
description: "This is a fast-ejs powered website with Tailwind CSS.",
},
// dynamic routes
"user/$id": (params) => ({
id: params.id,
username: params.name,
}),
"blog/$id": ({ title, id }) => ({
id,
blogTitle: title,
}),
};- Route params : Params sent to dynamic routes. Should return an array of object
// data/route.js
module.exports = {
"user/$id": [{ id: "u1", name: "John Doe" }],
"blog/$id": () => {
const arr = [];
for (let i = 0; i < 5; i++) {
arr.push({ title: "Article " + i, id: i, isPremium: i % 2 == 0 });
}
return arr;
},
// or getter
get "blog/$id"() {
// ...
},
};Note that all JS data can be asynchronous, and each asynchronous data will affect the build time.
Use cases
You can use fast-ejs to build any static website. Just keep in mind that all pages are pre-built, not in runtime like front-end frameworks.
For example, if you want to create a blog, you have to use dynamic routes (ex: articles/$id.ejs) in order to generate each article page at once:
// - Create "articles/$id.ejs" with the logic
// - Inside "data/route.js", add all articles with their data
module.exports = {
// ...,
"articles/$id": async () => {
const list = await db.getArticles();
return list.map((a) => ({ ...a, id: a._id })); // ensure "id" key
},
};
// - Add your view data inside "data/local.js"
module.exports = {
// ...,
"articles/$id": (article /*represents the data from route.js*/) => {
const { name, date } = article;
return { name, date }; // if only name and date are used in the view
},
};
/** Then, when the build is done, route.js will generate files based on your list. And each files built with the corresponding data.
* /articles/1 (retrieve data "articles/$id" but with the article with id=1 )
* etc..
*/Commands
fast-ejs dev: Start development server with live reloadingfast-ejs build: Build static files for production
How it works
Here is the real building flow :
- Get the data inside
data.dirthat match thedata.allow. - Scan the specified
pages.dir.- All empty folder will be ignored
- If any
.ejsfile is found, search for used component insidecomponents.dir.- If a component is missing, it will be generated if
components.autoGenerateistrue - If the file it is dynamic file (ex: $id.ejs), search for corresponding routes inside
data.dir/route.js(json)
- If a component is missing, it will be generated if
- Render the html with the corresponding data and generate the right file inside
build.outputdepending onbuild.useIndexRouting. - For any other file, just copy it inside the
build.output.
- Generate the css with tailwind at
tailwind.outputalong withtailwind.importsif specified. - Scan the
build.outputand clean all junk files and folders (files from previous build and empty folders).
Notice that all empty folders inside pages.dir will be ignored in the build.
Usage tips
- Prefer using JS data
By setting data.allow to json, you allow only json data. But in real case you'll mostly need js data because you can write logic inside.
By example, using this:
{
"year": 2026
}is less effective that using:
module.exports = {
year: new Date().getFullYear(),
};because in the second case, year can change at build time.
Also js data can be async which is useful to get data from a database. So prefer setting data.allow to all or js when you need dynamic data.
Duplicate data
- If
data.allowisall, js data are prioritized over json data. In the previous example, ifyearis in both js and json file, the js one will be used. - You can't override base data. For example, declaring
$routewill have no effect.
- If
Don't misuse EJS tags
<!-- Avoid this ❌ -->
<% $("header")%>
<%= $css %>
<%- user.name %>Using <% means you're not expecting an output but $("header") should return a component.
Using <%= means you're expecting an escaped value but $css requires to be unescaped.
Using <%- means you're expecting an unescaped but user.name may return a string.
- Don't put components or data directory inside the pages directory
Any file inside the pages dir is considered a static file cause this directory is scanned to find ejs files and other files to generate an output.
pages.dir contains files that will be built (ejs, images, css, etc..)
components.dir contains parts of pages. Not pages.
data.dir contains data that will be used by the pages.
Therefore, adding components dir inside the pages dir, means components will be generated as pages and data dir files will be generated as static files.
- Fast EJS is a builder / generator. Not a framework
Because of some features like access of the current route ($route) or usage of async data, you might think you're using a kind of framework. But no. You're basically coding ejs templates with 'super powers', and something is generating the html and css files for you.
When someone visit your site, he will just see the static files early built. Even if some of your data are retrieved at build time, they can't change once the build is done.
So, you're not using a big framework that will run your SaaS.
Please consider using this for small projects like static portfolios or landing pages.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Support
If you have any questions or need help, please open an issue on GitHub.
