@webmate-studio/builder 0.2.19 → 0.2.21
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/package.json +1 -1
- package/src/bundler.js +45 -25
- package/src/html-cleaner.js +22 -13
package/package.json
CHANGED
package/src/bundler.js
CHANGED
|
@@ -3,6 +3,17 @@ import esbuildSvelte from 'esbuild-svelte';
|
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate content hash for asset file
|
|
10
|
+
* @param {Buffer} content - File content as buffer
|
|
11
|
+
* @param {number} length - Hash length (default: 8)
|
|
12
|
+
* @returns {string} - Content hash (hex)
|
|
13
|
+
*/
|
|
14
|
+
function generateAssetHash(content, length = 8) {
|
|
15
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, length);
|
|
16
|
+
}
|
|
6
17
|
|
|
7
18
|
/**
|
|
8
19
|
* Bundle island JavaScript files with esbuild
|
|
@@ -71,39 +82,48 @@ export async function bundleIsland(islandPath, outputPath, options = {}) {
|
|
|
71
82
|
'__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': 'false'
|
|
72
83
|
},
|
|
73
84
|
plugins: [
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// -
|
|
78
|
-
//
|
|
85
|
+
// Asset URL resolver with content hashing
|
|
86
|
+
// Instead of base64 encoding (bad for large images, no caching, huge bundles),
|
|
87
|
+
// we use asset markers with content hashes that get resolved at runtime:
|
|
88
|
+
// - wm dev: /assets/{componentName}/{filename} (no hash in dev)
|
|
89
|
+
// - CMS: /api/components/proxy/organization-components/{uuid}/assets/{filename}.{hash}.{ext}
|
|
79
90
|
{
|
|
80
|
-
name: '
|
|
91
|
+
name: 'asset-url-resolver',
|
|
81
92
|
setup(build) {
|
|
82
93
|
// Handle all image formats
|
|
83
94
|
build.onLoad({ filter: /\.(svg|jpg|jpeg|png|gif|webp)$/i }, async (args) => {
|
|
84
|
-
|
|
85
|
-
const mimeTypes = {
|
|
86
|
-
svg: 'image/svg+xml',
|
|
87
|
-
jpg: 'image/jpeg',
|
|
88
|
-
jpeg: 'image/jpeg',
|
|
89
|
-
png: 'image/png',
|
|
90
|
-
gif: 'image/gif',
|
|
91
|
-
webp: 'image/webp'
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Read file as binary buffer
|
|
95
|
+
// Read file to generate content hash
|
|
95
96
|
const buffer = await fs.readFile(args.path);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
const hash = generateAssetHash(buffer);
|
|
98
|
+
|
|
99
|
+
// Get relative path from component directory
|
|
100
|
+
// Example: /full/path/components/Hero/assets/logo.svg
|
|
101
|
+
// → Find "assets/" segment and extract path from there
|
|
102
|
+
const assetPath = args.path;
|
|
103
|
+
const assetsIndex = assetPath.lastIndexOf('/assets/');
|
|
104
|
+
|
|
105
|
+
if (assetsIndex === -1) {
|
|
106
|
+
throw new Error(`Asset must be in "assets/" directory: ${assetPath}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Extract relative path from assets/ onwards
|
|
110
|
+
// Example: assets/logo.svg
|
|
111
|
+
const relativePath = assetPath.substring(assetsIndex + 1); // +1 to keep "assets/"
|
|
112
|
+
|
|
113
|
+
// Parse filename and extension
|
|
114
|
+
const filename = path.basename(relativePath);
|
|
115
|
+
const ext = path.extname(filename);
|
|
116
|
+
const nameWithoutExt = path.basename(filename, ext);
|
|
117
|
+
const dir = path.dirname(relativePath);
|
|
99
118
|
|
|
100
|
-
// Build
|
|
101
|
-
const
|
|
119
|
+
// Build hashed filename: logo.abc123.svg
|
|
120
|
+
const hashedFilename = `${nameWithoutExt}.${hash}${ext}`;
|
|
121
|
+
const hashedPath = path.join(dir, hashedFilename);
|
|
102
122
|
|
|
103
|
-
// Return as
|
|
104
|
-
//
|
|
123
|
+
// Return as asset marker (will be resolved at runtime)
|
|
124
|
+
// Runtime resolver will replace __ASSET__: with correct base path
|
|
105
125
|
return {
|
|
106
|
-
contents: `export default
|
|
126
|
+
contents: `export default "__ASSET__:${hashedPath}";`,
|
|
107
127
|
loader: 'js'
|
|
108
128
|
};
|
|
109
129
|
});
|
package/src/html-cleaner.js
CHANGED
|
@@ -32,8 +32,11 @@ export function cleanComponentHTML(html, islands = []) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Transform PascalCase island elements to
|
|
36
|
-
* Example: <SwiperTest title={title} /> → <
|
|
35
|
+
* Transform PascalCase island elements to Custom Elements (Web Components)
|
|
36
|
+
* Example: <SwiperTest title={title} /> → <swiper-test title="{title}"></swiper-test>
|
|
37
|
+
*
|
|
38
|
+
* Islands with <svelte:options customElement> self-register as Custom Elements,
|
|
39
|
+
* so we convert to kebab-case tags with inline props.
|
|
37
40
|
*
|
|
38
41
|
* @param {string} html - Original HTML
|
|
39
42
|
* @param {Array<string>} availableIslands - List of available island names
|
|
@@ -48,8 +51,16 @@ function transformIslandsToDataAttributes(html, availableIslands = []) {
|
|
|
48
51
|
return match; // Not an island, keep as-is
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
//
|
|
52
|
-
|
|
54
|
+
// Convert island name to kebab-case for Custom Element tag
|
|
55
|
+
// SwiperTest → swiper-test
|
|
56
|
+
// MyAwesomeComponent → my-awesome-component
|
|
57
|
+
const kebabTag = tagName
|
|
58
|
+
.replace(/([A-Z])/g, '-$1')
|
|
59
|
+
.toLowerCase()
|
|
60
|
+
.replace(/^-/, '');
|
|
61
|
+
|
|
62
|
+
// Parse attributes and convert to HTML attributes
|
|
63
|
+
const attrs = [];
|
|
53
64
|
|
|
54
65
|
// Match prop={value} or prop="value" patterns
|
|
55
66
|
const attrPattern = /([a-z][a-zA-Z0-9]*)\s*=\s*(?:\{([^}]+)\}|"([^"]*)"|'([^']*)')/g;
|
|
@@ -62,17 +73,15 @@ function transformIslandsToDataAttributes(html, availableIslands = []) {
|
|
|
62
73
|
|
|
63
74
|
// If it was in curly braces, keep the braces for runtime evaluation
|
|
64
75
|
// Otherwise it's a literal string
|
|
65
|
-
|
|
66
|
-
}
|
|
76
|
+
const value = attrMatch[2] ? `{${propValue}}` : propValue;
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Serialize props to JSON (escape quotes)
|
|
72
|
-
const propsJson = JSON.stringify(props).replace(/"/g, '"');
|
|
78
|
+
// Add as HTML attribute
|
|
79
|
+
attrs.push(`${propName}="${value}"`);
|
|
80
|
+
}
|
|
73
81
|
|
|
74
|
-
// Generate
|
|
75
|
-
|
|
82
|
+
// Generate Custom Element tag
|
|
83
|
+
const attrsString = attrs.length > 0 ? ' ' + attrs.join(' ') : '';
|
|
84
|
+
return `<${kebabTag}${attrsString}></${kebabTag}>`;
|
|
76
85
|
});
|
|
77
86
|
}
|
|
78
87
|
|