mikel 0.33.0 → 0.34.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.
Files changed (4) hide show
  1. package/README.md +116 -7
  2. package/index.d.ts +25 -4
  3. package/index.js +48 -6
  4. package/package.json +3 -2
package/README.md CHANGED
@@ -25,11 +25,41 @@ Mikel supports the following syntax for rendering templates:
25
25
 
26
26
  Use double curly braces `{{ }}` to insert variables into your template. Variables will be replaced with the corresponding values from the data object.
27
27
 
28
+ ```javascript
29
+ const result = m(`Hello {{name}}!`, { name: "World" });
30
+ // Output: 'Hello World!'
31
+ ```
32
+
33
+ #### Nested values
34
+
35
+ You can access nested properties of an object using dot notation.
36
+
37
+ ```javascript
38
+ const result = m(`Hello {{user.name}}!`, {
39
+ user: { name: "John" },
40
+ });
41
+ // Output: 'Hello John!'
42
+ ```
43
+
44
+ #### Array values
45
+
46
+ You can access a specific element of an array using its index in dot notation.
47
+
48
+ ```javascript
49
+ const result = m(`Hello {{users.0.name}}!`, {
50
+ users: [
51
+ { name: "John" },
52
+ { name: "Alice" },
53
+ ],
54
+ });
55
+ // Output: 'Hello John!'
56
+ ```
57
+
28
58
  #### Fallback values
29
59
 
30
60
  > Added in `v0.14.0`.
31
61
 
32
- You can specify a value as a fallback, using the double OR `||` operator and followed by the fallback value.
62
+ You can specify a fallback value using the `||` operator. This value will be used when the variable is not defined or is empty.
33
63
 
34
64
  ```javascript
35
65
  const result = m(`Hello {{name || "World"}}!`, {});
@@ -700,9 +730,6 @@ const result = m("Users: {{=fullName ...user1}} and {{=fullName ...user2}}", dat
700
730
  console.log(result); // --> "Users: John Doe and Alice Smith"
701
731
  ```
702
732
 
703
- Of course, Jose — here’s a version of the **Subexpressions** documentation written to perfectly match the tone, structure, and formatting conventions of the current README.
704
- It follows the same patterns: short intro, version note, examples, concise explanations, no extra fluff.
705
-
706
733
  ### Subexpressions
707
734
 
708
735
  > Added in `v0.30.0`.
@@ -814,15 +841,31 @@ It also exposes the following additional methods:
814
841
 
815
842
  > Added in `v0.19.0`.
816
843
 
817
- Allows to extend the templating with custom **helpers**, **functions**, and **partials**.
818
-
844
+ Extends the instance with additional helpers, functions, partials, or hooks. Accepts either an options object or a plugin function.
845
+
846
+ When called with an **object**, it registers the provided helpers, functions, and partials directly:
847
+
819
848
  ```javascript
820
849
  mk.use({
850
+ helpers: {
851
+ uppercase: ({ fn, data }) => fn(data).toUpperCase(),
852
+ },
821
853
  partials: {
822
- foo: "bar",
854
+ foo: "Hello {{name}}!",
823
855
  },
824
856
  });
825
857
  ```
858
+
859
+ When called with a **function**, the function receives the internal context of the instance, giving full access to the hooks system and all registered helpers, functions, and partials. This is the recommended approach for writing reusable plugins:
860
+
861
+ ```javascript
862
+ const myPlugin = (ctx) => {
863
+ ctx.hooks.add("preRender", template => template.trim());
864
+ ctx.hooks.add("postRender", output => output.trim());
865
+ };
866
+
867
+ mk.use(myPlugin);
868
+ ```
826
869
 
827
870
  #### `mk.addHelper(helperName, helperFn)`
828
871
 
@@ -880,6 +923,72 @@ This function converts special HTML characters `&`, `<`, `>`, `"`, and `'` to th
880
923
 
881
924
  This function returns the value in `object` following the provided `path` string.
882
925
 
