vite-plugin-html-elements 0.0.9
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/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +97 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vincent Medina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# 💧 vite-plugin-html-elements
|
|
2
|
+
|
|
3
|
+
> Modular HTML without the JavaScript
|
|
4
|
+
|
|
5
|
+
A lightweight Vite plugin for composing reusable HTML elements in static sites. Keep your HTML DRY and maintainable without adding runtime JavaScript.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- **Zero JavaScript** - Pure static HTML output
|
|
10
|
+
- **Hot Reload** - Instant updates during development
|
|
11
|
+
- **Intuitive Syntax** - Natural self-closing `<element />` tags
|
|
12
|
+
- **Shorthand Support** - `<element src="header.html" />`
|
|
13
|
+
- **TypeScript** - Full type safety
|
|
14
|
+
- **Tiny** - Minimal footprint, maximum impact
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -D vite-plugin-html-elements
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 🚀 Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Add to your Vite config
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
// vite.config.js
|
|
28
|
+
import { defineConfig } from 'vite';
|
|
29
|
+
import { htmlElements } from 'vite-plugin-html-elements';
|
|
30
|
+
|
|
31
|
+
export default defineConfig({
|
|
32
|
+
plugins: [htmlElements()],
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Create your project structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
project/
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── elements/
|
|
42
|
+
│ │ ├── header.html
|
|
43
|
+
│ │ └── footer.html
|
|
44
|
+
│ └── index.html
|
|
45
|
+
└── vite.config.js
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Create reusable elements
|
|
49
|
+
|
|
50
|
+
**src/elements/header.html:**
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<header>
|
|
54
|
+
<nav>
|
|
55
|
+
<a href="/">Home</a>
|
|
56
|
+
<a href="/about">About</a>
|
|
57
|
+
<a href="/contact">Contact</a>
|
|
58
|
+
</nav>
|
|
59
|
+
</header>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. Use elements in your pages
|
|
63
|
+
|
|
64
|
+
**src/index.html:**
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<!doctype html>
|
|
68
|
+
<html lang="en">
|
|
69
|
+
<head>
|
|
70
|
+
<title>My Site</title>
|
|
71
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<element src="header.html" />
|
|
75
|
+
|
|
76
|
+
<main>
|
|
77
|
+
<h1>Welcome to my site!</h1>
|
|
78
|
+
</main>
|
|
79
|
+
|
|
80
|
+
<element src="footer.html" />
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 5. Build
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run dev # Development with hot reload
|
|
89
|
+
npm run build # Production build
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The output is pure static HTML with all elements inlined - no `<element>` tags remain.
|
|
93
|
+
|
|
94
|
+
## 📝 Syntax
|
|
95
|
+
|
|
96
|
+
### Shorthand (Recommended)
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<element src="header.html" />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Automatically resolves to `elements/header.html`
|
|
103
|
+
|
|
104
|
+
### Explicit Paths
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<element src="elements/nav/mobile.html" />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## ⚙️ Configuration
|
|
111
|
+
|
|
112
|
+
### Debug Mode
|
|
113
|
+
|
|
114
|
+
Enable verbose logging to see which elements are being included:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
htmlElements({ debug: true });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Output:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
💧 Element included: elements/header.html
|
|
124
|
+
💧 Element included: elements/footer.html
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 📁 Project Structure
|
|
128
|
+
|
|
129
|
+
Organize your project however you prefer. Here are some common patterns:
|
|
130
|
+
|
|
131
|
+
### Simple Structure
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
project/
|
|
135
|
+
├── src/
|
|
136
|
+
│ ├── elements/
|
|
137
|
+
│ │ ├── header.html
|
|
138
|
+
│ │ └── footer.html
|
|
139
|
+
│ ├── index.html
|
|
140
|
+
│ └── about.html
|
|
141
|
+
└── vite.config.js
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Organized Structure
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
project/
|
|
148
|
+
├── src/
|
|
149
|
+
│ ├── elements/
|
|
150
|
+
│ │ ├── head.html
|
|
151
|
+
│ │ ├── header.html
|
|
152
|
+
│ │ ├── footer.html
|
|
153
|
+
│ │ └── nav/
|
|
154
|
+
│ │ ├── mobile.html
|
|
155
|
+
│ │ └── desktop.html
|
|
156
|
+
│ ├── index.html
|
|
157
|
+
│ ├── about.html
|
|
158
|
+
│ └── styles.css
|
|
159
|
+
├── public/
|
|
160
|
+
│ └── favicon.ico
|
|
161
|
+
└── vite.config.js
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 🎯 Use Cases
|
|
165
|
+
|
|
166
|
+
Perfect for:
|
|
167
|
+
|
|
168
|
+
- 📄 Landing pages
|
|
169
|
+
- 📝 Documentation sites
|
|
170
|
+
- 📰 Blogs
|
|
171
|
+
- 🎨 Marketing sites
|
|
172
|
+
- 🏢 Company websites
|
|
173
|
+
- 🚀 Any multi-page static site
|
|
174
|
+
|
|
175
|
+
Ideal when you want:
|
|
176
|
+
|
|
177
|
+
- Reusable HTML components
|
|
178
|
+
- No build complexity
|
|
179
|
+
- Progressive enhancement
|
|
180
|
+
- Fast, accessible sites
|
|
181
|
+
- SEO-friendly output
|
|
182
|
+
|
|
183
|
+
## 💡 Philosophy
|
|
184
|
+
|
|
185
|
+
HTML and CSS are powerful. JavaScript is optional.
|
|
186
|
+
|
|
187
|
+
This plugin embraces the web platform by making static HTML modular and maintainable without introducing runtime dependencies or complex tooling.
|
|
188
|
+
|
|
189
|
+
Start with solid HTML, add CSS for style, and layer JavaScript only where it adds value.
|
|
190
|
+
|
|
191
|
+
## 📚 Examples
|
|
192
|
+
|
|
193
|
+
### Shared Head Element
|
|
194
|
+
|
|
195
|
+
**src/elements/head.html:**
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<head>
|
|
199
|
+
<meta charset="UTF-8" />
|
|
200
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
201
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
202
|
+
<link rel="icon" href="/favicon.ico" />
|
|
203
|
+
</head>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**src/index.html:**
|
|
207
|
+
|
|
208
|
+
```html
|
|
209
|
+
<!doctype html>
|
|
210
|
+
<html lang="en">
|
|
211
|
+
<element src="head.html" />
|
|
212
|
+
<body>
|
|
213
|
+
<!-- content -->
|
|
214
|
+
</body>
|
|
215
|
+
</html>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Component Library
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
elements/
|
|
222
|
+
├── buttons/
|
|
223
|
+
│ ├── primary.html
|
|
224
|
+
│ └── secondary.html
|
|
225
|
+
├── cards/
|
|
226
|
+
│ ├── product.html
|
|
227
|
+
│ └── blog.html
|
|
228
|
+
└── layouts/
|
|
229
|
+
├── header.html
|
|
230
|
+
└── footer.html
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```html
|
|
234
|
+
<element src="buttons/primary.html" /> <element src="cards/product.html" />
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## ❓ FAQ
|
|
238
|
+
|
|
239
|
+
**Can I use relative paths?**
|
|
240
|
+
Yes! Paths are resolved relative to the HTML file containing the `<element />` tag.
|
|
241
|
+
|
|
242
|
+
**Does it work with other Vite plugins?**
|
|
243
|
+
Yes! Fully compatible with Tailwind CSS and other Vite plugins.
|
|
244
|
+
|
|
245
|
+
**Can elements include JavaScript?**
|
|
246
|
+
Elements are just HTML. You can include `<script>` tags in your elements if needed.
|
|
247
|
+
|
|
248
|
+
**What about dynamic content?**
|
|
249
|
+
This plugin is for static composition at build time. For dynamic content, layer on JavaScript after your HTML is built.
|
|
250
|
+
|
|
251
|
+
**Do I need Node.js at runtime?**
|
|
252
|
+
No! The plugin runs at build time. The output is pure static HTML that works anywhere.
|
|
253
|
+
|
|
254
|
+
## 🤝 Contributing
|
|
255
|
+
|
|
256
|
+
Contributions welcome! Please open an issue or PR on GitHub.
|
|
257
|
+
|
|
258
|
+
## 📄 License
|
|
259
|
+
|
|
260
|
+
MIT
|
|
261
|
+
|
|
262
|
+
## 🔗 Links
|
|
263
|
+
|
|
264
|
+
- [Documentation](https://htmlelements.dev)
|
|
265
|
+
- [GitHub](https://codeberg.org/nexusocean/vite-plugin-html-elements)
|
|
266
|
+
- [npm](https://www.npmjs.com/package/vite-plugin-html-elements)
|
|
267
|
+
- [Issues](https://codeberg.org/nexusocean/vite-plugin-html-elements/issues)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export interface HtmlElementsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Enable debug logging
|
|
5
|
+
* @default false
|
|
6
|
+
*/
|
|
7
|
+
debug: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function htmlElements(options?: HtmlElementsOptions): Plugin;
|
|
10
|
+
export declare function getHtmlEntries(srcDir: string): Record<string, string>;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA6B,MAAM,MAAM,CAAC;AAI9D,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAsC,GAC9C,MAAM,CA8BR;AAgED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiBrE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
export function htmlElements(options = { debug: false }) {
|
|
4
|
+
const { debug } = options;
|
|
5
|
+
return {
|
|
6
|
+
name: 'vite-plugin-html-elements',
|
|
7
|
+
config() {
|
|
8
|
+
const srcDir = 'src';
|
|
9
|
+
const resolvedSrcDir = resolve(process.cwd(), srcDir);
|
|
10
|
+
return {
|
|
11
|
+
root: srcDir,
|
|
12
|
+
publicDir: '../public',
|
|
13
|
+
build: {
|
|
14
|
+
outDir: '../dist',
|
|
15
|
+
emptyOutDir: true,
|
|
16
|
+
rollupOptions: {
|
|
17
|
+
input: getHtmlEntries(resolvedSrcDir),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
transformIndexHtml: {
|
|
23
|
+
order: 'pre',
|
|
24
|
+
handler(html, _ctx) {
|
|
25
|
+
return processElements(html, debug);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function processElements(html, debug) {
|
|
31
|
+
const pattern = /<element\s+src=["']([^"']+)["']([^>]*?)(?:\/>|>([\s\S]*?)<\/element>)/g;
|
|
32
|
+
return html.replace(pattern, (_match, filepath, attributes, slotContent = '') => {
|
|
33
|
+
let resolvedPath = filepath;
|
|
34
|
+
if (!filepath.includes('elements/') && filepath.includes('.html')) {
|
|
35
|
+
resolvedPath = `elements/${filepath}`;
|
|
36
|
+
}
|
|
37
|
+
const srcDir = resolve(process.cwd(), 'src');
|
|
38
|
+
const fullPath = resolve(srcDir, resolvedPath);
|
|
39
|
+
if (!existsSync(fullPath)) {
|
|
40
|
+
console.error(`❌ Element not found: ${filepath}`);
|
|
41
|
+
if (debug) {
|
|
42
|
+
console.error(` Tried path: ${fullPath}`);
|
|
43
|
+
}
|
|
44
|
+
return `<!-- Element error: Could not load ${filepath} -->`;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
let content = readFileSync(fullPath, 'utf-8');
|
|
48
|
+
// Parse props from attributes
|
|
49
|
+
const props = parseAttributes(attributes);
|
|
50
|
+
// Replace props: {{propName}}
|
|
51
|
+
content = content.replace(/\{\{(\w+)\}\}/g, (_, key) => props[key] || '');
|
|
52
|
+
// Replace slot
|
|
53
|
+
if (slotContent.trim()) {
|
|
54
|
+
content = content.replace(/<slot\s*\/>/g, slotContent);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
content = content.replace(/<slot\s*\/>/g, '');
|
|
58
|
+
}
|
|
59
|
+
if (debug) {
|
|
60
|
+
console.log(`💧 Element included: ${resolvedPath}${slotContent.trim() ? ' (with slot)' : ''}${Object.keys(props).length ? ` (props: ${Object.keys(props).join(', ')})` : ''}`);
|
|
61
|
+
}
|
|
62
|
+
return content;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
66
|
+
console.error(`❌ Error reading element: ${filepath}`);
|
|
67
|
+
if (debug) {
|
|
68
|
+
console.error(` ${errorMessage}`);
|
|
69
|
+
}
|
|
70
|
+
return `<!-- Element error: Could not load ${filepath} -->`;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function parseAttributes(attrString) {
|
|
75
|
+
const attrs = {};
|
|
76
|
+
const attrPattern = /(\w+)=["']([^"']*)["']/g;
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = attrPattern.exec(attrString)) !== null) {
|
|
79
|
+
attrs[match[1]] = match[2];
|
|
80
|
+
}
|
|
81
|
+
return attrs;
|
|
82
|
+
}
|
|
83
|
+
export function getHtmlEntries(srcDir) {
|
|
84
|
+
try {
|
|
85
|
+
const htmlFiles = readdirSync(srcDir).filter((file) => file.endsWith('.html'));
|
|
86
|
+
const entries = {};
|
|
87
|
+
htmlFiles.forEach((file) => {
|
|
88
|
+
const name = file.replace('.html', '');
|
|
89
|
+
entries[name] = resolve(srcDir, file);
|
|
90
|
+
});
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.warn(`⚠️ Could not read directory: ${srcDir}`);
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-html-elements",
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"description": "Modular HTML without the JavaScript",
|
|
5
|
+
"author": "Vincent Medina",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://codeberg.org/nexusocean/vite-plugin-html-elements"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"vite",
|
|
13
|
+
"vite-plugin",
|
|
14
|
+
"html",
|
|
15
|
+
"elements",
|
|
16
|
+
"static"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://htmlelements.dev",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
31
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
32
|
+
"eslint": "^8.0.0",
|
|
33
|
+
"eslint-config-prettier": "^9.0.0",
|
|
34
|
+
"prettier": "^3.6.2",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"vite": "^7.2.2"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"lint": "eslint src --ext .ts",
|
|
41
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
42
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
43
|
+
"prepublis`hO`nly": "npm run build"
|
|
44
|
+
}
|
|
45
|
+
}
|