@webmate-studio/builder 0.2.168 → 0.2.170
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/build-service.js +24 -6
- package/package.json +1 -1
- package/src/bundler.js +8 -2
- package/src/html-cleaner.js +24 -3
- package/src/index.js +2 -2
package/build-service.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import http from 'http';
|
|
9
9
|
import { generateComponentCSS } from './src/tailwind-generator.js';
|
|
10
10
|
import { bundleIsland } from './src/bundler.js';
|
|
11
|
-
import { cleanComponentHTML } from './src/html-cleaner.js';
|
|
11
|
+
import { cleanComponentHTML, getComponentSuffix } from './src/html-cleaner.js';
|
|
12
12
|
import { mkdtemp, writeFile, rm, mkdir } from 'fs/promises';
|
|
13
13
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
14
14
|
import { join } from 'path';
|
|
@@ -68,7 +68,7 @@ async function installDependencies(componentDir, packageJson) {
|
|
|
68
68
|
* Converts filename to kebab-case tag name (e.g., SwiperTest.svelte → swiper-test)
|
|
69
69
|
* Generates props definition from component.json metadata
|
|
70
70
|
*/
|
|
71
|
-
function injectSvelteOptions(filename, content, componentMetadata) {
|
|
71
|
+
function injectSvelteOptions(filename, content, componentMetadata, componentId) {
|
|
72
72
|
// Convert filename to kebab-case tag name
|
|
73
73
|
// SwiperTest.svelte → swiper-test
|
|
74
74
|
// MyAwesomeComponent.svelte → my-awesome-component
|
|
@@ -84,6 +84,11 @@ function injectSvelteOptions(filename, content, componentMetadata) {
|
|
|
84
84
|
tagName = 'wm-' + tagName; // e.g., "button" → "wm-button"
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Scope tag name with component suffix to avoid collisions
|
|
88
|
+
if (componentId) {
|
|
89
|
+
tagName = tagName + '-' + getComponentSuffix(componentId);
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
// Generate props definition from component.json
|
|
88
93
|
const props = {};
|
|
89
94
|
if (componentMetadata.props) {
|
|
@@ -213,8 +218,9 @@ async function buildComponent(payload) {
|
|
|
213
218
|
|
|
214
219
|
// Auto-inject <svelte:options> for Svelte islands
|
|
215
220
|
let content = island.content;
|
|
221
|
+
const componentId = componentMetadata?.id;
|
|
216
222
|
if (island.file.endsWith('.svelte') && componentMetadata && componentMetadata.props) {
|
|
217
|
-
content = injectSvelteOptions(island.file, content, componentMetadata);
|
|
223
|
+
content = injectSvelteOptions(island.file, content, componentMetadata, componentId);
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
// Write source file
|
|
@@ -245,11 +251,22 @@ async function buildComponent(payload) {
|
|
|
245
251
|
|
|
246
252
|
console.log(`[Build Service] Bundling ${island.file} (top-level island)...`);
|
|
247
253
|
|
|
254
|
+
// Compute scoped tag name for non-Svelte islands
|
|
255
|
+
const cId = componentMetadata?.id;
|
|
256
|
+
let scopedTagName;
|
|
257
|
+
if (cId && !island.file.endsWith('.svelte')) {
|
|
258
|
+
const baseName = island.file.replace(/\.(jsx?|tsx?|vue)$/, '');
|
|
259
|
+
let tag = baseName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
260
|
+
if (!tag.includes('-')) tag = 'wm-' + tag;
|
|
261
|
+
scopedTagName = tag + '-' + getComponentSuffix(cId);
|
|
262
|
+
}
|
|
263
|
+
|
|
248
264
|
// Bundle with esbuild (pass tmpDir for node_modules resolution)
|
|
249
265
|
const result = await bundleIsland(inputPath, outputPath, {
|
|
250
266
|
componentDir: tmpDir,
|
|
251
267
|
minify: true,
|
|
252
|
-
sourcemap: false
|
|
268
|
+
sourcemap: false,
|
|
269
|
+
scopedTagName
|
|
253
270
|
});
|
|
254
271
|
|
|
255
272
|
if (!result.success) {
|
|
@@ -281,8 +298,9 @@ async function buildComponent(payload) {
|
|
|
281
298
|
let transformedHtml = html;
|
|
282
299
|
if (html && islands.length > 0) {
|
|
283
300
|
const islandNames = islands.map(i => i.file.replace(/\.(jsx?|tsx?|svelte|vue)$/, ''));
|
|
284
|
-
|
|
285
|
-
|
|
301
|
+
const componentId = componentMetadata?.id;
|
|
302
|
+
transformedHtml = cleanComponentHTML(html, islandNames, componentId);
|
|
303
|
+
console.log(`[Build Service] ✓ Transformed HTML: ${islandNames.length} island(s) → Custom Elements${componentId ? ` (scoped: -${getComponentSuffix(componentId)})` : ''}`);
|
|
286
304
|
}
|
|
287
305
|
|
|
288
306
|
// Generate CSS from HTML + Islands
|
package/package.json
CHANGED
package/src/bundler.js
CHANGED
|
@@ -26,7 +26,8 @@ export async function bundleIsland(islandPath, outputPath, options = {}) {
|
|
|
26
26
|
target = 'es2020',
|
|
27
27
|
format = 'esm',
|
|
28
28
|
componentDir = null, // Component directory for component-specific node_modules
|
|
29
|
-
svelteCustomElement = true // Set false for mount()-based islands (preview mode)
|
|
29
|
+
svelteCustomElement = true, // Set false for mount()-based islands (preview mode)
|
|
30
|
+
scopedTagName = null // Scoped custom element tag name (e.g. "wm-slide-550e")
|
|
30
31
|
} = options;
|
|
31
32
|
|
|
32
33
|
let entryPoint = islandPath;
|
|
@@ -53,9 +54,14 @@ export async function bundleIsland(islandPath, outputPath, options = {}) {
|
|
|
53
54
|
if (!sourceCode.includes('customElements.define')) {
|
|
54
55
|
// Derive tag name from filename: NewsSlider.js → news-slider
|
|
55
56
|
const baseName = path.basename(islandPath).replace(/\.(js|jsx|ts|tsx|vue)$/, '');
|
|
56
|
-
|
|
57
|
+
let tagName = scopedTagName || baseName
|
|
57
58
|
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
58
59
|
.toLowerCase();
|
|
60
|
+
// Note: scopedTagName already includes the hyphen requirement,
|
|
61
|
+
// so we only need the wm- prefix check for the fallback path
|
|
62
|
+
if (!scopedTagName && !tagName.includes('-')) {
|
|
63
|
+
tagName = 'wm-' + tagName;
|
|
64
|
+
}
|
|
59
65
|
|
|
60
66
|
// Create temporary wrapper entry file
|
|
61
67
|
wrapperPath = islandPath.replace(/\.(js|jsx|ts|tsx|vue)$/, '.__wm-wrapper.js');
|
package/src/html-cleaner.js
CHANGED
|
@@ -2,18 +2,32 @@ import { parseDocument } from 'htmlparser2';
|
|
|
2
2
|
import { DomUtils } from 'htmlparser2';
|
|
3
3
|
import render from 'dom-serializer';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Get a 4-char hex suffix from a component ID (UUID/CUID).
|
|
7
|
+
* Uses the first 4 hex characters after removing hyphens.
|
|
8
|
+
* Example: "550e8400-e29b-41d4-a716-446655440000" → "550e"
|
|
9
|
+
*
|
|
10
|
+
* @param {string} componentId - UUID or CUID string
|
|
11
|
+
* @returns {string} 4-char hex suffix
|
|
12
|
+
*/
|
|
13
|
+
export function getComponentSuffix(componentId) {
|
|
14
|
+
const hex = componentId.replace(/-/g, '');
|
|
15
|
+
return hex.substring(0, 4).toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
/**
|
|
6
19
|
* Clean component HTML by removing all wm: attributes
|
|
7
20
|
* Keeps the HTML structure intact, only removes markers
|
|
8
21
|
*
|
|
9
22
|
* @param {string} html - Original component HTML
|
|
10
23
|
* @param {Array<string>} islands - List of available island names (optional)
|
|
24
|
+
* @param {string} [componentId] - Component UUID for scoped island tag names
|
|
11
25
|
* @returns {string} Cleaned HTML without wm: attributes
|
|
12
26
|
*/
|
|
13
|
-
export function cleanComponentHTML(html, islands = []) {
|
|
27
|
+
export function cleanComponentHTML(html, islands = [], componentId) {
|
|
14
28
|
// Transform PascalCase island elements to data-island FIRST
|
|
15
29
|
if (islands && islands.length > 0) {
|
|
16
|
-
html = transformIslandsToDataAttributes(html, islands);
|
|
30
|
+
html = transformIslandsToDataAttributes(html, islands, componentId);
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
// Remove wm: attributes using regex (avoids htmlparser2 breaking class: directives)
|
|
@@ -46,7 +60,10 @@ export function cleanComponentHTML(html, islands = []) {
|
|
|
46
60
|
* @param {Array<string>} availableIslands - List of available island names
|
|
47
61
|
* @returns {string} HTML with transformed islands
|
|
48
62
|
*/
|
|
49
|
-
function transformIslandsToDataAttributes(html, availableIslands = []) {
|
|
63
|
+
function transformIslandsToDataAttributes(html, availableIslands = [], componentId) {
|
|
64
|
+
// Compute suffix once if componentId is provided
|
|
65
|
+
const suffix = componentId ? getComponentSuffix(componentId) : null;
|
|
66
|
+
|
|
50
67
|
// Convert PascalCase island name to kebab-case Custom Element tag
|
|
51
68
|
function toKebabTag(tagName) {
|
|
52
69
|
let kebabTag = tagName
|
|
@@ -57,6 +74,10 @@ function transformIslandsToDataAttributes(html, availableIslands = []) {
|
|
|
57
74
|
if (!kebabTag.includes('-')) {
|
|
58
75
|
kebabTag = 'wm-' + kebabTag;
|
|
59
76
|
}
|
|
77
|
+
// Scope tag name with component suffix to avoid collisions
|
|
78
|
+
if (suffix) {
|
|
79
|
+
kebabTag = kebabTag + '-' + suffix;
|
|
80
|
+
}
|
|
60
81
|
return kebabTag;
|
|
61
82
|
}
|
|
62
83
|
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { build } from './build.js';
|
|
2
2
|
import { generateComponentCSS, generateTailwindCSS, extractTailwindClasses } from './tailwind-generator.js';
|
|
3
|
-
import { cleanComponentHTML } from './html-cleaner.js';
|
|
3
|
+
import { cleanComponentHTML, getComponentSuffix } from './html-cleaner.js';
|
|
4
4
|
import { bundleIsland, bundleComponentIslands } from './bundler.js';
|
|
5
5
|
import { deduplicateCSS } from './css-deduplicator.js';
|
|
6
6
|
import { markdownToHtml, processMarkdownProps } from './markdown.js';
|
|
@@ -26,7 +26,7 @@ function getMotionRuntime() {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// V1 exports (backward compatible)
|
|
29
|
-
export { build, generateComponentCSS, generateTailwindCSS, extractTailwindClasses, cleanComponentHTML, bundleIsland, bundleComponentIslands, deduplicateCSS, markdownToHtml, processMarkdownProps, SafeHtml, getMotionRuntime, TemplateProcessor, templateProcessor, defaultDesignTokens, generateTailwindV4Theme, generateTailwindConfig, generateCSSFromTokens, generateFontImports };
|
|
29
|
+
export { build, generateComponentCSS, generateTailwindCSS, extractTailwindClasses, cleanComponentHTML, getComponentSuffix, bundleIsland, bundleComponentIslands, deduplicateCSS, markdownToHtml, processMarkdownProps, SafeHtml, getMotionRuntime, TemplateProcessor, templateProcessor, defaultDesignTokens, generateTailwindV4Theme, generateTailwindConfig, generateCSSFromTokens, generateFontImports };
|
|
30
30
|
|
|
31
31
|
// V2 exports
|
|
32
32
|
export { defaultDesignTokensV2, generateTailwindV4ThemeV2, generateCSSFromTokensV2, generateFontImportsV2, migrateDesignTokensV1toV2, validateDesignTokensV2, migrateResponsiveToFluid, generateColorScale, generateDarkColorScale, calculateOnColor, isV1Format, isV2Format, COLOR_WORLDS, SEMANTIC_COLOR_WORLDS, TEXT_VOICES, TEXT_LEVELS, BUTTON_VARIANTS, BUTTON_SIZES, DEFAULT_SEMANTIC_MAPPINGS, DEFAULT_STATUS_SEMANTIC_MAPPINGS, getDefaultMappingsForWorld, DEFAULT_TEXT_ALIASES };
|