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

jepy

v3.0.0

Published

Tiny, expandable and simple to use composition based JS template engine

Readme

About The Project

jepy is a tiny, flexible, easy-to-use template engine that does not require extra dependencies or precompilation. It features a basic syntax that supports placeholders and block statements, which should allow you to design complicated, reusable templates.

Here is what you get:

  • It is ~5KB
  • It doesn't require pre-building to use the templates and has no external dependencies
  • It supports parameter paths (for example "first-level.second-level.third-level..."), allowing you to refer to any value within your parameter object
  • It is indent-aware. Multi-line parameters will be indented to your placeholders, and Indented Blocks will be applied to multi-line block content
  • You can use block statements and partials to construct as complex templates as you like. For example, you may stitch together many templates with partials, add callback functions against the parameters, and create very complex conditional block expressions
  • You may either use a Template, directly use Block classes, or a combination of these
  • You can use the loop.index, loop.first, loop.last, loop.number, and loop.size parameters within your Repeating block, which can be quite handy
  • It has useful filters that you can use with param and partial placeholders (see filters section)

The things you don't get:

  • It definitely does not work with IE11 (it is based on classes) or any other ancient browser versions. Fortunately, these are mostly extinct
  • It might not work with really old Edge Legacy versions. These shouldn't be out in the wild anymore with the automatic updates, but possible if you didn't have any update from 2016

I do not consider this a disadvantage, but you may. Since Microsoft has already stopped supporting IE11 and the non-Chrome based Edge Legacy, I would prefer not to bother supporting these.

Install

Build your own, use the "dist" folder's prebuilt files or use one of the following options.

CDN

You may add jepy to your site using

npm

Run the following to add jepy to your project:

npm install jepy --save

Usage

You can create your templates with the following building blocks. If you want to see how this works, go visit the examples page

jepy.Template

The most powerful and adaptable building block available. This will fulfil most of your needs as a standalone logic and will cover what most template engines do. It could replace placeholders with a parameter value, and add blocks or partials to insert a text, Block or execute a function on the parameters to render your placeholder value. This supports parameter paths so you can use the following format to point to a value "first-level.second-level.third-level...".

jepy.Template with parameter value

This could be useful if you need to insert HTML into your template

const templateBlock = new jepy.Template('<div>%{text}</div>');
templateBlock.render({
    text: '<img src="test.jpg">',
});
// output: <div><img src="test.jpg"></div>

jepy.Template with partials

Partials can be used to add advanced fetures to your template. These could refer to a string, Block or a callback

const templateBlock = new jepy.Template(
    '%{@rawPartialString}%{@escapedPartialBlock|e}%{@rawPartialCallback}',
    {
        rawPartialString: '<img ',
        escapedPartialBlock: new jepy.Template('src="%{imageUrl}"'),
        rawPartialCallback: (params) => params.endChar,
    },
);
templateBlock.render({
    imageUrl: 'test.jpg',
    endChar: '>',
});
// output: <img src=&#34;test.jpg&#34;>

jepy.Template with Conditional Block against a parameter

This is your simple "if ... else ..." building block. It is useful to build a simple logic based on a parameter

const templateBlock = new jepy.Template(
    'Hello ?{firstName}%{firstName}?{!firstName}guest?{/firstName}!',
);
templateBlock.render({
    firstName: 'Adam',
});
// output: Hello Adam!
templateBlock.render({
    firstName: '',
});
// output: Hello guest!

jepy.Template with Conditional Block against a partial

This is your more advance "if ... else ..." building block with a custom criteria

const templateBlock = new jepy.Template('You are?{!@isSmith} not?{/@isSmith}? a Smith', {
    isSmith: (params) => params.lastName === 'Smith',
});
templateBlock.render({
    lastName: 'Smith',
});
// output: You are a Smith
templateBlock.render({
    lastName: 'Wolf',
});
// output: You are not a Smith

jepy.Template with simple Repeating Block

This is your "foreach ..." building block. You may use the loop.index, loop.first, loop.last, loop.number, and loop.size parameters inside this block to render or set criteria against it

const templateBlock = new jepy.Template(
    '#{items}#%{loop.number} - %{name} ?{!inStock}[Not in stock]?{/inStock}?{!loop.last}, ?{/loop.last}#{/items}',
);
templateBlock.render({
    items: [
        {
            name: 'pen',
            inStock: true,
        },
        {
            name: 'apple',
            inStock: false,
        },
    ],
});
// output: #1 - pen, #2 - apple [Not in stock]

