Skip to content

Commit ccb6ca0

Browse files
bpbuchnrkruk
authored andcommitted
feat: single component preview backport
1 parent ca49e81 commit ccb6ca0

File tree

7 files changed

+328
-256
lines changed

7 files changed

+328
-256
lines changed

messages/lightning.dev.component.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,33 @@ Preview LWC components in isolation.
44

55
# description
66

7-
Preview LWC components in isolation. Replacement for: https://developer.salesforce.com/docs/platform/sfvscode-extensions/guide/lwclocaldev.html
7+
Component preview launches an isolated development environment for Lightning Web Components, enabling rapid iteration without needing to deploy changes. The server provides real-time previews of your components through hot module replacement (HMR), automatically refreshing the view when source files are modified.
8+
9+
When running the development server, these changes are immediately reflected:
10+
11+
- Component template (HTML) modifications
12+
- Styling updates in component CSS files
13+
- JavaScript logic changes that don't modify the component's API
14+
- Adding or updating internal component dependencies
15+
- Modifying static resources used by the component
16+
17+
See the LWC Development Guide for more information about component development best practices and limitations.
818

919
# flags.name.summary
1020

11-
Description of a flag.
21+
Name of a component to preview.
22+
23+
# error.directory
24+
25+
Unable to find components
1226

13-
# flags.name.description
27+
# error.component
1428

15-
More information about a flag. Don't repeat the summary.
29+
Unable to determine component name
1630

1731
# examples
1832

19-
- <%= config.bin %> <%= command.id %>
33+
- Select a component and launch the component preview:
34+
<%= config.bin %> <%= command.id %>
35+
- Launch component preview for "myComponent":
36+
<%= config.bin %> <%= command.id %> --name myComponent

messages/prompts.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ Which device do you want to use for the preview?
3333
# error.device.enumeration
3434

3535
Unable to enumerate a list of available devices.
36+
37+
# component.select
38+
39+
Which Lightning Web Component would you like to preview (Use arrow keys)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"@inquirer/select": "^2.4.7",
1010
"@lwc/lwc-dev-server": "~11.1.0",
1111
"@lwc/sfdc-lwc-compiler": "~11.1.0",
12-
"@lwrjs/api": "0.16.6",
12+
"@lwrjs/api": "0.16.8",
1313
"@oclif/core": "^4.1.0",
1414
"@salesforce/core": "^8.6.2",
1515
"@salesforce/kit": "^3.1.6",

src/commands/lightning/dev/component.ts

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,84 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8+
import fs from 'node:fs';
9+
import path from 'node:path';
810
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
911
import { Messages } from '@salesforce/core';
1012
import { cmpDev } from '@lwrjs/api';
13+
import { PromptUtils } from '../../../shared/promptUtils.js';
1114

1215
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
1316
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.component');
1417

15-
export type LightningDevComponentResult = {
16-
path: string;
17-
};
18+
// TODO support other module directories
19+
const MODULES_DIR = path.resolve(path.join('force-app', 'main', 'default', 'lwc'));
1820

19-
export default class LightningDevComponent extends SfCommand<LightningDevComponentResult> {
21+
function getDirectories(filePath: string): string[] {
22+
try {
23+
const items = fs.readdirSync(filePath);
24+
25+
const directories = items.filter((item) => fs.statSync(path.join(filePath, item)).isDirectory());
26+
27+
return directories;
28+
} catch (error) {
29+
return [];
30+
}
31+
}
32+
33+
export default class LightningDevComponent extends SfCommand<void> {
2034
public static readonly summary = messages.getMessage('summary');
2135
public static readonly description = messages.getMessage('description');
2236
public static readonly examples = messages.getMessages('examples');
2337

2438
public static readonly flags = {
2539
name: Flags.string({
2640
summary: messages.getMessage('flags.name.summary'),
27-
description: messages.getMessage('flags.name.description'),
2841
char: 'n',
29-
required: false,
42+
requiredOrDefaulted: false,
3043
}),
3144
// TODO should this be required or optional?
3245
// We don't technically need this if your components are simple / don't need any data from your org
33-
'target-org': Flags.requiredOrg(),
46+
'target-org': Flags.optionalOrg(),
3447
};
3548

36-
public async run(): Promise<LightningDevComponentResult> {
49+
public async run(): Promise<void> {
3750
const { flags } = await this.parse(LightningDevComponent);
3851

39-
const name = flags.name ?? 'world';
40-
this.log(`preview component: ${name}`);
52+
let name = flags.name;
53+
if (!name) {
54+
const dirs = getDirectories(path.resolve(MODULES_DIR));
55+
if (!dirs) {
56+
throw new Error(messages.getMessage('error.directory'));
57+
}
58+
59+
const components = dirs.map((dir) => {
60+
const xmlPath = path.resolve(path.join(MODULES_DIR, dir, `${dir}.js-meta.xml`));
61+
const xmlContent = fs.readFileSync(xmlPath, 'utf-8');
62+
const label = xmlContent.match(/<masterLabel>(.*?)<\/masterLabel>/);
63+
const description = xmlContent.match(/<description>(.*?)<\/description>/);
64+
65+
return {
66+
name: dir,
67+
label: label ? label[1] : '',
68+
description: description ? description[1] : '',
69+
};
70+
});
71+
72+
name = await PromptUtils.promptUserToSelectComponent(components);
73+
if (!name) {
74+
throw new Error(messages.getMessage('error.component'));
75+
}
76+
}
77+
78+
this.log('Starting application on port 3000...');
79+
80+
const port = parseInt(process.env.PORT ?? '3000', 10);
4181

42-
// TODO implement me
43-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
4482
await cmpDev({
45-
componentName: name,
83+
mode: 'dev',
84+
port,
85+
name: `c/${name}`,
4686
});
47-
48-
return {
49-
path: '',
50-
};
5187
}
5288
}

src/commands/lightning/dev/site.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import fs from 'node:fs';
88
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
99
import { Messages } from '@salesforce/core';
10-
import { expDev, LocalDevOptions, setupDev } from '@lwrjs/api';
10+
import { expDev, SitesLocalDevOptions, setupDev } from '@lwrjs/api';
1111
import { OrgUtils } from '../../../shared/orgUtils.js';
1212
import { PromptUtils } from '../../../shared/promptUtils.js';
1313
import { ExperienceSite } from '../../../shared/experience/expSite.js';
@@ -86,7 +86,7 @@ export default class LightningDevSite extends SfCommand<void> {
8686

8787
// Start the dev server
8888
const port = parseInt(process.env.PORT ?? '3000', 10);
89-
const startupParams: LocalDevOptions = {
89+
const startupParams: SitesLocalDevOptions = {
9090
sfCLI: true,
9191
authToken,
9292
open: process.env.OPEN_BROWSER === 'false' ? false : true,

src/shared/promptUtils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ export class PromptUtils {
9090
return response;
9191
}
9292

93+
public static async promptUserToSelectComponent(components: Array<Record<string, string>>): Promise<string> {
94+
const choices = components.map((component) => ({
95+
name: component.label.length > 0 ? component.label : component.name,
96+
value: component.name,
97+
description: component.description,
98+
}));
99+
100+
const response = await select({
101+
message: messages.getMessage('component.select'),
102+
choices,
103+
});
104+
105+
return response;
106+
}
107+
93108
// returns the shorthand version of a Version object (eg. 17.0.0 => 17, 17.4.0 => 17.4, 17.4.1 => 17.4.1)
94109
private static getShortVersion(version: Version | string): string {
95110
// TODO: consider making this function part of the Version class in @lwc-dev-mobile-core

0 commit comments

Comments
 (0)