npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dokebi/template-ejs

v0.4.3

Published

Precompiled EJS templates for Dokebi apps: layouts, partials and per-app scopes.

Readme

@dokebi/template-ejs 0.4.3

An optional package for Node >=24.0.0 and core ^0.4.0 (public on npm). The registered ID is dokebi-template-ejs. API-only apps do not need it. It replaces the earlier names @dokebi/template, dokebi-template and plugins/dokebi-template. In an existing CLI project, remove the old plugin, add the new package, and change imports and explicit dependency IDs. There is no automatic alias.

Usage

It follows plugin contract v2 (D-0028): createPlugin({ app: app.view, options?, entry? }), where app is required. The old top-level templates, defaultLayout and engine inputs are gone. Options come from options or app.config['dokebi-template-ejs']; code input goes through entry.

import { createPlugin } from '@dokebi/template-ejs';
const view = createPlugin({
  app: app.view,
  options: { defaultLayout: 'layouts/base' },
  entry: {
    directory: new URL('./views/', import.meta.url), // optional: reads the .ejs files below it recursively
    templates: {
      'layouts/base': '<html><body><%- data.$body %></body></html>',
      'layouts/member': { source: '<main><%- data.$body %></main>', layout: 'layouts/base' },
      heading: '<h1><%= data.name %></h1>',
      member: { source: '<%- await data.$include("heading") %>', layout: 'layouts/member' },
    },
  },
});
// Declare the dependency in setup and read it with context.get(view).
// await api.render('member', { name: '<Alice>' });
// handler: (request, reply) => api.reply(reply, 'member', { name: 'Alice' })

Options

dokebi-template-ejs:
  defaultLayout: layouts/main   # optional
  extension: .ejs               # default .ejs
  maxTemplates: 256             # 1..4096, file and code templates together
  maxTemplateBytes: 1048576     # 1..16777216, bytes of one template

Invalid options are INVALID_PLUGIN_OPTIONS in the factory.

entry

{ directory?: URL; templates?: Record<string, string | { source, layout? }>; engine?: TemplateEngine }. It accepts a plain object or an entry module namespace; other keys are INVALID_PLUGIN_INPUT.

  • directory must be a file: URL. Setup reads everything below it recursively and registers only files with the configured extension. The name is the /-separated relative path without the extension (layouts/main.ejs → layouts/main). Files with other extensions (such as the entry file index.ts) are ignored.
  • Symbolic links are not skipped but refused with a start failure (the directory itself included). Exceeding the count or size limits, bad names, folders deeper than 32 levels, files that are not UTF-8 and a file colliding with a templates name also fail the start. When the start is cancelled (context.signal), reading stops.
  • Layouts are chosen only through options or entry, never by a directive inside the file. File templates follow defaultLayout; for a different layout per template, put it in templates as { source, layout }.

Rendering

render(name, data, { layout? }?) returns a Promise of the HTML string. reply(reply, name, data, options?) renders, calls reply.type('text/html; charset=utf-8') and reply.send(html), and returns the same reply. To avoid depending on Fastify types it accepts any object shaped { type(v: string): unknown; send(p: string): unknown }. When the render fails, type and send are not called.

Layout priority is the call option → the page definition → defaultLayout. false turns the layout off. The chosen layout nests only through its own layout definition and never applies defaultLayout again. Rendering a name that is used as a layout (defaultLayout and every definition's layout) directly does not apply defaultLayout.

Template syntax (contract v2 change): data.$body holds the result of the previous render. await data.$include(name, locals?) renders a registered partial without a layout; locals override the current data. The old data.body and data.include are plain data keys now. Data or locals with a top-level key starting with $ are refused with INVALID_DATA (since 0.4.0 every failure is a TemplateError: a DokebiError with code, plugin and fix). Each name is compiled once in setup. Missing layouts and definition cycles fail the start; unregistered names, partial recursion, more than 32 levels and calls after shutdown fail the render.

The default engine uses EJS 7.0.1 with strict mode and data locals. <%= escapes HTML and <%- prints raw output. Register only trusted templates: EJS is JavaScript, not a sandbox. File reads by the native include() are blocked; use the registered partial API. There is no file watching or dynamic loader while running.

The engine can be replaced with TemplateEngine.compile(source) => (data) => string | Promise<string>. No inheritance is needed. A replacement engine's escaping, security and resource policy are its author's responsibility. Create a new instance per Application. Return or await render Promises in HTTP handlers, or wait for them in the owning module's drain; the registry is cleaned up after the whole drain.

Install

dokebi add uses a static install declaration to create the entry file src/shared/views/index.ts (export const directory = new URL('./', import.meta.url);) only. Standard EJS app creation in CLI 0.4.9 adds the shared layout and its default setting. The generated code passes the entry module as entry, so every .ejs file under src/shared/views is registered.

The runnable example 22 verifies an API-only setup and EJS layouts and partials. Run the tests with npm test -w @dokebi/template-ejs.

Adding folders and template scopes (D-0030)

  • addDirectory(url, prefix): during setup, adds the templates below a folder under the names <prefix>/<relative path> (for example an app's views). Compilation happens once in the plugin's start, so render and reply work only after start and are refused before.
  • scope(prefixes): returns a renderer that looks up names in prefix order and finally without a prefix. The rendered name, the layouts (defaultLayout, defined layouts, options.layout) and $include all follow this order. Example: scope(['shop/christmas', 'shop/default']) finds files missing from christmas in default, then in the shared folder.
  • defaultLayout is valid when the name exists without a prefix or under any scope.

See the AI guide for error codes and the view engine contribution point.

Since 0.4.3, installing the engine creates only its entry. CLI 0.4.9 creates the shared default layout when an EJS app is chosen; Inertia and mail-only installations need no web layout. Existing templates and layout settings are preserved on upgrade.