926
+ ## Advanced
927
+
928
+ ### Built‑in Plugins
929
+
930
+ > Added in `v0.34.0`.
931
+
932
+ Mikel includes a small set of built‑in plugins that provide common functionality without requiring additional packages. These plugins integrate directly into the compilation lifecycle and use the same hook system available to any external plugin.
933
+
934
+ #### `mikel.WrapperPlugin(options)`
935
+
936
+ Wraps the original template by inserting custom text before and/or after it. The wrapper is applied before rendering, which means it can contain mikel expressions (`{{variables}}`, helpers, etc.).
937
+
938
+ ```javascript
939
+ mk.use(mikel.WrapperPlugin({
940
+ header: "<!-- START -->\n",
941
+ footer: "\n<!-- END -->",
942
+ }));
943
+ ```
944
+
945
+ #### `mikel.StatePlugin(state)`
946
+
947
+ Defines static state variables that become available inside templates through the `@variable` syntax. These values are merged into the initial state before rendering.
948
+
949
+ ```javascript
950
+ mk.use(mikel.StatePlugin({
951
+ version: "1.0.0",
952
+ environment: "development",
953
+ }));
954
+ ```
955
+
956
+ ### Hooks
957
+
958
+ > Added in `v0.34.0`.
959
+
960
+ Hooks allows plugins to tap into different stages of the rendering pipeline. Hooks are registered using the `hooks.add` method inside a plugin function passed to `mk.use()`.
961
+
962
+ ```javascript
963
+ mk.use((context) => {
964
+ context.hooks.add("someHook", (params) => {
965
+ // ...
966
+ });
967
+ });
968
+ ```
969
+
970
+ Available hooks:
971
+
972
+ | Hook | Type | Receives | Must return | Description |
973
+ |------|-------|----------|-------------|-------------|
974
+ | `preRender` | Waterfall | `template` (string) | `template` (string) | Called before rendering. The returned value is used as the template. |
975
+ | `postRender` | Waterfall | `output` (string) | `output` (string) | Called after rendering. The returned value is used as the final output. |
976
+
977
+ Example:
978
+
979
+ ```javascript
980
+ const myPlugin = (context) => {
981
+ // trim the template before rendering
982
+ context.hooks.add("preRender", template => template.trim());
983
+ // minify the output after rendering
984
+ context.hooks.add("postRender", output => output.replace(/\s+/g, " ").trim());
985
+ };
986
+
987
+ mk.use(myPlugin);
988
+
989
+ console.log(mk(" Hello {{name}}! ", { name: "World" })); // --> "Hello World!"
990
+ ```
991
+
883
992
  ## License
884
993
 
885
994
  This project is licensed under the [MIT License](LICENSE).
package/index.d.ts CHANGED
@@ -24,26 +24,45 @@ export type MikelFunction = (params: {
24
24
  state: Record<string, any>;
25
25
  }) => string | void;
26
26
 
27
+ export type MikelState = Record<string, string>;
28
+
27
29
  export type MikelOptions = {
28
30
  helpers?: Record<string, MikelHelper>;
29
31
  partials?: Record<string, string | MikelPartial>;
30
32
  functions?: Record<string, MikelFunction>;
31
33
  };
32
34
 
33
- export type MikelUseOptions = MikelOptions & {
34
- initialState?: Record<string, any>;
35
+ export type MikelPluginOptions = MikelOptions & {
36
+ initialState?: MikelState;
37
+ };
38
+
39
+ export type MikelContext = {
40
+ helpers: Record<string, MikelFunction>;
41
+ functions: Record<string, MikelFunction>;
42
+ partials: Record<string, MikelPartial>;
43
+ hooks: {
44
+ add: (hookName: string, listener: Function) => void;
45
+ call: (hookName: string, ...args: any[]) => void;
46
+ callWaterfall: (hookName: string, value: any) => any;
47
+ };
48
+ initialState: MikelState;
35
49
  };
36
50
 
51
+ export type MikelPlugin = (ctx: MikelContext) => void;
52
+
37
53
  export type Mikel = {
38
54
  (template: string, data?: any): string;
39
- use(options: Partial<MikelUseOptions>): void;
55
+ use(plugin: Partial<MikelPluginOptions> | MikelPlugin): void;
40
56
  addHelper(name: string, fn: MikelHelper): void;
41
57
  removeHelper(name: string): void;
42
58
  addFunction(name: string, fn: MikelFunction): void;
43
59
  removeFunction(name: string): void;
44
60
  addPartial(name: string, partial: string | MikelPartial): void;
45
61
  removePartial(name: string): void;
46
- }
62
+ };
63
+
64
+ export type MikelWrapperPlugin = (options: { header?: string, footer?: string }) => MikelPlugin;
65
+ export type MikelStatePlugin = (state: MikelState) => MikelPlugin;
47
66
 
