diff --git a/V5_PLANNING.md b/V5_PLANNING.md index 8f749dcabf6..931fab8386b 100644 --- a/V5_PLANNING.md +++ b/V5_PLANNING.md @@ -149,6 +149,7 @@ Modernize Stencil after 10 years: shed tech debt, embrace modern tooling, simpli - **`setTagTransformer` auto-exported** - now auto-injected into generated library entry points (same as `setNonce`), so library authors no longer need to manually re-export it from their `index.ts`. - **Signals** - opt-in signal-backed reactivity via `extras.signalBacking: true`. Zero API changes for component authors; `@State` and `@Prop` are backed by `@preact/signals-core` signals internally. New `@stencil/core/signals` subpath exports `signal`, `computed`, `effect`, `batch`, `untracked`, `@Effect()`, `getSignal()`, and `STENCIL_SIGNALS_SYMBOL` for cross-framework interop. Signal values are valid JSX children and attributes - DOM updates bypass the vdom diff entirely. `@Prop`-only signals are exposed on the host element via `Symbol.for('stencil.signals')` for framework adapters without requiring a `@stencil/core` import. - **`stencil init` wizard** — ground-up redesign of project scaffolding and capability management. Context-aware: scaffolds a new project from the `component-starter` template, or runs in "add capabilities" mode on an existing project. Third-party packages participate by exporting a `wizard` object from a `stencil.wizard` entry declared in their `package.json` — no central registry. The `init` contribution's `run(context)` function owns its entire setup (prompts, peer dep installs, config file writes, example tests, script updates), matching the pattern used by `@nuxt/test-utils`. `generate` contributions add file templates and style extensions to `stencil generate`. `STENCIL_WIZARD_DEV=./path/to/wizard.js` injects a local wizard during development without publishing. Replaces `create-stencil` as the primary project bootstrapping path. +- **`docs-readme` output target supports `customColumns`** — add extra columns to the generated Properties/Events tables, driven by arbitrary JSDoc tags (already captured unfiltered in `JsonDocsProp.docsTags`/`JsonDocsEvent.docsTags`). `content(member, cmp)` is invoked per row; columns are appended left-to-right after the built-in ones, in array order. Scoped to props/events only — methods render as prose per-signature (no per-row table to attach a column to), and slots/parts/custom-states/CSS custom-properties don't carry `docsTags` at all. New `DocsReadmeCustomColumn` type in `stencil-public-compiler.ts`; rendering in `compiler/docs/readme/markdown-props.ts` and `markdown-events.ts`, wired through `compiler/docs/readme/output-docs.ts`. --- diff --git a/packages/core/src/compiler/docs/_test_/markdown-events.spec.ts b/packages/core/src/compiler/docs/_test_/markdown-events.spec.ts new file mode 100644 index 00000000000..5e9d3c85aba --- /dev/null +++ b/packages/core/src/compiler/docs/_test_/markdown-events.spec.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest'; + +import { eventsToMarkdown } from '../readme/markdown-events'; + +describe('markdown events', () => { + it('renders the base Events table', () => { + const markdown = eventsToMarkdown([ + { + event: 'myEventOne', + bubbles: true, + cancelable: true, + composed: true, + complexType: { original: 'void', resolved: 'void', references: {} }, + docs: 'Emitted when one happens', + docsTags: [], + detail: 'void', + }, + { + event: 'myEventTwo', + bubbles: false, + cancelable: false, + composed: false, + complexType: { original: 'string', resolved: 'string', references: {} }, + docs: 'Emitted when two happens', + docsTags: [], + detail: 'string', + }, + ]).join('\n'); + + expect(markdown).toEqual(`## Events + +| Event | Description | Type | +| ------------ | ------------------------ | --------------------- | +| \`myEventOne\` | Emitted when one happens | \`CustomEvent\` | +| \`myEventTwo\` | Emitted when two happens | \`CustomEvent\` | + +`); + }); + + it('renders no content when there are no events', () => { + expect(eventsToMarkdown([])).toEqual([]); + }); + + it('renders custom columns driven by docsTags, in array order', () => { + const markdown = eventsToMarkdown( + [ + { + event: 'configEvent', + bubbles: true, + cancelable: true, + composed: true, + complexType: { original: 'void', resolved: 'void', references: {} }, + docs: 'A configurable event', + docsTags: [{ name: 'config', text: 'true' }], + detail: 'void', + }, + { + event: 'plainEvent', + bubbles: true, + cancelable: true, + composed: true, + complexType: { original: 'void', resolved: 'void', references: {} }, + docs: 'A plain event', + docsTags: [], + detail: 'void', + }, + ], + undefined, + [ + { + header: 'Configurable', + content: (ev) => (ev.docsTags.some((t) => t.name === 'config') ? '✅' : '❌'), + }, + ], + ).join('\n'); + + expect(markdown).toContain( + '| Event | Description | Type | Configurable |', + ); + expect(markdown).toContain('`configEvent`'); + expect(markdown).toContain('✅'); + expect(markdown).toContain('`plainEvent`'); + expect(markdown).toContain('❌'); + }); +}); diff --git a/packages/core/src/compiler/docs/_test_/markdown-props.spec.ts b/packages/core/src/compiler/docs/_test_/markdown-props.spec.ts index 52c0554c906..587227256cd 100644 --- a/packages/core/src/compiler/docs/_test_/markdown-props.spec.ts +++ b/packages/core/src/compiler/docs/_test_/markdown-props.spec.ts @@ -127,6 +127,59 @@ describe('markdown props', () => { | -------- | --------- | ----------- | -------- | ----------- | | \`first\` | \`first\` | First name | \`string\` | \`undefined\` | +`); + }); + + it('renders custom columns driven by docsTags, in array order', () => { + const markdown = propsToMarkdown( + [ + { + name: 'configurable', + attr: 'configurable', + docs: 'A configurable prop', + default: 'undefined', + type: 'string', + mutable: false, + optional: false, + required: false, + reflectToAttr: false, + docsTags: [{ name: 'config', text: 'true' }], + values: [], + getter: false, + setter: false, + }, + { + name: 'plain', + attr: 'plain', + docs: 'A plain prop', + default: 'undefined', + type: 'string', + mutable: false, + optional: false, + required: false, + reflectToAttr: false, + docsTags: [], + values: [], + getter: false, + setter: false, + }, + ], + undefined, + [ + { + header: 'Configurable', + content: (prop) => (prop.docsTags.some((t) => t.name === 'config') ? '✅' : '❌'), + }, + ], + ).join('\n'); + + expect(markdown).toEqual(`## Properties + +| Property | Attribute | Description | Type | Default | Configurable | +| -------------- | -------------- | ------------------- | -------- | ----------- | ------------ | +| \`configurable\` | \`configurable\` | A configurable prop | \`string\` | \`undefined\` | ✅ | +| \`plain\` | \`plain\` | A plain prop | \`string\` | \`undefined\` | ❌ | + `); }); }); diff --git a/packages/core/src/compiler/docs/_test_/output-docs.spec.ts b/packages/core/src/compiler/docs/_test_/output-docs.spec.ts index a54b42429b8..5650322faac 100644 --- a/packages/core/src/compiler/docs/_test_/output-docs.spec.ts +++ b/packages/core/src/compiler/docs/_test_/output-docs.spec.ts @@ -135,5 +135,70 @@ describe('css-props to markdown', () => { 'Defaults to var(--nano-color-blue-cerulean-1000); with \\| pipes', ); }); + + describe('customColumns', () => { + it('renders extra props/events columns wired through readmeOutput.customColumns', () => { + const readmeOutput: d.OutputTargetDocsReadme = { + type: 'docs-readme', + footer: '*Built with StencilJS*', + customColumns: { + props: [ + { + header: 'Configurable', + content: (prop) => (prop.docsTags.some((t) => t.name === 'config') ? '✅' : '❌'), + }, + ], + events: [ + { + header: 'Internal', + content: (ev) => (ev.docsTags.some((t) => t.name === 'internal') ? '✅' : '❌'), + }, + ], + }, + }; + + const component: d.JsonDocsComponent = { + ...mockComponent, + props: [ + { + name: 'configurable', + attr: 'configurable', + docs: 'A configurable prop', + default: 'undefined', + type: 'string', + mutable: false, + optional: false, + required: false, + reflectToAttr: false, + docsTags: [{ name: 'config', text: 'true' }], + values: [], + getter: false, + setter: false, + }, + ], + events: [ + { + event: 'myEvent', + bubbles: true, + cancelable: true, + composed: true, + complexType: { original: 'void', resolved: 'void', references: {} }, + docs: 'An event', + docsTags: [], + detail: 'void', + }, + ], + }; + + const markdown = generateMarkdown('# my-component', component, [], readmeOutput); + + expect(markdown).toContain('## Properties'); + expect(markdown).toContain('Configurable'); + expect(markdown).toContain('✅'); + expect(markdown).toContain('## Events'); + expect(markdown).toContain('Internal'); + expect(markdown).toContain('❌'); + }); + }); }); }); diff --git a/packages/core/src/compiler/docs/readme/markdown-events.ts b/packages/core/src/compiler/docs/readme/markdown-events.ts index e3dfc1706bb..e897a1b2077 100644 --- a/packages/core/src/compiler/docs/readme/markdown-events.ts +++ b/packages/core/src/compiler/docs/readme/markdown-events.ts @@ -2,7 +2,11 @@ import type * as d from '@stencil/core'; import { MarkdownTable } from './docs-util'; -export const eventsToMarkdown = (events: d.JsonDocsEvent[]) => { +export const eventsToMarkdown = ( + events: d.JsonDocsEvent[], + cmp?: d.JsonDocsComponent, + customColumns: d.DocsReadmeCustomColumn[] = [], +) => { const content: string[] = []; if (events.length === 0) { return content; @@ -13,10 +17,15 @@ export const eventsToMarkdown = (events: d.JsonDocsEvent[]) => { const table = new MarkdownTable(); - table.addHeader(['Event', 'Description', 'Type']); + table.addHeader(['Event', 'Description', 'Type', ...customColumns.map((c) => c.header)]); events.forEach((ev) => { - table.addRow([`\`${ev.event}\``, getDocsField(ev), `\`CustomEvent<${ev.detail}>\``]); + table.addRow([ + `\`${ev.event}\``, + getDocsField(ev), + `\`CustomEvent<${ev.detail}>\``, + ...customColumns.map((c) => c.content(ev, cmp!)), + ]); }); content.push(...table.toMarkdown()); diff --git a/packages/core/src/compiler/docs/readme/markdown-props.ts b/packages/core/src/compiler/docs/readme/markdown-props.ts index d21cdeed9a0..8d6051d6cba 100644 --- a/packages/core/src/compiler/docs/readme/markdown-props.ts +++ b/packages/core/src/compiler/docs/readme/markdown-props.ts @@ -2,7 +2,11 @@ import type * as d from '@stencil/core'; import { MarkdownTable } from './docs-util'; -export const propsToMarkdown = (props: d.JsonDocsProp[]) => { +export const propsToMarkdown = ( + props: d.JsonDocsProp[], + cmp?: d.JsonDocsComponent, + customColumns: d.DocsReadmeCustomColumn[] = [], +) => { const content: string[] = []; if (props.length === 0) { return content; @@ -13,7 +17,14 @@ export const propsToMarkdown = (props: d.JsonDocsProp[]) => { const table = new MarkdownTable(); - table.addHeader(['Property', 'Attribute', 'Description', 'Type', 'Default']); + table.addHeader([ + 'Property', + 'Attribute', + 'Description', + 'Type', + 'Default', + ...customColumns.map((c) => c.header), + ]); props.forEach((prop) => { table.addRow([ @@ -22,6 +33,7 @@ export const propsToMarkdown = (props: d.JsonDocsProp[]) => { getDocsField(prop), getTypeField(prop), getDefaultValueField(prop), + ...customColumns.map((c) => c.content(prop, cmp!)), ]); }); diff --git a/packages/core/src/compiler/docs/readme/output-docs.ts b/packages/core/src/compiler/docs/readme/output-docs.ts index 17e1713dc91..739f4d0df8e 100644 --- a/packages/core/src/compiler/docs/readme/output-docs.ts +++ b/packages/core/src/compiler/docs/readme/output-docs.ts @@ -212,8 +212,8 @@ const generateComponentBody = ( ...getDocsDeprecation(cmp), ...overviewToMarkdown(cmp.overview), ...usageToMarkdown(cmp.usage), - ...propsToMarkdown(cmp.props), - ...eventsToMarkdown(cmp.events), + ...propsToMarkdown(cmp.props, cmp, readmeOutput.customColumns?.props), + ...eventsToMarkdown(cmp.events, cmp, readmeOutput.customColumns?.events), ...methodsToMarkdown(cmp.methods), ...slotsToMarkdown(cmp.slots), ...partsToMarkdown(cmp.parts), diff --git a/packages/core/src/declarations/stencil-public-compiler.ts b/packages/core/src/declarations/stencil-public-compiler.ts index af06c610316..fba53381190 100644 --- a/packages/core/src/declarations/stencil-public-compiler.ts +++ b/packages/core/src/declarations/stencil-public-compiler.ts @@ -9,7 +9,12 @@ import type { PrerenderUrlResults, PrintLine, } from './stencil-private'; -import type { JsonDocs } from './stencil-public-docs'; +import type { + JsonDocs, + JsonDocsComponent, + JsonDocsEvent, + JsonDocsProp, +} from './stencil-public-docs'; import type { ResolutionHandler } from './stencil-public-runtime'; export * from './stencil-public-docs'; @@ -2268,6 +2273,23 @@ export interface OutputTargetDocsReadme extends OutputTargetBase { overwriteExisting?: boolean | 'if-missing'; footer?: string; strict?: boolean; + /** + * Add extra columns to the generated Properties/Events tables, e.g. to + * surface custom JSDoc tags as a column of their own. + */ + customColumns?: { + props?: DocsReadmeCustomColumn[]; + events?: DocsReadmeCustomColumn[]; + }; +} + +/** + * A custom column to render in a `docs-readme` Properties/Events table. + * `content` is invoked once per row. + */ +export interface DocsReadmeCustomColumn { + header: string; + content: (member: T, cmp: JsonDocsComponent) => string; } export interface OutputTargetDocsJson extends OutputTargetBase {