lite-template 0.1.0 → 0.1.1

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 CHANGED
@@ -23,7 +23,7 @@ To prioritize performance and maintain its minimalist footprint (<10KB), `lite-t
23
23
 
24
24
  1. **Strictly Logic-Driven**: Unlike EJS, it does not include a complex built-in caching layer. Reusable templates should be pre-compiled using `compile()` at the application level.
25
25
  2. **Explicit Scope**: Uses the `with` block for performance. Variables must be defined within the provided `data` object to be accessible; it does not automatically pull from global scope.
26
- 3. **No Layout System**: Does not include a proprietary EJS `<%- layout() %>` system. Layout/Include handling is left to the developer via standard async calls (e.g., `<%- await include(...) %>`).
26
+ 3. **Include System**: A native `include()` function works out-of-the-box (just like EJS) when `options.filename` is provided to `render()`, managing recursive resolution automatically. Does not implement `<%- layout() %>` systems.
27
27
  4. **No Middleware Integration**: This is a pure string-to-HTML engine—no native Express.js view-engine integration is included out of the box.
28
28
  5. **ESM Only**: Built exclusively for modern ESM toolchains.
29
29
 
@@ -76,6 +76,22 @@ const html = await render(template, {
76
76
  });
77
77
  ```
78
78
 
79
+ ### Native Built-in Includes
80
+
81
+ When you pass `filename` in the options, `lite-template` automatically exposes a built-in cross-file `include()` resolver.
82
+
83
+ ```javascript
84
+ // page.ejs
85
+ // <h1>My Page</h1>
86
+ // <%- await include('footer', { text: "Copyright" }) %>
87
+
88
+ const html = await render(
89
+ '<%- await include("footer") %>',
90
+ { globalVar: true },
91
+ { filename: '/path/to/page.ejs' }
92
+ );
93
+ ```
94
+
79
95
  ### First-Class Async/Await
80
96
 
