vite-file-include 1.1.1 → 1.2.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 +621 -86
- package/package.json +1 -1
- package/src/index.js +394 -311
package/README.md
CHANGED
|
@@ -1,87 +1,121 @@
|
|
|
1
|
-
# vite-file-include
|
|
1
|
+
# vite-file-include
|
|
2
2
|
|
|
3
|
-
`vite-file-include` is a modern **Vite plugin** for HTML templating that supports **file inclusion**, **looping**, **conditional rendering**, and **
|
|
4
|
-
|
|
3
|
+
`vite-file-include` is a modern **Vite plugin** for HTML templating that supports **file inclusion**, **looping**, **conditional rendering**, and **true hot module replacement (HMR)** for rapid static site development.
|
|
4
|
+
Perfect for managing repetitive HTML structures in static sites or prototyping environments.
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Features
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
10
|
+
- Nested file includes with variable support and data passing
|
|
11
|
+
- Loop rendering for data arrays and JSON files
|
|
12
|
+
- Conditional blocks using inline JavaScript expressions
|
|
13
|
+
- Custom helper functions for advanced templating
|
|
14
|
+
- JavaScript expression evaluation inside templates
|
|
15
|
+
- **True Hot Module Replacement (HMR)** - updates without full page reload
|
|
16
|
+
- Loop indices (\_index, \_total) for enhanced iteration control
|
|
17
|
+
- Circular include detection - prevents infinite loops
|
|
18
|
+
- Infinite loop protection with configurable iteration limits
|
|
19
|
+
- Enhanced error reporting with detailed file path logging
|
|
20
|
+
- Runtime API for dynamic function and context updates
|
|
18
21
|
|
|
19
22
|
---
|
|
20
23
|
|
|
21
|
-
##
|
|
24
|
+
## Installation
|
|
22
25
|
|
|
23
26
|
```bash
|
|
24
27
|
npm install vite-file-include --save-dev
|
|
25
28
|
```
|
|
26
29
|
|
|
30
|
+
Or manually copy the plugin file to your project.
|
|
31
|
+
|
|
27
32
|
---
|
|
28
33
|
|
|
29
|
-
##
|
|
34
|
+
## Configuration
|
|
30
35
|
|
|
31
36
|
Add the plugin to your `vite.config.js`:
|
|
32
37
|
|
|
33
38
|
```js
|
|
34
|
-
import { defineConfig } from
|
|
35
|
-
import fileIncludePlugin from
|
|
39
|
+
import { defineConfig } from "vite";
|
|
40
|
+
import fileIncludePlugin from "./vite-plugin-file-include.js";
|
|
41
|
+
import path from "path";
|
|
42
|
+
import fs from "fs";
|
|
43
|
+
import { fileURLToPath } from "url";
|
|
44
|
+
|
|
45
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
46
|
+
const __dirname = path.dirname(__filename);
|
|
36
47
|
|
|
37
48
|
export default defineConfig({
|
|
38
49
|
plugins: [
|
|
39
50
|
fileIncludePlugin({
|
|
40
|
-
|
|
41
|
-
loopPattern: "@@loop",
|
|
42
|
-
ifPattern: "@@if",
|
|
43
|
-
baseDir: process.cwd(),
|
|
51
|
+
baseDir: "./src",
|
|
44
52
|
context: {
|
|
45
|
-
siteName:
|
|
53
|
+
siteName: "My Static Site",
|
|
54
|
+
showFooter: true,
|
|
46
55
|
},
|
|
47
56
|
customFunctions: {
|
|
48
57
|
uppercase: (str) => str.toUpperCase(),
|
|
49
|
-
currentYear: () => new Date().getFullYear()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
currentYear: () => new Date().getFullYear(),
|
|
59
|
+
loadSvg: function (svgFile, classes = "") {
|
|
60
|
+
try {
|
|
61
|
+
const svgPath = path.join(
|
|
62
|
+
__dirname,
|
|
63
|
+
"src/assets/images/icons",
|
|
64
|
+
svgFile
|
|
65
|
+
);
|
|
66
|
+
if (!fs.existsSync(svgPath)) {
|
|
67
|
+
console.error(`SVG not found: ${svgPath}`);
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
const svgContent = fs
|
|
71
|
+
.readFileSync(svgPath, "utf-8")
|
|
72
|
+
.replace(/<\?xml.*?\?>/g, "")
|
|
73
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
74
|
+
.trim();
|
|
75
|
+
return `<span class='app-icon ${classes}'>${svgContent}</span>`;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(`Error loading SVG: ${svgFile}`, error);
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
});
|
|
54
85
|
```
|
|
55
86
|
|
|
56
87
|
---
|
|
57
88
|
|
|
58
|
-
##
|
|
89
|
+
## Plugin Options
|
|
59
90
|
|
|
60
|
-
| Option
|
|
61
|
-
|
|
62
|
-
| **`includePattern`**
|
|
63
|
-
| **`loopPattern`**
|
|
64
|
-
| **`ifPattern`**
|
|
65
|
-
| **`baseDir`**
|
|
66
|
-
| **`context`**
|
|
67
|
-
| **`customFunctions`** | `object` | `{}`
|
|
91
|
+
| Option | Type | Default | Description |
|
|
92
|
+
| --------------------- | -------- | --------------- | -------------------------------------------- |
|
|
93
|
+
| **`includePattern`** | `string` | `@@include` | Directive for including files |
|
|
94
|
+
| **`loopPattern`** | `string` | `@@loop` | Directive for looping over arrays/JSON |
|
|
95
|
+
| **`ifPattern`** | `string` | `@@if` | Directive for conditional rendering |
|
|
96
|
+
| **`baseDir`** | `string` | `process.cwd()` | Base directory for resolving paths |
|
|
97
|
+
| **`context`** | `object` | `{}` | Global variables accessible in all templates |
|
|
98
|
+
| **`customFunctions`** | `object` | `{}` | Custom functions callable in templates |
|
|
68
99
|
|
|
69
100
|
---
|
|
70
101
|
|
|
71
|
-
##
|
|
102
|
+
## Directives
|
|
72
103
|
|
|
73
|
-
###
|
|
104
|
+
### `@@include`
|
|
74
105
|
|
|
75
106
|
Include another HTML file into your main file.
|
|
76
107
|
|
|
108
|
+
**Basic usage:**
|
|
109
|
+
|
|
77
110
|
```html
|
|
78
111
|
@@include('partials/header.html')
|
|
79
112
|
```
|
|
80
113
|
|
|
81
|
-
With data
|
|
114
|
+
**With data:**
|
|
82
115
|
|
|
83
116
|
```html
|
|
84
|
-
@@include('partials/header.html', { "title": "Home Page"
|
|
117
|
+
@@include('partials/header.html', { "title": "Home Page", "subtitle": "Welcome"
|
|
118
|
+
})
|
|
85
119
|
```
|
|
86
120
|
|
|
87
121
|
**Example** (`partials/header.html`):
|
|
@@ -89,140 +123,641 @@ With data:
|
|
|
89
123
|
```html
|
|
90
124
|
<header>
|
|
91
125
|
<h1>{{ title }}</h1>
|
|
126
|
+
<p>{{ subtitle }}</p>
|
|
92
127
|
</header>
|
|
93
128
|
```
|
|
94
129
|
|
|
130
|
+
**Nested includes:**
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<!-- main.html -->
|
|
134
|
+
@@include('partials/layout.html', { "page": "home" })
|
|
135
|
+
|
|
136
|
+
<!-- partials/layout.html -->
|
|
137
|
+
<div class="layout">
|
|
138
|
+
@@include('sections/header.html')
|
|
139
|
+
<main>{{ page }} content</main>
|
|
140
|
+
</div>
|
|
141
|
+
```
|
|
142
|
+
|
|
95
143
|
---
|
|
96
144
|
|
|
97
|
-
###
|
|
145
|
+
### `@@loop`
|
|
98
146
|
|
|
99
147
|
Repeat an HTML block for each item in a data array or JSON file.
|
|
100
148
|
|
|
149
|
+
**From JSON file:**
|
|
150
|
+
|
|
101
151
|
```html
|
|
102
152
|
@@loop('partials/article.html', 'data/articles.json')
|
|
103
153
|
```
|
|
104
154
|
|
|
105
|
-
|
|
155
|
+
**Inline data:**
|
|
106
156
|
|
|
107
157
|
```html
|
|
108
|
-
@@loop('partials/
|
|
109
|
-
|
|
110
|
-
{ "title": "Article 2" }
|
|
111
|
-
])
|
|
158
|
+
@@loop('partials/card.html', [ { "title": "Card 1", "description": "First card"
|
|
159
|
+
}, { "title": "Card 2", "description": "Second card" } ])
|
|
112
160
|
```
|
|
113
161
|
|
|
114
162
|
**Example** (`partials/article.html`):
|
|
115
163
|
|
|
116
164
|
```html
|
|
117
|
-
<article>
|
|
165
|
+
<article class="post">
|
|
118
166
|
<h2>{{ title }}</h2>
|
|
167
|
+
<p>{{ description }}</p>
|
|
168
|
+
<span>{{ author }} - {{ date }}</span>
|
|
169
|
+
<small>Item {{ _index + 1 }} of {{ _total }}</small>
|
|
119
170
|
</article>
|
|
120
171
|
```
|
|
121
172
|
|
|
173
|
+
**Loop variables:**
|
|
174
|
+
|
|
175
|
+
- `{{ _index }}` - Current item index (0-based)
|
|
176
|
+
- `{{ _total }}` - Total number of items
|
|
177
|
+
|
|
178
|
+
**Example** (`data/articles.json`):
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
[
|
|
182
|
+
{
|
|
183
|
+
"title": "Getting Started with Vite",
|
|
184
|
+
"description": "Learn the basics",
|
|
185
|
+
"author": "John Doe",
|
|
186
|
+
"date": "2025-01-15"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"title": "Advanced Templating",
|
|
190
|
+
"description": "Pro tips and tricks",
|
|
191
|
+
"author": "Jane Smith",
|
|
192
|
+
"date": "2025-01-20"
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
```
|
|
196
|
+
|
|
122
197
|
---
|
|
123
198
|
|
|
124
|
-
###
|
|
199
|
+
### `@@if`
|
|
125
200
|
|
|
126
|
-
Conditionally render content based on
|
|
201
|
+
Conditionally render content based on JavaScript expressions.
|
|
202
|
+
|
|
203
|
+
**Basic condition:**
|
|
127
204
|
|
|
128
205
|
```html
|
|
129
|
-
@@if(showFooter) {
|
|
130
|
-
@@include('partials/footer.html')
|
|
131
|
-
};
|
|
206
|
+
@@if(showFooter) { @@include('partials/footer.html') }
|
|
132
207
|
```
|
|
133
208
|
|
|
134
|
-
**
|
|
209
|
+
**Complex conditions:**
|
|
135
210
|
|
|
136
211
|
```html
|
|
137
|
-
@@if(user.isLoggedIn) {
|
|
138
|
-
|
|
139
|
-
|
|
212
|
+
@@if(user.isLoggedIn && user.role === 'admin') {
|
|
213
|
+
<div class="admin-panel">
|
|
214
|
+
<h2>Admin Controls</h2>
|
|
215
|
+
</div>
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**With includes and loops:**
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
@@if(articles.length > 0) { @@loop('partials/article.html',
|
|
223
|
+
'data/articles.json') }
|
|
140
224
|
```
|
|
141
225
|
|
|
142
226
|
---
|
|
143
227
|
|
|
144
|
-
##
|
|
228
|
+
## Variable Interpolation
|
|
145
229
|
|
|
146
|
-
Use
|
|
230
|
+
Use double curly braces `{{ }}` for dynamic content:
|
|
231
|
+
|
|
232
|
+
**Simple variables:**
|
|
233
|
+
|
|
234
|
+
```html
|
|
235
|
+
<h1>{{ title }}</h1>
|
|
236
|
+
<p>{{ description }}</p>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**JavaScript expressions:**
|
|
147
240
|
|
|
148
241
|
```html
|
|
149
242
|
<p>Year: {{ new Date().getFullYear() }}</p>
|
|
150
243
|
<p>Uppercase: {{ 'vite'.toUpperCase() }}</p>
|
|
244
|
+
<p>Math: {{ 5 + 3 * 2 }}</p>
|
|
245
|
+
<p>Ternary: {{ isActive ? 'Active' : 'Inactive' }}</p>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Accessing nested properties:**
|
|
249
|
+
|
|
250
|
+
```html
|
|
251
|
+
<p>{{ user.profile.name }}</p>
|
|
252
|
+
<p>{{ config.api.endpoint }}</p>
|
|
151
253
|
```
|
|
152
254
|
|
|
153
255
|
---
|
|
154
256
|
|
|
155
|
-
##
|
|
257
|
+
## Custom Functions
|
|
156
258
|
|
|
157
|
-
Define reusable
|
|
259
|
+
Define reusable helper functions in your config:
|
|
158
260
|
|
|
159
261
|
```js
|
|
160
262
|
customFunctions: {
|
|
161
263
|
uppercase: (str) => str.toUpperCase(),
|
|
162
264
|
currentYear: () => new Date().getFullYear(),
|
|
265
|
+
formatDate: (date) => new Date(date).toLocaleDateString(),
|
|
266
|
+
truncate: (str, len) => str.length > len ? str.slice(0, len) + '...' : str,
|
|
267
|
+
loadSvg: (file, classes = '') => {
|
|
268
|
+
// Load and inline SVG files
|
|
269
|
+
}
|
|
163
270
|
}
|
|
164
271
|
```
|
|
165
272
|
|
|
166
|
-
Usage
|
|
273
|
+
**Usage in templates:**
|
|
167
274
|
|
|
168
275
|
```html
|
|
169
276
|
<h1>{{ uppercase(title) }}</h1>
|
|
170
|
-
<footer>© {{ currentYear() }}</footer>
|
|
277
|
+
<footer>© {{ currentYear() }} {{ siteName }}</footer>
|
|
278
|
+
<p>{{ formatDate('2025-01-15') }}</p>
|
|
279
|
+
<p>{{ truncate(description, 100) }}</p>
|
|
280
|
+
<button>{{ loadSvg('icon-search.svg', 'icon-sm') }} Search</button>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Hot Module Replacement (HMR)
|
|
286
|
+
|
|
287
|
+
The plugin features **true HMR** that updates your HTML without full page reload:
|
|
288
|
+
|
|
289
|
+
**What makes it special:**
|
|
290
|
+
|
|
291
|
+
- Updates HTML changes instantly without page reload
|
|
292
|
+
- Preserves JavaScript state (variables, counters, timers)
|
|
293
|
+
- Maintains scroll position during updates
|
|
294
|
+
- Keeps form data intact (no lost input)
|
|
295
|
+
- Tracks dependencies between HTML files
|
|
296
|
+
- Smart DOM content replacement
|
|
297
|
+
- Automatic fallback to full reload on errors
|
|
298
|
+
|
|
299
|
+
**How it works:**
|
|
300
|
+
|
|
301
|
+
1. Edit any HTML file (main pages or partials)
|
|
302
|
+
2. Plugin detects the change and invalidates affected modules
|
|
303
|
+
3. Sends custom HMR event to the browser
|
|
304
|
+
4. Browser fetches updated HTML via fetch
|
|
305
|
+
5. Parses new HTML with DOMParser
|
|
306
|
+
6. Replaces body content while preserving state
|
|
307
|
+
7. Restores scroll position automatically
|
|
308
|
+
8. Re-executes necessary scripts
|
|
309
|
+
|
|
310
|
+
**Technical implementation:**
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
// Automatically injected HMR client
|
|
314
|
+
if (import.meta.hot) {
|
|
315
|
+
import.meta.hot.on("vite-file-include:update", async (data) => {
|
|
316
|
+
// Fetch updated HTML
|
|
317
|
+
const response = await fetch(window.location.pathname);
|
|
318
|
+
const html = await response.text();
|
|
319
|
+
|
|
320
|
+
// Parse and update body
|
|
321
|
+
const parser = new DOMParser();
|
|
322
|
+
const newDoc = parser.parseFromString(html, "text/html");
|
|
323
|
+
|
|
324
|
+
// Preserve state and update content
|
|
325
|
+
const scrollPos = window.scrollY;
|
|
326
|
+
document.body.innerHTML = newDoc.body.innerHTML;
|
|
327
|
+
window.scrollTo(0, scrollPos);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Console output:**
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
[vite-file-include] HTML changed: header.html
|
|
336
|
+
[HMR] HTML file updated: header.html
|
|
337
|
+
[HMR] Content updated successfully
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Testing HMR:**
|
|
341
|
+
|
|
342
|
+
Add a counter to test state preservation:
|
|
343
|
+
|
|
344
|
+
```html
|
|
345
|
+
<button onclick="this.textContent = parseInt(this.textContent || 0) + 1">
|
|
346
|
+
0
|
|
347
|
+
</button>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Click it several times, then edit any HTML file. The counter value will remain unchanged, proving no page reload occurred.
|
|
351
|
+
|
|
352
|
+
**Custom HMR events:**
|
|
353
|
+
|
|
354
|
+
Listen for updates in your JavaScript:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
if (import.meta.hot) {
|
|
358
|
+
import.meta.hot.on("vite-file-include:update", (data) => {
|
|
359
|
+
console.log("HTML updated:", data.file, data.timestamp);
|
|
360
|
+
// Your custom logic here
|
|
361
|
+
});
|
|
362
|
+
}
|
|
171
363
|
```
|
|
172
364
|
|
|
365
|
+
**Error handling:**
|
|
366
|
+
|
|
367
|
+
If HMR update fails for any reason, the plugin automatically falls back to a full page reload to ensure the page is never in a broken state.
|
|
368
|
+
|
|
173
369
|
---
|
|
174
370
|
|
|
175
|
-
##
|
|
371
|
+
## Runtime API
|
|
372
|
+
|
|
373
|
+
Access plugin features at runtime:
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
// In vite.config.js
|
|
377
|
+
const plugin = fileIncludePlugin(options);
|
|
176
378
|
|
|
177
|
-
|
|
379
|
+
// Add functions dynamically
|
|
380
|
+
plugin.api.addFunction("customHelper", (val) => val * 2);
|
|
178
381
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
- Works seamlessly with Vite’s dev server
|
|
382
|
+
// Update context at runtime
|
|
383
|
+
plugin.api.updateContext({ newVar: "value" });
|
|
182
384
|
|
|
183
|
-
|
|
385
|
+
// Access processor directly
|
|
386
|
+
const processor = plugin.api.processor;
|
|
387
|
+
```
|
|
184
388
|
|
|
185
389
|
---
|
|
186
390
|
|
|
187
|
-
##
|
|
391
|
+
## Example Project Structure
|
|
188
392
|
|
|
189
393
|
```
|
|
190
394
|
project/
|
|
191
|
-
├─
|
|
192
|
-
├─
|
|
193
|
-
│
|
|
194
|
-
│
|
|
195
|
-
│
|
|
196
|
-
|
|
197
|
-
│
|
|
198
|
-
|
|
395
|
+
├─ src/
|
|
396
|
+
│ ├─ assets/
|
|
397
|
+
│ │ └─ images/
|
|
398
|
+
│ │ └─ icons/
|
|
399
|
+
│ │ ├─ calendar.svg
|
|
400
|
+
│ │ └─ search.svg
|
|
401
|
+
│ ├─ data/
|
|
402
|
+
│ │ ├─ articles.json
|
|
403
|
+
│ │ └─ team.json
|
|
404
|
+
│ ├─ partials/
|
|
405
|
+
│ │ ├─ layouts/
|
|
406
|
+
│ │ │ ├─ header.html
|
|
407
|
+
│ │ │ └─ footer.html
|
|
408
|
+
│ │ ├─ sections/
|
|
409
|
+
│ │ │ ├─ hero.html
|
|
410
|
+
│ │ │ └─ features.html
|
|
411
|
+
│ │ └─ components/
|
|
412
|
+
│ │ ├─ card.html
|
|
413
|
+
│ │ └─ button.html
|
|
414
|
+
│ ├─ index.html
|
|
415
|
+
│ └─ about.html
|
|
416
|
+
├─ vite.config.js
|
|
417
|
+
└─ vite-plugin-file-include.js
|
|
199
418
|
```
|
|
200
419
|
|
|
201
|
-
|
|
420
|
+
### Example Files
|
|
421
|
+
|
|
422
|
+
**src/index.html:**
|
|
202
423
|
|
|
203
424
|
```html
|
|
425
|
+
<!DOCTYPE html>
|
|
426
|
+
<html lang="en">
|
|
427
|
+
@@include('partials/layouts/header.html', { "title": "Home - My Site",
|
|
428
|
+
"metaDescription": "Welcome to our site" })
|
|
429
|
+
<body>
|
|
430
|
+
@@include('partials/sections/hero.html')
|
|
431
|
+
|
|
432
|
+
<section class="articles">
|
|
433
|
+
<h2>Latest Articles</h2>
|
|
434
|
+
@@loop('partials/components/card.html', 'data/articles.json')
|
|
435
|
+
</section>
|
|
436
|
+
|
|
437
|
+
@@if(showTeamSection) {
|
|
438
|
+
<section class="team">
|
|
439
|
+
<h2>Our Team</h2>
|
|
440
|
+
@@loop('partials/components/team-member.html', 'data/team.json')
|
|
441
|
+
</section>
|
|
442
|
+
} @@include('partials/layouts/footer.html')
|
|
443
|
+
</body>
|
|
444
|
+
</html>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**src/partials/components/card.html:**
|
|
448
|
+
|
|
449
|
+
```html
|
|
450
|
+
<article class="card">
|
|
451
|
+
<div class="card-icon">{{ loadSvg(icon, 'icon-large') }}</div>
|
|
452
|
+
<h3>{{ title }}</h3>
|
|
453
|
+
<p>{{ truncate(description, 120) }}</p>
|
|
454
|
+
<time>{{ formatDate(date) }}</time>
|
|
455
|
+
</article>
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**src/data/articles.json:**
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
[
|
|
462
|
+
{
|
|
463
|
+
"icon": "calendar.svg",
|
|
464
|
+
"title": "Getting Started",
|
|
465
|
+
"description": "Learn how to set up your first project with this comprehensive guide covering all the basics you need to know.",
|
|
466
|
+
"date": "2025-01-15"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"icon": "search.svg",
|
|
470
|
+
"title": "Advanced Features",
|
|
471
|
+
"description": "Explore powerful features and best practices for building scalable applications.",
|
|
472
|
+
"date": "2025-01-20"
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## 🛡️ Error Handling & Security
|
|
480
|
+
|
|
481
|
+
### Circular Include Detection
|
|
482
|
+
|
|
483
|
+
The plugin automatically detects and prevents circular includes:
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
⚠️ Circular include detected: /path/to/file.html
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Error Messages
|
|
490
|
+
|
|
491
|
+
Detailed error logging for common issues:
|
|
492
|
+
|
|
493
|
+
- **Missing files:** `Failed to include file: /path/to/missing.html`
|
|
494
|
+
- **Invalid JSON:** `Failed to parse JSON data: {...}`
|
|
495
|
+
- **Failed expressions:** `Failed to evaluate expression: invalidVar`
|
|
496
|
+
- **Missing SVGs:** `SVG file not found at path: /path/to/icon.svg`
|
|
497
|
+
|
|
498
|
+
### Security Note
|
|
499
|
+
|
|
500
|
+
The plugin uses `new Function()` to evaluate expressions. Only use trusted content in your templates and avoid user-generated input in template expressions.
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## 📚 Usage Examples
|
|
505
|
+
|
|
506
|
+
### Basic Template Usage
|
|
507
|
+
|
|
508
|
+
**Simple page with includes:**
|
|
509
|
+
|
|
510
|
+
```html
|
|
511
|
+
<!DOCTYPE html>
|
|
204
512
|
<html>
|
|
513
|
+
@@include('partials/head.html', { "pageTitle": "Home" })
|
|
205
514
|
<body>
|
|
206
|
-
@@include('partials/header.html'
|
|
207
|
-
|
|
208
|
-
|
|
515
|
+
@@include('partials/header.html')
|
|
516
|
+
<main>
|
|
517
|
+
<h1>{{ pageTitle }}</h1>
|
|
518
|
+
</main>
|
|
519
|
+
@@include('partials/footer.html')
|
|
209
520
|
</body>
|
|
210
521
|
</html>
|
|
211
522
|
```
|
|
212
523
|
|
|
524
|
+
### Loop with JSON File
|
|
525
|
+
|
|
526
|
+
**Display blog posts:**
|
|
527
|
+
|
|
528
|
+
```html
|
|
529
|
+
<section class="blog">
|
|
530
|
+
<h2>Latest Posts</h2>
|
|
531
|
+
<div class="posts-grid">
|
|
532
|
+
@@loop('partials/post-card.html', 'data/posts.json')
|
|
533
|
+
</div>
|
|
534
|
+
</section>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**data/posts.json:**
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
[
|
|
541
|
+
{
|
|
542
|
+
"title": "Getting Started with Vite",
|
|
543
|
+
"excerpt": "Learn the basics of Vite...",
|
|
544
|
+
"author": "John Doe",
|
|
545
|
+
"date": "2025-01-15",
|
|
546
|
+
"image": "vite-intro.jpg"
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**partials/post-card.html:**
|
|
552
|
+
|
|
553
|
+
```html
|
|
554
|
+
<article class="post-card">
|
|
555
|
+
<img src="/images/{{ image }}" alt="{{ title }}" />
|
|
556
|
+
<h3>{{ title }}</h3>
|
|
557
|
+
<p>{{ excerpt }}</p>
|
|
558
|
+
<div class="meta">
|
|
559
|
+
<span>{{ author }}</span>
|
|
560
|
+
<time>{{ formatDate(date) }}</time>
|
|
561
|
+
</div>
|
|
562
|
+
</article>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Inline Loop Data
|
|
566
|
+
|
|
567
|
+
**Quick lists without JSON files:**
|
|
568
|
+
|
|
569
|
+
```html
|
|
570
|
+
<ul class="features">
|
|
571
|
+
@@loop('partials/feature-item.html', [ { "icon": "speed.svg", "title": "Fast",
|
|
572
|
+
"desc": "Lightning quick builds" }, { "icon": "power.svg", "title":
|
|
573
|
+
"Powerful", "desc": "Full ES6+ support" }, { "icon": "simple.svg", "title":
|
|
574
|
+
"Simple", "desc": "Easy configuration" } ])
|
|
575
|
+
</ul>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Conditional Rendering
|
|
579
|
+
|
|
580
|
+
**Show/hide sections based on config:**
|
|
581
|
+
|
|
582
|
+
```html
|
|
583
|
+
@@if(showBanner) {
|
|
584
|
+
<div class="banner">
|
|
585
|
+
<p>{{ bannerMessage }}</p>
|
|
586
|
+
</div>
|
|
587
|
+
} @@if(userLoggedIn) { @@include('partials/dashboard.html') }
|
|
588
|
+
@@if(!userLoggedIn) { @@include('partials/login-form.html') }
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Custom Functions in Templates
|
|
592
|
+
|
|
593
|
+
**Using helper functions:**
|
|
594
|
+
|
|
595
|
+
```html
|
|
596
|
+
<header>
|
|
597
|
+
<h1>{{ uppercase(siteName) }}</h1>
|
|
598
|
+
<p>© {{ currentYear() }} All rights reserved</p>
|
|
599
|
+
</header>
|
|
600
|
+
|
|
601
|
+
<article>
|
|
602
|
+
<p>{{ truncate(longDescription, 150) }}</p>
|
|
603
|
+
<time>{{ formatDate(publishDate) }}</time>
|
|
604
|
+
</article>
|
|
605
|
+
|
|
606
|
+
<button>{{ loadSvg('icons/search.svg', 'icon-sm') }} Search</button>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Nested Includes
|
|
610
|
+
|
|
611
|
+
**Build complex layouts:**
|
|
612
|
+
|
|
613
|
+
```html
|
|
614
|
+
<!-- index.html -->
|
|
615
|
+
@@include('layouts/base.html', { "pageClass": "home-page", "contentFile":
|
|
616
|
+
"sections/home-content.html" })
|
|
617
|
+
|
|
618
|
+
<!-- layouts/base.html -->
|
|
619
|
+
<!DOCTYPE html>
|
|
620
|
+
<html>
|
|
621
|
+
@@include('partials/head.html')
|
|
622
|
+
<body class="{{ pageClass }}">
|
|
623
|
+
@@include('partials/header.html')
|
|
624
|
+
<main>@@include(contentFile)</main>
|
|
625
|
+
@@include('partials/footer.html')
|
|
626
|
+
</body>
|
|
627
|
+
</html>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Dynamic SVG Icons
|
|
631
|
+
|
|
632
|
+
**Load SVG icons inline:**
|
|
633
|
+
|
|
634
|
+
```html
|
|
635
|
+
<!-- Configuration in vite.config.js -->
|
|
636
|
+
customFunctions: { loadSvg: (file, classes = '') => { const svgPath =
|
|
637
|
+
path.join(__dirname, 'src/assets/icons', file); const svg =
|
|
638
|
+
fs.readFileSync(svgPath, 'utf-8') .replace(/<\?xml.*?\?>/g, '') .trim(); return
|
|
639
|
+
`<span class="icon ${classes}">${svg}</span>`; } }
|
|
640
|
+
|
|
641
|
+
<!-- Usage in HTML -->
|
|
642
|
+
<nav>
|
|
643
|
+
<a href="/">{{ loadSvg('home.svg', 'nav-icon') }} Home</a>
|
|
644
|
+
<a href="/about">{{ loadSvg('info.svg', 'nav-icon') }} About</a>
|
|
645
|
+
<a href="/contact">{{ loadSvg('mail.svg', 'nav-icon') }} Contact</a>
|
|
646
|
+
</nav>
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Combining Features
|
|
650
|
+
|
|
651
|
+
**Complex page structure:**
|
|
652
|
+
|
|
653
|
+
```html
|
|
654
|
+
<!DOCTYPE html>
|
|
655
|
+
<html>
|
|
656
|
+
@@include('partials/head.html', { "title": "Products" })
|
|
657
|
+
<body>
|
|
658
|
+
@@include('partials/header.html')
|
|
659
|
+
|
|
660
|
+
<main>
|
|
661
|
+
<section class="hero">
|
|
662
|
+
@@include('sections/hero.html', { "heading": "Our Products",
|
|
663
|
+
"subheading": "Discover amazing solutions" })
|
|
664
|
+
</section>
|
|
665
|
+
|
|
666
|
+
@@if(showFeatured) {
|
|
667
|
+
<section class="featured">
|
|
668
|
+
<h2>Featured Products</h2>
|
|
669
|
+
@@loop('partials/product-card.html', 'data/featured.json')
|
|
670
|
+
</section>
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
<section class="all-products">
|
|
674
|
+
<h2>All Products</h2>
|
|
675
|
+
<div class="product-grid">
|
|
676
|
+
@@loop('partials/product-card.html', 'data/products.json')
|
|
677
|
+
</div>
|
|
678
|
+
</section>
|
|
679
|
+
|
|
680
|
+
@@if(showNewsletter) { @@include('sections/newsletter.html') }
|
|
681
|
+
</main>
|
|
682
|
+
|
|
683
|
+
@@include('partials/footer.html')
|
|
684
|
+
</body>
|
|
685
|
+
</html>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
## 💡 Tips & Best Practices
|
|
689
|
+
|
|
690
|
+
1. **Organize partials by purpose:**
|
|
691
|
+
|
|
692
|
+
- `layouts/` for page structure (header, footer)
|
|
693
|
+
- `sections/` for page sections (hero, features)
|
|
694
|
+
- `components/` for reusable components (cards, buttons)
|
|
695
|
+
|
|
696
|
+
2. **Use JSON files for large datasets** instead of inline arrays
|
|
697
|
+
|
|
698
|
+
3. **Keep context data in vite.config.js** for site-wide variables
|
|
699
|
+
|
|
700
|
+
4. **Create utility functions** for common operations (date formatting, string manipulation)
|
|
701
|
+
|
|
702
|
+
5. **Use meaningful variable names** in your JSON data
|
|
703
|
+
|
|
704
|
+
6. **Test partials independently** before including them in larger pages
|
|
705
|
+
|
|
706
|
+
7. **Leverage conditionals** to create different layouts for different pages
|
|
707
|
+
|
|
213
708
|
---
|
|
214
709
|
|
|
215
|
-
##
|
|
710
|
+
## 🔧 Advanced Usage
|
|
711
|
+
|
|
712
|
+
### Dynamic SVG Icons
|
|
216
713
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
|
|
714
|
+
```html
|
|
715
|
+
<button class="btn">
|
|
716
|
+
{{ loadSvg('icon-' + buttonType + '.svg', 'btn-icon') }} {{ buttonLabel }}
|
|
717
|
+
</button>
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Nested Loops
|
|
721
|
+
|
|
722
|
+
```html
|
|
723
|
+
@@loop('partials/category.html', 'data/categories.json')
|
|
724
|
+
|
|
725
|
+
<!-- partials/category.html -->
|
|
726
|
+
<div class="category">
|
|
727
|
+
<h3>{{ name }}</h3>
|
|
728
|
+
@@loop('partials/item.html', items)
|
|
729
|
+
</div>
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Conditional Loops
|
|
733
|
+
|
|
734
|
+
```html
|
|
735
|
+
@@if(articles && articles.length > 0) {
|
|
736
|
+
<div class="articles-grid">
|
|
737
|
+
@@loop('partials/article.html', 'data/articles.json')
|
|
738
|
+
</div>
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
---
|
|
221
743
|
|
|
222
|
-
|
|
744
|
+
## 🤝 Contributing
|
|
745
|
+
|
|
746
|
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
|
223
747
|
|
|
224
748
|
---
|
|
225
749
|
|
|
226
750
|
## 📄 License
|
|
227
751
|
|
|
228
752
|
MIT © 2025
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## 🔗 Related
|
|
757
|
+
|
|
758
|
+
- [Vite](https://vitejs.dev/)
|
|
759
|
+
- [Vite Plugin API](https://vitejs.dev/guide/api-plugin.html)
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
**Made with ❤️ for the Vite community**
|