@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 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
- transformedHtml = cleanComponentHTML(html, islandNames);
285
- console.log(`[Build Service] Transformed HTML: ${islandNames.length} island(s) → Custom Elements`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmate-studio/builder",
3
- "version": "0.2.168",
3
+ "version": "0.2.170",
4
4
  "type": "module",
5
5
  "description": "Webmate Studio Component Builder",
6
6
  "keywords": [
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
- const tagName = baseName
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');
@@ -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 };