mcpField
S
sf-lwc

sf-lwc

github.com/clientell-ai开发20101 次安装32 周前更新
代码仓库

关于 sf-lwc

sf-lwc 是面向 Salesforce 开发者的 Lightning Web Components 脚手架技能,可生成组件文件、配置和 Jest 测试,并提供 SLDS 样式、数据访问、导航与消息传递模式。

主要功能

  1. 01生成 HTML、JS、CSS 和元数据文件
  2. 02创建含模拟数据的 Jest 测试
  3. 03提供 SLDS 样式指引
  4. 04采用 LDS 与 Apex 数据模式
  5. 05涵盖导航和 Lightning Message Service

适用场景

  1. 01搭建新的 LWC 组件包
  2. 02为组件添加测试
  3. 03构建记录 CRUD 界面
  4. 04实现跨 DOM 组件通信

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/clientell-ai/salesforce-skills --skill sf-lwc

安装后在 Claude Code 中输入/sf-lwc确认已加载。

附带文件references/lwc-patterns.md

详细介绍

LWC 脚手架

你是一位 Salesforce Lightning Web Component 专家。生成完整、可用于生产环境的 LWC 组件包。

LWC 组件包结构

每个 LWC 都由以下文件组成,位于 force-app/main/default/lwc/componentName/:

myComponent/
├── myComponent.html          # Template
├── myComponent.js            # Controller
├── myComponent.css           # Styles (SLDS-compliant)
├── myComponent.js-meta.xml   # Configuration
└── __tests__/
    └── myComponent.test.js   # Jest tests

命名约定

  • 组件包文件夹:camelCase(例如,accountList)
  • HTML 标记:kebab-case,带有 c- 命名空间(例如,<c-account-list>)
  • JS 类:PascalCase(例如,AccountList)
  • CSS:遵循组件名称

JavaScript 控制器模式

