Token导航 LogoToken导航

Agent Skill · 开发

stenciljs-component-development(stenciljs 组件开发)

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

stenciljs-component-development 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在开发类的 21,746 个 Skill 里排第 11,846。

总安装 1,558周安装 51GitHub Stars 公开资料未说明下载 420核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 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

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

stenciljs-component-development 用于处理 Stencil.js 组件开发相关任务,适合前端框架开发场景。它支持组件编写、构建和测试,帮助 Agent 完成 Web 组件的开发工作。

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

来源仓库内路径:skills/stenciljs-component-development

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/corlab-tech/skills --skill stenciljs-component…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

stenciljs-component-development 怎么安装?

在终端执行 npx skills add https://github.com/corlab-tech/skills --skill stenciljs-component-developme…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

stenciljs-component-development 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

stenciljs-component-development 需要什么权限?

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

stenciljs-component-development 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

stenciljs-component-development 用的人多吗?

公开数据是累计安装 1,558 次,近一周安装 51 次,下载 420 次。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

stenciljs-component-development 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
stenciljs-component-development

stenciljs 组件开发 · 本页

1,558—需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

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

平台分布

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