Skip to content

Commit f369e56

Browse files
committed
feat: initial single component preview
1 parent 3e1c4cd commit f369e56

File tree

6 files changed

+96
-28
lines changed

6 files changed

+96
-28
lines changed

command-snapshot.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"alias": [],
1212
"command": "lightning:dev:component",
1313
"flagAliases": [],
14-
"flagChars": ["n", "o"],
15-
"flags": ["flags-dir", "json", "name", "target-org"],
14+
"flagChars": ["n"],
15+
"flags": ["flags-dir", "json", "name"],
1616
"plugin": "@salesforce/plugin-lightning-dev"
1717
},
1818
{

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)

src/commands/lightning/dev/component.ts

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,80 @@
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+
const MODULES_DIR = './force-app/main/default/lwc';
1819

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

2437
public static readonly flags = {
2538
name: Flags.string({
2639
summary: messages.getMessage('flags.name.summary'),
27-
description: messages.getMessage('flags.name.description'),
2840
char: 'n',
29-
required: false,
41+
requiredOrDefaulted: false,
3042
}),
31-
// TODO should this be required or optional?
32-
// We don't technically need this if your components are simple / don't need any data from your org
33-
'target-org': Flags.requiredOrg(),
3443
};
3544

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

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

42-
// TODO implement me
43-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
4478
await cmpDev({
45-
componentName: name,
79+
mode: 'dev',
80+
port,
81+
name: `c/${name}`,
4682
});
47-
48-
return {
49-
path: '',
50-
};
5183
}
5284
}

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)