算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
stencil【设计与创意】Use this skill when the user says 'Stencil', 'Stencil.js', 'Stencil component', 'Stencil web component', 'Stencil setup', 'Stencil compiler', 'Stencil design system', 'web component compiler', or when building reusable web components with Stencil. This skill enforces: Stencil decorators (@Component, @Prop, @State, @Event), JSX-based component compila…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Build reusable, framework-agnostic web components using Stencil's TypeScript-first compiler — optimized for design systems, component libraries, and performance-critical UIs.
Exact user phrases: "Stencil setup", "Stencil component", "Stencil web component", "Stencil compiler", "Stencil design system", "Stencil project", "stencil component library".
Before activating, verify:
No file output. Produces code snippets and config examples as text.
Component definition:
@Component({ tag: 'my-button', styleUrl: 'my-button.css' })
export class MyButton { ... }
No preamble. No postamble. No explanations. Compress output — why use many token when few do trick.
~4096 tokens.
| Approach | Trade-off | When to Use |
|---|---|---|
| Component with shadow DOM | Style isolation | Design system components |
| Component with scoped CSS | Lighter, no shadow root | App-specific components |
| Lazy-loaded (default) | Loaded on demand | Library distributed via CDN |
| Eager (customElementsExportBehavior) | Available immediately | Bundled library |
| dist output target | Lazy loading + split | Most use cases |
| dist-custom-elements | Single bundle | Bundler integration |
Does the component need style isolation?
Yes -> shadow: true (Shadow DOM, full isolation)
No -> scoped: true (Scoped CSS, lighter, no shadow root)
Should the component be lazy-loaded?
Yes (default) -> Stencil's output target handles this
No (eager) -> Set customElementsExportBehavior
What output targets are needed?
Reusable library -> dist (custom elements bundle)
Framework integration -> dist-react, dist-vue, dist-angular
Vanilla HTML usage -> www (full app with lazy loading)
CDN distribution -> dist-custom-elements
Should the prop be reflected to attribute?
Yes -> reflect: true (useful for CSS selectors, framework bindings)
No -> reflect: false (default, better performance)
Can the component mutate the prop internally?
Yes -> mutable: true (component can change its own prop value)
No -> mutable: false (default, prop is read-only)
What type is the prop?
Primitive (string, number, boolean) -> Auto attribute serialization
Complex (object, array) -> Pass via JS property (not HTML attr)
import { Component, Prop, h } from '@stencil/core'
@Component({ tag: 'my-button', styleUrl: 'my-button.css', shadow: true })
export class MyButton {
@Prop({ reflect: true }) variant: 'primary' | 'secondary' = 'primary'
@Prop({ reflect: true }) disabled = false
render() {
return (
<button class={`btn btn--${this.variant}`} disabled={this.disabled}>
<slot />
</button>
)
}
}
import { Component, State, Event, EventEmitter, h } from '@stencil/core'
@Component({ tag: 'my-counter', shadow: true })
export class MyCounter {
@Prop({ mutable: true }) value = 0
@State() private internalValue = 0
@Event() valueChange: EventEmitter<number>
componentWillLoad() {
this.internalValue = this.value
}
@Watch('value')
watchValue(newValue: number) {
this.internalValue = newValue
}
private increment() {
this.internalValue++
this.valueChange.emit(this.internalValue)
}
render() {
return (
<div>
<p>Value: {this.internalValue}</p>
<button onClick={() => this.increment()}>+</button>
</div>
)
}
}
@Component({ tag: 'my-input', formAssociated: true, shadow: true })
export class MyInput {
@Prop() value = ''
@Event() valueChange: EventEmitter<string>
private handleInput(e: InputEvent) {
const target = e.target as HTMLInputElement
this.value = target.value
this.valueChange.emit(this.value)
}
render() {
return <input value={this.value} onInput={(e) => this.handleInput(e)} />
}
}
@State() private isOpen = false
@State() private items: Item[] = []
Use getters or methods. No built-in computed/watch pattern — use @Watch for side effects.
/* Parent sets tokens */
:host { --button-bg: blue; --button-color: white; }
/* Child consumes */
:host { background: var(--button-bg); color: var(--button-color); }
dist output target creates a custom elements bundle with lazy loading.dist-custom-elements target creates a single bundle without lazy loading.@Prop({ mutable: false }) for pure inputs — enables better compiler optimizations.@State for internal state, not @Prop({ mutable: true }).componentShouldUpdate() for fine-grained render control.stencil.config.ts with appropriate targets.dist output: Self-lazy-loading custom elements bundle.dist-custom-elements: Single-file non-lazy bundle for bundler integration.dist-react: React component wrappers with proper prop types.dist-vue: Vue 3 component wrappers.dist-angular: Angular component wrappers.npm run build with --prod flag.sourceMap: false.import { newSpecPage } from '@stencil/core/testing'
import { MyButton } from './my-button'
describe('my-button', () => {
it('renders with default props', async () => {
const page = await newSpecPage({
components: [MyButton],
html: '<my-button></my-button>',
})
expect(page.root).toEqualHtml(`
<my-button>
<mock:shadow-root>
<button class="btn btn--primary"><slot /></button>
</mock:shadow-root>
</my-button>
`)
})
it('renders with custom variant', async () => {
const page = await newSpecPage({
components: [MyButton],
html: '<my-button variant="secondary"></my-button>',
})
expect(page.root?.shadowRoot?.querySelector('button')?.classList.contains('btn--secondary')).toBe(true)
})
})
test('counter increments on click', async ({ page }) => {
await page.setContent('<my-counter initial-value="5"></my-counter>')
const counter = page.locator('my-counter')
await expect(counter).toContainText('5')
await page.click('my-counter')
await expect(counter).toContainText('6')
})
newSpecPage() for unit tests — simulates Stencil's rendering without browser.newE2EPage() for E2E tests — Playwright-based browser testing.page.root.shadowRoot.querySelector.const spy = await page.spyOnEvent('countChanged').| Vanilla WC | Stencil |
|---|---|
class MyEl extends HTMLElement | export class MyEl { (no extends) |
observedAttributes() | @Prop() decorator |
attributeChangedCallback() | @Watch(propName) |
connectedCallback() | componentWillLoad() / componentDidLoad() |
this.innerHTML = template | render() { return <div>...</div> } |
this.dispatchEvent(new CustomEvent(...)) | this.countChanged.emit(value) |
| React Concept | Stencil Equivalent |
|---|---|
| Props | @Prop() decorator |
| State | @State() decorator |
| useEffect | componentDidLoad() / @Watch() |
| JSX return | render() method |
| Event callbacks | @Event() emitter |
| React.memo | componentShouldUpdate() |
mutable: true or use @State copy.@Event() countChanged: EventEmitter<number> — always type the generic.formAssociated.componentWillLoad, componentDidLoad, componentWillUpdate.shadow: true or scoped: true.| Aspect | Stencil | Lit | Vanilla WC |
|---|---|---|---|
| Rendering | JSX (compiled) | lit-html (templates) | Manual DOM |
| Bundle | ~8KB runtime | ~5KB runtime | 0KB |
| Lazy loading | Built-in | Manual | Manual |
| Framework bindings | Auto (React, Vue, Angular) | Manual (@lit/react) | Manual |
| TypeScript | Required | Optional | Optional |
| Shadow DOM | Configurable | Configurable | Manual |
| Method | Purpose |
|---|---|
connectedCallback() | Element inserted into DOM |
disconnectedCallback() | Element removed from DOM |
componentWillLoad() | Once, before first render |
componentDidLoad() | Once, after first render |
componentWillRender() | Before each render |
componentDidRender() | After each render |
componentWillUpdate() | Before re-render |
componentDidUpdate() | After re-render |
render() | Return JSX for markup |
:host { --button-bg: blue; --button-color: white; }
:host { background: var(--button-bg); color: var(--button-color); }
// stencil.config.ts
import { reactOutputTarget } from '@stencil/react-output-target'
export const config: Config = {
outputTargets: [
reactOutputTarget({ componentCorePackage: 'my-components', proxiesFile: './react-bindings.ts' }),
{ type: 'dist' },
{ type: 'dist-custom-elements' },
],
}
npm init stencil — project scaffoldingnpm run build — production buildnpm start — dev server with HMRnpm test — Jest unit testsnpm run test:e2e — Playwright E2E@stencil/react-output-target — React bindings@stencil/vue-output-target — Vue bindings@stencil/angular-output-target — Angular bindings@stencil/sass — Sass supportshadow: true for style encapsulation (Shadow DOM).camelCase in JSX, kebab-case in HTML.@Event.@Method sparingly — prefer props and events for public API.@stencil/core/testing for unit tests, Playwright for e2e.mutable: true only when the component itself mutates them.No artifact produced. Next skill: stencil-design-system (if building a design system) or frontend-testing. Carry forward: @Component/@Prop/@Event pattern, shadow DOM, framework-agnostic output.
function createModule<T>(config: ModuleConfig): T { const dependencies = initializeDependencies(config); const module = new Module(dependencies); module.hooks.onInit(); return module as T; }
class ConfigBuilder { private config: AppConfig = new AppConfig(); withDatabase(url: string): ConfigBuilder { ... } withCache(ttl: number): ConfigBuilder { ... } withLogging(level: string): ConfigBuilder { ... } build(): AppConfig { return this.config; } }
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% | Critical | Rollback or fix |
| p95 latency | > 500ms | Warning | Profile and optimize |
| Uptime | < 99.9% | Critical | Investigate infrastructure |
| Memory usage | > 80% | Warning | Check for leaks |
| CPU usage | > 80% | Warning | Scale up or optimize |
Does the component encapsulate visual rendering?
├── No → Functional component (@stencil/store, no shadow DOM)
└── Yes → Does it need style isolation?
├── Yes → Shadow DOM component (shadow: true)
└── No → Scoped component (scoped: true)
Is it a leaf UI element or composite?
├── Leaf → @Prop-driven, no internal state management
└── Composite → @State + @Watch for internal coordination
Who will consume this component?
├── Same app only → dist-custom-elements or dist
├── Multiple frameworks → dist-custom-elements (framework-agnostic)
└── CDN script tag → dist (self-contained bundle)
Do consumers use React/Vue/Angular?
├── Yes → Generate framework wrappers via outputTargets.bindings
└── No → Raw custom elements, document usage