vite-file-include 1.0.3 → 1.1.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 +134 -117
- package/package.json +29 -30
- package/src/index.js +185 -74
package/README.md
CHANGED
|
@@ -1,211 +1,228 @@
|
|
|
1
|
-
# vite-file-include
|
|
1
|
+
# vite-file-include 🔗
|
|
2
2
|
|
|
3
|
-
`vite-file-include` is
|
|
3
|
+
`vite-file-include` is a modern **Vite plugin** for HTML templating that supports **file inclusion**, **looping**, **conditional rendering**, and **live hot-reload** without reloading the full page.
|
|
4
|
+
It’s ideal for managing repetitive HTML structures in static sites or prototyping environments.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
---
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- Looping through data arrays or JSON files
|
|
9
|
-
- Conditional rendering
|
|
10
|
-
- Custom function support for advanced templating
|
|
11
|
-
- Evaluate JavaScript expressions directly in your templates.
|
|
12
|
-
- Asynchronous file processing
|
|
13
|
-
- Enhanced error handling and debugging
|
|
8
|
+
## 🚀 Features
|
|
14
9
|
|
|
10
|
+
- 🧩 **Nested file includes** with variable support
|
|
11
|
+
- 🔁 **Loop rendering** for data arrays and JSON files
|
|
12
|
+
- ⚙️ **Conditional blocks** using inline JavaScript
|
|
13
|
+
- 🧠 **Custom helper functions** for advanced templating
|
|
14
|
+
- ⚡ **JavaScript expression evaluation** inside templates
|
|
15
|
+
- 🔄 **Hot reload support** — live updates without full page refresh
|
|
16
|
+
- 🧵 **Async file processing** and better performance
|
|
17
|
+
- 🪶 **Enhanced error reporting** with context hints
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
---
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
## 📦 Installation
|
|
19
22
|
|
|
20
23
|
```bash
|
|
21
|
-
npm install vite-file-include
|
|
24
|
+
npm install vite-file-include --save-dev
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
---
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
## ⚙️ Configuration
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
import fileIncludePlugin from 'vite-file-include';
|
|
31
|
+
Add the plugin to your `vite.config.js`:
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
```js
|
|
34
|
+
import { defineConfig } from 'vite'
|
|
35
|
+
import fileIncludePlugin from 'vite-file-include'
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
32
38
|
plugins: [
|
|
33
39
|
fileIncludePlugin({
|
|
34
40
|
includePattern: "@@include",
|
|
35
41
|
loopPattern: "@@loop",
|
|
36
42
|
ifPattern: "@@if",
|
|
37
43
|
baseDir: process.cwd(),
|
|
38
|
-
context: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
context: {
|
|
45
|
+
siteName: 'My Static Site'
|
|
46
|
+
},
|
|
47
|
+
customFunctions: {
|
|
48
|
+
uppercase: (str) => str.toUpperCase(),
|
|
49
|
+
currentYear: () => new Date().getFullYear()
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
})
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
---
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
- `loopPattern` (default: `@@loop`): The pattern used to loop through data arrays.
|
|
49
|
-
- `ifPattern` (default: `@@if`): The pattern used to conditionally render content.
|
|
50
|
-
- `baseDir` (default: `process.cwd()`): The base directory for resolving paths.
|
|
51
|
-
- `context` (default: `{}`): An object containing global variables that can be used in includes, loops, and conditionals.
|
|
52
|
-
- `customFunctions` (default: {}): An object containing custom functions that can be used in your templates.
|
|
58
|
+
## 🧩 Plugin Options
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
| Option | Type | Default | Description |
|
|
61
|
+
|--------|------|----------|-------------|
|
|
62
|
+
| **`includePattern`** | `string` | `@@include` | Directive for including files |
|
|
63
|
+
| **`loopPattern`** | `string` | `@@loop` | Directive for looping over arrays/JSON |
|
|
64
|
+
| **`ifPattern`** | `string` | `@@if` | Directive for conditional rendering |
|
|
65
|
+
| **`baseDir`** | `string` | `process.cwd()` | Base directory for resolving paths |
|
|
66
|
+
| **`context`** | `object` | `{}` | Global variables accessible in templates |
|
|
67
|
+
| **`customFunctions`** | `object` | `{}` | Custom functions available in templates |
|
|
55
68
|
|
|
56
|
-
|
|
69
|
+
---
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
## 🧱 Directives
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
### 🔹 `@@include`
|
|
74
|
+
|
|
75
|
+
Include another HTML file into your main file.
|
|
61
76
|
|
|
62
77
|
```html
|
|
63
|
-
@@include('
|
|
78
|
+
@@include('partials/header.html')
|
|
64
79
|
```
|
|
65
80
|
|
|
66
|
-
|
|
81
|
+
With data:
|
|
67
82
|
|
|
68
83
|
```html
|
|
69
|
-
@@include('
|
|
84
|
+
@@include('partials/header.html', { "title": "Home Page" })
|
|
70
85
|
```
|
|
71
86
|
|
|
72
|
-
**Example** (`
|
|
87
|
+
**Example** (`partials/header.html`):
|
|
73
88
|
|
|
74
89
|
```html
|
|
75
|
-
<
|
|
90
|
+
<header>
|
|
91
|
+
<h1>{{ title }}</h1>
|
|
92
|
+
</header>
|
|
76
93
|
```
|
|
77
94
|
|
|
78
|
-
|
|
95
|
+
---
|
|
79
96
|
|
|
80
|
-
|
|
97
|
+
### 🔹 `@@loop`
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
Repeat an HTML block for each item in a data array or JSON file.
|
|
83
100
|
|
|
84
101
|
```html
|
|
85
|
-
@@loop('
|
|
102
|
+
@@loop('partials/article.html', 'data/articles.json')
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
Or inline data:
|
|
89
106
|
|
|
90
107
|
```html
|
|
91
|
-
@@loop('
|
|
108
|
+
@@loop('partials/article.html', [
|
|
109
|
+
{ "title": "Article 1" },
|
|
110
|
+
{ "title": "Article 2" }
|
|
111
|
+
])
|
|
92
112
|
```
|
|
93
113
|
|
|
94
|
-
**Example
|
|
114
|
+
**Example** (`partials/article.html`):
|
|
95
115
|
|
|
96
116
|
```html
|
|
97
117
|
<article>
|
|
98
|
-
<h2>{{
|
|
118
|
+
<h2>{{ title }}</h2>
|
|
99
119
|
</article>
|
|
100
120
|
```
|
|
101
121
|
|
|
102
|
-
|
|
122
|
+
---
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
### 🔹 `@@if`
|
|
105
125
|
|
|
106
|
-
|
|
126
|
+
Conditionally render content based on an expression.
|
|
107
127
|
|
|
108
128
|
```html
|
|
109
|
-
@@if(
|
|
110
|
-
|
|
111
|
-
}
|
|
129
|
+
@@if(showFooter) {
|
|
130
|
+
@@include('partials/footer.html')
|
|
131
|
+
};
|
|
112
132
|
```
|
|
113
133
|
|
|
114
134
|
**Example:**
|
|
115
135
|
|
|
116
136
|
```html
|
|
117
|
-
@@if(
|
|
118
|
-
<p>Welcome,
|
|
119
|
-
}
|
|
137
|
+
@@if(user.isLoggedIn) {
|
|
138
|
+
<p>Welcome, {{ user.name }}</p>
|
|
139
|
+
};
|
|
120
140
|
```
|
|
121
141
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
You can define custom functions to use in your templates. These functions are passed to the plugin through the `customFunctions` option:
|
|
142
|
+
---
|
|
125
143
|
|
|
126
|
-
|
|
127
|
-
fileIncludePlugin({
|
|
128
|
-
customFunctions: {
|
|
129
|
-
uppercase: (str) => str.toUpperCase(),
|
|
130
|
-
currentYear: () => new Date().getFullYear()
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
```
|
|
144
|
+
## 🧮 JavaScript Expressions
|
|
134
145
|
|
|
135
|
-
|
|
146
|
+
Use JS directly inside templates:
|
|
136
147
|
|
|
137
148
|
```html
|
|
138
|
-
<p>{{
|
|
139
|
-
<
|
|
149
|
+
<p>Year: {{ new Date().getFullYear() }}</p>
|
|
150
|
+
<p>Uppercase: {{ 'vite'.toUpperCase() }}</p>
|
|
140
151
|
```
|
|
141
152
|
|
|
153
|
+
---
|
|
142
154
|
|
|
143
|
-
##
|
|
155
|
+
## 🧰 Custom Functions
|
|
144
156
|
|
|
145
|
-
|
|
157
|
+
Define reusable helpers in your config:
|
|
146
158
|
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
```js
|
|
160
|
+
customFunctions: {
|
|
161
|
+
uppercase: (str) => str.toUpperCase(),
|
|
162
|
+
currentYear: () => new Date().getFullYear(),
|
|
163
|
+
}
|
|
150
164
|
```
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
Below is an example of how you might structure your HTML files using the plugin's directives:
|
|
166
|
+
Usage:
|
|
155
167
|
|
|
156
168
|
```html
|
|
157
|
-
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
@@include('header.html', { "title": "My Website" });
|
|
169
|
+
<h1>{{ uppercase(title) }}</h1>
|
|
170
|
+
<footer>© {{ currentYear() }}</footer>
|
|
171
|
+
```
|
|
161
172
|
|
|
162
|
-
|
|
173
|
+
---
|
|
163
174
|
|
|
164
|
-
|
|
165
|
-
@@include('footer.html');
|
|
166
|
-
}
|
|
167
|
-
</body>
|
|
168
|
-
</html>
|
|
169
|
-
```
|
|
175
|
+
## 🔄 Hot Reload
|
|
170
176
|
|
|
171
|
-
|
|
177
|
+
Unlike static include tools, `vite-file-include` supports **Vite’s HMR (Hot Module Replacement)**.
|
|
172
178
|
|
|
173
|
-
-
|
|
179
|
+
- Changes to included files update **instantly** in the browser
|
|
180
|
+
- No full page reload
|
|
181
|
+
- Works seamlessly with Vite’s dev server
|
|
174
182
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
💡 Tip: Useful for quickly editing partials like headers, footers, and repeating components.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🧰 Example Project Structure
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
project/
|
|
191
|
+
├─ index.html
|
|
192
|
+
├─ partials/
|
|
193
|
+
│ ├─ header.html
|
|
194
|
+
│ ├─ footer.html
|
|
195
|
+
│ └─ article.html
|
|
196
|
+
├─ data/
|
|
197
|
+
│ └─ articles.json
|
|
198
|
+
└─ vite.config.js
|
|
179
199
|
```
|
|
180
200
|
|
|
181
|
-
|
|
201
|
+
**index.html**
|
|
182
202
|
|
|
183
203
|
```html
|
|
184
|
-
<
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
204
|
+
<html>
|
|
205
|
+
<body>
|
|
206
|
+
@@include('partials/header.html', { "title": "My Site" })
|
|
207
|
+
@@loop('partials/article.html', 'data/articles.json')
|
|
208
|
+
@@if(showFooter) { @@include('partials/footer.html') };
|
|
209
|
+
</body>
|
|
210
|
+
</html>
|
|
188
211
|
```
|
|
189
212
|
|
|
190
|
-
|
|
213
|
+
---
|
|
191
214
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{
|
|
199
|
-
"title": "Article 2",
|
|
200
|
-
"content": "Content of the second article."
|
|
201
|
-
}
|
|
202
|
-
]
|
|
203
|
-
```
|
|
215
|
+
## ⚠️ Error Handling
|
|
216
|
+
|
|
217
|
+
The plugin provides detailed error messages for:
|
|
218
|
+
- Missing include files
|
|
219
|
+
- Invalid JSON syntax
|
|
220
|
+
- Undefined variables
|
|
204
221
|
|
|
205
|
-
|
|
222
|
+
Each error logs file path and directive line for easier debugging.
|
|
206
223
|
|
|
207
|
-
|
|
224
|
+
---
|
|
208
225
|
|
|
209
|
-
## License
|
|
226
|
+
## 📄 License
|
|
210
227
|
|
|
211
|
-
|
|
228
|
+
MIT © 2025
|
package/package.json
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vite-file-include",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "A Vite plugin for file inclusion, loops, and conditionals in HTML files",
|
|
5
|
-
"main": "src/index.js",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"vite",
|
|
12
|
-
"plugin",
|
|
13
|
-
"file-include",
|
|
14
|
-
"html"
|
|
15
|
-
],
|
|
16
|
-
"author": "Ahmed Abdulgid",
|
|
17
|
-
"license": "MIT",
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "https://github.com/abdulgidcoder/vite-file-
|
|
21
|
-
},
|
|
22
|
-
"peerDependencies": {
|
|
23
|
-
"vite": "^4.0.0"
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"fs": "0.0.1-security",
|
|
27
|
-
"path": "^0.12.7"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-file-include",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "A Vite plugin for file inclusion, loops, and conditionals in HTML files",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"vite",
|
|
12
|
+
"plugin",
|
|
13
|
+
"file-include",
|
|
14
|
+
"html"
|
|
15
|
+
],
|
|
16
|
+
"author": "Ahmed Abdulgid",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/abdulgidcoder/vite-file-include.git"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"vite": "^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"fs": "0.0.1-security",
|
|
27
|
+
"path": "^0.12.7"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,16 @@ function fileIncludePlugin(options = {}) {
|
|
|
11
11
|
customFunctions = {},
|
|
12
12
|
} = options;
|
|
13
13
|
|
|
14
|
+
// Cache for file reads
|
|
15
|
+
const fileCache = new Map();
|
|
16
|
+
|
|
17
|
+
const readFileCached = (filePath) => {
|
|
18
|
+
if (!fileCache.has(filePath)) {
|
|
19
|
+
fileCache.set(filePath, fs.readFileSync(filePath, "utf-8"));
|
|
20
|
+
}
|
|
21
|
+
return fileCache.get(filePath);
|
|
22
|
+
};
|
|
23
|
+
|
|
14
24
|
return {
|
|
15
25
|
name: "vite-plugin-file-include",
|
|
16
26
|
|
|
@@ -22,35 +32,64 @@ function fileIncludePlugin(options = {}) {
|
|
|
22
32
|
loopPattern,
|
|
23
33
|
ifPattern,
|
|
24
34
|
context,
|
|
25
|
-
customFunctions
|
|
35
|
+
customFunctions,
|
|
36
|
+
new Set(),
|
|
37
|
+
readFileCached
|
|
26
38
|
);
|
|
27
39
|
},
|
|
28
40
|
|
|
29
41
|
transform(code, id) {
|
|
30
42
|
if (id.endsWith(".html")) {
|
|
31
|
-
return
|
|
32
|
-
code
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
return {
|
|
44
|
+
code: processIncludes(
|
|
45
|
+
code,
|
|
46
|
+
baseDir,
|
|
47
|
+
includePattern,
|
|
48
|
+
loopPattern,
|
|
49
|
+
ifPattern,
|
|
50
|
+
context,
|
|
51
|
+
customFunctions,
|
|
52
|
+
new Set(),
|
|
53
|
+
readFileCached
|
|
54
|
+
),
|
|
55
|
+
};
|
|
40
56
|
}
|
|
41
|
-
return code;
|
|
57
|
+
return { code };
|
|
42
58
|
},
|
|
43
59
|
|
|
44
|
-
handleHotUpdate({ file, server }) {
|
|
60
|
+
handleHotUpdate({ file, server, modules }) {
|
|
45
61
|
if (file.endsWith(".html")) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
const mod = modules.find((m) => m.file && m.file.endsWith(".html"));
|
|
63
|
+
|
|
64
|
+
if (mod) {
|
|
65
|
+
server.moduleGraph.invalidateModule(mod);
|
|
66
|
+
server.ws.send({
|
|
67
|
+
type: "update",
|
|
68
|
+
updates: [
|
|
69
|
+
{
|
|
70
|
+
type: "js-update",
|
|
71
|
+
path: mod.url,
|
|
72
|
+
acceptedPath: mod.url,
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
server.ws.send({
|
|
79
|
+
type: "custom",
|
|
80
|
+
event: "vite-file-include:update",
|
|
81
|
+
data: { file },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [];
|
|
49
86
|
}
|
|
50
87
|
},
|
|
51
88
|
};
|
|
52
89
|
}
|
|
53
90
|
|
|
91
|
+
/* ---------------- Core Processing ---------------- */
|
|
92
|
+
|
|
54
93
|
function processIncludes(
|
|
55
94
|
content,
|
|
56
95
|
dir,
|
|
@@ -58,27 +97,47 @@ function processIncludes(
|
|
|
58
97
|
loopPattern,
|
|
59
98
|
ifPattern,
|
|
60
99
|
context,
|
|
61
|
-
customFunctions
|
|
100
|
+
customFunctions,
|
|
101
|
+
visited,
|
|
102
|
+
readFileCached
|
|
62
103
|
) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
let lastContent;
|
|
105
|
+
do {
|
|
106
|
+
lastContent = content;
|
|
107
|
+
content = processIncludesWithPattern(
|
|
108
|
+
content,
|
|
109
|
+
dir,
|
|
110
|
+
includePattern,
|
|
111
|
+
loopPattern,
|
|
112
|
+
ifPattern,
|
|
113
|
+
context,
|
|
114
|
+
customFunctions,
|
|
115
|
+
visited,
|
|
116
|
+
readFileCached
|
|
117
|
+
);
|
|
118
|
+
content = processLoops(
|
|
119
|
+
content,
|
|
120
|
+
dir,
|
|
121
|
+
loopPattern,
|
|
122
|
+
context,
|
|
123
|
+
customFunctions,
|
|
124
|
+
includePattern,
|
|
125
|
+
ifPattern,
|
|
126
|
+
visited,
|
|
127
|
+
readFileCached
|
|
128
|
+
);
|
|
129
|
+
content = processConditionals(
|
|
130
|
+
content,
|
|
131
|
+
dir,
|
|
132
|
+
ifPattern,
|
|
133
|
+
includePattern,
|
|
134
|
+
loopPattern,
|
|
135
|
+
context,
|
|
136
|
+
customFunctions,
|
|
137
|
+
visited,
|
|
138
|
+
readFileCached
|
|
139
|
+
);
|
|
140
|
+
} while (content !== lastContent);
|
|
82
141
|
|
|
83
142
|
return content;
|
|
84
143
|
}
|
|
@@ -90,27 +149,34 @@ function processIncludesWithPattern(
|
|
|
90
149
|
loopPattern,
|
|
91
150
|
ifPattern,
|
|
92
151
|
context,
|
|
93
|
-
customFunctions
|
|
152
|
+
customFunctions,
|
|
153
|
+
visited,
|
|
154
|
+
readFileCached
|
|
94
155
|
) {
|
|
95
156
|
const regex = new RegExp(
|
|
96
|
-
`${includePattern}\\(\\s*['"](
|
|
157
|
+
`${includePattern}\\(\\s*['"]([^'"]+)['"]\\s*(?:,\\s*({[\\s\\S]*?}))?\\s*\\)\\s*;?`,
|
|
97
158
|
"g"
|
|
98
159
|
);
|
|
99
160
|
|
|
100
161
|
return content.replace(regex, (match, filePath, jsonData) => {
|
|
101
162
|
const includePath = path.resolve(dir, filePath);
|
|
102
|
-
|
|
163
|
+
if (visited.has(includePath)) {
|
|
164
|
+
console.warn(`⚠️ Circular include detected: ${includePath}`);
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
visited.add(includePath);
|
|
103
168
|
|
|
169
|
+
let data = {};
|
|
104
170
|
if (jsonData) {
|
|
105
171
|
try {
|
|
106
172
|
data = JSON.parse(jsonData);
|
|
107
|
-
} catch
|
|
173
|
+
} catch {
|
|
108
174
|
console.error(`Failed to parse JSON data: ${jsonData}`);
|
|
109
175
|
}
|
|
110
176
|
}
|
|
111
177
|
|
|
112
178
|
try {
|
|
113
|
-
let includedContent =
|
|
179
|
+
let includedContent = readFileCached(includePath);
|
|
114
180
|
includedContent = injectData(
|
|
115
181
|
includedContent,
|
|
116
182
|
{ ...context, ...data },
|
|
@@ -122,19 +188,31 @@ function processIncludesWithPattern(
|
|
|
122
188
|
includePattern,
|
|
123
189
|
loopPattern,
|
|
124
190
|
ifPattern,
|
|
125
|
-
context,
|
|
126
|
-
customFunctions
|
|
191
|
+
{ ...context, ...data },
|
|
192
|
+
customFunctions,
|
|
193
|
+
visited,
|
|
194
|
+
readFileCached
|
|
127
195
|
);
|
|
128
|
-
} catch (
|
|
196
|
+
} catch (err) {
|
|
129
197
|
console.error(`Failed to include file: ${includePath}`);
|
|
130
198
|
return "";
|
|
131
199
|
}
|
|
132
200
|
});
|
|
133
201
|
}
|
|
134
202
|
|
|
135
|
-
function processLoops(
|
|
203
|
+
function processLoops(
|
|
204
|
+
content,
|
|
205
|
+
dir,
|
|
206
|
+
loopPattern,
|
|
207
|
+
context,
|
|
208
|
+
customFunctions,
|
|
209
|
+
includePattern,
|
|
210
|
+
ifPattern,
|
|
211
|
+
visited,
|
|
212
|
+
readFileCached
|
|
213
|
+
) {
|
|
136
214
|
const regex = new RegExp(
|
|
137
|
-
`${loopPattern}\\(\\s*['"](
|
|
215
|
+
`${loopPattern}\\(\\s*['"]([^'"]+)['"]\\s*,\\s*(\\[[\\s\\S]*?\\]|['"][^'"]+['"])\\s*\\)\\s*;?`,
|
|
138
216
|
"g"
|
|
139
217
|
);
|
|
140
218
|
|
|
@@ -143,31 +221,46 @@ function processLoops(content, dir, loopPattern, context, customFunctions) {
|
|
|
143
221
|
let dataArray = [];
|
|
144
222
|
|
|
145
223
|
try {
|
|
146
|
-
if (
|
|
147
|
-
jsonArrayOrFilePath.startsWith("[") ||
|
|
148
|
-
jsonArrayOrFilePath.startsWith("{")
|
|
149
|
-
) {
|
|
224
|
+
if (jsonArrayOrFilePath.trim().startsWith("[")) {
|
|
150
225
|
dataArray = JSON.parse(jsonArrayOrFilePath);
|
|
151
226
|
} else {
|
|
152
227
|
const jsonFilePath = path.resolve(
|
|
153
228
|
dir,
|
|
154
229
|
jsonArrayOrFilePath.replace(/['"]/g, "")
|
|
155
230
|
);
|
|
156
|
-
const jsonData =
|
|
231
|
+
const jsonData = readFileCached(jsonFilePath);
|
|
157
232
|
dataArray = JSON.parse(jsonData);
|
|
158
233
|
}
|
|
159
234
|
} catch (error) {
|
|
160
|
-
console.error(`Failed to parse JSON: ${jsonArrayOrFilePath}`);
|
|
161
|
-
|
|
235
|
+
console.error(`Failed to parse loop JSON: ${jsonArrayOrFilePath}`);
|
|
236
|
+
return "";
|
|
162
237
|
}
|
|
163
238
|
|
|
164
239
|
try {
|
|
165
|
-
let loopTemplate =
|
|
240
|
+
let loopTemplate = readFileCached(loopPath);
|
|
166
241
|
return dataArray
|
|
167
|
-
.map((data) =>
|
|
242
|
+
.map((data) => {
|
|
243
|
+
const mergedContext = { ...context, ...data };
|
|
244
|
+
const loopContent = injectData(
|
|
245
|
+
loopTemplate,
|
|
246
|
+
mergedContext,
|
|
247
|
+
customFunctions
|
|
248
|
+
);
|
|
249
|
+
return processIncludes(
|
|
250
|
+
loopContent,
|
|
251
|
+
dir,
|
|
252
|
+
includePattern,
|
|
253
|
+
loopPattern,
|
|
254
|
+
ifPattern,
|
|
255
|
+
mergedContext,
|
|
256
|
+
customFunctions,
|
|
257
|
+
visited,
|
|
258
|
+
readFileCached
|
|
259
|
+
);
|
|
260
|
+
})
|
|
168
261
|
.join("");
|
|
169
262
|
} catch (error) {
|
|
170
|
-
console.error(`Failed to include file: ${loopPath}`);
|
|
263
|
+
console.error(`Failed to include loop file: ${loopPath}`);
|
|
171
264
|
return "";
|
|
172
265
|
}
|
|
173
266
|
});
|
|
@@ -180,10 +273,12 @@ function processConditionals(
|
|
|
180
273
|
includePattern,
|
|
181
274
|
loopPattern,
|
|
182
275
|
context,
|
|
183
|
-
customFunctions
|
|
276
|
+
customFunctions,
|
|
277
|
+
visited,
|
|
278
|
+
readFileCached
|
|
184
279
|
) {
|
|
185
280
|
const regex = new RegExp(
|
|
186
|
-
`${ifPattern}\\s*\\(([^)]+)\\)\\s*{([\\s\\S]*?)}
|
|
281
|
+
`${ifPattern}\\s*\\(([^)]+)\\)\\s*{([\\s\\S]*?)};?`,
|
|
187
282
|
"g"
|
|
188
283
|
);
|
|
189
284
|
|
|
@@ -192,21 +287,45 @@ function processConditionals(
|
|
|
192
287
|
const result = evaluateCondition(condition, context, customFunctions);
|
|
193
288
|
|
|
194
289
|
if (result) {
|
|
195
|
-
|
|
290
|
+
let processed = processIncludesWithPattern(
|
|
196
291
|
body.trim(),
|
|
197
292
|
dir,
|
|
198
293
|
includePattern,
|
|
199
294
|
loopPattern,
|
|
200
295
|
ifPattern,
|
|
201
296
|
context,
|
|
202
|
-
customFunctions
|
|
297
|
+
customFunctions,
|
|
298
|
+
visited,
|
|
299
|
+
readFileCached
|
|
300
|
+
);
|
|
301
|
+
processed = processLoops(
|
|
302
|
+
processed,
|
|
303
|
+
dir,
|
|
304
|
+
loopPattern,
|
|
305
|
+
context,
|
|
306
|
+
customFunctions,
|
|
307
|
+
includePattern,
|
|
308
|
+
ifPattern,
|
|
309
|
+
visited,
|
|
310
|
+
readFileCached
|
|
311
|
+
);
|
|
312
|
+
processed = processConditionals(
|
|
313
|
+
processed,
|
|
314
|
+
dir,
|
|
315
|
+
ifPattern,
|
|
316
|
+
includePattern,
|
|
317
|
+
loopPattern,
|
|
318
|
+
context,
|
|
319
|
+
customFunctions,
|
|
320
|
+
visited,
|
|
321
|
+
readFileCached
|
|
203
322
|
);
|
|
323
|
+
return processed;
|
|
204
324
|
}
|
|
205
325
|
|
|
206
326
|
return "";
|
|
207
327
|
} catch (error) {
|
|
208
328
|
console.error(`Failed to evaluate condition: ${condition}`);
|
|
209
|
-
console.error(error);
|
|
210
329
|
return "";
|
|
211
330
|
}
|
|
212
331
|
});
|
|
@@ -217,30 +336,22 @@ function injectData(content, data, customFunctions = {}) {
|
|
|
217
336
|
try {
|
|
218
337
|
const result = evaluateExpression(expression, data, customFunctions);
|
|
219
338
|
return result !== undefined ? result : match;
|
|
220
|
-
} catch
|
|
221
|
-
console.error(`Failed to evaluate expression: ${expression}`);
|
|
222
|
-
console.error(error);
|
|
339
|
+
} catch {
|
|
223
340
|
return match;
|
|
224
341
|
}
|
|
225
342
|
});
|
|
226
343
|
}
|
|
227
344
|
|
|
228
345
|
function evaluateExpression(expression, data, customFunctions) {
|
|
229
|
-
// Bind custom functions to the evaluation context
|
|
230
346
|
const context = { ...data, ...customFunctions };
|
|
231
|
-
return new Function(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)(context);
|
|
347
|
+
return new Function("context", `with (context) { return ${expression}; }`)(
|
|
348
|
+
context
|
|
349
|
+
);
|
|
235
350
|
}
|
|
236
351
|
|
|
237
352
|
function evaluateCondition(condition, context, customFunctions) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return new Function(
|
|
241
|
-
"context",
|
|
242
|
-
"with (context) { return " + condition + "; }"
|
|
243
|
-
)(contextWithFunctions);
|
|
353
|
+
const ctx = { ...context, ...customFunctions };
|
|
354
|
+
return new Function("context", `with (context) { return ${condition}; }`)(ctx);
|
|
244
355
|
}
|
|
245
356
|
|
|
246
357
|
export default fileIncludePlugin;
|