Skip to content

Commit be20445

Browse files
authored
feat(types): add hidden helper to make inferring element types better @W-18442478 (#5362)
* feat(types): add hidden helper to make inferring element types better * Update packages/@lwc/template-compiler/src/parser/expression-complex/html.ts * Update packages/@lwc/engine-dom/src/apis/create-element.ts
1 parent 1a4c4b6 commit be20445

File tree

1 file changed

+37
-7
lines changed

1 file changed

+37
-7
lines changed

packages/@lwc/engine-dom/src/apis/create-element.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,28 @@ if (process.env.NODE_ENV !== 'production') {
9898
}
9999

100100
/**
101-
* Properties defined on the component class, excluding those inherited from `LightningElement`.
101+
* Gets the public properties of a component class. If the `__lwc_public_property_types__` property
102+
* is defined, it will be used as the source of truth for the property types. Otherwise, all of the
103+
* properties defined on the component class are used, excluding those inherited from `LightningElement`.
104+
*
105+
* IMPORTANT: If the fallback is used, then _all_ component properties are returned, rather than
106+
* just the public properties.
102107
*/
103-
// TODO [#4292]: Restrict this to only @api props
104-
type ComponentClassProperties<T> = Omit<T, keyof LightningElement>;
108+
type ComponentClassProperties<T> = T extends {
109+
readonly __lwc_public_property_types__?: infer Props extends object;
110+
}
111+
? Props
112+
: Omit<T, keyof LightningElement>;
105113

106114
/**
107115
* The custom element returned when calling {@linkcode createElement} with the given component
108116
* constructor.
109117
*
110-
* NOTE: The returned type incorrectly includes _all_ properties defined on the component class,
118+
* NOTE: By default, the returned type includes _all_ properties defined on the component class,
111119
* even though the runtime object only uses those decorated with `@api`. This is due to a
112-
* limitation of TypeScript. To avoid inferring incorrect properties, provide an explicit generic
113-
* parameter, e.g. `createElement<typeof LightningElement>('x-foo', { is: FooCtor })`.
120+
* limitation of TypeScript. For example:
114121
*
115-
* @example ```
122+
* ```
116123
* class Example extends LightningElement {
117124
* @api exposed = 'hello'
118125
* internal = 'secret'
@@ -124,6 +131,29 @@ type ComponentClassProperties<T> = Omit<T, keyof LightningElement>;
124131
* const internal = example.internal // type is 'string'
125132
* console.log(internal) // prints `undefined`
126133
* ```
134+
*
135+
* One way to avoid inferring incorrect properties, is to provide an explicit generic parameter.
136+
* For example:
137+
* ```
138+
* const example = createElement<{exposed: string}>('c-example', { is: Example })
139+
* const exposed = example.exposed // type is 'string'
140+
* const internal = example.internal // Now a type error! ✅
141+
* ```
142+
*
143+
* Alternatively, you can define a type for property on the component, `__lwc_public_property_types__`,
144+
* that explicitly lists only the public properties, and the types will be inferred from that prop.
145+
* The property should be optional, because it does not actually exist at runtime.
146+
* For example:
147+
* ```
148+
* class Example extends LightningElement {
149+
* @api exposed = 'hello'
150+
* internal = 'secret'
151+
* __lwc_public_property_types__?: { exposed: string }
152+
* }
153+
* const example = createElement('c-example', { is: Example })
154+
* const exposed = example.exposed // type is 'string'
155+
* const internal = example.internal // Now a type error! ✅
156+
* ```
127157
*/
128158
export type LightningHTMLElement<T> = HTMLElement & ComponentClassProperties<T>;
129159

0 commit comments

Comments
 (0)