jepy.Template with Repeating Block using an alias

This is your "foreach ... as ..." building block. It is useful when you have an array of items that you cannot refer by name

const templateBlock = new jepy.Template('#{items:item}%{item},#{/items}');
templateBlock.render({
    items: ['apple', 'pen'],
});
// output: apple,pen,

jepy.Template with Idented Block using spaces

const templateBlock = new jepy.Template('_{indentedBlock:1}%{text}_{/indentedBlock}');
templateBlock.render({
    text: 'space indented text',
});
// output: " space indented text"

jepy.Template with Idented Block using tabs

const templateBlock = new jepy.Template('>{indentedBlock:2}%{text}>{/indentedBlock}');
templateBlock.render({
    text: 'tab indented text',
});
// output: "\t\ttab indented text"

jepy.Template with Cached Block

const templateBlock = new jepy.Template(
    '={cachedBlock}%{text} ={/cachedBlock}={cachedBlock}={/cachedBlock}',
);
templateBlock.render({
    text: 'cached text',
});
// output: cached text cached text

jepy.Template filters

These work with both parameters and partials.

Generic filters

  • %{name|stringify} is used to JSON.stingify your placeholder value

String filters

  • %{name|lower} is used to lower case your placeholder value
  • %{name|upper} is used to upper case your placeholder value
  • %{name|capitalize} is used to capitalise your placeholder value
  • %{name|trim} is used to trim your placeholder value
  • %{name|esc} or %{name|e} is used to escape your placeholder value

Number filters

  • %{name|abs} is used get the absolute value of a placeholder value
  • %{name|round} is used get the rounded value of a placeholder value
  • %{name|floor} is used get the rounded down value of a placeholder value
  • %{name|ceil} is used get the rounded up value of a placeholder value

Array filters

  • %{name|first} is used get the first element of an array placeholder value
  • %{name|last} is used get the last element of an array placeholder value
  • %{name|min} is used get the min element of an array placeholder value
  • %{name|max} is used get the max element of an array placeholder value

jepy.Simple

This is the most basic building block, with no performance implications. The text you specified will be returned. Nothing flashy, but practical for jepy.Conditional and jepy.Composite, because these require a Block to render. This may be replaced with jepy.Template, although with a performance impact.

const simpleBlock = new jepy.Simple('<div>Hello World</div>');
simpleBlock.render();
// output: <div>Hello World</div>

jepy.Conditional

This is your "if ... else ..." building block. It needs a function to check the condition on the params, a Block when the condition is true, and an optional Block when the condition is false. This condition function can be as simple or complicated as you want, so it should meet all your needs.

// without optional "else"
const conditionalBlock = new jepy.Conditional(
    (params) => params.who !== undefined,
    new jepy.Template('<div>Hello %{who}</div>'),
);
conditionalBlock.render();
// output:

conditionalBlock.render({
    who: 'World',
});
// output: <div>Hello World</div>

// with "else"
const conditionalBlock = new jepy.Conditional(
    (params) => params.who !== undefined,
    new jepy.Template('<div>Hello %{who}</div>'),
    new jepy.Simple("<div>Sorry, I don't have your name</div>"),
);
conditionalBlock.render();
// output: <div>Sorry, I don\'t have your name</div>

conditionalBlock.render({
    who: 'Adam',
});
// output: <div>Hello Adam</div>

jepy.Repeating

This is your "foreach ..." building block. This needs a path (same format as the placeholders) to an array parameter, a Block to render the values of the array and an optional function to modify or add parameters. By default only the item parameters will be passed to the Block, so you may need to add this optional function to pass other parameters

// without parameter modifier function
const repeatingBlock = new jepy.Repeating('items', new jepy.Template('<div>#%{id} %{name}</div>'));
repeatingBlock.render({
    items: [
        {
            id: 1,
            name: 'first',
        },
        {
            id: 2,
            name: 'second',
        },
    ],
});
// output: <div>#1 first</div><div>#2 second</div>