import { LightningElement, api, wire, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';
import getRecords from '@salesforce/apex/MyController.getRecords';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';

export default class MyComponent extends NavigationMixin(LightningElement) {
    @api recordId;
    @track records = [];
    error;
    isLoading = false;

    @wire(getRecords, { recordId: '$recordId' })
    wiredRecords({ error, data }) {
        if (data) {
            this.records = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.records = [];
        }
    }

    handleAction() {
        this.isLoading = true;
        imperativeMethod({ param: this.recordId })
            .then(result => {
                this.dispatchEvent(new ShowToastEvent({
                    title: 'Success',
                    message: 'Operation completed',
                    variant: 'success'
                }));
            })
            .catch(error => {
                this.dispatchEvent(new ShowToastEvent({
                    title: 'Error',
                    message: error.body?.message || 'An error occurred',
                    variant: 'error'
                }));
            })
            .finally(() => {
                this.isLoading = false;
            });
    }
}

Meta XML 配置

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <objects>
                <object>Account</object>
            </objects>
            <property name="title" type="String" default="My Component"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Jest 测试模式

import { createElement } from 'lwc';
import MyComponent from 'c/myComponent';
import getRecords from '@salesforce/apex/MyController.getRecords';

// Mock Apex method
jest.mock('@salesforce/apex/MyController.getRecords', () => ({
    default: jest.fn()
}), { virtual: true });

const MOCK_DATA = [
    { Id: '001xx000003ABCDEF', Name: 'Test Account' }
];

describe('c-my-component', () => {
    afterEach(() => {
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
        jest.clearAllMocks();
    });

    it('renders records when data is returned', async () => {
        getRecords.mockResolvedValue(MOCK_DATA);

        const element = createElement('c-my-component', { is: MyComponent });
        element.recordId = '001xx000003ABCDEF';
        document.body.appendChild(element);

        await Promise.resolve();

        const items = element.shadowRoot.querySelectorAll('.record-item');
        expect(items.length).toBe(1);
    });

    it('shows error when apex call fails', async () => {
        getRecords.mockRejectedValue(new Error('Test error'));

        const element = createElement('c-my-component', { is: MyComponent });
        document.body.appendChild(element);

        await Promise.resolve();

        const errorEl = element.shadowRoot.querySelector('.error-message');
        expect(errorEl).toBeTruthy();
    });
});
Lightning Data Service(LDS)

使用 lightning/uiRecordApi 执行 CRUD,无需 Apex:

  • getRecord wire 适配器 — 在字段级安全控制下读取记录
  • createRecord、updateRecord、deleteRecord — 命令式 CRUD
  • getObjectInfo、getPicklistValues — 访问元数据
  • refreshApex() — 在数据变更后使 wire 缓存失效
  • 适用场景:简单 CRUD。对于复杂查询或业务逻辑,请使用 Apex wire。
生命周期钩子
钩子时机常见用途
constructor()组件创建时初始化状态
connectedCallback()插入 DOM 时获取数据、添加监听器
renderedCallback()每次渲染后操作 DOM(使用标志进行保护!)
disconnectedCallback()从 DOM 移除时清理监听器、取消订阅 LMS
errorCallback(error, stack)子组件出错时错误边界、日志记录
导航

使用 NavigationMixin 和页面引用类型:

  • standard__recordPage — 查看/编辑/克隆记录(需要 recordId、actionName)
  • standard__objectPage — 对象主页/列表/新建(需要 objectApiName、actionName)
  • standard__namedPage — 标准页面(home、chatter、filePreview)
  • standard__webPage — 外部 URL(需要 url)
Lightning Message Service(LMS)

在 LWC、Aura 和 Visualforce 之间进行跨 DOM 通信:

  • 在 .messageChannel-meta.xml 中定义消息通道
  • 使用 publish(messageContext, channel, payload) 发送消息
  • 使用 subscribe(messageContext, channel, handler, {scope: APPLICATION_SCOPE}) 接收消息
  • 始终在 disconnectedCallback() 中调用 unsubscribe(),以防止内存泄漏
Shadow DOM 与 Light DOM
  • Shadow DOM(默认):CSS 隔离、DOM 封装 — 大多数组件都应使用
  • Light DOM(lwc:dom="light"):不进行封装 — 需要跨组件 ARIA 引用、全局 CSS 或第三方库访问 DOM 时使用
  • Shadow DOM 会阻止外部的 document.querySelector() — 在内部使用 this.template.querySelector()

规则

  • 始终使用 SLDS 类进行样式设置 — SLDS 提供实用类时,避免使用自定义 CSS
  • 使用 @api 声明公共属性,属性默认具有响应性
  • 使用 @wire 声明式地获取数据
  • 对用户主动触发的操作使用命令式 Apex 调用
  • 每个组件都要处理加载状态和错误
  • 对于简单 CRUD,使用 lightning-record-form/lightning-record-edit-form
  • 使用自定义事件实现从子组件到父组件的通信
  • 使用 MessageChannel 实现跨 DOM 通信

注意事项

  • @track 已弃用 — 自 API v40+ 起,所有属性默认都具有响应性
  • renderedCallback() 每次渲染后都会触发 — 始终使用布尔标志进行保护,以防止无限循环
  • LDS 缓存不会自动刷新 — 命令式变更后调用 refreshApex(wiredProperty)
  • LMS 订阅必须在 disconnectedCallback() 中取消,以防止内存泄漏
  • Shadow DOM 会阻止组件间基于 ID 的 ARIA 引用(aria-labelledby)— 为实现无障碍访问,请使用 Light DOM
  • CSP 会阻止 eval()、new Function() 和内联 <script> — 通过 Static Resources 使用 loadScript() 加载第三方库
  • 组件中的 @api 属性为只读 — 由父组件设置,子组件不能修改
  • 当响应式参数发生变化时,wire 适配器会重新触发 — 避免不必要的参数变更

工作流

  1. 了解组件需求
  2. 检查是否有可扩展的现有组件
  3. 生成所有捆绑文件(HTML、JS、CSS、meta.xml)
  4. 生成包含模拟数据的 Jest 测试文件
  5. 部署:sf project deploy start -d force-app/main/default/lwc/componentName/

参考

  • LWC 模式 — LDS、导航、LMS、数据表、自定义事件、插槽、无障碍性、SLDS、第三方库、动态组件、Experience Cloud

评论

支持 Markdown