Token导航 LogoToken导航TokenDH.com
开发需要联网github未标认证来源可访问许可证需确认审计通过

stenciljs-component-developmentstenciljs 组件开发

Agent Skill

stenciljs-component-development 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

1,558

周安装

51

GitHub Stars

公开资料未说明

下载量

420
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:stenciljs-component-development(stenciljs 组件开发)
来源仓库:https://github.com/corlab-tech/skills
仓库路径:skills/stenciljs-component-development
安装命令:
npx skills add https://github.com/corlab-tech/skills --skill stenciljs-component-development
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/corlab-tech/skills --skill stenciljs-component-development

简介

stenciljs-component-development 用于处理 Stencil.js 组件开发相关任务,适合前端框架开发场景。

  • 它支持组件编写、构建和测试,帮助 Agent 完成 Web 组件的开发工作。
  • 通过 npx skills add 命令从 GitHub 仓库安装,具体用法可参考原始 README 和 SKILL.md。
  • 安装前需确认项目依赖和维护状态,注意是否涉及文件读写或构建命令执行。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Stencil.js - Component Development

Build scalable, enterprise-ready web components using Stencil.js with TypeScript and Web Component standards. Stencil components are framework-agnostic and can be distributed to React, Angular, Vue, and traditional web applications.

Key Concepts

Component Structure

A Stencil component is a TypeScript class decorated with @Component():

import { Component, Prop, State, Event, EventEmitter, Watch, h } from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true,
})
export class MyComponent {
  @Prop() name: string;
  @State() isActive: boolean = false;

  render() {
    return (
      <div>
        <p>Hello, {this.name}!</p>
      </div>
    );
  }
}

Component Decorator Options

@Component({
  tag: 'my-component',              // Required: Custom element tag name (must contain hyphen)
  styleUrl: 'my-component.css',     // Single stylesheet
  styleUrls: {                      // Multiple stylesheets for different modes
    ios: 'my-component.ios.css',
    md: 'my-component.md.css'
  },
  shadow: true,                     // Use Shadow DOM (recommended)
  scoped: false,                    // Scoped CSS without Shadow DOM
  assetsDirs: ['assets'],           // Asset directories to copy
  formAssociated: false,            // Form-associated custom element
})

Decorators

@Prop() - Component Properties

Public properties exposed as attributes:

// Basic prop
@Prop() name: string;

// With default value
@Prop() size: 'small' | 'medium' | 'large' = 'medium';

// Mutable prop (can be changed internally)
@Prop({ mutable: true }) value: string;

// Reflect to attribute (sync prop changes to DOM attribute)
@Prop({ reflect: true }) active: boolean;

// Attribute name different from property name
@Prop({ attribute: 'data-id' }) dataId: string;

Best Practices:

  • Always type your props
  • Use JSDoc comments for public API documentation
  • Set sensible defaults when appropriate
  • Use reflect: true sparingly (performance cost)

@State() - Internal State

Private state that triggers re-renders when changed:

@State() isOpen: boolean = false;
@State() items: string[] = [];

// Updating state triggers re-render
this.isOpen = true;

Important:

  • State is internal only, not exposed as attributes
  • Mutating arrays/objects directly won't trigger re-render
  • Use immutable patterns for complex state

@Watch() - Property Change Handlers

Watch for prop or state changes:

@Prop() value: string;

@Watch('value')
valueChanged(newValue: string, oldValue: string) {
  console.log(`Value changed from ${oldValue} to ${newValue}`);
  this.validateValue(newValue);
}

// Place @Watch() immediately after the prop it watches
@Prop() swipeEnabled: boolean = true;

@Watch('swipeEnabled')
swipeEnabledChanged(newSwipeEnabled: boolean, oldSwipeEnabled: boolean) {
  this.updateState();
}

@Event() - Custom Events

Emit custom events:

@Event() itemSelected: EventEmitter<string>;
@Event() formSubmit: EventEmitter<{value: string}>;

handleClick() {
  this.itemSelected.emit('item-1');
}

Best Practices:

  • Use descriptive event names
  • Type the event payload
  • Document event details with JSDoc

@Listen() - Event Listeners

Listen to DOM events:

@Listen('click')
handleClick(event: MouseEvent) {
  console.log('Component clicked', event);
}

@Listen('scroll', { target: 'window' })
handleScroll(event: Event) {
  console.log('Window scrolled');
}

