jepy
v3.0.0
Published
Tiny, expandable and simple to use composition based JS template engine
Maintainers
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, andloop.sizeparameters 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 --saveUsage
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="test.jpg">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 Smithjepy.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 textjepy.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 testjepy.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]
