@webmate-studio/builder 0.1.13 → 0.2.0
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 +230 -0
- package/package.json +9 -2
- package/src/build.js +133 -7
- package/src/bundler.js +11 -4
- package/src/html-cleaner.js +18 -4
- package/src/index.js +2 -1
package/build-service.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Component Build Micro-Service
|
|
4
|
+
* Runs a simple HTTP server that builds components (CSS + Islands)
|
|
5
|
+
* Extends the CSS service with full component building capabilities
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import http from 'http';
|
|
9
|
+
import { generateComponentCSS } from './src/tailwind-generator.js';
|
|
10
|
+
import { bundleIsland } from './src/bundler.js';
|
|
11
|
+
import { mkdtemp, writeFile, rm, mkdir } from 'fs/promises';
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { tmpdir } from 'os';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import { createHash } from 'crypto';
|
|
17
|
+
|
|
18
|
+
const PORT = 3031;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Install npm dependencies for a component
|
|
22
|
+
* @param {string} componentDir - Component directory
|
|
23
|
+
* @param {Object} packageJson - package.json content
|
|
24
|
+
*/
|
|
25
|
+
async function installDependencies(componentDir, packageJson) {
|
|
26
|
+
if (!packageJson || !packageJson.dependencies || Object.keys(packageJson.dependencies).length === 0) {
|
|
27
|
+
return; // No dependencies
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Write package.json
|
|
31
|
+
const packageJsonPath = join(componentDir, 'package.json');
|
|
32
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
33
|
+
|
|
34
|
+
// Check cache
|
|
35
|
+
const cacheDir = join(componentDir, '.wm-cache');
|
|
36
|
+
const hashFile = join(cacheDir, 'deps.hash');
|
|
37
|
+
const nodeModulesExists = existsSync(join(componentDir, 'node_modules'));
|
|
38
|
+
|
|
39
|
+
const currentHash = createHash('sha256').update(JSON.stringify(packageJson)).digest('hex');
|
|
40
|
+
const cachedHash = existsSync(hashFile) ? readFileSync(hashFile, 'utf8').trim() : null;
|
|
41
|
+
|
|
42
|
+
if (currentHash === cachedHash && nodeModulesExists) {
|
|
43
|
+
console.log('[Build Service] Dependencies cached');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Install
|
|
48
|
+
console.log('[Build Service] Installing dependencies...');
|
|
49
|
+
try {
|
|
50
|
+
execSync('npm install --no-save --no-audit --no-fund', {
|
|
51
|
+
cwd: componentDir,
|
|
52
|
+
stdio: 'inherit'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Save cache
|
|
56
|
+
await mkdir(cacheDir, { recursive: true });
|
|
57
|
+
await writeFile(hashFile, currentHash);
|
|
58
|
+
|
|
59
|
+
console.log('[Build Service] ✓ Dependencies installed');
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new Error(`Failed to install dependencies: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build a component (islands + CSS)
|
|
67
|
+
* @param {Object} payload - Build request payload
|
|
68
|
+
* @returns {Object} Build result
|
|
69
|
+
*/
|
|
70
|
+
async function buildComponent(payload) {
|
|
71
|
+
const { packageJson, islands, html, componentName = 'component' } = payload;
|
|
72
|
+
|
|
73
|
+
// Create temporary directory
|
|
74
|
+
const tmpDir = await mkdtemp(join(tmpdir(), 'wm-build-'));
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
console.log(`[Build Service] Building ${componentName} in ${tmpDir}`);
|
|
78
|
+
|
|
79
|
+
// Install dependencies if needed
|
|
80
|
+
if (packageJson) {
|
|
81
|
+
await installDependencies(tmpDir, packageJson);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Build islands
|
|
85
|
+
const bundledIslands = [];
|
|
86
|
+
if (islands && islands.length > 0) {
|
|
87
|
+
const islandsDir = join(tmpDir, 'islands');
|
|
88
|
+
await mkdir(islandsDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
for (const island of islands) {
|
|
91
|
+
const inputPath = join(islandsDir, island.file);
|
|
92
|
+
const outputPath = join(islandsDir, island.file.replace(/\.(jsx?|tsx?|svelte|vue)$/, '.js'));
|
|
93
|
+
|
|
94
|
+
// Write source file
|
|
95
|
+
await writeFile(inputPath, island.content);
|
|
96
|
+
|
|
97
|
+
console.log(`[Build Service] Bundling ${island.file}...`);
|
|
98
|
+
|
|
99
|
+
// Bundle with esbuild (pass tmpDir for node_modules resolution)
|
|
100
|
+
const result = await bundleIsland(inputPath, outputPath, {
|
|
101
|
+
componentDir: tmpDir,
|
|
102
|
+
minify: true,
|
|
103
|
+
sourcemap: false
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!result.success) {
|
|
107
|
+
throw new Error(`Failed to bundle ${island.file}: ${result.error}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Read bundled content
|
|
111
|
+
const bundledContent = readFileSync(outputPath, 'utf8');
|
|
112
|
+
|
|
113
|
+
bundledIslands.push({
|
|
114
|
+
file: island.file,
|
|
115
|
+
content: bundledContent,
|
|
116
|
+
size: result.size,
|
|
117
|
+
originalSize: island.content.length
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
console.log(`[Build Service] ✓ ${island.file} → ${(result.size / 1024).toFixed(2)}kb`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Generate CSS
|
|
125
|
+
let css = '';
|
|
126
|
+
if (html) {
|
|
127
|
+
console.log('[Build Service] Generating CSS...');
|
|
128
|
+
const cssResult = await generateComponentCSS(html, {
|
|
129
|
+
designTokens: null,
|
|
130
|
+
minify: true
|
|
131
|
+
});
|
|
132
|
+
css = cssResult.css;
|
|
133
|
+
console.log(`[Build Service] ✓ CSS → ${(css.length / 1024).toFixed(2)}kb`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
bundledIslands,
|
|
139
|
+
css,
|
|
140
|
+
stats: {
|
|
141
|
+
islands: bundledIslands.length,
|
|
142
|
+
totalSize: bundledIslands.reduce((sum, i) => sum + i.size, 0) + css.length
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
} finally {
|
|
146
|
+
// Cleanup temp directory
|
|
147
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const server = http.createServer(async (req, res) => {
|
|
152
|
+
// CORS headers
|
|
153
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
154
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
155
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
156
|
+
|
|
157
|
+
// Handle preflight
|
|
158
|
+
if (req.method === 'OPTIONS') {
|
|
159
|
+
res.writeHead(200);
|
|
160
|
+
res.end();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Only accept POST
|
|
165
|
+
if (req.method !== 'POST') {
|
|
166
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
167
|
+
res.end(JSON.stringify({ success: false, error: 'Not found' }));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Read body
|
|
172
|
+
let body = '';
|
|
173
|
+
req.on('data', chunk => {
|
|
174
|
+
body += chunk.toString();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
req.on('end', async () => {
|
|
178
|
+
try {
|
|
179
|
+
const payload = JSON.parse(body);
|
|
180
|
+
|
|
181
|
+
// Route to appropriate handler
|
|
182
|
+
if (req.url === '/css/generate' || req.url === '/generate') {
|
|
183
|
+
// Legacy CSS generation endpoint
|
|
184
|
+
const { html } = payload;
|
|
185
|
+
|
|
186
|
+
if (!html) {
|
|
187
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
188
|
+
res.end(JSON.stringify({ success: false, error: 'Missing html parameter' }));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const result = await generateComponentCSS(html, {
|
|
193
|
+
designTokens: null,
|
|
194
|
+
minify: true
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
198
|
+
res.end(JSON.stringify({
|
|
199
|
+
success: true,
|
|
200
|
+
css: result.css,
|
|
201
|
+
classes: result.classes
|
|
202
|
+
}));
|
|
203
|
+
} else if (req.url === '/build' || req.url === '/component/build') {
|
|
204
|
+
// NEW: Full component build (islands + CSS)
|
|
205
|
+
const result = await buildComponent(payload);
|
|
206
|
+
|
|
207
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
208
|
+
res.end(JSON.stringify(result));
|
|
209
|
+
} else {
|
|
210
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
211
|
+
res.end(JSON.stringify({ success: false, error: 'Not found' }));
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('[Build Service] Error:', error);
|
|
215
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
216
|
+
res.end(JSON.stringify({
|
|
217
|
+
success: false,
|
|
218
|
+
error: error.message,
|
|
219
|
+
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
server.listen(PORT, () => {
|
|
226
|
+
console.log(`[Build Service] Running on http://localhost:${PORT}`);
|
|
227
|
+
console.log(`[Build Service] Endpoints:`);
|
|
228
|
+
console.log(` POST /css/generate - Generate Tailwind CSS`);
|
|
229
|
+
console.log(` POST /build - Build component (islands + CSS)`);
|
|
230
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webmate-studio/builder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Webmate Studio Component Builder",
|
|
6
6
|
"keywords": [
|
|
@@ -15,11 +15,18 @@
|
|
|
15
15
|
"url": "https://github.com/webmate-studio/builder.git"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
-
"src"
|
|
18
|
+
"src",
|
|
19
|
+
"build-service.js"
|
|
19
20
|
],
|
|
20
21
|
"exports": {
|
|
21
22
|
".": "./src/index.js"
|
|
22
23
|
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"wm-build-service": "./build-service.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"service": "node build-service.js"
|
|
29
|
+
},
|
|
23
30
|
"publishConfig": {
|
|
24
31
|
"access": "public"
|
|
25
32
|
},
|
package/src/build.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, cpSync } from 'fs';
|
|
2
2
|
import { join, dirname, relative, extname, basename } from 'path';
|
|
3
3
|
import { glob } from 'glob';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { createHash } from 'crypto';
|
|
4
6
|
import { parseComponent } from '@webmate-studio/parser';
|
|
5
|
-
import { loadConfig, logger } from '@webmate-studio/core';
|
|
7
|
+
import { loadConfig, logger, validateComponents, ComponentValidationError } from '@webmate-studio/core';
|
|
6
8
|
import { cleanComponentHTML, extractStyles } from './html-cleaner.js';
|
|
7
9
|
import { generateManifest } from './manifest.js';
|
|
8
10
|
import { generateComponentCSS } from './tailwind-generator.js';
|
|
@@ -11,6 +13,67 @@ import { parseDocument } from 'htmlparser2';
|
|
|
11
13
|
import { DomUtils } from 'htmlparser2';
|
|
12
14
|
import render from 'dom-serializer';
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Install component dependencies from package.json
|
|
18
|
+
* Uses caching to avoid unnecessary npm installs
|
|
19
|
+
* @param {string} componentDir - Component directory path
|
|
20
|
+
* @param {string} componentName - Component name for logging
|
|
21
|
+
*/
|
|
22
|
+
async function installComponentDependencies(componentDir, componentName) {
|
|
23
|
+
const packageJsonPath = join(componentDir, 'package.json');
|
|
24
|
+
|
|
25
|
+
// No package.json? Skip.
|
|
26
|
+
if (!existsSync(packageJsonPath)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
31
|
+
|
|
32
|
+
// No dependencies? Skip.
|
|
33
|
+
if (!packageJson.dependencies || Object.keys(packageJson.dependencies).length === 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check cache: Has package.json changed since last install?
|
|
38
|
+
const cacheDir = join(componentDir, '.wm-cache');
|
|
39
|
+
const hashFile = join(cacheDir, 'deps.hash');
|
|
40
|
+
const nodeModulesExists = existsSync(join(componentDir, 'node_modules'));
|
|
41
|
+
|
|
42
|
+
// Calculate hash of package.json content
|
|
43
|
+
const packageJsonContent = readFileSync(packageJsonPath, 'utf8');
|
|
44
|
+
const currentHash = createHash('sha256').update(packageJsonContent).digest('hex');
|
|
45
|
+
|
|
46
|
+
// Read cached hash
|
|
47
|
+
const cachedHash = existsSync(hashFile) ? readFileSync(hashFile, 'utf8').trim() : null;
|
|
48
|
+
|
|
49
|
+
// Skip install if:
|
|
50
|
+
// 1. Hash matches (package.json unchanged)
|
|
51
|
+
// 2. node_modules exists
|
|
52
|
+
if (currentHash === cachedHash && nodeModulesExists) {
|
|
53
|
+
logger.debug(` ↳ Dependencies cached (${componentName})`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Install dependencies
|
|
58
|
+
logger.info(` ↳ Installing dependencies for ${componentName}...`);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
execSync('npm install --no-save --no-audit --no-fund', {
|
|
62
|
+
cwd: componentDir,
|
|
63
|
+
stdio: 'inherit'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Save hash to cache
|
|
67
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
68
|
+
writeFileSync(hashFile, currentHash, 'utf8');
|
|
69
|
+
|
|
70
|
+
logger.success(` ↳ Dependencies installed (${componentName})`);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.error(` ✗ Failed to install dependencies for ${componentName}: ${error.message}`);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
14
77
|
/**
|
|
15
78
|
* Add scoping attribute to root element of component
|
|
16
79
|
* @param {string} html - Component HTML
|
|
@@ -150,6 +213,44 @@ export async function build(options = {}) {
|
|
|
150
213
|
|
|
151
214
|
logger.info(`Found ${allComponents.length} components`);
|
|
152
215
|
|
|
216
|
+
// Validate all components BEFORE building
|
|
217
|
+
logger.info('Validating component.json files...');
|
|
218
|
+
try {
|
|
219
|
+
const componentsToValidate = allComponents
|
|
220
|
+
.filter(file => file.endsWith('component.html')) // Only directory-based components need component.json
|
|
221
|
+
.map(file => {
|
|
222
|
+
const componentDir = dirname(join(componentsDir, file));
|
|
223
|
+
const componentJsonPath = join(componentDir, 'component.json');
|
|
224
|
+
const folderName = basename(componentDir);
|
|
225
|
+
|
|
226
|
+
let componentJson = null;
|
|
227
|
+
if (existsSync(componentJsonPath)) {
|
|
228
|
+
try {
|
|
229
|
+
componentJson = JSON.parse(readFileSync(componentJsonPath, 'utf8'));
|
|
230
|
+
} catch (error) {
|
|
231
|
+
// Will be caught by validator
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
name: folderName,
|
|
237
|
+
path: relative(componentsDir, componentDir),
|
|
238
|
+
json: componentJson
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (componentsToValidate.length > 0) {
|
|
243
|
+
validateComponents(componentsToValidate);
|
|
244
|
+
logger.success(`✓ All ${componentsToValidate.length} components have valid UUIDs`);
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error instanceof ComponentValidationError) {
|
|
248
|
+
logger.error(error.message);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
|
|
153
254
|
const manifest = {
|
|
154
255
|
version: '1.0.0',
|
|
155
256
|
components: [],
|
|
@@ -165,6 +266,15 @@ export async function build(options = {}) {
|
|
|
165
266
|
const componentPath = join(componentsDir, file);
|
|
166
267
|
const html = readFileSync(componentPath, 'utf8');
|
|
167
268
|
|
|
269
|
+
// Determine if this is a directory-based component (before parsing)
|
|
270
|
+
const isDirectoryComponent = file.endsWith('component.html');
|
|
271
|
+
const componentDir = isDirectoryComponent ? dirname(join(componentsDir, file)) : null;
|
|
272
|
+
|
|
273
|
+
// Install component dependencies if package.json exists
|
|
274
|
+
if (componentDir) {
|
|
275
|
+
await installComponentDependencies(componentDir, basename(componentDir));
|
|
276
|
+
}
|
|
277
|
+
|
|
168
278
|
try {
|
|
169
279
|
// Parse component to extract schema
|
|
170
280
|
const component = parseComponent(html, file);
|
|
@@ -182,18 +292,29 @@ export async function build(options = {}) {
|
|
|
182
292
|
const isDirectoryComponent = file.endsWith('component.html');
|
|
183
293
|
const componentDir = isDirectoryComponent ? dirname(join(componentsDir, file)) : null;
|
|
184
294
|
|
|
185
|
-
// For directory-based components, load component.json to get
|
|
295
|
+
// For directory-based components, load component.json to get UUID, name, etc.
|
|
296
|
+
let componentUuid = null;
|
|
186
297
|
if (isDirectoryComponent && componentDir) {
|
|
187
298
|
const componentJsonPath = join(componentDir, 'component.json');
|
|
188
299
|
if (existsSync(componentJsonPath)) {
|
|
189
300
|
try {
|
|
190
301
|
const componentJson = JSON.parse(readFileSync(componentJsonPath, 'utf8'));
|
|
191
|
-
|
|
302
|
+
|
|
303
|
+
// Extract UUID (required, validated earlier)
|
|
304
|
+
if (componentJson.id) {
|
|
305
|
+
componentUuid = componentJson.id.toLowerCase();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Use displayName if available, otherwise name, otherwise fallback to parsed name
|
|
309
|
+
if (componentJson.displayName) {
|
|
310
|
+
component.name = componentJson.displayName;
|
|
311
|
+
} else if (componentJson.name) {
|
|
192
312
|
component.name = componentJson.name;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Also merge props from component.json
|
|
316
|
+
if (componentJson.props) {
|
|
317
|
+
component.props = { ...component.props, ...componentJson.props };
|
|
197
318
|
}
|
|
198
319
|
} catch (error) {
|
|
199
320
|
logger.warn(`Failed to parse component.json for ${file}: ${error.message}`);
|
|
@@ -272,6 +393,11 @@ export async function build(options = {}) {
|
|
|
272
393
|
source: file
|
|
273
394
|
};
|
|
274
395
|
|
|
396
|
+
// Add UUID for directory-based components (required for CMS sync)
|
|
397
|
+
if (componentUuid) {
|
|
398
|
+
manifestEntry.id = componentUuid;
|
|
399
|
+
}
|
|
400
|
+
|
|
275
401
|
// Handle directory-based components (islands + assets)
|
|
276
402
|
if (isDirectoryComponent && componentDir) {
|
|
277
403
|
const componentOutputDir = dirname(outputPath);
|
package/src/bundler.js
CHANGED
|
@@ -16,7 +16,8 @@ export async function bundleIsland(islandPath, outputPath, options = {}) {
|
|
|
16
16
|
minify = true,
|
|
17
17
|
sourcemap = true,
|
|
18
18
|
target = 'es2020',
|
|
19
|
-
format = 'esm'
|
|
19
|
+
format = 'esm',
|
|
20
|
+
componentDir = null // NEW: Component directory for component-specific node_modules
|
|
20
21
|
} = options;
|
|
21
22
|
|
|
22
23
|
try {
|
|
@@ -28,6 +29,9 @@ export async function bundleIsland(islandPath, outputPath, options = {}) {
|
|
|
28
29
|
const cwd = process.cwd();
|
|
29
30
|
const workspaceNodeModules = path.join(cwd, 'node_modules');
|
|
30
31
|
|
|
32
|
+
// NEW: Component-specific node_modules (highest priority)
|
|
33
|
+
const componentNodeModules = componentDir ? path.join(componentDir, 'node_modules') : null;
|
|
34
|
+
|
|
31
35
|
// Determine if this file should use JSX loader
|
|
32
36
|
// Only use JSX for .jsx files (React/Preact), not for .js files (Lit/Alpine/Vue/Vanilla)
|
|
33
37
|
const useJsxLoader = islandPath.endsWith('.jsx');
|
|
@@ -88,8 +92,10 @@ export async function bundleIsland(islandPath, outputPath, options = {}) {
|
|
|
88
92
|
],
|
|
89
93
|
// Don't bundle browser globals
|
|
90
94
|
external: [],
|
|
91
|
-
// Add multiple resolution paths -
|
|
92
|
-
nodePaths:
|
|
95
|
+
// Add multiple resolution paths - Component node_modules has highest priority
|
|
96
|
+
nodePaths: componentNodeModules
|
|
97
|
+
? [componentNodeModules, builderNodeModules, workspaceNodeModules]
|
|
98
|
+
: [builderNodeModules, workspaceNodeModules],
|
|
93
99
|
// Enable package.json conditions for proper Svelte module resolution
|
|
94
100
|
conditions: ['svelte', 'browser', 'import'],
|
|
95
101
|
// Log level
|
|
@@ -144,7 +150,8 @@ export async function bundleComponentIslands(componentDir, outputDir) {
|
|
|
144
150
|
|
|
145
151
|
console.log(pc.dim(` Bundling ${islandFile}...`));
|
|
146
152
|
|
|
147
|
-
|
|
153
|
+
// Pass componentDir to bundler for component-specific node_modules resolution
|
|
154
|
+
const result = await bundleIsland(inputPath, outputPath, { componentDir });
|
|
148
155
|
|
|
149
156
|
if (result.success) {
|
|
150
157
|
const sizeKb = (result.size / 1024).toFixed(2);
|
package/src/html-cleaner.js
CHANGED
|
@@ -27,16 +27,30 @@ export function cleanComponentHTML(html) {
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Recursively remove metadata wm: attributes from DOM tree
|
|
30
|
-
* KEEPS runtime attributes
|
|
30
|
+
* KEEPS runtime attributes: wm:if, wm:for, wm:island, wm:island-prop:*, wm:island-props, wm:class:*
|
|
31
|
+
* REMOVES metadata attributes: wm:component, wm:prop, wm:props, wm:schema, wm:description
|
|
32
|
+
*
|
|
33
|
+
* Note: Runtime attributes use Svelte-style syntax with curly braces:
|
|
34
|
+
* - wm:if={expression}
|
|
35
|
+
* - wm:for={item in items}
|
|
36
|
+
* - wm:class:active={isActive}
|
|
37
|
+
* - wm:island-prop:count={0}
|
|
38
|
+
*
|
|
31
39
|
* @param {Object} node - DOM node
|
|
32
40
|
*/
|
|
33
41
|
function removeWmAttributes(node) {
|
|
34
42
|
if (node.type === 'tag' && node.attribs) {
|
|
35
|
-
//
|
|
36
|
-
const metadataAttributes = [
|
|
43
|
+
// Metadata attributes to remove (no longer used - everything in component.json!)
|
|
44
|
+
const metadataAttributes = [
|
|
45
|
+
'wm:component', // Removed: Use component.json instead
|
|
46
|
+
'wm:prop', // Removed: Use component.json instead
|
|
47
|
+
'wm:props', // Removed: Use component.json instead
|
|
48
|
+
'wm:schema', // Legacy, removed
|
|
49
|
+
'wm:description' // Metadata only
|
|
50
|
+
];
|
|
37
51
|
|
|
38
52
|
for (const attr in node.attribs) {
|
|
39
|
-
// Remove
|
|
53
|
+
// Remove metadata attributes
|
|
40
54
|
if (metadataAttributes.includes(attr)) {
|
|
41
55
|
delete node.attribs[attr];
|
|
42
56
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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
4
|
|
|
4
|
-
export { build, generateComponentCSS, generateTailwindCSS, extractTailwindClasses };
|
|
5
|
+
export { build, generateComponentCSS, generateTailwindCSS, extractTailwindClasses, cleanComponentHTML };
|