48
67
  declare const mikel: {
49
68
  (template: string, data?: any, options?: Partial<MikelOptions>): string;
@@ -53,6 +72,8 @@ declare const mikel: {
53
72
  parse(value: string, context?: any, vars?: any): any;
54
73
  tokenize(str: string): string[];
55
74
  untokenize(tokens: string[], start?: string, end?: string): string;
75
+ WrapperPlugin: MikelWrapperPlugin;
76
+ StatePlugin: MikelStatePlugin;
56
77
  };
57
78
 
58
79
  export default mikel;
package/index.js CHANGED
@@ -122,6 +122,24 @@ const findClosingToken = (tokens, i, token) => {
122
122
  throw new Error(`Unmatched section end: {{${token}}}`);
123
123
  };
124
124
 
125
+ // @description create a hook manager for the provided hooks map
126
+ const createHookManager = (hooks = new Map()) => {
127
+ return {
128
+ add: (hookName, listener) => {
129
+ if (!hooks.has(hookName.toLowerCase())) {
130
+ hooks.set(hookName.toLowerCase(), []);
131
+ }
132
+ hooks.get(hookName.toLowerCase()).push(listener);
133
+ },
134
+ call: (hookName, ...args) => {
135
+ hooks.get(hookName.toLowerCase())?.forEach((listener) => listener(...args));
136
+ },
137
+ callWaterfall: (hookName, value) => {
138
+ return (hooks.get(hookName.toLowerCase()) || []).reduce((v, listener) => listener(v), value);
139
+ },
140
+ };
141
+ };
142
+
125
143
  // @description internal method to compile the template
126
144
  const compile = (ctx, tokens, output, data, state, index = 0, section = "") => {
127
145
  let i = index;
@@ -278,18 +296,26 @@ const create = (options = {}) => {
278
296
  partials: Object.assign({}, options?.partials || {}),
279
297
  functions: Object.assign({}, options?.functions || {}),
280
298
  initialState: {}, // Object.assign({}, options?.initialState || {}),
299
+ hooks: createHookManager(new Map()),
281
300
  });
282
301
  // entry method to compile the template with the provided data object
283
- const compileTemplate = (template, data = {}, output = []) => {
302
+ const compileTemplate = (originalTemplate, data = {}) => {
303
+ const output = [];
304
+ const template = ctx.hooks.callWaterfall("prerender", originalTemplate || "");
284
305
  compile(ctx, tokenize(template), output, data, { ...ctx.initialState, root: data }, 0, "");
285
- return output.join("");
306
+ return ctx.hooks.callWaterfall("postrender", output.join(""));
286
307
  };
287
308
  // assign api methods and return method to compile the template
288
309
  return Object.assign(compileTemplate, {
289
- use: (newOptions = {}) => {
290
- ["helpers", "functions", "partials", "initialState"].forEach(field => {
291
- Object.assign(ctx[field], newOptions?.[field] || {});
292
- });
310
+ use: (plugin) => {
311
+ if (typeof plugin === "function") {
312
+ plugin(ctx);
313
+ }
314
+ else if (typeof plugin === "object" && !!plugin) {
315
+ ["helpers", "functions", "partials", "initialState"].forEach(field => {
316
+ Object.assign(ctx[field], plugin?.[field] || {});
317
+ });
318
+ }
293
319
  },
294
320
  addHelper: (name, fn) => ctx.helpers[name] = fn,
295
321
  removeHelper: name => delete ctx.helpers[name],
@@ -305,6 +331,22 @@ const mikel = (template = "", data = {}, options = {}) => {
305
331
  return create(options)(template, data);
306
332
  };
307
333
 
334
+ // @description plugin to wrap template with custom text
335
+ mikel.WrapperPlugin = (options = {}) => {
336
+ return (context) => {
337
+ context.hooks.add("prerender", template => {
338
+ return [options.header || "", template, options.footer || ""].join("");
339
+ });
340
+ };
341
+ };
342
+
343
+ // @description plugin to define state variables
344
+ mikel.StatePlugin = (state = {}) => {
345
+ return (context) => {
346
+ Object.assign(context.initialState, state || {});
347
+ };
348
+ };
349
+
308
350
  // @description assign utilities
309
351
  mikel.create = create;
310
352
  mikel.escape = escape;
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.33.0",
4
+ "version": "0.34.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 && yarn test:eval && yarn test:frontmatter && yarn test:markdown",
22
+ "test": "node test.js && yarn test:cli && yarn test:eval && yarn test:frontmatter && yarn test:markdown",
23
+ "test:cli": "cd packages/mikel-cli && yarn test",
23
24
  "test:eval": "node ./packages/mikel-eval/test.js",
24
25
  "test:frontmatter": "node ./packages/mikel-frontmatter/test.js",
25
26
  "test:markdown": "node ./packages/mikel-markdown/test.js"