// with parameter modifier
const repeatingBlock = new jepy.Repeating(
    'items',
    new jepy.Template('<div>#%{id} %{colour} %{name}</div>'),
    (item, params) => {
        item.name = params.itemName;
        return item;
    },
);
repeatingBlock.render({
    itemName: 'pencil',
    items: [
        {
            id: 1,
            colour: 'green',
        },
        {
            id: 2,
            colour: 'red',
        },
    ],
});
// output: <div>#1 green pencil</div><div>#2 red pencil</div>

jepy.Composite

This is used to stich together multiple Blocks into one. You can use this to make complex and extendable templates

const compositeBlock = new jepy.Composite([
    new jepy.Simple('<div>'),
    new jepy.Template('<div>Hello %{who}</div>'),
    new jepy.Repeating('items', new jepy.Template('<div>#%{id} %{name}</div>')),
    new jepy.Simple('</div>'),
]);
compositeBlock.render({
    who: 'World',
    items: [
        {
            id: 1,
            name: 'first',
        },
        {
            id: 2,
            name: 'second',
        },
    ],
});
// output: <div><div>Hello World</div><div>#1 first</div><div>#2 second</div></div>

jepy.Callback

Use this if you want something with complicated logic that is also self-contained. This will return the text produced by the callback function that was passed on initialisation.

const callbackBlock = new jepy.Callback((params) => {
    const itemCount = params.items.length;
    const singularOrPlural = (noun, counter) => (counter > 1 ? noun + 's' : noun);
    const basketBlock = new jepy.Template(
        '<div>You have %{itemCount} %{itemText} in your basket</div>',
    );
    return basketBlock.render({
        itemCount: itemCount,
        itemText: singularOrPlural('item', itemCount),
    });
});
callbackBlock.render({
    items: ['pineapple', 'pen'],
});
// output: <div>You have 2 items in your basket</div>

jepy.Cached

It will return the cached value of a Block on subsequent render requests to boost the performance. You can validate against this cached value with the optional validation callback in case the value need to be updated on param changes. Without the validation callback the cached value will never update for that run session. This could be useful if you cache something that should be static. Do not cache a block that only rendered once as it won't give you any advantage.

const cachedBlock = new jepy.Cached(
    new jepy.Composite([
        new jepy.Callback((params) =>
            params.name === undefined ? '' : 'Hello ' + params.name + '.',
        ),
        new jepy.Simple('This is a test'),
    ]),
    (params, block) => {
        const isValid = block.name === params.name;
        if (!isValid) {
            block.name = params.name;
        }
        return isValid;
    },
);
const templateParams = {
    name: 'Adam',
};
cachedBlock.render(templateParams);
// output (non-cached, rendered in runtime): Hello Adam. This is a test
cachedBlock.render(templateParams);
// output (returned from cache): Hello Adam. This is a test
cachedBlock.render();
// output (non-cached, rendered in runtime): This is a test
cachedBlock.render();
// output (returned from cache): This is a test

jepy.Indented

This can be used to indent a single or multi-line Block.

const compositeBlock = new jepy.Composite([
    new jepy.Simple('<div>\n'),
    new jepy.Indented(
        new jepy.Template('<div>\n    %{name}\n</div>')
        jepy.IndentType.SPACE,
        4
    ),
    new jepy.Simple('\n</div>'),
]);
const templateParams = {
    name: 'Adam'
};
compositeBlock.render(templateParams);
/**
 * output:
 * <div>
 *     <div>
 *         Adam
 *     </div>
 * </div>
 */

Roadmap

  • [x] Add basic building blocks and Block interface for custom classes
  • [x] Improve the "Usage" part of this README
  • [x] Add optional parser to generate and cache blocks based on a simple template format
  • [x] Add special parameters like "loop.first" and "loop.last" that could be used inside a Repeating block in jepy.Template
  • [x] Add the "else" tag to Conditional blocks in jepy.Template to make it more readable and lean
  • [x] Add an option for validation partial to the Cached blocks in jepy.Template
  • [x] Add parameter and partial filters to jepy.Template
  • [ ] Replace readme description with detailed documentation page that has a sandbox and some examples

See the open issues for a full list of proposed features (and known issues).

Contributing

Your support is greatly appreciated! If you have ideas for enhancements, please fork the repository and submit a pull request to the "development" branch. Remember to execute "npm run build" before committing to ensure that everything is still working! You can alternatively create a new issue with the tag "enhancement". Don't forget to star the project! Thank you once more!

License

Distributed under the MIT License. See LICENSE for more information.

Contact

Sandor - [email protected]