Repository navigation
Conversation
Codecov Report
@@ Coverage Diff @@
## master #279 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 3 3
Lines 146 210 +64
Branches 44 75 +31
=====================================
+ Hits 146 210 +64
Continue to review full report at Codecov.
|
|
Thanks @Swizz! This is looking pretty awesome. Question: Can't Try this: "format": "prettier --semi false --write 'src/**/*.{js,ts}'", |
|
@jbucaran Yes, prettier works with ts files and d.ts files. I was waiting your comment to use it. Because it produce a more longer file. |
No worries. Change package.json's format script to this: and run |
|
The declaration file is not under src folder but in the root folder. |
|
|
@Swizz I tried using the file in a project with vscode and intellisense didn't fire like I expected. Is there any additional work required so that vscode/$EDITOR/etc., picks up this file? |
|
@Swizz What about a typings.json file too? {
"name": "hyperapp",
"main": "src/hyperapp.d.ts"
} |
|
@jbucaran From where are you using it ? As it declared as a module, exported declarations will follow with import { h, app, Actions, Events, Application, emitFunction } from 'hyperapp'For a local use, you will need to /// <reference path="..." />Isnt typings deprecated in favor of |
Sweet! How can I explicitly import the declaration file? |
| ActionsInterface extends Actions<State> | undefined | ||
| > { | ||
| loaded: | ||
| | (( |
There was a problem hiding this comment.
I can remove the extra parentheses, it was for readability purpose but prettier do it as well
There was a problem hiding this comment.
Let's give prettier the last word! :)
There was a problem hiding this comment.
It was not :/
Cause union types :
(a: number, b: string) => number | string
A function that take a as a number and b as a string and return a number or a string
is not
((a: number, b: string) => number) | string
A function that take a as a number and b as a string and return a number. Or a string
So let's let them here.
There was a problem hiding this comment.
I was right by writing them; prettier was right to keep them ; I was wrong at reading them again to take them as extra parentheses
/// <reference path="./hyperapp.d.ts" /> |
|
@Swizz Sorry, but where do I put |
|
Sorry, because it was defined as module, using it in a local environement is pretty tricky /// <reference path="./hyperapp.d.ts" />
import { h, app, Actions, Events, Application, emitFunction } from 'hyperapp'But you will not be able to use it directly, because node will look at That is why I recommend the use of or something like the following instead of modules export = HyperApp
declare namespace HyperApp {
...
} |
| actions?: Actions<State>, | ||
| data?: any, | ||
| emit?: emitFunction<any> | ||
| ) => Partial<State> | any) |
There was a problem hiding this comment.
So, following #280 (comment)
An action could return either a Partial<State>, any or void or never ?
There was a problem hiding this comment.
@Swizz An action can return an entire state (the same or a new one), a fragment of the state, a Promise, null or undefined.
There was a problem hiding this comment.
A Promise that resolve to a State (or fragment) ?
=> Partial<State> | Promise<Partial<State>> | void | neverThere was a problem hiding this comment.
A Promise that resolves to any.
There was a problem hiding this comment.
Something more like ?
=> Partial<State> | Promise<Partial<State> | any> | void | neverThere was a problem hiding this comment.
type ActionResult = Partial<State> | Promise<ActionResult> | any | void | null=> ActionResult There was a problem hiding this comment.
Is void === undefined?
There was a problem hiding this comment.
void is the opposite of any both undefined or null.
But with strictNullChecks enable, it is needed to define null explicitly.
There was a problem hiding this comment.
in TypeScript void works a bit differently than other languages (C, Java etc); it's not just "no return value", but it's more like an alias for undefined. For example:
function a(): void {}
let b = a(); // strange, but this is allowed, and it's b === undefinedSo I think it's ok to have void and left out undefined from our signature. I would also omit any because it makes the signature useless for type checking.
There was a problem hiding this comment.
So we are back on a circular type reference ?
type ActionResult = Partial<State> | Promise<ActionResult> | void | null
export interface Actions<State> {
[key: string]:
| ((
state?: State,
actions?: Actions<State>,
data?: any,
emit?: emitFunction<any>
) => ActionResult)
| Actions<State>
}|
If you are thinking about it this following implementation is also working as expected type State = number;
interface ActionsInterface extends Actions<State> {
inc(count: State): State,
dec(count: State): State
}interface ActionsInterface extends Actions<number> {
inc(count: number): number,
dec(count: number): number
} |
| actions?: Actions<State>, | ||
| data?: any, | ||
| emit?: emitFunction<any> | ||
| ) => Partial<State> | any) |
There was a problem hiding this comment.
I think it should be Partial<State> | Promise<Partial<State>> | void
There was a problem hiding this comment.
We are on discussion about it above ^^'
|
I know @DanielRosenwasser expressed interest in helping out with the typings. Can you help review this PR? 👋😏 |
| @@ -0,0 +1,149 @@ | |||
| declare module "hyperapp" { | |||
There was a problem hiding this comment.
Both are similar but different. declaring module are more ES6/CommonJS where declaring namespace is more like exposing a global object.
This is why if you export and object you will need to export the namespace too.
export = HyperApp
declare namespace HyperApp {
...
}There was a problem hiding this comment.
I feel like you need to consider the various consumption cases for hyperapp
There was a problem hiding this comment.
Since hyperapp ends up being packaged as a UMD module, should we follow the module.d.ts UMD example from the TypeScript handbook?
| @@ -0,0 +1,149 @@ | |||
| declare module "hyperapp" { | |||
| export interface VNodeData { | |||
| [key: string]: any | |||
There was a problem hiding this comment.
Is this good? I ask because attributes can only be a string, number, function, null/undefined, an object in the case of key=style.
There was a problem hiding this comment.
- Less naive VNodeData interface
Here, as you wish, we can be really straight but generics :
export interface VNodeData {
[key: string]: string | number | Function | null
| undefined | Object //as everything is object this is like all : `any`
}Or we can dive into the horrific (like did in @Types/React), add a type to every attrs 👎
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L2101
There was a problem hiding this comment.
@Swizz I don't know how to write this file, so I am only asking to learn. Please make the typings in the best way you know. Size doesn't matter.
There was a problem hiding this comment.
Size doesn't matter, but I dont want you to be in charge of a monster. The react types is too too.
Here, I think we can allow the use of any. I dont want to maintain a complete list of HTML attribute, and how about custom ones ? And how about new ones ?
An attribute is nothing more a key and a value, and a value can be everything so any.
@nippur72 Do you agree ?
edit : Preact was on the thoughts to type all the attrs too 😕
https://github.com/developit/preact/blob/master/src/preact.d.ts
Only for JSX. For Vnode they let the same at us
interface VNode {
nodeName:ComponentConstructor<any, any>|string;
attributes:{[name:string]:any};
children:VNode[];
key:string;
}There was a problem hiding this comment.
@Swizz attribute typing is a cool feature that lets you spot a lot of errors at compile time, so we should attempt to cover it. We don't have to implement it from scratch, we can easily adapt react or preact (their typings are MIT licensed). Custom attributes are not a problem: they do appear in custom elements which in turn are extensions of native HTML elements; their typings should be automatic if the component is written the right way.
The only problem I see is with events when an action is passed as event handler, e.g.
<button onclick={actions.some}></button>but I think that can be solved by something like EventHanlder | Action
There was a problem hiding this comment.
I think you can use the example I have from Picodom. We should absolutely avoid using any and prefer {} when possible.
There was a problem hiding this comment.
For the JSX types, you could put them in a separate file (e.g. jsx.d.ts) and bring them in here using export * from "./jsx";. Might make ongoing maintenance a little easier since the massive typings of JSX will be separated from the core library typings. Just a thought
There was a problem hiding this comment.
Is it VNodeData where lifecycle methods and keys would go? Would be nice to demonstrate those here too.
| children: string | VNode | Array<VNode> | ||
| } | ||
|
|
||
| export type viewFunction<State, Actions> = ( |
There was a problem hiding this comment.
Why declare viewFunction's type here? and what is vnodeFunction?
There was a problem hiding this comment.
It is requested into the Application interface
export interface Application<
State,
ActionsInterface extends Actions<State> | undefined,
EventsInterface extends Partial<Events<State, ActionsInterface>> | undefined
> extends Partial<Mixin<State, ActionsInterface, EventsInterface>> {
root: HTMLElement
view: viewFunction<State, ActionsInterface>
}and the render event
export interface Events<
State,
ActionsInterface extends Actions<State> | undefined
> {
render:
| (
state: State,
actions: ActionsInterface,
view: viewFunction<State, ActionsInterface>,
emit: emitFunction<any>
) => viewFunction<State, ActionsInterface>
}For vnodeFunction I wanted a way to express a function that return a vnode for h, and I wasnt sure about Component. But as the discution is closed, we can move into now.
export function h(
tag: string | vnodeFunction,
data?: VNodeData,
children?: Array<number | string | VNode>
): VNodeThere was a problem hiding this comment.
In general, it's better to declare interfaces than types. You can make an interface like,
interface View<S, A> {
(state: S, actions: A): VNode;
}|
@Swizz How is this coming along? 😄 Do you think you could help me a bit understand what's going on this file? It's really big and I am not |
|
Let me check, what is needed to do right now.
type ActionResult = Partial<State> | Promise<ActionResult> | void | null
export interface Actions<State> {
[key: string]:
| ((
state?: State,
actions?: Actions<State>,
data?: any,
emit?: emitFunction<any>
) => ActionResult)
| Actions<State>
}
export interface VNodeData {
[key: string]: any
}
Isnt Typescript Guide a good teacher ? ^^ |
|
Can I ask @nippur72 @rbiggs @LucianU and @DanielRosenwasser reviews and feedbacks on it ? |
That sounds correct. The term we use in the documentation is Component, so that's what it should be. |
|
@Swizz Actions/Events are no longer passed the |
|
@jbucaran You are right, I need to rethink the API regarding recent changes |
|
Would it be helpful to have another reviewer? I made a PR for Picodom, Preact, and I've done a lot of the type definitions in DefinitelyTyped. |
|
@pspeter3 Welcome aboard! 👍 |
pspeter3
left a comment
There was a problem hiding this comment.
I think there are a few things to think through/improve:
- How we want to handle various module types
- How typed JSX and VNodes should be
- Naming types and generics
I think the last one is the most important. In a lot of ways, the DTS can serve as documentation for the library so good types/names are critical.
| @@ -0,0 +1,149 @@ | |||
| declare module "hyperapp" { | |||
There was a problem hiding this comment.
I feel like you need to consider the various consumption cases for hyperapp
| @@ -0,0 +1,149 @@ | |||
| declare module "hyperapp" { | |||
| export interface VNodeData { | |||
| [key: string]: any | |||
There was a problem hiding this comment.
I think you can use the example I have from Picodom. We should absolutely avoid using any and prefer {} when possible.
| children: string | VNode | Array<VNode> | ||
| } | ||
|
|
||
| export type viewFunction<State, Actions> = ( |
There was a problem hiding this comment.
In general, it's better to declare interfaces than types. You can make an interface like,
interface View<S, A> {
(state: S, actions: A): VNode;
}| export interface Actions<State> { | ||
| [key: string]: | ||
| | (( | ||
| state?: State, |
There was a problem hiding this comment.
I feel like this is begging for a named interface. TypeScript produces better error messages against named interfaces rather than anonymous types.
|
@pspeter3 May I ask you to make a PR on my fork ? I am a little bit busy these days. Or I can make you as contributor as well |
|
Yeah, I can do that. |
andrewiggins
left a comment
There was a problem hiding this comment.
Thanks for starting this! I was just writing my own until I found this PR.
| @@ -0,0 +1,149 @@ | |||
| declare module "hyperapp" { | |||
There was a problem hiding this comment.
Since hyperapp ends up being packaged as a UMD module, should we follow the module.d.ts UMD example from the TypeScript handbook?
| @@ -0,0 +1,149 @@ | |||
| declare module "hyperapp" { | |||
| export interface VNodeData { | |||
| [key: string]: any | |||
There was a problem hiding this comment.
For the JSX types, you could put them in a separate file (e.g. jsx.d.ts) and bring them in here using export * from "./jsx";. Might make ongoing maintenance a little easier since the massive typings of JSX will be separated from the core library typings. Just a thought
| state: State, | ||
| actions: Actions | ||
| ) => VNode | ||
| export type emitFunction<Data> = (event: string, data?: Data) => Data |
There was a problem hiding this comment.
Hmmm would it possible to make the type of the event function stronger by taking the EventInterface as a generic and using the keyof operator. Something like
export type emitFunction<Data, EventInterface> = (event: keyof EventInterface, data: Data) => DataThat should give better intellisense for any emit function that knows the event interface and enforce the event types an app supports.
There was a problem hiding this comment.
Should the EventInterface of emitFunction extend Events?
| state: State | ||
| actions: ActionsInterface | ||
| events: EventsInterface | ||
| mixins: Array<Partial<Mixin<State, ActionsInterface, EventsInterface>>> |
There was a problem hiding this comment.
Per the doc, does this need to be an array of functions that return Partial<Mixin>'s?
There was a problem hiding this comment.
Your Mixins can contains only events or actions for example. You do not need to define all the Mixin props. So, Partial<Mixin> ?
There was a problem hiding this comment.
True. Partial is definitely needed. I was suggesting, should the type of the mixins property be:
mixins: Array<() => Partial<Mixin<State, ActionsInterface, EventsInterface>>>Unless I'm misunderstanding the API doc
| @@ -0,0 +1,149 @@ | |||
| declare module "hyperapp" { | |||
| export interface VNodeData { | |||
| [key: string]: any | |||
There was a problem hiding this comment.
Is it VNodeData where lifecycle methods and keys would go? Would be nice to demonstrate those here too.
| export type emitFunction<Data> = (event: string, data?: Data) => Data | ||
| export type vnodeFunction = ( | ||
| props: VNodeData, | ||
| children: string | VNode | Array<VNode> |
There was a problem hiding this comment.
Is this a type for the Stateless Components doc? If I'm reading the code correctly, the last line in h.js (tag(data, children)) seems to say the children type would always be an array:
children: Array<VNode | string>|
We reached a lot of comments here.
|
|
@Swizz Sounds good to me! 👍 |
|
Happy to help out |
|
Closed in favor of #311 |
Following #87 : I present you my version of a TypeScript declaration only for the Hyperapp API.
The following define lot of interfaces as fixed one, but make it able to partially use them trough
Partial<Interface>(Thanks to @nippur72).All the interfaces need to be extended in order to be used.
Custom Events support is free by extending the Events interface, but without fixed typing yet.
This declaration file has been tested against the following features with the following implementation.