Skip to content

Commit 04f498b

Browse files
authored
Merge pull request #12 from cho45/compile
returns compiled function with keys of stash
2 parents 10998f5 + 8bdbef9 commit 04f498b

File tree

4 files changed

+64
-40
lines changed

4 files changed

+64
-40
lines changed

README.md

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,16 @@ SYNTAX
8383
DESCRIPTION
8484
-----------
8585

86-
### template(id or source , data)
86+
### template(id or source , data | array )
8787

8888
If the first argument of template matches `/^[\w\-]+$/`, it is treated as `id` of template. In this case, use `document.getElementById(id).innerHTML` to get source.
8989

9090
Otherwise, the first argument is treated as source directly.
9191

92+
If the second argument is an **Array**, it is treated as a list of property names for the data object. In this case, the template function will be compiled with these property names as its local variables, and the compiled function itself will be returned (not executed). This allows you to precompile a template for repeated use with the same set of variable names.
93+
94+
If the second argument is an **Object**, the template will be rendered immediately using the object's properties as local variables.
95+
9296

9397
CUSTOM `get` FUNCTION
9498
---------------------
@@ -107,16 +111,19 @@ DEFINE DATA VARIABLE SCOPE
107111

108112
micro-template now always expands data variables as local variables in the template function. The template API only supports two arguments: the template source/id and the data object. All keys of the data object are available as local variables in the template code.
109113

114+
If the second argument is an **Array**, it is treated as a list of property names for the data object. In this case, the template function will be compiled with these property names as its local variables, and the function itself will be returned (not executed). This allows you to precompile a template for repeated use with the same set of variable names.
115+
110116
For example:
111117

112118
```js
113-
const result = template('aaa <% foo %> bbb', { foo: 'bar' });
119+
const render = template('aaa <% foo %> bbb', ['foo']);
120+
const result = render({ foo: 'bar' });
114121
// result: 'aaa bar bbb'
115122
```
116123

117124
You can access all properties of the data object directly as variables inside the template.
118125

119-
**Note:** The previous API that allowed calling `template(tmpl)` to return a function is removed. Always use the two-argument form: `template(tmpl, data)`.
126+
**Note:** The previous API that allowed calling `template(tmpl)` to return a function is removed. Always use the two-argument form: `template(tmpl, data)` or `template(tmpl, [prop1, prop2, ...])`.
120127

121128
EXTENDED FEATURES
122129
-----------------
@@ -169,7 +176,7 @@ BENCHMARK
169176
---------
170177

