Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions V5_PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>()`, 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<T>` 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`.

---

Expand Down
85 changes: 85 additions & 0 deletions packages/core/src/compiler/docs/_test_/markdown-events.spec.ts
Original file line number Diff line number Diff line change
@@ -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<void>\` |
| \`myEventTwo\` | Emitted when two happens | \`CustomEvent<string>\` |

`);
});

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('❌');
});
});
53 changes: 53 additions & 0 deletions packages/core/src/compiler/docs/_test_/markdown-props.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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\` | ❌ |

`);
});
});
65 changes: 65 additions & 0 deletions packages/core/src/compiler/docs/_test_/output-docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('❌');
});
});
});
});
15 changes: 12 additions & 3 deletions packages/core/src/compiler/docs/readme/markdown-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<d.JsonDocsEvent>[] = [],
) => {
const content: string[] = [];
if (events.length === 0) {
return content;
Expand All @@ -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());
Expand Down
16 changes: 14 additions & 2 deletions packages/core/src/compiler/docs/readme/markdown-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<d.JsonDocsProp>[] = [],
) => {
const content: string[] = [];
if (props.length === 0) {
return content;
Expand All @@ -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([
Expand All @@ -22,6 +33,7 @@ export const propsToMarkdown = (props: d.JsonDocsProp[]) => {
getDocsField(prop),
getTypeField(prop),
getDefaultValueField(prop),
...customColumns.map((c) => c.content(prop, cmp!)),
]);
});

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/compiler/docs/readme/output-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
24 changes: 23 additions & 1 deletion packages/core/src/declarations/stencil-public-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<JsonDocsProp>[];
events?: DocsReadmeCustomColumn<JsonDocsEvent>[];
};
}

/**
* A custom column to render in a `docs-readme` Properties/Events table.
* `content` is invoked once per row.
*/
export interface DocsReadmeCustomColumn<T> {
header: string;
content: (member: T, cmp: JsonDocsComponent) => string;
}

export interface OutputTargetDocsJson extends OutputTargetBase {
Expand Down
Loading