81
97
  ```javascript
package/dist/index.d.ts CHANGED
@@ -1,12 +1,38 @@
1
1
  /**
2
2
  * Compiles an EJS-style template string into an async function.
3
- * Supports: <% js %>, <%= escaped %>, <%- unescaped %>
3
+ * Supports: <% js %>, <%= escaped %>, <%- unescaped %>, <%# comment %>
4
4
  */
5
5
  export declare function compile(template: any): (data: any) => any;
6
6
  /**
7
- * Convenience method to render template directly.
7
+ * Renders an EJS-style template string with data.
8
+ *
9
+ * Provides a built-in `include()` function when `options.filename` is set,
10
+ * enabling file-based template inclusion just like EJS.
11
+ *
12
+ * If the caller supplies their own `include` in `data`, it takes precedence
13
+ * over the built-in version (allowing frameworks like docmd to extend behavior).
14
+ *
15
+ * @param template - The EJS template string to render
16
+ * @param data - Data object accessible inside the template via `with(data)`
17
+ * @param options - Options: `filename` (path of this template, enables include resolution)
18
+ * @returns The rendered string
19
+ *
20
+ * @example
21
+ * ```js
22
+ * import tpl from 'lite-template';
23
+ *
24
+ * // Simple render
25
+ * const html = await tpl.render('<h1><%= title %></h1>', { title: 'Hello' });
26
+ *
27
+ * // With file-based includes
28
+ * const html = await tpl.render(
29
+ * '<%- include("header") %><p>Body</p>',
30
+ * { siteName: 'My Site' },
31
+ * { filename: '/path/to/current/template.ejs' }
32
+ * );
33
+ * ```
8
34
  */
9
- export declare function render(template: any, data?: {}, options?: {}): Promise<any>;
35
+ export declare function render(template: any, data?: {}, options?: any): Promise<any>;
10
36
  declare const _default: {
11
37
  render: typeof render;
12
38
  compile: typeof compile;
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ function escapeHtml(str) {
9
9
  }
10
10
  /**
11
11
  * Compiles an EJS-style template string into an async function.
12
- * Supports: <% js %>, <%= escaped %>, <%- unescaped %>
12
+ * Supports: <% js %>, <%= escaped %>, <%- unescaped %>, <%# comment %>
13
13
  */
14
14
  export function compile(template) {
15
15
  let code = "";
@@ -50,7 +50,7 @@ export function compile(template) {
50
50
  fn = new AsyncFunction("__data", "escapeHtml", wrappedCode);
51
51
  }
52
52
  catch (e) {
53
- console.error("COMPILATION ERROR CODE:\n" + wrappedCode.split('\\n').map((l, i) => `${i + 1}: ${l}`).join('\\n'));
53
+ console.error("COMPILATION ERROR CODE:\n" + wrappedCode.split('\n').map((l, i) => `${i + 1}: ${l}`).join('\n'));
54
54
  throw e;
55
55
  }
56
56
  return function (data) {
@@ -58,13 +58,94 @@ export function compile(template) {
58
58
  };
59
59
  }
60
60
  /**
61
- * Convenience method to render template directly.
61
+ * Built-in include function for file-based template inclusion.
62
+ * Reads a file relative to the current template's location, compiles and renders it.
63
+ *
64
+ * This provides the core EJS-compatible `include()` behavior:
65
+ * - Resolves paths relative to the current template's `filename`
66
+ * - Auto-appends `.ejs` extension if missing
67
+ * - Recursively renders included templates
68
+ * - Merges parent data with include-specific data
69
+ *
70
+ * Consumers can override this by passing their own `include` function in `data`.
71
+ *
72
+ * @param parentFilename - Absolute path of the template doing the including
73
+ * @param parentData - Data context from the parent template
74
+ * @param options - Render options (passed through to recursive render calls)
75
+ */
76
+ async function builtinInclude(parentFilename, parentData, options, name, includeData = {}) {
77
+ const extName = !name.endsWith('.ejs') ? name + '.ejs' : name;
78
+ // Custom includer support (e.g. for virtual file systems or memory templates)
79
+ if (options.includer) {
80
+ const res = options.includer(extName, parentFilename);
81
+ if (res && res.template) {
82
+ const mergedData = { ...parentData, ...includeData };
83
+ delete mergedData.include;
84
+ return await render(res.template, mergedData, { ...options, filename: res.filename || parentFilename });
85
+ }
86
+ }
87
+ // Lazy-load Node.js modules (keeps the package usable in non-Node environments at compile time)
88
+ const { promises: fsPromises } = await import('node:fs');
89
+ const path = await import('node:path');
90
+ let targetPath;
91
+ if (parentFilename) {
92
+ targetPath = path.resolve(path.dirname(parentFilename), extName);
93
+ }
94
+ else {
95
+ targetPath = path.resolve(extName);
96
+ }
97
+ let content = await fsPromises.readFile(targetPath, 'utf8');
98
+ // Custom preprocessor hook before rendering (useful for stripping syntax outside of the engine's concern)
99
+ if (options.preprocessor) {
100
+ content = options.preprocessor(content, targetPath);
101
+ }
102
+ const mergedData = { ...parentData, ...includeData };
103
+ // Remove the parent's include fn — render() will create a new one scoped to the new file
104
+ delete mergedData.include;
105
+ return await render(content, mergedData, { ...options, filename: targetPath });
106
+ }
107
+ /**
108
+ * Renders an EJS-style template string with data.
109
+ *
110
+ * Provides a built-in `include()` function when `options.filename` is set,
111
+ * enabling file-based template inclusion just like EJS.
112
+ *
113
+ * If the caller supplies their own `include` in `data`, it takes precedence
114
+ * over the built-in version (allowing frameworks like docmd to extend behavior).
115
+ *
116
+ * @param template - The EJS template string to render
117
+ * @param data - Data object accessible inside the template via `with(data)`
118
+ * @param options - Options: `filename` (path of this template, enables include resolution)
119
+ * @returns The rendered string
120
+ *
121
+ * @example
122
+ * ```js
123
+ * import tpl from 'lite-template';
124
+ *
125
+ * // Simple render
126
+ * const html = await tpl.render('<h1><%= title %></h1>', { title: 'Hello' });
127
+ *
128
+ * // With file-based includes
129
+ * const html = await tpl.render(
130
+ * '<%- include("header") %><p>Body</p>',
131
+ * { siteName: 'My Site' },
132
+ * { filename: '/path/to/current/template.ejs' }
133
+ * );
134
+ * ```
62
135
  */
63
136
  export async function render(template, data = {}, options = {}) {
64
- // Allow caching if 'filename' or 'id' is passed usually, but here we just compile and run.
65
137
  const fn = compile(template);
66
- // Add locals pseudo-property common in EJS contexts
67
- return await fn({ locals: data, ...data });
138
+ const filename = options.filename || data.__filename || null;
139
+ const enriched = {
140
+ locals: data,
141
+ ...data,
142
+ __filename: filename
143
+ };
144
+ // Provide built-in include() if filename OR a virtual includer is set AND the caller hasn't provided their own
145
+ if ((filename || options.includer) && !enriched.include) {
146
+ enriched.include = (name, includeData = {}) => builtinInclude(filename, enriched, options, name, includeData);
147
+ }
148
+ return await fn(enriched);
68
149
  }
69
150
  export default {
70
151
  render,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lite-template",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Ultra lightweight, zero-dependency async template engine compatible with basic EJS syntax.",
5
5
  "type": "module",
6
6
  "files": [
@@ -12,6 +12,7 @@
12
12
  "build": "tsc"
13
13
  },
14
14
  "devDependencies": {
15
+ "@types/node": "^25.5.2",
15
16
  "typescript": "^5.0.0"
16
17
  },
17
18
  "keywords": [