Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ yarn update-snapshots
<!-- commands -->

- [`sf lightning dev app`](#sf-lightning-dev-app)
- [`sf lightning dev component`](#sf-lightning-dev-component)
- [`sf lightning dev site`](#sf-lightning-dev-site)

## `sf lightning dev app`
Expand Down Expand Up @@ -200,7 +201,52 @@ EXAMPLES
$ sf lightning dev app --target-org myOrg --device-type ios --device-id "iPhone 15 Pro Max"
```

_See code: [src/commands/lightning/dev/app.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/2.10.2/src/commands/lightning/dev/app.ts)_
_See code: [src/commands/lightning/dev/app.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/2.10.3-demo.0/src/commands/lightning/dev/app.ts)_

## `sf lightning dev component`

Preview LWC components in isolation.

```
USAGE
$ sf lightning dev component [--json] [--flags-dir <value>] [-n <value>] [-o <value>]

FLAGS
-n, --name=<value> Name of a component to preview.
-o, --target-org=<value> Username or alias of the target org.

GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.

DESCRIPTION
Preview LWC components in isolation.

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.

When running the development server, these changes are immediately reflected:

- Component template (HTML) modifications
- Styling updates in component CSS files
- JavaScript logic changes that don't modify the component's API
- Adding or updating internal component dependencies
- Modifying static resources used by the component

See the LWC Development Guide for more information about component development best practices and limitations.

EXAMPLES
Select a component and launch the component preview:

$ sf lightning dev component

Launch component preview for "myComponent":

$ sf lightning dev component --name myComponent
```

_See code: [src/commands/lightning/dev/component.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/2.10.3-demo.0/src/commands/lightning/dev/component.ts)_

## `sf lightning dev site`

Expand Down Expand Up @@ -254,6 +300,6 @@ EXAMPLES
$ sf lightning dev site --name "Partner Central" --target-org myOrg --get-latest
```

_See code: [src/commands/lightning/dev/site.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/2.10.2/src/commands/lightning/dev/site.ts)_
_See code: [src/commands/lightning/dev/site.ts](https://github.com/salesforcecli/plugin-lightning-dev/blob/2.10.3-demo.0/src/commands/lightning/dev/site.ts)_

<!-- commandsstop -->
8 changes: 8 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
"flags": ["device-id", "device-type", "flags-dir", "name", "target-org"],
"plugin": "@salesforce/plugin-lightning-dev"
},
{
"alias": [],
"command": "lightning:dev:component",
"flagAliases": [],
"flagChars": ["n", "o"],
"flags": ["flags-dir", "json", "name", "target-org"],
"plugin": "@salesforce/plugin-lightning-dev"
},
{
"alias": [],
"command": "lightning:dev:site",
Expand Down
36 changes: 36 additions & 0 deletions messages/lightning.dev.component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# summary

Preview LWC components in isolation.

# description

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.

When running the development server, these changes are immediately reflected:

- Component template (HTML) modifications
- Styling updates in component CSS files
- JavaScript logic changes that don't modify the component's API
- Adding or updating internal component dependencies
- Modifying static resources used by the component

See the LWC Development Guide for more information about component development best practices and limitations.

# flags.name.summary

Name of a component to preview.

# error.directory

Unable to find components

# error.component

Unable to determine component name

# examples

- Select a component and launch the component preview:
<%= config.bin %> <%= command.id %>
- Launch component preview for "myComponent":
<%= config.bin %> <%= command.id %> --name myComponent
4 changes: 4 additions & 0 deletions messages/prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ Which device do you want to use for the preview?
# error.device.enumeration

Unable to enumerate a list of available devices.

# component.select

Which Lightning Web Component would you like to preview (Use arrow keys)
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"name": "@salesforce/plugin-lightning-dev",
"description": "Lightning development tools for LEX, Mobile, and Experience Sites",
"version": "2.10.2",
"version": "2.10.3-demo.0",
"author": "Salesforce",
"bugs": "https://github.com/forcedotcom/cli/issues",
"dependencies": {
"@inquirer/prompts": "^5.3.8",
"@inquirer/select": "^2.4.7",
"@lwc/lwc-dev-server": "~11.1.0",
"@lwc/sfdc-lwc-compiler": "~11.1.0",
"@lwrjs/api": "0.16.3",
"@lwrjs/api": "0.16.8",
"@oclif/core": "^4.1.0",
"@salesforce/core": "^8.6.2",
"@salesforce/kit": "^3.1.6",
Expand Down
88 changes: 88 additions & 0 deletions src/commands/lightning/dev/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import fs from 'node:fs';
import path from 'node:path';
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { cmpDev } from '@lwrjs/api';
import { PromptUtils } from '../../../shared/promptUtils.js';

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

// TODO support other module directories
const MODULES_DIR = path.resolve(path.join('force-app', 'main', 'default', 'lwc'));

function getDirectories(filePath: string): string[] {
try {
const items = fs.readdirSync(filePath);

const directories = items.filter((item) => fs.statSync(path.join(filePath, item)).isDirectory());

return directories;
} catch (error) {
return [];
}
}

export default class LightningDevComponent extends SfCommand<void> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');

public static readonly flags = {
name: Flags.string({
summary: messages.getMessage('flags.name.summary'),
char: 'n',
requiredOrDefaulted: false,
}),
// TODO should this be required or optional?
// We don't technically need this if your components are simple / don't need any data from your org
'target-org': Flags.optionalOrg(),
};

public async run(): Promise<void> {
const { flags } = await this.parse(LightningDevComponent);

let name = flags.name;
if (!name) {
const dirs = getDirectories(path.resolve(MODULES_DIR));
if (!dirs) {
throw new Error(messages.getMessage('error.directory'));
}

const components = dirs.map((dir) => {
const xmlPath = path.resolve(path.join(MODULES_DIR, dir, `${dir}.js-meta.xml`));
const xmlContent = fs.readFileSync(xmlPath, 'utf-8');
const label = xmlContent.match(/<masterLabel>(.*?)<\/masterLabel>/);
const description = xmlContent.match(/<description>(.*?)<\/description>/);

return {
name: dir,
label: label ? label[1] : '',
description: description ? description[1] : '',
};
});

name = await PromptUtils.promptUserToSelectComponent(components);
if (!name) {
throw new Error(messages.getMessage('error.component'));
}
}

this.log('Starting application on port 3000...');

const port = parseInt(process.env.PORT ?? '3000', 10);

await cmpDev({
mode: 'dev',
port,
name: `c/${name}`,
});
}
}
4 changes: 2 additions & 2 deletions src/commands/lightning/dev/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import fs from 'node:fs';
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { expDev, LocalDevOptions, setupDev } from '@lwrjs/api';
import { expDev, SitesLocalDevOptions, setupDev } from '@lwrjs/api';
import { OrgUtils } from '../../../shared/orgUtils.js';
import { PromptUtils } from '../../../shared/promptUtils.js';
import { ExperienceSite } from '../../../shared/experience/expSite.js';
Expand Down Expand Up @@ -86,7 +86,7 @@ export default class LightningDevSite extends SfCommand<void> {

// Start the dev server
const port = parseInt(process.env.PORT ?? '3000', 10);
const startupParams: LocalDevOptions = {
const startupParams: SitesLocalDevOptions = {
sfCLI: true,
authToken,
open: process.env.OPEN_BROWSER === 'false' ? false : true,
Expand Down
15 changes: 15 additions & 0 deletions src/shared/promptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,21 @@ export class PromptUtils {
return response;
}

public static async promptUserToSelectComponent(components: Array<Record<string, string>>): Promise<string> {
const choices = components.map((component) => ({
name: component.label.length > 0 ? component.label : component.name,
value: component.name,
description: component.description,
}));

const response = await select({
message: messages.getMessage('component.select'),
choices,
});

return response;
}

// returns the shorthand version of a Version object (eg. 17.0.0 => 17, 17.4.0 => 17.4, 17.4.1 => 17.4.1)
private static getShortVersion(version: Version | string): string {
// TODO: consider making this function part of the Version class in @lwc-dev-mobile-core
Expand Down
30 changes: 30 additions & 0 deletions test/commands/lightning/dev/component.nut.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
// TODO - add proper NUT tests
/*
import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
import { expect } from 'chai';

describe('lightning preview component NUTs', () => {
let session: TestSession;

before(async () => {
session = await TestSession.create({ devhubAuthStrategy: 'NONE' });
});

after(async () => {
await session?.clean();
});

it('should display provided name', () => {
const name = 'World';
const command = `lightning preview component --name ${name}`;
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout;
expect(output).to.contain(name);
});
});
*/
24 changes: 24 additions & 0 deletions test/commands/lightning/dev/component.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { TestContext } from '@salesforce/core/testSetup';
// import { expect } from 'chai';
// import { stubSfCommandUx } from '@salesforce/sf-plugins-core';

describe('lightning single component preview', () => {
const $$ = new TestContext();
// let sfCommandStubs: ReturnType<typeof stubSfCommandUx>;

beforeEach(() => {
// sfCommandStubs = stubSfCommandUx($$.SANDBOX);
});

afterEach(() => {
$$.restore();
});

it('todo add unit tests', async () => {});
});
Loading
Loading