@Listen('resize', { target: 'window', passive: true })
handleResize() {
  this.updateDimensions();
}

@Element() - Host Element Reference

Reference to the host element:

@Element() el: HTMLElement;

componentDidLoad() {
  console.log('Host element:', this.el);
  this.el.classList.add('loaded');
}

@Method() - Public Methods

Expose public async methods:

@Method()
async open(): Promise<void> {
  this.isOpen = true;
}

@Method()
async getValue(): Promise<string> {
  return this.value;
}

Important:

  • All public methods must be async
  • Document with JSDoc for public API

Lifecycle Methods

Lifecycle methods in order of execution:

export class MyComponent {
  // 1. Called when component is connected to DOM (can be called multiple times)
  connectedCallback() {
    console.log('Component connected to DOM');
  }

  // 2. Called once before first render (good for async data loading)
  componentWillLoad() {
    console.log('Component will load');
    // Can return Promise to delay first render
  }

  // 3. Called once after first render
  componentDidLoad() {
    console.log('Component loaded');
  }

  // 4. Called before every render (after first)
  componentWillRender() {
    console.log('Component will render');
  }

  // 5. Called after every render (after first)
  componentDidRender() {
    console.log('Component rendered');
  }

  // 6. Called when component will update (not on first render)
  componentWillUpdate() {
    console.log('Component will update');
  }

  // 7. Called after component updates (not on first render)
  componentDidUpdate() {
    console.log('Component updated');
  }

  // 8. Determine if component should re-render
  componentShouldUpdate(newVal: any, oldVal: any, propName: string): boolean {
    return newVal !== oldVal;
  }

  // 9. Called when disconnected from DOM (can be called multiple times)
  disconnectedCallback() {
    console.log('Component disconnected');
  }
}

Lifecycle Best Practices:

  • Use componentWillLoad() for async data fetching
  • Use componentDidLoad() for DOM manipulation
  • Use connectedCallback() for logic that runs every time element is attached
  • Clean up in disconnectedCallback() (remove listeners, clear timers)

JSX and Rendering

render() Method

The render() method returns JSX:

import { h } from '@stencil/core';

render() {
  return (
    <div class="container">
      <h1>Hello, {this.name}!</h1>
      {this.isActive && <p>Active!</p>}
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    </div>
  );
}

Host Element

Use <Host> to set attributes on the host element:

import { Host } from '@stencil/core';

render() {
  return (
    <Host
      class={{
        'is-active': this.isActive,
        'is-disabled': this.disabled
      }}
      aria-label={this.label}
    >
      <slot></slot>
    </Host>
  );
}

Slots

Use slots for content projection:

render() {
  return (
    <div>
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>  {/* Default slot */}
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  );
}

Conditional Rendering

render() {
  return (
    <div>
      {this.loading ? (
        <div class="spinner">Loading...</div>
      ) : (
        <div class="content">{this.content}</div>
      )}

      {this.error && <div class="error">{this.error}</div>}
    </div>
  );
}

Lists

