html-component-engine 0.1.1 → 0.1.3
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 +36 -36
- package/bin/cli.js +529 -529
- package/package.json +37 -37
- package/src/engine/compiler.js +317 -312
- package/src/engine/utils.js +74 -74
- package/src/index.js +47 -18
package/src/engine/utils.js
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
// Utility functions for the HTML Component Engine
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Parse a self-closing Component tag to extract attributes
|
|
5
|
-
* @param {string} tag - The full component tag, e.g., <Component src="Button" text="Click" />
|
|
6
|
-
* @returns {object|null} - Object with attribute key-value pairs, or null if invalid
|
|
7
|
-
*/
|
|
8
|
-
export function parseSelfClosingComponentTag(tag) {
|
|
9
|
-
const regex = /<Component\s+([^>]+)\s*\/>/;
|
|
10
|
-
const match = tag.match(regex);
|
|
11
|
-
if (!match) return null;
|
|
12
|
-
|
|
13
|
-
const attrs = {};
|
|
14
|
-
// Support hyphenated attributes like data-test, aria-label
|
|
15
|
-
const attrRegex = /([\w-]+)="([^"]*)"/g;
|
|
16
|
-
let attrMatch;
|
|
17
|
-
while ((attrMatch = attrRegex.exec(match[1])) !== null) {
|
|
18
|
-
attrs[attrMatch[1]] = attrMatch[2];
|
|
19
|
-
}
|
|
20
|
-
return attrs;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Parse a Component tag to extract attributes (legacy alias)
|
|
25
|
-
* @param {string} tag - The full component tag
|
|
26
|
-
* @returns {object|null} - Object with attribute key-value pairs, or null if invalid
|
|
27
|
-
*/
|
|
28
|
-
export function parseComponentTag(tag) {
|
|
29
|
-
return parseSelfClosingComponentTag(tag);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parse variants from component HTML
|
|
34
|
-
* Looks for <!-- variants: primary=class1 class2, secondary=class3 -->
|
|
35
|
-
* @param {string} html - The component HTML
|
|
36
|
-
* @returns {object} - Map of variant name to classes
|
|
37
|
-
*/
|
|
38
|
-
export function parseVariants(html) {
|
|
39
|
-
const variants = {};
|
|
40
|
-
// Match HTML comment with variants - use non-greedy match
|
|
41
|
-
const regex = /<!--\s*variants:\s*(.+?)\s*-->/;
|
|
42
|
-
const match = html.match(regex);
|
|
43
|
-
if (match) {
|
|
44
|
-
const variantsStr = match[1];
|
|
45
|
-
const variantPairs = variantsStr.split(',');
|
|
46
|
-
for (const pair of variantPairs) {
|
|
47
|
-
const [name, ...classParts] = pair.split('=');
|
|
48
|
-
const classes = classParts.join('='); // Handle = in class names (unlikely but safe)
|
|
49
|
-
if (name && classes) {
|
|
50
|
-
variants[name.trim()] = classes.trim();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return variants;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Clean unused placeholders from compiled HTML
|
|
59
|
-
* @param {string} html - The compiled HTML
|
|
60
|
-
* @returns {string} - Cleaned HTML
|
|
61
|
-
*/
|
|
62
|
-
export function cleanUnusedPlaceholders(html) {
|
|
63
|
-
// Remove any remaining {{ ... }} placeholders
|
|
64
|
-
return html.replace(/\{\{\s*\w+\s*\}\}/g, '');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Normalize path separators to forward slashes
|
|
69
|
-
* @param {string} p - Path to normalize
|
|
70
|
-
* @returns {string} - Normalized path
|
|
71
|
-
*/
|
|
72
|
-
export function normalizePath(p) {
|
|
73
|
-
return p.replace(/\\/g, '/');
|
|
74
|
-
}
|
|
1
|
+
// Utility functions for the HTML Component Engine
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a self-closing Component tag to extract attributes
|
|
5
|
+
* @param {string} tag - The full component tag, e.g., <Component src="Button" text="Click" />
|
|
6
|
+
* @returns {object|null} - Object with attribute key-value pairs, or null if invalid
|
|
7
|
+
*/
|
|
8
|
+
export function parseSelfClosingComponentTag(tag) {
|
|
9
|
+
const regex = /<Component\s+([^>]+)\s*\/>/;
|
|
10
|
+
const match = tag.match(regex);
|
|
11
|
+
if (!match) return null;
|
|
12
|
+
|
|
13
|
+
const attrs = {};
|
|
14
|
+
// Support hyphenated attributes like data-test, aria-label
|
|
15
|
+
const attrRegex = /([\w-]+)="([^"]*)"/g;
|
|
16
|
+
let attrMatch;
|
|
17
|
+
while ((attrMatch = attrRegex.exec(match[1])) !== null) {
|
|
18
|
+
attrs[attrMatch[1]] = attrMatch[2];
|
|
19
|
+
}
|
|
20
|
+
return attrs;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parse a Component tag to extract attributes (legacy alias)
|
|
25
|
+
* @param {string} tag - The full component tag
|
|
26
|
+
* @returns {object|null} - Object with attribute key-value pairs, or null if invalid
|
|
27
|
+
*/
|
|
28
|
+
export function parseComponentTag(tag) {
|
|
29
|
+
return parseSelfClosingComponentTag(tag);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse variants from component HTML
|
|
34
|
+
* Looks for <!-- variants: primary=class1 class2, secondary=class3 -->
|
|
35
|
+
* @param {string} html - The component HTML
|
|
36
|
+
* @returns {object} - Map of variant name to classes
|
|
37
|
+
*/
|
|
38
|
+
export function parseVariants(html) {
|
|
39
|
+
const variants = {};
|
|
40
|
+
// Match HTML comment with variants - use non-greedy match
|
|
41
|
+
const regex = /<!--\s*variants:\s*(.+?)\s*-->/;
|
|
42
|
+
const match = html.match(regex);
|
|
43
|
+
if (match) {
|
|
44
|
+
const variantsStr = match[1];
|
|
45
|
+
const variantPairs = variantsStr.split(',');
|
|
46
|
+
for (const pair of variantPairs) {
|
|
47
|
+
const [name, ...classParts] = pair.split('=');
|
|
48
|
+
const classes = classParts.join('='); // Handle = in class names (unlikely but safe)
|
|
49
|
+
if (name && classes) {
|
|
50
|
+
variants[name.trim()] = classes.trim();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return variants;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Clean unused placeholders from compiled HTML
|
|
59
|
+
* @param {string} html - The compiled HTML
|
|
60
|
+
* @returns {string} - Cleaned HTML
|
|
61
|
+
*/
|
|
62
|
+
export function cleanUnusedPlaceholders(html) {
|
|
63
|
+
// Remove any remaining {{ ... }} placeholders
|
|
64
|
+
return html.replace(/\{\{\s*\w+\s*\}\}/g, '');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Normalize path separators to forward slashes
|
|
69
|
+
* @param {string} p - Path to normalize
|
|
70
|
+
* @returns {string} - Normalized path
|
|
71
|
+
*/
|
|
72
|
+
export function normalizePath(p) {
|
|
73
|
+
return p.replace(/\\/g, '/');
|
|
74
|
+
}
|
package/src/index.js
CHANGED
|
@@ -198,8 +198,8 @@ export default function htmlComponentEngine(options = {}) {
|
|
|
198
198
|
console.log(` ✓ Compiled: ${htmlFile} → ${outputFileName}`);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
// Copy ALL
|
|
202
|
-
await
|
|
201
|
+
// Copy ALL non-HTML files from src/ to dist/ (excluding components/)
|
|
202
|
+
await copyAllNonComponentFiles(srcRoot, componentsDir, this);
|
|
203
203
|
},
|
|
204
204
|
|
|
205
205
|
/**
|
|
@@ -247,33 +247,62 @@ async function getHtmlFiles(dir, base = '', componentsDir = 'components') {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
/**
|
|
250
|
-
* Copy ALL
|
|
251
|
-
*
|
|
250
|
+
* Copy ALL non-HTML files under src/ to dist/ preserving paths
|
|
251
|
+
* Excludes any directory named componentsDir (defaults to "components").
|
|
252
|
+
* @param {string} srcPath - src directory path
|
|
253
|
+
* @param {string} componentsDir - directory name to exclude
|
|
252
254
|
* @param {object} context - Rollup plugin context
|
|
253
255
|
*/
|
|
254
|
-
async function
|
|
255
|
-
|
|
256
|
-
await fs.access(assetsPath);
|
|
257
|
-
} catch {
|
|
258
|
-
console.log(' No assets directory found, skipping asset copy');
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
256
|
+
async function copyAllNonComponentFiles(srcPath, componentsDir = 'components', context) {
|
|
257
|
+
const allFiles = await getAllFilesExcludingDir(srcPath, componentsDir);
|
|
261
258
|
|
|
262
|
-
|
|
259
|
+
let copiedCount = 0;
|
|
260
|
+
for (const file of allFiles) {
|
|
261
|
+
const relativePath = path.relative(srcPath, file).replace(/\\/g, '/');
|
|
263
262
|
|
|
264
|
-
|
|
263
|
+
// HTML is handled separately (compiled + emitted)
|
|
264
|
+
if (relativePath.toLowerCase().endsWith('.html')) continue;
|
|
265
265
|
|
|
266
|
-
for (const file of assetFiles) {
|
|
267
|
-
const relativePath = path.relative(assetsPath, file);
|
|
268
266
|
const content = await fs.readFile(file);
|
|
269
|
-
|
|
270
|
-
// Copy to dist/assets/ subdirectory
|
|
271
267
|
context.emitFile({
|
|
272
268
|
type: 'asset',
|
|
273
|
-
fileName:
|
|
269
|
+
fileName: relativePath,
|
|
274
270
|
source: content,
|
|
275
271
|
});
|
|
272
|
+
copiedCount++;
|
|
276
273
|
}
|
|
274
|
+
|
|
275
|
+
console.log(` Copied ${copiedCount} non-HTML file(s)`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get all files recursively from a directory, excluding a directory name.
|
|
280
|
+
* @param {string} dir - Directory to search
|
|
281
|
+
* @param {string} excludedDirName - Directory name to exclude (e.g., "components")
|
|
282
|
+
* @returns {Promise<string[]>} - Array of absolute file paths
|
|
283
|
+
*/
|
|
284
|
+
async function getAllFilesExcludingDir(dir, excludedDirName) {
|
|
285
|
+
const files = [];
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
289
|
+
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
const fullPath = path.join(dir, entry.name);
|
|
292
|
+
|
|
293
|
+
if (entry.isDirectory()) {
|
|
294
|
+
if (entry.name === excludedDirName) continue;
|
|
295
|
+
const subFiles = await getAllFilesExcludingDir(fullPath, excludedDirName);
|
|
296
|
+
files.push(...subFiles);
|
|
297
|
+
} else {
|
|
298
|
+
files.push(fullPath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.warn(`Could not read directory ${dir}: ${error.message}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return files;
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
/**
|