171178
online:
172-
[benchmark on perf.link](https://perf.link/#eyJpZCI6IjdyMGN5dHJzazdvIiwidGl0bGUiOiJtaWNyby10ZW1wbGF0ZS5qcyB2cyBlanMiLCJiZWZvcmUiOiJpbXBvcnQgeyBleHRlbmRlZCBhcyB0ZW1wbGF0ZSB9IGZyb20gXCJodHRwczovL2NobzQ1LmdpdGh1Yi5pby9taWNyby10ZW1wbGF0ZS5qcy9saWIvbWljcm8tdGVtcGxhdGUuanNcIjtcbmltcG9ydCBlanMgZnJvbSAnaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9lanMvZWpzLmpzLytlc20nO1xuXG5jb25zdCBURU1QTEFURSA9IGBcbjwlIGZvciAodmFyIGkgPSAxOyBpIDw9IG47IGkrKykgeyAlPlxuXHQ8JT0gIGkgKyAnID0nICU%2BXG5cdDwlIGlmIChpICUgMTUgPT0gMCkgeyAlPlxuXHRGaXp6QnV6elxuXHQ8JSB9IGVsc2UgeyAlPlxuXHRcdDwlIGlmIChpICUgMyA9PSAwKSB7ICU%2BXG5cdFx0Rml6elxuXHRcdDwlIH0gZWxzZSB7ICU%2BXG5cdFx0XHQ8JSBpZiAoaSAlIDUgPT0gMCkgeyAlPlxuXHRcdFx0QnV6elxuXHRcdFx0PCUgfSBlbHNlIHsgJT5cblx0XHRcdDwlPSBpICU%2BXG5cdFx0XHQ8JSB9ICU%2BXG5cdFx0PCUgfSAlPlxuXHQ8JSB9ICU%2BXG5cdC9cbjwlIH0gJT5cbmAudHJpbSgpO1xuXG5jb25zdCBjb21waWxlZEVqcyA9IGVqcy5jb21waWxlKFRFTVBMQVRFKTsiLCJ0ZXN0cyI6W3sibmFtZSI6Im1pY3JvLXRlbXBsYXRlLmpzIiwiY29kZSI6InRlbXBsYXRlKFRFTVBMQVRFLCB7IG4gOiAxMDAwIH0pOyIsInJ1bnMiOlsxMjQ1NCwxMTYzNiwxNDAwMCwxMzM2MywxMzQ1NCwxMjgxOCwxMjgxOCwxMjU0NSwxMjcyNywxMzE4MSwxMzAwMCwxMjE4MSwxMjM2MywxMjU0NSwxMjAwMCwxMTM2MywxMzI3MiwxMzQ1NCwxMjgxOCwxMjE4MSwxMjM2MywxMzgxOCwxMjcyNywxMjQ1NCwxMzAwMCwxMzgxOCwxNDM2MywxMzAwMCwxMjYzNiwxMDkwOSwxMzQ1NCwxMTU0NSwxMzA5MCwxMjgxOCwxMTcyNywxMTgxOCwxMjkwOSwxMTE4MSwxMjcyNywxMzI3MiwxMjM2MywxMTU0NSwxMzU0NSwxMzI3MiwxNDA5MCwxMzYzNiwxMjQ1NCwxMzU0NSwxMjkwOSwxMjU0NSwxMzAwMCwxMjAwMCwxMjQ1NCwxMjgxOCwxMzM2MywxMjM2MywxMjI3MiwxMjkwOSwxMjgxOCwxMzkwOSwxMzA5MCwxMjI3MiwxMzQ1NCwxMzQ1NCwxMjI3MiwxMzE4MSwxMzYzNiwxMjQ1NCwxMjU0NSwxMjA5MCwxMzA5MCwxMTgxOCwxMzM2MywxMjA5MCwxMjYzNiwxMjQ1NCwxMzYzNiwxMDQ1NCwxMTM2MywxMTYzNiwxMDgxOCwxMjAwMCwxMzkwOSwxMjcyNywxMjQ1NCwxMTgxOCwxMzI3MiwxMjU0NSwxMjcyNywxMzQ1NCwxMjI3MiwxMTAwMCwxMjcyNywxMjQ1NCwxMjgxOCwxMjYzNiwxMzYzNiwxMjM2MywxMjgxOCwxNDE4MV0sIm9wcyI6MTI2OTJ9LHsibmFtZSI6ImVqcyIsImNvZGUiOiJjb21waWxlZEVqcyh7IG4gOiAxMDAwIH0pOyIsInJ1bnMiOls5MDksOTA5LDkwOSw4MTgsOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsOTA5LDkwOSw4MTgsOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksODE4LDgxOCw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsODE4LDgxOCw3MjcsNzI3LDkwOSw5MDksOTA5LDkwOSw5MDksODE4LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksODE4LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksODE4LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsOTA5LDgxOCw4MTgsOTA5LDgxOCw4MTgsOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsOTA5LDkwOSw4MTgsOTA5LDkwOSw5MDksODE4LDkwOSw5MDksOTA5LDgxOCw5MDldLCJvcHMiOjg4N31dLCJ1cGRhdGVkIjoiMjAyNS0wNi0xMFQxMToyMzoyMS43MTlaIn0%3D )
179+
[benchmark on perf.link](https://perf.link/#eyJpZCI6IjdyMGN5dHJzazdvIiwidGl0bGUiOiJtaWNyby10ZW1wbGF0ZS5qcyB2cyBlanMiLCJiZWZvcmUiOiJpbXBvcnQgeyBleHRlbmRlZCBhcyB0ZW1wbGF0ZSB9IGZyb20gXCJodHRwczovL2NobzQ1LmdpdGh1Yi5pby9taWNyby10ZW1wbGF0ZS5qcy9saWIvbWljcm8tdGVtcGxhdGUuanNcIjtcbmltcG9ydCBlanMgZnJvbSAnaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9lanMvZWpzLmpzLytlc20nO1xuXG5jb25zdCBURU1QTEFURSA9IGBcbjwlIGZvciAodmFyIGkgPSAxOyBpIDw9IG47IGkrKykgeyAlPlxuXHQ8JT0gIGkgKyAnID0nICU%2BXG5cdDwlIGlmIChpICUgMTUgPT0gMCkgeyAlPlxuXHRGaXp6QnV6elxuXHQ8JSB9IGVsc2UgeyAlPlxuXHRcdDwlIGlmIChpICUgMyA9PSAwKSB7ICU%2BXG5cdFx0Rml6elxuXHRcdDwlIH0gZWxzZSB7ICU%2BXG5cdFx0XHQ8JSBpZiAoaSAlIDUgPT0gMCkgeyAlPlxuXHRcdFx0QnV6elxuXHRcdFx0PCUgfSBlbHNlIHsgJT5cblx0XHRcdDwlPSBpICU%2BXG5cdFx0XHQ8JSB9ICU%2BXG5cdFx0PCUgfSAlPlxuXHQ8JSB9ICU%2BXG5cdC9cbjwlIH0gJT5cbmAudHJpbSgpO1xuXG5jb25zdCBjb21waWxlZEVqcyA9IGVqcy5jb21waWxlKFRFTVBMQVRFKTsiLCJ0ZXN0cyI6W3sibmFtZSI6Im1pY3JvLXRlbXBsYXRlLmpzIiwiY29kZSI6InRlbXBsYXRlKFRFTVBMQVRFLCB7IG4gOiAxMDAwIH0pOyIsInJ1bnMiOlsxMjQ1NCwxMTYzNiwxNDAwMCwxMzM2MywxMzQ1NCwxMjgxOCwxMjgxOCwxMjU0NSwxMjcyNywxMzE4MSwxMzAwMCwxMjE4MSwxMjM2MywxMjU0NSwxMjAwMCwxMTM2MywxMzI3MiwxMzQ1NCwxMjgxOCwxMjE4MSwxMjM2MywxMzgxOCwxMjcyNywxMjQ1NCwxMzAwMCwxMzgxOCwxNDM2MywxMzAwMCwxMjYzNiwxMDkwOSwxMzQ1NCwxMTU0NSwxMzA5MCwxMjgxOCwxMTcyNywxMTgxOCwxMjkwOSwxMTE4MSwxMjcyNywxMzI3MiwxMjM2MywxMTU0NSwxMzU0NSwxMzI3MiwxNDA5MCwxMzYzNiwxMjQ1NCwxMzU0NSwxMjkwOSwxMjU0NSwxMzAwMCwxMjAwMCwxMjQ1NCwxMjgxOCwxMzM2MywxMjM2MywxMjI3MiwxMjkwOSwxMjgxOCwxMzkwOSwxMzA5MCwxMjI3MiwxMzQ1NCwxMzQ1NCwxMjI3MiwxMzE4MSwxMzYzNiwxMjQ1NCwxMjU0NSwxMjA5MCwxMzA5MCwxMTgxOCwxMzM2MywxMjA5MCwxMjYzNiwxMjQ1NCwxMzYzNiwxMDQ1NCwxMTM2MywxMTYzNiwxMDgxOCwxMjAwMCwxMzkwOSwxMjcyNywxMjQ1NCwxMTgxOCwxMzI3MiwxMjU0NSwxMjcyNywxMzQ1NCwxMjI3MiwxMTAwMCwxMjcyNywxMjQ1NCwxMjgxOCwxMjYzNiwxMzYzNiwxMjM2MywxMjgxOCwxNDE4MV0sIm9wcyI6MTI2OTJ9LHsibmFtZSI6ImVqcyIsImNvZGUiOiJjb21waWxlZEVqcyh7IG4gOiAxMDAwIH0pOyIsInJ1bnMiOls5MDksOTA5LDkwOSw4MTgsOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsOTA5LDkwOSw4MTgsOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksODE4LDgxOCw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsODE4LDgxOCw3MjcsNzI3LDkwOSw5MDksOTA5LDkwOSw5MDksODE4LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsOTA5LDgxOCw4MTgsOTA5LDgxOCw4MTgsOTA5LDkwOSw5MDksOTA5LDkwOSw4MTgsOTA5LDkwOSw4MTgsOTA5LDkwOSw5MDksODE4LDkwOSw5MDksOTA5LDgxOCw5MDldLCJvcHMiOjg4N31dLCJ1cGRhdGVkIjoiMjAyNS0wNi0xMFQxMToyMzoyMS43MTlaIn0%3D )
173180

174181
node:
175182

@@ -178,46 +185,53 @@ node:
178185
```
179186
> node --expose-gc ./misc/benchmark.js
180187
181-
clk: ~3.04 GHz
188+
1 = 1 / 2 = 2 / 3 = Fizz / 4 = 4 / 5 = Buzz / 6 = Fizz / 7 = 7 / 8 = 8 / 9 = Fizz / 10 = Buzz / 11 = 11 / 12 = Fizz / 13 = 13 / 14 = 14 / 15 = FizzBuzz / 16 = 16 / 17 = 17 / 18 = Fizz / 19 = 19 / 20 = Buzz / 21 = Fizz / 22 = 22 / 23 = 23 / 24 = Fizz / 25 = Buzz / 26 = 26 / 27 = Fizz / 28 = 28 / 29 = 29 / 30 = FizzBuzz /
189+
clk: ~3.01 GHz
182190
cpu: Apple M1
183191
runtime: node 20.10.0 (arm64-darwin)
184192
185193
benchmark avg (min … max) p75 / p99 (min top 1%)
186194
------------------------------------------------- -------------------------------
187-
micro-template 24.53 µs/iter 23.04 µs
188-
(21.83 µs … 243.04 µs) 65.54 µs
189-
( 80.00 b … 341.16 kb) 146.07 kb ██▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
195+
micro-template 21.13 µs/iter 19.88 µs ▇
196+
(18.79 µs … 343.21 µs) 49.29 µs
197+
( 12.35 kb … 365.21 kb) 108.72 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
198+
199+
micro-template pre compiled 20.22 µs/iter 19.98 µs
200+
(19.76 µs … 22.35 µs) 21.46 µs
201+
( 1.92 kb … 1.94 kb) 1.92 kb ▅█▅█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅
190202
191-
micro-template (not cached) 32.47 µs/iter 30.17 µs
192-
(28.75 µs … 333.62 µs) 93.33 µs
193-
( 3.81 kb … 606.79 kb) 159.79 kb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
203+
micro-template (not cached) 29.17 µs/iter 27.50 µs
204+
(26.21 µs … 311.92 µs) 58.13 µs
205+
( 2.14 kb … 378.82 kb) 123.25 kb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
194206
195-
micro-template (template.variable) 24.58 µs/iter 23.13 µs
196-
(21.92 µs … 216.00 µs) 65.12 µs
197-
( 22.73 kb … 497.89 kb) 146.28 kb ██▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
207+
micro-template (template.variable) 20.91 µs/iter 20.00 µs
208+
(19.12 µs … 219.87 µs) 30.67 µs
209+
( 2.98 kb … 492.50 kb) 108.87 kb ▁█▇▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
198210
199-
ejs.render pre compiled 348.73 µs/iter 352.79 µs
200-
(332.54 µs … 574.04 µs) 393.13 µs
201-
( 5.10 kb … 613.10 kb) 72.51 kb ▂▂███▆▅▄▄▄▃▄▃▂▂▂▂▂▁▁▁
211+
ejs.render pre compiled 430.88 µs/iter 432.54 µs
212+
(421.38 µs … 642.92 µs) 477.00 µs
213+
( 18.84 kb … 830.84 kb) 99.63 kb ███▇▆▄▃▃▂▂▂▂▂▁▂▁▁▁▁▁▁
202214
203-
John Resig's tmpl 226.28 µs/iter 231.58 µs
204-
(214.42 µs … 436.08 µs) 268.42 µs
205-
( 18.88 kb … 442.88 kb) 101.19 kb ████▆▄▄▅▆▅▄▄▃▂▂▂▁▁▁▁▁
215+
John Resig's tmpl 239.00 µs/iter 238.17 µs
216+
(232.62 µs … 424.21 µs) 274.75 µs
217+
( 33.55 kb … 470.89 kb) 126.63 kb ███▄▂▃▂▂▂▁▁▁▁▁▁▁▁▁▁
206218
207219
┌ ┐
208-
micro-template ┤ 24.53 s
209-
micro-template (not cached) ┤■ 32.47sµ
210-
micro-template (template.variable) ┤ 24.58 s
211-
ejs.render pre compiled ┤■■■■■■■■■■■■■■■■■■■■■s ■■■■■■■■■■■■ 348.73 µ
212-
John Resig's tmpl ┤■■■■■■■■■■■■■■ s■■■■■■ 226.28 µ
220+
micro-template ┤ 21.13 s
221+
micro-template pre compiled ┤ 20.22 s
222+
micro-template (not cached) ┤■ 29.17sµ
223+
micro-template (template.variable) ┤ 20.91 s
224+
ejs.render pre compiled ┤■■■■■■■■■■■■■■■■■■■■■s ■■■■■■■■■■■■ 430.88 µ
225+
John Resig's tmpl ┤■■■■■■■■■■■■■s ■■■■ 239.00 µ
213226
└ ┘
214227
215228
summary
216-
micro-template
217-
1x faster than micro-template (template.variable)
218-
1.32x faster than micro-template (not cached)
219-
9.22x faster than John Resig's tmpl
220-
14.22x faster than ejs.render pre compiled
229+
micro-template pre compiled
230+
1.03x faster than micro-template (template.variable)
231+
1.05x faster than micro-template
232+
1.44x faster than micro-template (not cached)
233+
11.82x faster than John Resig's tmpl
234+
21.31x faster than ejs.render pre compiled
221235
```
222236

223237
LICENSE

lib/micro-template.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
const template = function (id, data) {
66
if (arguments.length < 2) throw new Error('template() must be called with (template, data)');
7-
const me = template, keys = Object.keys(data || {}), key = `data:${id}:${keys.join(':')}`;
7+
const me = template, isArray = Array.isArray(data), keys = isArray ? data : Object.keys(data || {}), key = `data:${id}:${keys.sort().join(':')}`;
88
if (!me.cache.has(key)) me.cache.set(key, (function () {
99
let name = id, string = /^[\w\-]+$/.test(id) ? me.get(id): (name = 'template(string)', id); // no warnings
1010
let line = 1;
@@ -27,9 +27,9 @@ const template = function (id, data) {
2727
`//@ sourceURL=template.js\n`
2828
);
2929
const func = new Function("__this", ...keys, body);
30-
return function (stash, ...args) { return func.call(null, me.context = { escapeHTML: me.escapeHTML, line: 1, ret : '', stash: stash }, ...args) };
30+
return function (stash) { return func.call(null, me.context = { escapeHTML: me.escapeHTML, line: 1, ret : '', stash: stash }, ...keys.map(key => stash[key])) };
3131
})());
32-
return me.cache.get(key)(data, ...keys.map(key => data[key]));
32+
return isArray ? me.cache.get(key) : me.cache.get(key)(data);
3333
}
3434
template.cache = new Map();
3535
template.get = function (id) { return document.getElementById(id).innerHTML };

misc/benchmark.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,27 @@ const fizzbuzz = `
6565
`.trim();
6666
const fizzbuzzVar = fizzbuzz.replace(/^/, '<% var n = stash.n; %>');
6767
const ejsFunc = ejs.compile(fizzbuzz);
68+
const compiled = template(fizzbuzz, ['n']);
6869

6970
const output1 = template(fizzbuzz, {n: 30}).replace(/\s+/g, ' ');
7071
const output2 = ejsFunc({n: 30 }).replace(/\s+/g, ' ');
7172
template.variable = 'stash';
7273
const output3 = template(fizzbuzzVar, {n: 30}).replace(/\s+/g, ' ');
74+
const output4 = compiled({ n: 30 }).replace(/\s+/g, ' ');
7375
console.log(output1);
7476
assert.equal(output1, output2, 'output should be same');
7577
assert.equal(output1, output3, 'output should be same');
78+
assert.equal(output1, output4, 'output should be same');
7679

7780
barplot(() => {
7881
summary(() => {
7982
bench('micro-template', () => {
8083
template.variable = null;
8184
template(fizzbuzz, { n: 300 });
8285
});
86+
bench('micro-template pre compiled', () => {
87+
compiled({ n: 300 });
88+
});
8389
bench('micro-template (not cached)', () => {
8490
template.variable = null;
8591
template.cache.clear();

test/test.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ test('template renders with data', (t) => {
1515
assert.strictEqual(result, '<b>foo</b><i>bar</i>');
1616
});
1717

18+
test('template pre-compiled func', (t) => {
19+
const stash = { foo: 'foo', bar: 'bar' };
20+
const result = template('<b><%= foo %></b><i><%= bar %></i>', Object.keys(stash))( stash );
21+
assert.strictEqual(result, '<b>foo</b><i>bar</i>');
22+
});
23+
1824
test('template renders static html (single/double quote)', (t) => {
1925
assert.strictEqual(template("<a href='foo'>foo</a>", {}), "<a href='foo'>foo</a>");
2026
assert.strictEqual(template('<a href="foo">foo</a>', {}), '<a href="foo">foo</a>');
@@ -245,12 +251,10 @@ test('template with ES6 syntax', (t) => {
245251
assert.strictEqual(template('<% let x = 1; const y = 2; %><%= x + y %>', {}), '3');
246252
});
247253

248-
test('template with consecutive script tags', (t) => {
249-
let called = [];
250-
template('<% foo %><% bar %>', {
251-
get foo() { called.push('foo'); },
252-
get bar() { called.push('bar'); }
254+
test('template with properties script tags', (t) => {
255+
const result = template('<%= foo %><%= bar %>', {
256+
get foo() { return 'foo' },
257+
get bar() { return 'bar' }
253258
});
254-
assert.deepStrictEqual(called, ['foo', 'bar']);
255-
assert.strictEqual(template('<%= foo %><%= bar %>', { foo: 'foo', bar: 'bar' }), 'foobar');
259+
assert.strictEqual(result, 'foobar');
256260
});

0 commit comments

Comments
 (0)