render() {
  return (
    <ul>
      {this.items.map(item => (
        <li key={item.id}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

File Structure & Naming

Directory Structure

One component per directory:

src/
├── components/
│   ├── my-button/
│   │   ├── my-button.tsx          # Component implementation
│   │   ├── my-button.css          # Styles
│   │   ├── my-button.spec.ts      # Unit tests
│   │   ├── my-button.e2e.ts       # E2E tests
│   │   └── readme.md              # Component documentation
│   ├── my-card/
│   │   ├── my-card.tsx
│   │   ├── my-card.ios.css        # iOS-specific styles
│   │   ├── my-card.md.css         # Material Design styles
│   │   └── my-card.css            # Base styles

Naming Conventions

  • Tag names: kebab-case with hyphen (required): my-component, app-header
  • Component class: PascalCase: MyComponent, AppHeader
  • File names: Match tag name: my-component.tsx
  • Props: camelCase: firstName, isActive
  • Events: camelCase: itemSelected, formSubmit
  • CSS classes: kebab-case: button-primary, is-active

Code Organization

Follow this order in component class:

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true,
})
export class MyComponent {
  // 1. Own Properties (private, not decorated)
  private internalValue: number;
  someText = 'default';

  // 2. Reference to host element
  @Element() el: HTMLElement;

  // 3. State() variables (alphabetical)
  @State() isValidated: boolean;
  @State() status = 0;

  // 4. Public Property API - @Prop() (alphabetical)
  @Prop() content: string;
  @Prop() enabled: boolean;
  @Prop() type = 'default';

  // 5. Prop lifecycle events (@Watch) - immediately after related @Prop
  @Prop() value: string;

  @Watch('value')
  valueChanged(newValue: string, oldValue: string) {
    this.validate(newValue);
  }

  // 6. Events section (alphabetical)
  @Event() itemSelected: EventEmitter<string>;
  @Event() statusChanged: EventEmitter<number>;

  // 7. Component lifecycle events (in natural order)
  connectedCallback() {}
  disconnectedCallback() {}
  componentWillLoad() {}
  componentDidLoad() {}
  componentShouldUpdate() {}
  componentWillRender() {}
  componentDidRender() {}
  componentWillUpdate() {}
  componentDidUpdate() {}

  // 8. Listeners
  @Listen('click')
  onClick(event: MouseEvent) {
    console.log('Clicked');
  }

  // 9. Public methods API (alphabetical)
  @Method()
  async open(): Promise<void> {
    this.isOpen = true;
  }

  @Method()
  async close(): Promise<void> {
    this.isOpen = false;
  }

  // 10. Local methods (private business logic)
  private validate(value: string): boolean {
    return value.length > 0;
  }

  private updateState() {
    // Internal logic
  }

  // 11. render() function (always last)
  render() {
    return (
      <Host>
        <div class="container">
          <slot></slot>
        </div>
      </Host>
    );
  }
}

Best Practices

1. Use TypeScript Strictly

// Good - Fully typed
@Prop() items: Array<{id: string; name: string}>;
@State() count: number = 0;

// Bad - Any types
@Prop() items: any;
@State() count;

2. Immutable State Updates

// Bad - Direct mutation won't trigger re-render
this.items.push(newItem);

// Good - Create new array
this.items = [...this.items, newItem];

// Bad - Direct object mutation
this.user.name = 'New Name';

// Good - Create new object
this.user = { ...this.user, name: 'New Name' };

3. Use Shadow DOM When Possible

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true,  // Encapsulation and style isolation
})

4. Document Public API

/**
 * The button component for user interactions
 */
@Component({
  tag: 'my-button',
})
export class MyButton {
  /**
   * The button label text
   */
  @Prop() label: string;

  /**
   * Emitted when the button is clicked
   */
  @Event() buttonClick: EventEmitter<void>;

  /**
   * Opens the button's associated menu
   */
  @Method()
  async openMenu(): Promise<void> {
    // ...
  }
}

5. Handle Async Operations Properly

async componentWillLoad() {
  try {
    this.data = await this.fetchData();
  } catch (error) {
    console.error('Failed to load data:', error);
    this.error = 'Failed to load';
  }
}

6. Clean Up Resources

private intervalId: number;

componentDidLoad() {
  this.intervalId = window.setInterval(() => {
    this.updateTime();
  }, 1000);
}

disconnectedCallback() {
  if (this.intervalId) {
    clearInterval(this.intervalId);
  }
}

7. Use Functional Components for Simple Cases

import { h, FunctionalComponent } from '@stencil/core';

interface IconProps {
  name: string;
  size?: number;
}

export const Icon: FunctionalComponent<IconProps> = ({ name, size = 24 }) => (
  <svg width={size} height={size}>
    <use xlinkHref={`#icon-${name}`} />
  </svg>
);

Anti-Patterns

❌ Don't Mutate Props

// Bad
@Prop() value: string;

handleChange() {
  this.value = 'new value';  // Error if not mutable
}

// Good
@Prop({ mutable: true }) value: string;
// Or better: emit event and let parent handle
@Event() valueChange: EventEmitter<string>;

handleChange() {
  this.valueChange.emit('new value');
}

❌ Don't Use Constructor for Initialization

// Bad
constructor() {
  this.data = this.fetchData();  // Won't work properly
}

// Good
async componentWillLoad() {
  this.data = await this.fetchData();
}

❌ Don't Access DOM in render()

// Bad
render() {
  const width = this.el.offsetWidth;  // Can cause issues
  return <div style={{ width: `${width}px` }}></div>;
}

// Good
componentDidLoad() {
  this.width = this.el.offsetWidth;
}

render() {
  return <div style={{ width: `${this.width}px` }}></div>;
}

❌ Don't Forget Keys in Lists

// Bad
render() {
  return (
    <ul>
      {this.items.map(item => <li>{item.name}</li>)}
    </ul>
  );
}

// Good
render() {
  return (
    <ul>
      {this.items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

❌ Don't Use Arrow Functions in render() for Handlers

// Bad - Creates new function on every render
render() {
  return <button onClick={() => this.handleClick()}>Click</button>;
}

// Good - Bind in class or use arrow function property
private handleClick = () => {
  console.log('Clicked');
}

render() {
  return <button onClick={this.handleClick}>Click</button>;
}

Testing

Unit Tests

import { newSpecPage } from '@stencil/core/testing';
import { MyComponent } from './my-component';

describe('my-component', () => {
  it('renders', async () => {
    const page = await newSpecPage({
      components: [MyComponent],
      html: `<my-component></my-component>`,
    });
    expect(page.root).toEqualHtml(`
      <my-component>
        <mock:shadow-root>
          <div>
            Hello, World!
          </div>
        </mock:shadow-root>
      </my-component>
    `);
  });

  it('renders with props', async () => {
    const page = await newSpecPage({
      components: [MyComponent],
      html: `<my-component name="Stencil"></my-component>`,
    });
    expect(page.root).toEqualHtml(`
      <my-component name="Stencil">
        <mock:shadow-root>
          <div>
            Hello, Stencil!
          </div>
        </mock:shadow-root>
      </my-component>
    `);
  });
});

E2E Tests

import { newE2EPage } from '@stencil/core/testing';

describe('my-component', () => {
  it('renders', async () => {
    const page = await newE2EPage();
    await page.setContent('<my-component></my-component>');

    const element = await page.find('my-component');
    expect(element).toHaveClass('hydrated');
  });

  it('emits event on click', async () => {
    const page = await newE2EPage();
    await page.setContent('<my-component></my-component>');

    const itemSelected = await page.spyOnEvent('itemSelected');
    const button = await page.find('my-component >>> button');
    await button.click();

    expect(itemSelected).toHaveReceivedEvent();
  });
});

Common Patterns

Form-Associated Components

@Component({
  tag: 'my-input',
  formAssociated: true,
})
export class MyInput {
  @Element() el: HTMLElement;

  private internals: ElementInternals;

  componentWillLoad() {
    this.internals = (this.el as any).attachInternals();
  }

  @Prop() value: string = '';

  @Watch('value')
  valueChanged(newValue: string) {
    this.internals.setFormValue(newValue);
  }
}

Loading States

@State() loading: boolean = false;
@State() error: string | null = null;
@State() data: any = null;

async componentWillLoad() {
  await this.loadData();
}

private async loadData() {
  this.loading = true;
  this.error = null;

  try {
    this.data = await fetch('/api/data').then(r => r.json());
  } catch (e) {
    this.error = 'Failed to load data';
  } finally {
    this.loading = false;
  }
}

render() {
  if (this.loading) return <div>Loading...</div>;
  if (this.error) return <div class="error">{this.error}</div>;
  return <div>{JSON.stringify(this.data)}</div>;
}

Controlled vs Uncontrolled

// Controlled - value managed by parent
@Prop() value: string;
@Event() valueChange: EventEmitter<string>;

handleInput(event: Event) {
  const value = (event.target as HTMLInputElement).value;
  this.valueChange.emit(value);
}

render() {
  return <input value={this.value} onInput={e => this.handleInput(e)} />;
}

// Uncontrolled - internal state
@State() internalValue: string = '';

handleInput(event: Event) {
  this.internalValue = (event.target as HTMLInputElement).value;
}

render() {
  return <input value={this.internalValue} onInput={e => this.handleInput(e)} />;
}

Performance Tips

  1. Use componentShouldUpdate() to prevent unnecessary renders
  2. Avoid complex computations in render()
  3. Use memoize for expensive calculations
  4. Lazy load components with import()
  5. Use Shadow DOM for style encapsulation
  6. Minimize prop changes from parent
  7. Use event delegation for lists

Related Resources

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

32.71%
按下载量换算137

Claude

31.87%
按下载量换算134

Cursor

18.91%
按下载量换算79

Gemini CLI

9.6%
按下载量换算40

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills