mikel 0.18.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/index.d.ts +7 -0
- package/index.js +58 -42
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -380,7 +380,8 @@ Custom helper functions receive a single object as argument, containing the foll
|
|
|
380
380
|
|
|
381
381
|
- `args`: an array containing the variables with the helper is called in the template.
|
|
382
382
|
- `opt`: an object containing the keyword arguments provided to the helper.
|
|
383
|
-
- `
|
|
383
|
+
- `data`: the current data where the helper has been executed.
|
|
384
|
+
- `context` (**DEPRECATED**): the current context (data) where the helper has been executed.
|
|
384
385
|
- `fn`: a function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
|
|
385
386
|
|
|
386
387
|
The helper function must return a string, which will be injected into the result string. Example:
|
|
@@ -537,6 +538,20 @@ console.log(template({name: "Susan"})); // --> "Hello, Susan!"
|
|
|
537
538
|
|
|
538
539
|
It also exposes the following additional methods:
|
|
539
540
|
|
|
541
|
+
#### `template.use(options)`
|
|
542
|
+
|
|
543
|
+
> Added in `v0.19.0`.
|
|
544
|
+
|
|
545
|
+
Allows to extend the templating with custom **helpers**, **functions**, and **partials**.
|
|
546
|
+
|
|
547
|
+
```javascript
|
|
548
|
+
template.use({
|
|
549
|
+
partials: {
|
|
550
|
+
foo: "bar",
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
540
555
|
#### `template.addHelper(helperName, helperFn)`
|
|
541
556
|
|
|
542
557
|
Allows to register a new helper instead of using the `options` object.
|
package/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ declare type HelperFunction = (params: {
|
|
|
2
2
|
args: any[];
|
|
3
3
|
opt: Record<string, any>;
|
|
4
4
|
context: Record<string, any>;
|
|
5
|
+
data: Record<string, any>;
|
|
5
6
|
fn: (context?: Record<string, any>, vars?: Record<string, any>, output?: string[]) => string;
|
|
6
7
|
}) => string;
|
|
7
8
|
|
|
@@ -21,14 +22,20 @@ declare interface Functions {
|
|
|
21
22
|
}) => string | void;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
declare interface Variables {
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
declare interface MikelOptions {
|
|
25
30
|
helpers?: Helpers;
|
|
26
31
|
partials?: Partials;
|
|
27
32
|
functions?: Functions;
|
|
33
|
+
variables?: Variables;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
declare interface MikelTemplate {
|
|
31
37
|
(data?: any): string;
|
|
38
|
+
use(options: Partial<MikelOptions>): MikelTemplate;
|
|
32
39
|
addHelper(name: string, fn: HelperFunction): void;
|
|
33
40
|
removeHelper(name: string): void;
|
|
34
41
|
addFunction(name: string, fn: (params: any) => string | void): void;
|
package/index.js
CHANGED
|
@@ -17,22 +17,22 @@ const untokenize = (ts = [], s = "{{", e = "}}") => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// @description parse string arguments
|
|
20
|
-
const parseArgs = (argString = "",
|
|
20
|
+
const parseArgs = (argString = "", data = {}, vars = {}) => {
|
|
21
21
|
const [t, ...args] = argString.trim().match(/(?:[^\s"]+|"[^"]*")+/g);
|
|
22
|
-
const argv = args.filter(a => !a.includes("=")).map(a => parse(a,
|
|
22
|
+
const argv = args.filter(a => !a.includes("=")).map(a => parse(a, data, vars));
|
|
23
23
|
const opt = Object.fromEntries(args.filter(a => a.includes("=")).map(a => {
|
|
24
24
|
const [k, v] = a.split("=");
|
|
25
|
-
return [k, parse(v,
|
|
25
|
+
return [k, parse(v, data, vars)];
|
|
26
26
|
}));
|
|
27
27
|
return [t, argv, opt];
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
// @description parse a string value to a native type
|
|
31
|
-
const parse = (v,
|
|
31
|
+
const parse = (v, data = {}, vars = {}) => {
|
|
32
32
|
if ((v.startsWith(`"`) && v.endsWith(`"`)) || /^-?\d+\.?\d*$/.test(v) || v === "true" || v === "false" || v === "null") {
|
|
33
33
|
return JSON.parse(v);
|
|
34
34
|
}
|
|
35
|
-
return (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(
|
|
35
|
+
return (v || "").startsWith("@") ? get(vars, v.slice(1)) : get(data, v || ".");
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
// @description default helpers
|
|
@@ -44,35 +44,40 @@ const defaultHelpers = {
|
|
|
44
44
|
.map((item, index) => p.fn(item[1], {index: index, key: item[0], value: item[1], first: index === 0, last: index === items.length - 1}))
|
|
45
45
|
.join("");
|
|
46
46
|
},
|
|
47
|
-
"if": p => !!p.args[0] ? p.fn(p.
|
|
48
|
-
"unless": p => !!!p.args[0] ? p.fn(p.
|
|
49
|
-
"eq": p => p.args[0] === p.args[1] ? p.fn(p.
|
|
50
|
-
"ne": p => p.args[0] !== p.args[1] ? p.fn(p.
|
|
47
|
+
"if": p => !!p.args[0] ? p.fn(p.data) : "",
|
|
48
|
+
"unless": p => !!!p.args[0] ? p.fn(p.data) : "",
|
|
49
|
+
"eq": p => p.args[0] === p.args[1] ? p.fn(p.data) : "",
|
|
50
|
+
"ne": p => p.args[0] !== p.args[1] ? p.fn(p.data) : "",
|
|
51
51
|
"with": p => p.fn(p.args[0]),
|
|
52
|
-
"escape": p => escape(p.fn(p.
|
|
52
|
+
"escape": p => escape(p.fn(p.data)),
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
// @description create a new instance of mikel
|
|
56
56
|
const create = (template = "", options = {}) => {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const ctx = {
|
|
58
|
+
tokens: tokenize(template),
|
|
59
|
+
helpers: Object.assign({}, defaultHelpers, options?.helpers || {}),
|
|
60
|
+
partials: Object.assign({}, options?.partials || {}),
|
|
61
|
+
functions: options?.functions || {},
|
|
62
|
+
variables: {},
|
|
63
|
+
};
|
|
60
64
|
// internal method to compile the template
|
|
61
|
-
const compile = (tokens, output,
|
|
65
|
+
const compile = (tokens, output, data, vars, index = 0, section = "") => {
|
|
62
66
|
let i = index;
|
|
63
67
|
while (i < tokens.length) {
|
|
64
68
|
if (i % 2 === 0) {
|
|
65
69
|
output.push(tokens[i]);
|
|
66
70
|
}
|
|
67
|
-
else if (tokens[i].startsWith("#") && typeof helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
|
|
68
|
-
const [t, args, opt] = parseArgs(tokens[i].slice(1),
|
|
71
|
+
else if (tokens[i].startsWith("#") && typeof ctx.helpers[tokens[i].slice(1).trim().split(" ")[0]] === "function") {
|
|
72
|
+
const [t, args, opt] = parseArgs(tokens[i].slice(1), data, vars);
|
|
69
73
|
const j = i + 1;
|
|
70
|
-
output.push(helpers[t]({
|
|
74
|
+
output.push(ctx.helpers[t]({
|
|
71
75
|
args: args,
|
|
72
76
|
opt: opt,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
data: data,
|
|
78
|
+
context: data,
|
|
79
|
+
fn: (blockData = {}, blockVars = {}, blockOutput = []) => {
|
|
80
|
+
i = compile(tokens, blockOutput, blockData, {...vars, ...blockVars, root: vars.root}, j, t);
|
|
76
81
|
return blockOutput.join("");
|
|
77
82
|
},
|
|
78
83
|
}));
|
|
@@ -83,7 +88,7 @@ const create = (template = "", options = {}) => {
|
|
|
83
88
|
}
|
|
84
89
|
else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
|
|
85
90
|
const t = tokens[i].slice(1).trim();
|
|
86
|
-
const value = get(
|
|
91
|
+
const value = get(data, t);
|
|
87
92
|
const negate = tokens[i].startsWith("^");
|
|
88
93
|
if (!negate && value && Array.isArray(value)) {
|
|
89
94
|
const j = i + 1;
|
|
@@ -93,7 +98,7 @@ const create = (template = "", options = {}) => {
|
|
|
93
98
|
}
|
|
94
99
|
else {
|
|
95
100
|
const includeOutput = (!negate && !!value) || (negate && !!!value);
|
|
96
|
-
i = compile(tokens, includeOutput ? output : [],
|
|
101
|
+
i = compile(tokens, includeOutput ? output : [], data, vars, i + 1, t);
|
|
97
102
|
}
|
|
98
103
|
}
|
|
99
104
|
else if (tokens[i].startsWith("<")) {
|
|
@@ -101,31 +106,31 @@ const create = (template = "", options = {}) => {
|
|
|
101
106
|
const lastIndex = partialTokens.findIndex((token, j) => {
|
|
102
107
|
return j % 2 !== 0 && token.trim().startsWith("/") && token.trim().endsWith(t);
|
|
103
108
|
});
|
|
104
|
-
if (typeof partials[t] === "undefined") {
|
|
105
|
-
partials[t] = untokenize(partialTokens.slice(0, lastIndex));
|
|
109
|
+
if (typeof ctx.partials[t] === "undefined") {
|
|
110
|
+
ctx.partials[t] = untokenize(partialTokens.slice(0, lastIndex));
|
|
106
111
|
}
|
|
107
112
|
i = i + lastIndex + 1;
|
|
108
113
|
}
|
|
109
114
|
else if (tokens[i].startsWith(">")) {
|
|
110
|
-
const [t, args, opt] = parseArgs(tokens[i].replace(/^>{1,2}/, ""),
|
|
115
|
+
const [t, args, opt] = parseArgs(tokens[i].replace(/^>{1,2}/, ""), data, vars);
|
|
111
116
|
const blockContent = []; // to store partial block content
|
|
112
117
|
if (tokens[i].startsWith(">>")) {
|
|
113
|
-
i = compile(tokens, blockContent,
|
|
118
|
+
i = compile(tokens, blockContent, data, vars, i + 1, t);
|
|
114
119
|
}
|
|
115
|
-
if (typeof partials[t] === "string" || typeof partials[t]?.body === "string") {
|
|
116
|
-
const
|
|
120
|
+
if (typeof ctx.partials[t] === "string" || typeof ctx.partials[t]?.body === "string") {
|
|
121
|
+
const newData = args.length > 0 ? args[0] : (Object.keys(opt).length > 0 ? opt : data);
|
|
117
122
|
const newVars = {
|
|
118
123
|
...vars,
|
|
119
124
|
content: blockContent.join(""),
|
|
120
|
-
partial: partials[t]?.attributes || partials[t]?.data || {},
|
|
125
|
+
partial: ctx.partials[t]?.attributes || ctx.partials[t]?.data || {},
|
|
121
126
|
};
|
|
122
|
-
compile(tokenize(partials[t]?.body || partials[t]), output,
|
|
127
|
+
compile(tokenize(ctx.partials[t]?.body || ctx.partials[t]), output, newData, newVars, 0, "");
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
else if (tokens[i].startsWith("=")) {
|
|
126
|
-
const [t, args, opt] = parseArgs(tokens[i].slice(1),
|
|
127
|
-
if (typeof functions[t] === "function") {
|
|
128
|
-
output.push(functions[t]({args, opt,
|
|
131
|
+
const [t, args, opt] = parseArgs(tokens[i].slice(1), data, vars);
|
|
132
|
+
if (typeof ctx.functions[t] === "function") {
|
|
133
|
+
output.push(ctx.functions[t]({args, opt, data}) || "");
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
else if (tokens[i].startsWith("/")) {
|
|
@@ -138,10 +143,10 @@ const create = (template = "", options = {}) => {
|
|
|
138
143
|
const t = tokens[i].split("||").map(v => {
|
|
139
144
|
// check if the returned value should not be escaped
|
|
140
145
|
if (v.trim().startsWith("!")) {
|
|
141
|
-
return parse(v.trim().slice(1).trim(),
|
|
146
|
+
return parse(v.trim().slice(1).trim(), data, vars);
|
|
142
147
|
}
|
|
143
148
|
// escape the returned value
|
|
144
|
-
return escape(parse(v.trim(),
|
|
149
|
+
return escape(parse(v.trim(), data, vars));
|
|
145
150
|
});
|
|
146
151
|
output.push(t.find(v => !!v) ?? "");
|
|
147
152
|
}
|
|
@@ -151,17 +156,28 @@ const create = (template = "", options = {}) => {
|
|
|
151
156
|
};
|
|
152
157
|
// entry method to compile the template with the provided data object
|
|
153
158
|
const compileTemplate = (data = {}, output = []) => {
|
|
154
|
-
compile(
|
|
159
|
+
compile(ctx.tokens, output, data, {root: data, ...ctx.variables}, 0, "");
|
|
155
160
|
return output.join("");
|
|
156
161
|
};
|
|
157
162
|
// assign api methods and return method to compile the template
|
|
158
163
|
return Object.assign(compileTemplate, {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
use: newOptions => {
|
|
165
|
+
if (typeof newOptions === "function") {
|
|
166
|
+
newOptions(ctx);
|
|
167
|
+
}
|
|
168
|
+
else if (!!newOptions && typeof newOptions === "object") {
|
|
169
|
+
["helpers", "functions", "partials", "variables"].forEach(field => {
|
|
170
|
+
Object.assign(ctx[field], newOptions?.[field] || {});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return compileTemplate;
|
|
174
|
+
},
|
|
175
|
+
addHelper: (name, fn) => ctx.helpers[name] = fn,
|
|
176
|
+
removeHelper: name => delete ctx.helpers[name],
|
|
177
|
+
addFunction: (name, fn) => ctx.functions[name] = fn,
|
|
178
|
+
removeFunction: name => delete ctx.functions[name],
|
|
179
|
+
addPartial: (name, partial) => ctx.partials[name] = partial,
|
|
180
|
+
removePartial: name => delete ctx.partials[name],
|
|
165
181
|
});
|
|
166
182
|
};
|
|
167
183
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mikel",
|
|
3
3
|
"description": "Micro templating library with zero dependencies",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Josemi Juanes",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"release": "node ./scripts/release.js",
|
|
22
|
-
"test": "node test.js"
|
|
22
|
+
"test": "node test.js && yarn test:markdown",
|
|
23
|
+
"test:markdown": "node ./packages/mikel-markdown/test.js"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
25
26
|
"template",
|