lwc-convert 1.0.0 → 1.0.1
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/LICENSE +21 -21
- package/README.md +719 -719
- package/dist/cli/commands/aura.d.ts.map +1 -1
- package/dist/cli/commands/aura.js +10 -0
- package/dist/cli/commands/aura.js.map +1 -1
- package/dist/cli/commands/vf.d.ts.map +1 -1
- package/dist/cli/commands/vf.js +10 -0
- package/dist/cli/commands/vf.js.map +1 -1
- package/dist/cli/interactive.d.ts +1 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +10 -0
- package/dist/cli/interactive.js.map +1 -1
- package/dist/cli/options.d.ts +2 -1
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/cli/options.js +14 -13
- package/dist/cli/options.js.map +1 -1
- package/dist/generators/full-conversion.js +6 -6
- package/dist/generators/scaffolding.js +90 -90
- package/dist/generators/test-comparison.js +149 -149
- package/dist/generators/test-generator.js +231 -231
- package/dist/index.js +49 -35
- package/dist/index.js.map +1 -1
- package/dist/transformers/aura-to-lwc/events.js +130 -130
- package/dist/transformers/vf-to-lwc/components.js +79 -79
- package/dist/transformers/vf-to-lwc/data-binding.js +165 -165
- package/dist/utils/file-io.js +15 -15
- package/dist/utils/preview-generator.d.ts +20 -0
- package/dist/utils/preview-generator.d.ts.map +1 -0
- package/dist/utils/preview-generator.js +833 -0
- package/dist/utils/preview-generator.js.map +1 -0
- package/dist/utils/session-store.js +32 -32
- package/package.json +85 -81
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Preview Generator - Creates standalone HTML previews of converted LWC components
|
|
4
|
+
*
|
|
5
|
+
* This allows users to evaluate the UI of their converted components in a browser
|
|
6
|
+
* without needing a full Salesforce deployment.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.generatePreviewHtml = generatePreviewHtml;
|
|
43
|
+
exports.writePreviewFile = writePreviewFile;
|
|
44
|
+
exports.openPreview = openPreview;
|
|
45
|
+
const fs = __importStar(require("fs-extra"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const logger_1 = require("./logger");
|
|
48
|
+
/**
|
|
49
|
+
* Mapping of Lightning Web Components to HTML approximations
|
|
50
|
+
* These provide visual approximations using standard HTML + SLDS classes
|
|
51
|
+
* selfContained: true means the transform returns complete element with closing tag
|
|
52
|
+
*/
|
|
53
|
+
const LIGHTNING_TO_HTML = {
|
|
54
|
+
'lightning-button': {
|
|
55
|
+
tag: 'button',
|
|
56
|
+
class: 'slds-button slds-button_neutral',
|
|
57
|
+
selfContained: true,
|
|
58
|
+
transform: (attrs) => {
|
|
59
|
+
let className = 'slds-button';
|
|
60
|
+
if (attrs.variant === 'brand')
|
|
61
|
+
className = 'slds-button slds-button_brand';
|
|
62
|
+
else if (attrs.variant === 'destructive')
|
|
63
|
+
className = 'slds-button slds-button_destructive';
|
|
64
|
+
else if (attrs.variant === 'success')
|
|
65
|
+
className = 'slds-button slds-button_success';
|
|
66
|
+
else
|
|
67
|
+
className = 'slds-button slds-button_neutral';
|
|
68
|
+
const label = attrs.label || 'Button';
|
|
69
|
+
const disabled = attrs.disabled ? 'disabled' : '';
|
|
70
|
+
return `<button class="${className}" ${disabled}>${label}</button>`;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
'lightning-input': {
|
|
74
|
+
tag: 'div',
|
|
75
|
+
class: 'slds-form-element',
|
|
76
|
+
transform: (attrs) => {
|
|
77
|
+
const type = attrs.type || 'text';
|
|
78
|
+
const label = attrs.label || 'Input';
|
|
79
|
+
const placeholder = attrs.placeholder || '';
|
|
80
|
+
const value = attrs.value || '';
|
|
81
|
+
const required = attrs.required ? '<abbr class="slds-required" title="required">*</abbr>' : '';
|
|
82
|
+
return `
|
|
83
|
+
<div class="slds-form-element">
|
|
84
|
+
<label class="slds-form-element__label">${required}${label}</label>
|
|
85
|
+
<div class="slds-form-element__control">
|
|
86
|
+
<input type="${type}" class="slds-input" placeholder="${placeholder}" value="${value}">
|
|
87
|
+
</div>
|
|
88
|
+
</div>`;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
'lightning-card': {
|
|
92
|
+
tag: 'article',
|
|
93
|
+
class: 'slds-card',
|
|
94
|
+
transform: (attrs) => {
|
|
95
|
+
const title = attrs.title || 'Card Title';
|
|
96
|
+
return `
|
|
97
|
+
<article class="slds-card">
|
|
98
|
+
<div class="slds-card__header slds-grid">
|
|
99
|
+
<header class="slds-media slds-media_center slds-has-flexi-truncate">
|
|
100
|
+
<div class="slds-media__body">
|
|
101
|
+
<h2 class="slds-card__header-title">
|
|
102
|
+
<span>${title}</span>
|
|
103
|
+
</h2>
|
|
104
|
+
</div>
|
|
105
|
+
</header>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="slds-card__body slds-card__body_inner">
|
|
108
|
+
<!-- SLOT_CONTENT -->
|
|
109
|
+
</div>
|
|
110
|
+
</article>`;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
'lightning-icon': {
|
|
114
|
+
tag: 'span',
|
|
115
|
+
transform: (attrs) => {
|
|
116
|
+
const iconName = attrs['icon-name'] || 'utility:info';
|
|
117
|
+
const size = attrs.size || 'medium';
|
|
118
|
+
return `<span class="slds-icon_container" title="${iconName}">
|
|
119
|
+
<svg class="slds-icon slds-icon_${size}" aria-hidden="true">
|
|
120
|
+
<title>${iconName}</title>
|
|
121
|
+
</svg>
|
|
122
|
+
<span class="slds-assistive-text">${iconName}</span>
|
|
123
|
+
</span>`;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
'lightning-spinner': {
|
|
127
|
+
tag: 'div',
|
|
128
|
+
transform: (attrs) => {
|
|
129
|
+
const size = attrs.size || 'medium';
|
|
130
|
+
return `
|
|
131
|
+
<div role="status" class="slds-spinner slds-spinner_${size}">
|
|
132
|
+
<span class="slds-assistive-text">Loading</span>
|
|
133
|
+
<div class="slds-spinner__dot-a"></div>
|
|
134
|
+
<div class="slds-spinner__dot-b"></div>
|
|
135
|
+
</div>`;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
'lightning-datatable': {
|
|
139
|
+
tag: 'div',
|
|
140
|
+
transform: () => `
|
|
141
|
+
<div class="slds-table_header-fixed_container">
|
|
142
|
+
<table class="slds-table slds-table_bordered slds-table_cell-buffer">
|
|
143
|
+
<thead>
|
|
144
|
+
<tr class="slds-line-height_reset">
|
|
145
|
+
<th scope="col"><div class="slds-truncate" title="Column 1">Column 1</div></th>
|
|
146
|
+
<th scope="col"><div class="slds-truncate" title="Column 2">Column 2</div></th>
|
|
147
|
+
<th scope="col"><div class="slds-truncate" title="Column 3">Column 3</div></th>
|
|
148
|
+
</tr>
|
|
149
|
+
</thead>
|
|
150
|
+
<tbody>
|
|
151
|
+
<tr><td>Sample Data</td><td>Sample Data</td><td>Sample Data</td></tr>
|
|
152
|
+
<tr><td>Sample Data</td><td>Sample Data</td><td>Sample Data</td></tr>
|
|
153
|
+
</tbody>
|
|
154
|
+
</table>
|
|
155
|
+
</div>`
|
|
156
|
+
},
|
|
157
|
+
'lightning-combobox': {
|
|
158
|
+
tag: 'div',
|
|
159
|
+
transform: (attrs) => {
|
|
160
|
+
const label = attrs.label || 'Select';
|
|
161
|
+
const placeholder = attrs.placeholder || '-- Select an Option --';
|
|
162
|
+
return `
|
|
163
|
+
<div class="slds-form-element">
|
|
164
|
+
<label class="slds-form-element__label">${label}</label>
|
|
165
|
+
<div class="slds-form-element__control">
|
|
166
|
+
<div class="slds-combobox_container">
|
|
167
|
+
<select class="slds-input slds-combobox__input">
|
|
168
|
+
<option>${placeholder}</option>
|
|
169
|
+
<option>Option 1</option>
|
|
170
|
+
<option>Option 2</option>
|
|
171
|
+
</select>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>`;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
'lightning-textarea': {
|
|
178
|
+
tag: 'div',
|
|
179
|
+
transform: (attrs) => {
|
|
180
|
+
const label = attrs.label || 'Text Area';
|
|
181
|
+
const placeholder = attrs.placeholder || '';
|
|
182
|
+
return `
|
|
183
|
+
<div class="slds-form-element">
|
|
184
|
+
<label class="slds-form-element__label">${label}</label>
|
|
185
|
+
<div class="slds-form-element__control">
|
|
186
|
+
<textarea class="slds-textarea" placeholder="${placeholder}"></textarea>
|
|
187
|
+
</div>
|
|
188
|
+
</div>`;
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
'lightning-checkbox-group': {
|
|
192
|
+
tag: 'fieldset',
|
|
193
|
+
transform: (attrs) => {
|
|
194
|
+
const label = attrs.label || 'Checkbox Group';
|
|
195
|
+
return `
|
|
196
|
+
<fieldset class="slds-form-element">
|
|
197
|
+
<legend class="slds-form-element__legend slds-form-element__label">${label}</legend>
|
|
198
|
+
<div class="slds-form-element__control">
|
|
199
|
+
<div class="slds-checkbox">
|
|
200
|
+
<input type="checkbox" id="cb1"><label class="slds-checkbox__label" for="cb1"><span class="slds-checkbox_faux"></span><span class="slds-form-element__label">Option 1</span></label>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="slds-checkbox">
|
|
203
|
+
<input type="checkbox" id="cb2"><label class="slds-checkbox__label" for="cb2"><span class="slds-checkbox_faux"></span><span class="slds-form-element__label">Option 2</span></label>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</fieldset>`;
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
'lightning-radio-group': {
|
|
210
|
+
tag: 'fieldset',
|
|
211
|
+
transform: (attrs) => {
|
|
212
|
+
const label = attrs.label || 'Radio Group';
|
|
213
|
+
return `
|
|
214
|
+
<fieldset class="slds-form-element">
|
|
215
|
+
<legend class="slds-form-element__legend slds-form-element__label">${label}</legend>
|
|
216
|
+
<div class="slds-form-element__control">
|
|
217
|
+
<div class="slds-radio">
|
|
218
|
+
<input type="radio" name="radio" id="r1"><label class="slds-radio__label" for="r1"><span class="slds-radio_faux"></span><span class="slds-form-element__label">Option 1</span></label>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="slds-radio">
|
|
221
|
+
<input type="radio" name="radio" id="r2"><label class="slds-radio__label" for="r2"><span class="slds-radio_faux"></span><span class="slds-form-element__label">Option 2</span></label>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</fieldset>`;
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
'lightning-tabset': {
|
|
228
|
+
tag: 'div',
|
|
229
|
+
transform: () => `
|
|
230
|
+
<div class="slds-tabs_default">
|
|
231
|
+
<ul class="slds-tabs_default__nav" role="tablist">
|
|
232
|
+
<li class="slds-tabs_default__item slds-is-active" role="presentation">
|
|
233
|
+
<a class="slds-tabs_default__link" role="tab" tabindex="0">Tab 1</a>
|
|
234
|
+
</li>
|
|
235
|
+
<li class="slds-tabs_default__item" role="presentation">
|
|
236
|
+
<a class="slds-tabs_default__link" role="tab" tabindex="-1">Tab 2</a>
|
|
237
|
+
</li>
|
|
238
|
+
</ul>
|
|
239
|
+
<div class="slds-tabs_default__content slds-show" role="tabpanel">
|
|
240
|
+
<!-- TAB_CONTENT -->
|
|
241
|
+
</div>
|
|
242
|
+
</div>`
|
|
243
|
+
},
|
|
244
|
+
'lightning-tab': {
|
|
245
|
+
tag: 'div',
|
|
246
|
+
transform: (attrs) => {
|
|
247
|
+
const label = attrs.label || 'Tab';
|
|
248
|
+
return `<div class="slds-tabs_default__content" data-tab-label="${label}"><!-- TAB_CONTENT --></div>`;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
'lightning-accordion': {
|
|
252
|
+
tag: 'ul',
|
|
253
|
+
class: 'slds-accordion'
|
|
254
|
+
},
|
|
255
|
+
'lightning-accordion-section': {
|
|
256
|
+
tag: 'li',
|
|
257
|
+
transform: (attrs) => {
|
|
258
|
+
const label = attrs.label || 'Section';
|
|
259
|
+
return `
|
|
260
|
+
<li class="slds-accordion__list-item">
|
|
261
|
+
<section class="slds-accordion__section slds-is-open">
|
|
262
|
+
<div class="slds-accordion__summary">
|
|
263
|
+
<h2 class="slds-accordion__summary-heading">
|
|
264
|
+
<button class="slds-button slds-button_reset slds-accordion__summary-action">
|
|
265
|
+
<span class="slds-accordion__summary-content">${label}</span>
|
|
266
|
+
</button>
|
|
267
|
+
</h2>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="slds-accordion__content">
|
|
270
|
+
<!-- SLOT_CONTENT -->
|
|
271
|
+
</div>
|
|
272
|
+
</section>
|
|
273
|
+
</li>`;
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
'lightning-badge': {
|
|
277
|
+
tag: 'span',
|
|
278
|
+
transform: (attrs) => {
|
|
279
|
+
const label = attrs.label || 'Badge';
|
|
280
|
+
return `<span class="slds-badge">${label}</span>`;
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
'lightning-progress-bar': {
|
|
284
|
+
tag: 'div',
|
|
285
|
+
transform: (attrs) => {
|
|
286
|
+
const value = attrs.value || '50';
|
|
287
|
+
return `
|
|
288
|
+
<div class="slds-progress-bar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${value}" role="progressbar">
|
|
289
|
+
<span class="slds-progress-bar__value" style="width: ${value}%;">
|
|
290
|
+
<span class="slds-assistive-text">Progress: ${value}%</span>
|
|
291
|
+
</span>
|
|
292
|
+
</div>`;
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
'lightning-helptext': {
|
|
296
|
+
tag: 'div',
|
|
297
|
+
transform: (attrs) => {
|
|
298
|
+
const content = attrs.content || 'Help text';
|
|
299
|
+
return `
|
|
300
|
+
<div class="slds-form-element__icon">
|
|
301
|
+
<button class="slds-button slds-button_icon" title="${content}">
|
|
302
|
+
<span class="slds-assistive-text">Help</span>
|
|
303
|
+
</button>
|
|
304
|
+
</div>`;
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
'lightning-formatted-text': {
|
|
308
|
+
tag: 'span',
|
|
309
|
+
transform: (attrs) => {
|
|
310
|
+
const value = attrs.value || '{text}';
|
|
311
|
+
return `<span class="lwc-preview-data">${value}</span>`;
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
'lightning-formatted-number': {
|
|
315
|
+
tag: 'span',
|
|
316
|
+
transform: (attrs) => {
|
|
317
|
+
const value = attrs.value || '0';
|
|
318
|
+
return `<span class="lwc-preview-data">${value}</span>`;
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
'lightning-formatted-date-time': {
|
|
322
|
+
tag: 'span',
|
|
323
|
+
transform: () => `<span class="lwc-preview-data">{date/time}</span>`
|
|
324
|
+
},
|
|
325
|
+
'lightning-record-edit-form': {
|
|
326
|
+
tag: 'form',
|
|
327
|
+
class: 'slds-form',
|
|
328
|
+
transform: () => `
|
|
329
|
+
<form class="slds-form">
|
|
330
|
+
<div class="lwc-preview-placeholder">
|
|
331
|
+
<span class="lwc-preview-label">Record Edit Form</span>
|
|
332
|
+
<!-- SLOT_CONTENT -->
|
|
333
|
+
</div>
|
|
334
|
+
</form>`
|
|
335
|
+
},
|
|
336
|
+
'lightning-record-view-form': {
|
|
337
|
+
tag: 'div',
|
|
338
|
+
transform: () => `
|
|
339
|
+
<div class="slds-form">
|
|
340
|
+
<div class="lwc-preview-placeholder">
|
|
341
|
+
<span class="lwc-preview-label">Record View Form</span>
|
|
342
|
+
<!-- SLOT_CONTENT -->
|
|
343
|
+
</div>
|
|
344
|
+
</div>`
|
|
345
|
+
},
|
|
346
|
+
'lightning-input-field': {
|
|
347
|
+
tag: 'div',
|
|
348
|
+
transform: (attrs) => {
|
|
349
|
+
const fieldName = attrs['field-name'] || 'Field';
|
|
350
|
+
return `
|
|
351
|
+
<div class="slds-form-element">
|
|
352
|
+
<label class="slds-form-element__label">${fieldName}</label>
|
|
353
|
+
<div class="slds-form-element__control">
|
|
354
|
+
<input class="slds-input" placeholder="{${fieldName}}">
|
|
355
|
+
</div>
|
|
356
|
+
</div>`;
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
'lightning-output-field': {
|
|
360
|
+
tag: 'div',
|
|
361
|
+
transform: (attrs) => {
|
|
362
|
+
const fieldName = attrs['field-name'] || 'Field';
|
|
363
|
+
return `
|
|
364
|
+
<div class="slds-form-element">
|
|
365
|
+
<span class="slds-form-element__label">${fieldName}</span>
|
|
366
|
+
<div class="slds-form-element__control slds-form-element__static">
|
|
367
|
+
<span class="lwc-preview-data">{${fieldName}}</span>
|
|
368
|
+
</div>
|
|
369
|
+
</div>`;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
'lightning-tile': {
|
|
373
|
+
tag: 'div',
|
|
374
|
+
transform: (attrs) => {
|
|
375
|
+
const label = attrs.label || 'Tile';
|
|
376
|
+
return `
|
|
377
|
+
<article class="slds-tile">
|
|
378
|
+
<h3 class="slds-tile__title slds-truncate">
|
|
379
|
+
<a href="#">${label}</a>
|
|
380
|
+
</h3>
|
|
381
|
+
<div class="slds-tile__detail">
|
|
382
|
+
<!-- SLOT_CONTENT -->
|
|
383
|
+
</div>
|
|
384
|
+
</article>`;
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
// Custom LWC components get a placeholder
|
|
388
|
+
'c-': {
|
|
389
|
+
tag: 'div',
|
|
390
|
+
transform: (attrs) => {
|
|
391
|
+
const componentName = attrs._componentName || 'custom-component';
|
|
392
|
+
return `
|
|
393
|
+
<div class="lwc-preview-custom-component">
|
|
394
|
+
<span class="lwc-preview-label"><${componentName}></span>
|
|
395
|
+
<!-- SLOT_CONTENT -->
|
|
396
|
+
</div>`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* Parse attributes from an HTML tag string
|
|
402
|
+
*/
|
|
403
|
+
function parseAttributes(tagContent) {
|
|
404
|
+
const attrs = {};
|
|
405
|
+
// Match attribute patterns: name="value" or name={expression} or name
|
|
406
|
+
const attrRegex = /(\S+?)(?:=(?:"([^"]*)"|{([^}]*)})|(?=\s|>|$))/g;
|
|
407
|
+
let match;
|
|
408
|
+
while ((match = attrRegex.exec(tagContent)) !== null) {
|
|
409
|
+
const name = match[1];
|
|
410
|
+
const value = match[2] || match[3] || 'true';
|
|
411
|
+
attrs[name] = value;
|
|
412
|
+
}
|
|
413
|
+
return attrs;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Transform LWC HTML to preview HTML
|
|
417
|
+
*/
|
|
418
|
+
function transformLwcToPreviewHtml(lwcHtml) {
|
|
419
|
+
let previewHtml = lwcHtml;
|
|
420
|
+
// Remove <template> root tag
|
|
421
|
+
previewHtml = previewHtml.replace(/^\s*<template>\s*/i, '');
|
|
422
|
+
previewHtml = previewHtml.replace(/\s*<\/template>\s*$/i, '');
|
|
423
|
+
// Handle lwc:if, lwc:elseif, lwc:else - keep content visible with indicator
|
|
424
|
+
previewHtml = previewHtml.replace(/<template\s+lwc:if={([^}]+)}>/gi, '<div class="lwc-preview-conditional" data-condition="if: $1">');
|
|
425
|
+
previewHtml = previewHtml.replace(/<template\s+lwc:elseif={([^}]+)}>/gi, '<div class="lwc-preview-conditional" data-condition="elseif: $1">');
|
|
426
|
+
previewHtml = previewHtml.replace(/<template\s+lwc:else>/gi, '<div class="lwc-preview-conditional" data-condition="else">');
|
|
427
|
+
// Legacy if:true, if:false
|
|
428
|
+
previewHtml = previewHtml.replace(/<template\s+if:true={([^}]+)}>/gi, '<div class="lwc-preview-conditional" data-condition="if: $1">');
|
|
429
|
+
previewHtml = previewHtml.replace(/<template\s+if:false={([^}]+)}>/gi, '<div class="lwc-preview-conditional" data-condition="if not: $1">');
|
|
430
|
+
// Handle for:each loops - show one iteration with indicator
|
|
431
|
+
previewHtml = previewHtml.replace(/<template\s+for:each={([^}]+)}\s+for:item="([^"]+)"(?:\s+for:index="([^"]+)")?>/gi, '<div class="lwc-preview-iteration" data-loop="for each $2 in $1">');
|
|
432
|
+
// Close template tags that were converted to divs
|
|
433
|
+
previewHtml = previewHtml.replace(/<\/template>/gi, '</div>');
|
|
434
|
+
// Transform Lightning components
|
|
435
|
+
for (const [lwcTag, config] of Object.entries(LIGHTNING_TO_HTML)) {
|
|
436
|
+
if (lwcTag === 'c-') {
|
|
437
|
+
// Handle custom components (c-* prefix)
|
|
438
|
+
const customRegex = new RegExp(`<(c-[a-z0-9-]+)([^>]*)>`, 'gi');
|
|
439
|
+
previewHtml = previewHtml.replace(customRegex, (_, tagName, attributes) => {
|
|
440
|
+
const attrs = parseAttributes(attributes);
|
|
441
|
+
attrs._componentName = tagName;
|
|
442
|
+
if (config.transform) {
|
|
443
|
+
return config.transform(attrs);
|
|
444
|
+
}
|
|
445
|
+
return `<div class="lwc-preview-custom-component" data-component="${tagName}">`;
|
|
446
|
+
});
|
|
447
|
+
previewHtml = previewHtml.replace(/<\/c-[a-z0-9-]+>/gi, '</div>');
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
// Handle standard Lightning components
|
|
451
|
+
const selfClosingRegex = new RegExp(`<${lwcTag}([^>]*?)\\s*/>`, 'gi');
|
|
452
|
+
previewHtml = previewHtml.replace(selfClosingRegex, (_, attributes) => {
|
|
453
|
+
const attrs = parseAttributes(attributes);
|
|
454
|
+
if (config.transform) {
|
|
455
|
+
return config.transform(attrs);
|
|
456
|
+
}
|
|
457
|
+
const className = config.class || '';
|
|
458
|
+
return `<${config.tag} class="${className}"></${config.tag}>`;
|
|
459
|
+
});
|
|
460
|
+
// Check if this is a self-contained transform (no slot content)
|
|
461
|
+
const isSelfContained = config.selfContained ||
|
|
462
|
+
(config.transform && !config.transform({}).includes('<!-- SLOT_CONTENT -->') && !config.transform({}).includes('<!-- TAB_CONTENT -->'));
|
|
463
|
+
// Handle open tags
|
|
464
|
+
const openRegex = new RegExp(`<${lwcTag}([^>]*)>`, 'gi');
|
|
465
|
+
previewHtml = previewHtml.replace(openRegex, (_, attributes) => {
|
|
466
|
+
const attrs = parseAttributes(attributes);
|
|
467
|
+
if (config.transform) {
|
|
468
|
+
// For components with content, we need to handle slots
|
|
469
|
+
let transformed = config.transform(attrs);
|
|
470
|
+
// Check if this is a container component (has SLOT_CONTENT marker)
|
|
471
|
+
if (transformed.includes('<!-- SLOT_CONTENT -->')) {
|
|
472
|
+
// Remove the closing part, it will be added by the close tag
|
|
473
|
+
transformed = transformed.replace(/<!-- SLOT_CONTENT -->[\s\S]*$/, '');
|
|
474
|
+
}
|
|
475
|
+
return transformed;
|
|
476
|
+
}
|
|
477
|
+
const className = config.class || '';
|
|
478
|
+
return `<${config.tag} class="${className}">`;
|
|
479
|
+
});
|
|
480
|
+
// Handle close tags - skip for self-contained transforms
|
|
481
|
+
if (!isSelfContained) {
|
|
482
|
+
const closeRegex = new RegExp(`</${lwcTag}>`, 'gi');
|
|
483
|
+
previewHtml = previewHtml.replace(closeRegex, `</${config.tag}>`);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
// Remove the close tag entirely for self-contained components
|
|
487
|
+
const closeRegex = new RegExp(`</${lwcTag}>`, 'gi');
|
|
488
|
+
previewHtml = previewHtml.replace(closeRegex, '');
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Transform data expressions {property} to visual placeholders
|
|
493
|
+
previewHtml = previewHtml.replace(/{([a-zA-Z_][a-zA-Z0-9_.]+)}/g, '<span class="lwc-preview-data">{$1}</span>');
|
|
494
|
+
// Transform event handlers - show as data attributes
|
|
495
|
+
previewHtml = previewHtml.replace(/on(\w+)={(\w+)}/gi, 'data-event-$1="$2"');
|
|
496
|
+
return previewHtml;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Generate a complete preview HTML document
|
|
500
|
+
*/
|
|
501
|
+
function generatePreviewHtml(bundle, componentCss) {
|
|
502
|
+
const previewContent = transformLwcToPreviewHtml(bundle.html);
|
|
503
|
+
const css = componentCss ?? bundle.css;
|
|
504
|
+
return `<!DOCTYPE html>
|
|
505
|
+
<html lang="en">
|
|
506
|
+
<head>
|
|
507
|
+
<meta charset="UTF-8">
|
|
508
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
509
|
+
<title>LWC Preview: ${bundle.name}</title>
|
|
510
|
+
|
|
511
|
+
<!-- Salesforce Lightning Design System -->
|
|
512
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/design-system/2.24.2/styles/salesforce-lightning-design-system.min.css">
|
|
513
|
+
|
|
514
|
+
<style>
|
|
515
|
+
/* Preview-specific styles */
|
|
516
|
+
body {
|
|
517
|
+
font-family: 'Salesforce Sans', Arial, sans-serif;
|
|
518
|
+
padding: 20px;
|
|
519
|
+
background: #f4f6f9;
|
|
520
|
+
margin: 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.preview-container {
|
|
524
|
+
max-width: 1200px;
|
|
525
|
+
margin: 0 auto;
|
|
526
|
+
background: white;
|
|
527
|
+
border-radius: 8px;
|
|
528
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
529
|
+
padding: 20px;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.preview-header {
|
|
533
|
+
border-bottom: 1px solid #e5e5e5;
|
|
534
|
+
padding-bottom: 16px;
|
|
535
|
+
margin-bottom: 20px;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.preview-header h1 {
|
|
539
|
+
font-size: 1.5rem;
|
|
540
|
+
color: #032d60;
|
|
541
|
+
margin: 0 0 8px 0;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.preview-header .meta {
|
|
545
|
+
color: #706e6b;
|
|
546
|
+
font-size: 0.875rem;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.preview-notice {
|
|
550
|
+
background: #fff8e1;
|
|
551
|
+
border: 1px solid #ffc107;
|
|
552
|
+
border-radius: 4px;
|
|
553
|
+
padding: 12px;
|
|
554
|
+
margin-bottom: 20px;
|
|
555
|
+
font-size: 0.875rem;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.preview-notice strong {
|
|
559
|
+
color: #ff8f00;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/* Data placeholder styling */
|
|
563
|
+
.lwc-preview-data {
|
|
564
|
+
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
|
565
|
+
color: #2e7d32;
|
|
566
|
+
padding: 2px 6px;
|
|
567
|
+
border-radius: 3px;
|
|
568
|
+
font-family: 'SF Mono', 'Consolas', monospace;
|
|
569
|
+
font-size: 0.85em;
|
|
570
|
+
border: 1px dashed #81c784;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* Conditional block styling */
|
|
574
|
+
.lwc-preview-conditional {
|
|
575
|
+
position: relative;
|
|
576
|
+
border: 2px dashed #2196f3;
|
|
577
|
+
border-radius: 4px;
|
|
578
|
+
padding: 12px;
|
|
579
|
+
margin: 8px 0;
|
|
580
|
+
background: rgba(33, 150, 243, 0.05);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.lwc-preview-conditional::before {
|
|
584
|
+
content: attr(data-condition);
|
|
585
|
+
position: absolute;
|
|
586
|
+
top: -10px;
|
|
587
|
+
left: 8px;
|
|
588
|
+
background: #2196f3;
|
|
589
|
+
color: white;
|
|
590
|
+
font-size: 0.7rem;
|
|
591
|
+
padding: 2px 8px;
|
|
592
|
+
border-radius: 3px;
|
|
593
|
+
font-family: 'SF Mono', 'Consolas', monospace;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/* Iteration block styling */
|
|
597
|
+
.lwc-preview-iteration {
|
|
598
|
+
position: relative;
|
|
599
|
+
border: 2px dashed #9c27b0;
|
|
600
|
+
border-radius: 4px;
|
|
601
|
+
padding: 12px;
|
|
602
|
+
margin: 8px 0;
|
|
603
|
+
background: rgba(156, 39, 176, 0.05);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.lwc-preview-iteration::before {
|
|
607
|
+
content: attr(data-loop);
|
|
608
|
+
position: absolute;
|
|
609
|
+
top: -10px;
|
|
610
|
+
left: 8px;
|
|
611
|
+
background: #9c27b0;
|
|
612
|
+
color: white;
|
|
613
|
+
font-size: 0.7rem;
|
|
614
|
+
padding: 2px 8px;
|
|
615
|
+
border-radius: 3px;
|
|
616
|
+
font-family: 'SF Mono', 'Consolas', monospace;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* Custom component placeholder */
|
|
620
|
+
.lwc-preview-custom-component {
|
|
621
|
+
border: 2px dashed #ff9800;
|
|
622
|
+
border-radius: 4px;
|
|
623
|
+
padding: 12px;
|
|
624
|
+
margin: 8px 0;
|
|
625
|
+
background: rgba(255, 152, 0, 0.05);
|
|
626
|
+
position: relative;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.lwc-preview-custom-component > .lwc-preview-label {
|
|
630
|
+
position: absolute;
|
|
631
|
+
top: -10px;
|
|
632
|
+
left: 8px;
|
|
633
|
+
background: #ff9800;
|
|
634
|
+
color: white;
|
|
635
|
+
font-size: 0.7rem;
|
|
636
|
+
padding: 2px 8px;
|
|
637
|
+
border-radius: 3px;
|
|
638
|
+
font-family: 'SF Mono', 'Consolas', monospace;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/* Placeholder for complex components */
|
|
642
|
+
.lwc-preview-placeholder {
|
|
643
|
+
background: #f5f5f5;
|
|
644
|
+
border: 1px solid #e0e0e0;
|
|
645
|
+
border-radius: 4px;
|
|
646
|
+
padding: 16px;
|
|
647
|
+
text-align: center;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.lwc-preview-label {
|
|
651
|
+
font-family: 'SF Mono', 'Consolas', monospace;
|
|
652
|
+
font-size: 0.8rem;
|
|
653
|
+
color: #757575;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/* Event handler indicators */
|
|
657
|
+
[data-event-click],
|
|
658
|
+
[data-event-change],
|
|
659
|
+
[data-event-submit] {
|
|
660
|
+
cursor: pointer;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
[data-event-click]::after,
|
|
664
|
+
[data-event-change]::after,
|
|
665
|
+
[data-event-submit]::after {
|
|
666
|
+
content: ' \u26a1';
|
|
667
|
+
font-size: 0.7em;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* Component-specific CSS from conversion */
|
|
671
|
+
${css || '/* No component CSS */'}
|
|
672
|
+
|
|
673
|
+
/* Legend */
|
|
674
|
+
.preview-legend {
|
|
675
|
+
margin-top: 24px;
|
|
676
|
+
padding-top: 16px;
|
|
677
|
+
border-top: 1px solid #e5e5e5;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.preview-legend h3 {
|
|
681
|
+
font-size: 0.875rem;
|
|
682
|
+
color: #706e6b;
|
|
683
|
+
margin: 0 0 12px 0;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.preview-legend-items {
|
|
687
|
+
display: flex;
|
|
688
|
+
flex-wrap: wrap;
|
|
689
|
+
gap: 16px;
|
|
690
|
+
font-size: 0.75rem;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.preview-legend-item {
|
|
694
|
+
display: flex;
|
|
695
|
+
align-items: center;
|
|
696
|
+
gap: 6px;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.preview-legend-item .sample {
|
|
700
|
+
width: 20px;
|
|
701
|
+
height: 20px;
|
|
702
|
+
border-radius: 3px;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.preview-legend-item .data-sample {
|
|
706
|
+
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
|
707
|
+
border: 1px dashed #81c784;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.preview-legend-item .conditional-sample {
|
|
711
|
+
border: 2px dashed #2196f3;
|
|
712
|
+
background: rgba(33, 150, 243, 0.1);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.preview-legend-item .loop-sample {
|
|
716
|
+
border: 2px dashed #9c27b0;
|
|
717
|
+
background: rgba(156, 39, 176, 0.1);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.preview-legend-item .custom-sample {
|
|
721
|
+
border: 2px dashed #ff9800;
|
|
722
|
+
background: rgba(255, 152, 0, 0.1);
|
|
723
|
+
}
|
|
724
|
+
</style>
|
|
725
|
+
</head>
|
|
726
|
+
<body>
|
|
727
|
+
<div class="preview-container">
|
|
728
|
+
<div class="preview-header">
|
|
729
|
+
<h1>${bundle.name}</h1>
|
|
730
|
+
<div class="meta">LWC Preview • Generated by lwc-convert</div>
|
|
731
|
+
</div>
|
|
732
|
+
|
|
733
|
+
<div class="preview-notice">
|
|
734
|
+
<strong>Preview Mode:</strong> This is a visual approximation of your LWC component.
|
|
735
|
+
Dynamic data is shown as placeholders, and Lightning components are rendered using SLDS styling.
|
|
736
|
+
Some interactive features may not work as they would in Salesforce.
|
|
737
|
+
</div>
|
|
738
|
+
|
|
739
|
+
<div class="preview-content">
|
|
740
|
+
${previewContent}
|
|
741
|
+
</div>
|
|
742
|
+
|
|
743
|
+
<div class="preview-legend">
|
|
744
|
+
<h3>Legend</h3>
|
|
745
|
+
<div class="preview-legend-items">
|
|
746
|
+
<div class="preview-legend-item">
|
|
747
|
+
<div class="sample data-sample"></div>
|
|
748
|
+
<span>Dynamic Data</span>
|
|
749
|
+
</div>
|
|
750
|
+
<div class="preview-legend-item">
|
|
751
|
+
<div class="sample conditional-sample"></div>
|
|
752
|
+
<span>Conditional Block</span>
|
|
753
|
+
</div>
|
|
754
|
+
<div class="preview-legend-item">
|
|
755
|
+
<div class="sample loop-sample"></div>
|
|
756
|
+
<span>Loop/Iteration</span>
|
|
757
|
+
</div>
|
|
758
|
+
<div class="preview-legend-item">
|
|
759
|
+
<div class="sample custom-sample"></div>
|
|
760
|
+
<span>Custom Component</span>
|
|
761
|
+
</div>
|
|
762
|
+
<div class="preview-legend-item">
|
|
763
|
+
<span>\u26a1</span>
|
|
764
|
+
<span>Event Handler</span>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
|
|
770
|
+
<script>
|
|
771
|
+
// Simple interactivity for tabs
|
|
772
|
+
document.querySelectorAll('.slds-tabs_default__item').forEach(tab => {
|
|
773
|
+
tab.addEventListener('click', function() {
|
|
774
|
+
const parent = this.closest('.slds-tabs_default');
|
|
775
|
+
parent.querySelectorAll('.slds-tabs_default__item').forEach(t => t.classList.remove('slds-is-active'));
|
|
776
|
+
this.classList.add('slds-is-active');
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// Accordion toggle
|
|
781
|
+
document.querySelectorAll('.slds-accordion__summary-action').forEach(btn => {
|
|
782
|
+
btn.addEventListener('click', function() {
|
|
783
|
+
const section = this.closest('.slds-accordion__section');
|
|
784
|
+
section.classList.toggle('slds-is-open');
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
</script>
|
|
788
|
+
</body>
|
|
789
|
+
</html>`;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Write the preview HTML file
|
|
793
|
+
*/
|
|
794
|
+
async function writePreviewFile(outputDir, bundle, dryRun = false) {
|
|
795
|
+
const previewPath = path.join(outputDir, bundle.name, `${bundle.name}-preview.html`);
|
|
796
|
+
const previewHtml = generatePreviewHtml(bundle, bundle.css);
|
|
797
|
+
if (dryRun) {
|
|
798
|
+
logger_1.logger.info(`[DRY RUN] Would write preview: ${previewPath}`);
|
|
799
|
+
return previewPath;
|
|
800
|
+
}
|
|
801
|
+
await fs.ensureDir(path.dirname(previewPath));
|
|
802
|
+
await fs.writeFile(previewPath, previewHtml, 'utf-8');
|
|
803
|
+
logger_1.logger.file('CREATE', previewPath);
|
|
804
|
+
return previewPath;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Open the preview file in the default browser
|
|
808
|
+
*/
|
|
809
|
+
async function openPreview(previewPath) {
|
|
810
|
+
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
811
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
|
|
812
|
+
const execAsync = promisify(exec);
|
|
813
|
+
const platform = process.platform;
|
|
814
|
+
let command;
|
|
815
|
+
if (platform === 'win32') {
|
|
816
|
+
command = `start "" "${previewPath}"`;
|
|
817
|
+
}
|
|
818
|
+
else if (platform === 'darwin') {
|
|
819
|
+
command = `open "${previewPath}"`;
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
command = `xdg-open "${previewPath}"`;
|
|
823
|
+
}
|
|
824
|
+
try {
|
|
825
|
+
await execAsync(command);
|
|
826
|
+
logger_1.logger.success('Opened preview in default browser');
|
|
827
|
+
}
|
|
828
|
+
catch (error) {
|
|
829
|
+
logger_1.logger.warn(`Could not open preview automatically: ${error.message}`);
|
|
830
|
+
logger_1.logger.info(`Open manually: ${previewPath}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
//# sourceMappingURL=preview-generator.js.map
|