Suggestion
๐ Search Terms
jsx jsxFactory
I found related tickets, but this ticket information is very long winded and I believe this requires its own ticket:
โ
Viability Checklist
My suggestion meets these guidelines:
This would be a type level only change, updating existing behaviour to be more inline with direct local scope types instead of reliance on global scope, for this reason, I believe all of the above items are true.
โญ Suggestion
Type completion from a defined jsxFactory function definition
๐ Motivating Example
Here is a type definition covering the internal process that occurs within the defined createNode function in the same file.
Sample:
interface CreateNodeFn<
O extends object = object,
S = Source<O>,
C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
<TO extends O, S extends CreateNodeFragmentSourceFirstStage>(source: S, options?: TO, ...children: C[]): FragmentVNode & {
source: S;
options: TO;
};
}
GitHub Link
The linked type definition allows for complex types to be defined, including static type resolution for leaves or scalar values:
it.concurrent.each<[SourceReference]>([
[Symbol("Unique Symbol")],
[true],
[false],
[1],
[0],
[1n],
[0n],
[""],
["Hello!"],
])("%p should produce a scalar node", async <I extends SourceReference>(input: I ) => {
const output = createNode(input);
expect(isScalarVNode(output)).toEqual(true);
const source: I = output.source;
expect(source).toEqual(input);
});
GitHub Link
From the perspective of a consumer, all of the above values will produce an object that contains source that matches the original input value. These nodes will never produce children so the type definition accounts for this by using
This is done by this section of the definition
interface CreateNodeFn<
O extends object = object,
S = Source<O>,
C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
<TO extends O, S extends SourceReference>(source: S): VNode & {
source: S;
options: never;
scalar: true;
children: never;
};
<TO extends O, S extends SourceReference>(source: S, options?: TO): VNode & {
source: S;
options: TO;
scalar: true;
children: never;
};
<TO extends O, S extends SourceReference>(source: S, options?: TO, ...children: C[]): VNode & {
source: S;
options: TO;
scalar: false;
};
}
From a type perspective, the the values that are known by the caller are returned in the return type. We also get whether or not children is available (or the other side of this, whether the node is a scalar node, where no children were passed)
Given jsxFactory can be typed using this function, then children itself can also be typed
children not typed:
interface CreateNodeFn<
O extends object = object,
S = Source<O>,
C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
<TO extends O, S extends CreateNodeFragmentSourceFirstStage>(source: S, options?: TO, ...children: C[]):
FragmentVNode & {
source: S;
options: TO;
}
}
GitHub Link
children typed:
interface CreateNodeFn<
O extends object = object,
S = Source<O>,
C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
<TO extends O, S extends CreateNodeFragmentSourceFirstStage, TC extends C = C>(source: S, options?: TO, ...children: TC[]): FragmentVNode & {
source: S;
options: TO;
children: AsyncIterable<ResolveNodeType<TC>[]>;
};
}
Given the above type was implemented, these types would be able to:
- Know what children will be yielded at type level, meaning this extract this statically using ts-morph or another tool.
- Restrict at compile time input state machines, if the input types are restricted for a node it could mean a full restriction in types throughout an entire structure. Typescript itself could eliminate code based on
never alone
- Given the previous point, at development time this can be utilised while typing to improve some dev ex as side processes could be emulating these restrictions.
While the above code is scalar values, the static leaves of a state tree, functions can be fully typed as well... only if the first point is true.
While it would make this definition vastly more complex, I believe it still is possible from a pure type perspective
Currently a function returns a fragment node, which produces only state through children.
it.concurrent.each<[CreateNodeFragmentSourceFirstStage]>([
[() => {}],
[Promise.resolve()],
[Fragment],
])("%p should produce a fragment node", async (input) => {
const output: FragmentVNode = createNode(input);
expect(isFragmentVNode(output)).toEqual(true);
});
GitHub Link
The matching types for functions and promises currently drop the type of children once passed.
export type CreateNodeFragmentSourceFirstStage =
| Function
| Promise<unknown>
| typeof Fragment;
interface CreateNodeFn<
O extends object = object,
S = Source<O>,
C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
<TO extends O, S extends CreateNodeFragmentSourceFirstStage>(source: S, options?: TO, ...children: C[]):
FragmentVNode & {
source: S;
options: TO;
};
}
The retain these we need to type children only additionally:
export type CreateNodeFragmentSourceFirstStage =
| Function
| Promise<unknown>
| typeof Fragment;
interface CreateNodeFn<
O extends object = object,
S = Source<O>,
C extends VNodeRepresentationSource = VNodeRepresentationSource
> {
<TO extends O, S extends CreateNodeFragmentSourceFirstStage, TC extends C = C>(source: S, options?: TO, ...children: TC[]): FragmentVNode & {
source: S;
options: TO;
children: AsyncIterable<ResolveNodeType<TC>[]>;
};
}
Because the types of all children nodes will be typed by the time they are defined as vnodes, all types will be complete.
From a top level at this point we would be able to statically resolve a complete type set from a tree of components.
๐ป Use Cases
Local string types:
#15217
Imported string | number | bigint | symbol | boolean types:
vnode defines "token" types, which allows definition of a partial component with no implementation.
<OfferCatalog>
<Product
name="This is my name 1"
sku="SKU 123"
>
<Brand name="Some brand" />
</Product>
<Product
name="This is my name 2"
sku="SKU 124"
>
<Brand name="Some brand" />
</Product>
<Product
name="This is my name 3"
sku="SKU 125"
>
<Brand name="Some other brand" />
</Product>
</OfferCatalog>
<Order
identifier="1"
orderDate={new Date()}
>
<Invoice identifier="2313132">
<PaymentMethod identifier="123243234" />
</Invoice>
<Product
sku="SKU 125"
/>
<DeliveryMethod identifier="123122222">
<Country name="New Zealand" />
</DeliveryMethod>
</Order>
GitHub Link
This can be used to directly create a set of jsx based components without implementation, if these were fully typed, this could be read statically excluding the value of Date (while still knowing it was a Date)
<scxml>
<datamodel>
<data id="eventStamp"/>
<data id="rectX" expr="0"/>
<data id="rectY" expr="0"/>
<data id="dx"/>
<data id="dy"/>
</datamodel>
<state id="idle">
<transition event="mousedown" target="dragging">
<assign location="eventStamp" expr="_event.data"/>
</transition>
</state>
<state id="dragging">
<transition event="mouseup" target="idle"/>
<transition event="mousemove" target="dragging">
<assign location="dx" expr="eventStamp.clientX - _event.data.clientX"/>
<assign location="dy" expr="eventStamp.clientY - _event.data.clientY"/>
<assign location="rectX" expr="rectX - dx"/>
<assign location="rectY" expr="rectY - dy"/>
<assign location="eventStamp" expr="_event.data"/>
</transition>
</state>
</scxml>
GitHub Link
The above code is a jsx depending on types defined within the same module.
scxml: SCXMLAttributes;
datamodel: DataModelAttributes;
data: DataAttributes;
state: StateAttributes;
transition: TransitionAttributes;
assign: AssignAttributes;
GitHub Link
Actions can also be defined statically, which if typed, would be readable statically.
export const Action: ActionNode = createToken(ActionSymbol);
GitHub Link
Related Tickets
Given the implementation of #34319 an implementor will be able to use
function h(source, options, ...children) implements CreateNodeFn;
Which can then be restricted by providing generics.
Suggestion
๐ Search Terms
jsx jsxFactory
I found related tickets, but this ticket information is very long winded and I believe this requires its own ticket:
โ Viability Checklist
My suggestion meets these guidelines:
โญ Suggestion
Type completion from a defined
jsxFactoryfunction definition๐ Motivating Example
Here is a type definition covering the internal process that occurs within the defined
createNodefunction in the same file.Sample:
GitHub Link
The linked type definition allows for complex types to be defined, including static type resolution for leaves or scalar values:
GitHub Link
From the perspective of a consumer, all of the above values will produce an object that contains
sourcethat matches the originalinputvalue. These nodes will never produce children so the type definition accounts for this by usingThis is done by this section of the definition
From a type perspective, the the values that are known by the caller are returned in the return type. We also get whether or not
childrenis available (or the other side of this, whether the node is ascalarnode, where nochildrenwere passed)Given
jsxFactorycan be typed using this function, thenchildrenitself can also be typedchildrennot typed:GitHub Link
childrentyped:Given the above type was implemented, these types would be able to:
neveraloneWhile the above code is scalar values, the static leaves of a state tree, functions can be fully typed as well... only if the first point is true.
While it would make this definition vastly more complex, I believe it still is possible from a pure type perspective
Currently a function returns a fragment node, which produces only state through
children.GitHub Link
The matching types for functions and promises currently drop the type of
childrenonce passed.The retain these we need to type
childrenonly additionally:Because the types of all children nodes will be typed by the time they are defined as vnodes, all types will be complete.
From a top level at this point we would be able to statically resolve a complete type set from a tree of components.
๐ป Use Cases
Local
stringtypes:#15217
Imported
string | number | bigint | symbol | booleantypes:vnodedefines "token" types, which allows definition of a partial component with no implementation.GitHub Link
This can be used to directly create a set of jsx based components without implementation, if these were fully typed, this could be read statically excluding the value of
Date(while still knowing it was aDate)GitHub Link
The above code is a jsx depending on types defined within the same module.
GitHub Link
Actions can also be defined statically, which if typed, would be readable statically.
GitHub Link
Related Tickets
Given the implementation of #34319 an implementor will be able to use
Which can then be restricted by providing generics.