mithriljs / mithril.d.ts Goto Github PK
View Code? Open in Web Editor NEWTypes for mithril.js
License: MIT License
Types for mithril.js
License: MIT License
Community health files are stuff like CONTRIBUTING.md, FUNDING.yml, and so on, stuff that normally lives in .github
, docs/
, or the repo's root. I've added default files for everything on this list of supported file types except for a CONTRIBUTING.md file, as that process varies pretty greatly across repos. For issue templates, there's two templates: one for bugs and one for feature requests. The pull request template and issue templates are each derived from the core project's own core templates but with core-specific stuff omitted.
No action is required on your part, but you may wish to customize these appropriately and/or take other related action in light of this like adding/removing issue templates.
If you have any questions, comments, or concerns, please file an issue and I'd be more than willing to address them.
The mithril link docs show this method of using m.route.link
:
m("a[href=/]", {oncreate: m.route.link({replace: true})})
however m.route.link
only accepts the type Vnode<any, any>
.
[ts]
Argument of type '{ replace: boolean; }' is not assignable to parameter of type 'Vnode<any, any>'.
Object literal may only specify known properties, and 'replace' does not exist in type 'Vnode<any, any>'. [2345]
How should this method be used in TS?
Just as a heads up.
At this point streams's index.d.ts exists in @types/mithril/stream
The #improved has moved the stream interfaces under the mihtril / stream and this breaks global style usage.
This would probably work in "module" style, but it cannot be pulled as an "environmental" global declaration. I understand that if I explicitly import this, it will make that source file a "module" which has other implications.
I was able to fix this by moving the stream folder to the top level of @types manually.
Recommendation: If it is going to be separated, perhaps, the best approach is to have another repository for this purpose or consolidate it like it used to be?
It would make more sense if we made it singular, as that's more idiomatic for that kind of type in general.
HTMLElement
isn't more appropriate here
Some Vnode properties will exist in some lifecycle methods and not in others. Investigate how many variations there are. Ideally, vnode properties would not be nullable when we know they will exist.
When writing the following code
return m(isExternalLink() ? 'a' : m.route.Link, { href: '' }, 'Link text')
The following exception is raised:
const title: string | m.Vnode<any, any>
Argument of type 'Component<RouteLinkAttrs, {}> | "a"' is not assignable to parameter of type 'ComponentTypes<RouteLinkAttrs, {}>'.
Type '"a"' is not assignable to type 'ComponentTypes<RouteLinkAttrs, {}>'.ts(2345)
while this is perfectly legal (and working) JS code.
See also gitter for the original discussion of the behaviour.
Given this example:
interface IComponentAttrs {
test: number;
}
class Component implements m.ClassComponent<IComponentAttrs> {
view({ attrs }: m.CVnode<IComponentAttrs>) {
return m('');
}
}
class Page {
view() {
return m(Component, {
// <== Not getting intellisense here
});
}
}
The test
property (along with lifcycle attrs) aren't showing up under the intellisense in Vscode.
Here's my tsconfig.json
:
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"importHelpers": true,
"lib": [
"dom",
"es7"
],
"module": "commonjs",
"sourceMap": false,
"target": "es5",
"typeRoots": [
"node_modules/@types"
]
},
"exclude": [
"node_modules"
]
}
If I set test
to nullable it shows up fine (along with lifecycle attrs). Any insight on this?
Some forks of the hyperscript function recognise lifecycle methods as potential matches on the attrs
input, but other than that, AFAICT, the interface is an Any
— so unless the author is reaching for a lifecycle method, the contents of attrs
are completely freeform, with no hints.
But there is a lot of further domain complexity to what can be described in attrs
, and we could make the IDE experience much more powerful out of the box by providing hints for authors that describe some of these domains.
The matching sequence for Attrs
ought IMO to behave as follows for the DOM element fork of the hyperscript function (non-element hyperscript forks are not affected by this proposal):
LifecycleMethod | Style | EventHandler | ElementProperty | Any
key
, is of type Any
anyway so merits no special consideration.ElementProperty
is AFAICT directly referencible via TypeScripts built-ins (Element
is used to qualify vnode.dom
in current types).Style
is its own discrete type, @panoply has this covered in #46EventHandler
is the type I think would bring the most value 👇@boazblake spent a while looking for some logic design mistake in some code; but eventually it turned out to be an easily-missed typo — onclick: {/*contents*/}
instead of onclick: () => {/*contents*/}
. Exposing the type semantics for on
-prefixed keys as being proxied to the DOM EventHandler interface would have caught this early. It turns out this is possible in TypeScript using the recent syntax addition:
interface ElementAttributes extends Element {
[k: `on${string}`]: EventHandler,
...
}
I’ve never written TypeScript day to day but figure we could merge something like that 👆 into a rewritten Attributes
.
If we can merge these things together without building an inheritance hierarchy, so much the better.
@spacejack what do you think?
External sources:
I've hardly contributed nor had reason to contribute here with the capacity of a committer, and I've never really been involved with this specific project beyond the very beginning. I've already unwatched the repo, too, as it's just clogging up my already-lengthy notification lists.
Hi, I made a quick stab at writing TypeScript definitions for ospec.
https://gist.github.com/maranomynet/1368f8ce0fd344efcd3b9dd5dea637b4
(BTW: Any corrections or improvements would we much appreciated.)
So, what would be the appropriate next step in order to get this published somewhere?
The following typescript that used to work earlier, stopped working after upgrade:
import Stream from "mithril/stream";
class Foo {
id: Stream<string>;
constructor(id?: string) {
this.id = Stream(id);
}
}
I understand v2 is a breaking change, I'm just trying to understand if it's an intentional breaking change. The change was introduced in commit 65ec929, where this line was moved here with a change in signature.
The fix is fairly simple, before I submit a PR, I thought I'd check if this was intentional, and if this fix is acceptable?
diff --git a/stream/index.d.ts b/stream/index.d.ts
index 1702f10..c47e250 100644
--- a/stream/index.d.ts
+++ b/stream/index.d.ts
@@ -1,7 +1,7 @@
/** Creates an empty stream. */
declare function Stream<T = any>(): Stream<T>;
/** Creates a stream with an initial value. */
-declare function Stream<T>(value: T): Stream<T>;
+declare function Stream<T>(value?: T): Stream<T>;
declare interface Stream<T> {
/** Returns the value of the stream. */
https://github.com/MithrilJS/mithril.d.ts/blob/master/index.d.ts#L191
In my opinion, null | undefined
should be removed from this type. It should be more strict, and users should have to use an empty array or placeholder vnode in order to avoid Mithril having to check if something is null or undefined.
Hey there :-)
It may be nice to have maybe a Mithril.Event
with an added redraw: boolean
field.
Or did I miss it?
Error:
Expected 2 arguments, but got 1.ts(2554)
index.d.ts(57, 71): An argument for 'children' was not provided.
This can be solved by adding a second entry in the d.ts:
/** Creates a fragment virtual element (Vnode). */
fragment(attrs: Lifecycle<any, any> & { [key: string]: any }, children: ChildArrayOrPrimitive): Vnode<any, any>;
fragment(children: ChildArrayOrPrimitive): Vnode<any, any>;
I propose to add all the html properties from TypeScript lib into the HyperScript.
Specifically I propose to add:
type MutablePartial<T extends { [x: string]: any }, K extends string> = {
[P in K]?: MutablePartial<T[P], keyof T[P]>;
}
interface Hyperscript {
<K extends keyof ElementTagNameMap>(selector: K, attributes: Attributes & MutablePartial<ElementTagNameMap[K], keyof ElementTagNameMap[K]>, ...children: Children[]): Vnode<any, any>;
...
and to modify
(selector: string, attributes: Attributes, ...children: Children[]): Vnode<any, any>;
into
(selector: string, attributes: Attributes & MutablePartial<HTMLElement, keyof HTMLElement>, ...children: Children[]): Vnode<any, any>;
And then to remove
/** The class name(s) for this virtual element, as a space-separated list. */
className?: string;
from Attributes
as it is already included in HTMLElement
.
This way gives a complete intellisense of all html elements also including all style
properties. Mutable extension must be there to convert readonly style
into mutable one. Partial to make all props optional.
With Typescript 2.3 it should be possible to modify the Component type to:
export interface Component<Attrs = {}, State extends Lifecycle<Attrs, State> = {}> extends Lifecycle<Attrs, State> {
//...
Meaning that you only need to declare Attrs
and State
if you need to. So a stateless component could be:
const c: m.Component<{title: string}> = {
view ({attrs: {title}}) {
return m('h1', title)
}
}
Without needing to declare an empty State
type.
A simple component without Attrs
or State
types should work as well:
const c: m.Component = {view() {return m('p', 'Hello!')}}
import * as m from 'mithril'
m.render(document.body, m('h1', 'Test'))
Gives the following errors:
TS7038: A namespace-style import cannot be called or constructed, and will cause a failure at runtime.
TS2349: Cannot invoke an expression whose type lacks a call signature. Type '{ default: Static; route: Route; mount: { (element: Element, component: ComponentTypes<any, any>)...' has no compatible call signatures.
using m.default('h1', 'Test')
instead works.
Not many people will run into this problem, but my preference of writing "universal" libraries (to be used in Mithril and React), in TypeScript, using Meiosis for state management, combined with Mithril's Stream functions lead to some problems in projects such as dialogic, glissando and use-stream.
The problem crops up in React projects where eslint
will suddenly complain: "React.Fragment cannot be used as a JSX component" (and so on, for many components).
I've spent ages to find the cause of problem. It happens when mithril.d.ts
is in node_modules
, because one of my libraries has marked Mithril as dependency.
My workaround so far is to move Mithril to devDependencies for React libraries, but that removes the linkage to Stream's type definition in IDE's as Visual Studio Code.
For my purpose the Stream definitions would be better placed in a separate type definition.
I re-report this issue (MithrilJS/mithril.js#1805)
Thank you!
My problem,
e.g) test.com/test/:test_number
interface MyAttrs {
test_number: string;
}
class MyState {
test: Test;
}
const TestRouteResolver: m.RouteResolver<MyState, MyAttrs> = {
onmatch(args, requestedPath) {
...
},
render(vnode) {
vnode.key = vnode.attrs.test_number; // <- compile error!
return vnode;
}
};
For me, it seems like definition of vnode in render function should be fixed.
While defining type of vnode interface, ‘Attrs’ comes first and ‘state’ follows.
However, order of vnode type in render function is ‘State, Params’
I think order of vnode type in render function be changed as defined in vnode interface, that is ‘Params, State’.
current code is..
render?(this: State, vnode: Vnode<State, Params>): Children; // vnode: Vnode<Params, State>?
Vnode interface is defined..
interface Vnode<Attrs, State extends Lifecycle<Attrs, State>>
Hey folks,
I've been playing with Mithril for the first time, trying to set up a basic project. I'm coming from React and TSX so trying to keep things as familiar as possible as I get started but I'm seeing a lot of TypeScript errors that I don't know how to resolve. The app does compile and run though, so that's cool 🤘
Am I trying to do something that's not supported? Or is there something I should be changing? Any help much appreciated!
I have a demo repo here - https://github.com/Vazerthon/mithril-test
In Test.tsx
I'm trying to use mithril-tsx-component but I get
Property '__tsx_attrs' is missing in type '{ view: () => JSX.Element; }' but required in type 'MithrilTsxComponent<IFooCompAttrs>'.ts(2741)
import * as m from "mithril";
import { MithrilTsxComponent } from "mithril-tsx-component";
export interface IFooCompAttrs {
attrs: {};
}
export function Test({}: IFooCompAttrs): MithrilTsxComponent<IFooCompAttrs> {
return {
view: () => <div>Hello World!</div>,
};
}
In Button.tsx
I tried a plain approach but got a different error
'Btn' cannot be used as a JSX component. Its instance type 'Button' is not a valid JSX element. Type 'Button' is missing the following properties from type 'ElementClass': render, context, setState, forceUpdate, and 3 more.ts(2786)
import * as m from "mithril";
import { Button as Btn } from "construct-ui";
interface Attrs {
attrs: {
onclick: () => void;
};
}
function Button({ attrs: { onclick } }: Attrs): m.Component<Attrs> {
const state = {
count: 0,
};
const handleClick = () => {
state.count++;
onclick();
};
return {
view: () => (
<Btn onclick={handleClick} fluid label={`Click me (${state.count})`} />
),
};
}
export default Button;
And in App.tsx
all custom components complain something along the lines of
'Button' cannot be used as a JSX component. Its return type 'Component<Attrs, {}>' is not a valid JSX element. Type 'Component<Attrs, {}>' is missing the following properties from type 'Element': tag, attrs, state, type, propsts(2786)
@spacejack Thank you for the invite!
The style we want to match is described in the TypeScript docs. Also some details in this comment.
This means we'll be splitting type declarations into multiple files, and organize them into a folder structure that matches Mithril's importable module paths. The ultimate goal is to have this on https://github.com/DefinitelyTyped/DefinitelyTyped.
I have found lodash
to be an interesting example of this approach. They have all declarations in the index.d.ts, and they have minimal content in subfolders. Example:
index.d.ts
export = _;
export as namespace _;
declare var _: _.LoDashStatic;
declare namespace _ {
interface LoDashStatic {
toSafeInteger(value: any): number;
}
interface Dictionary<T> {
[index: string]: T;
}
}
toSafeInteger/index.d.ts
import { toSafeInteger } from "../index";
export = toSafeInteger;
Example use 1 (script)
var dict: _.Dictionary<string>
_.toSafeInteger(3.2);
Example use 2 (target ES5)
import jQuery from 'jquery-ts'
var dict: _.Dictionary<string>
_.toSafeInteger(3.2); // [ts] '_' refers to a UMD global, but the current file is a module. Consider adding an import instead.
Example use 3 (target ES5)
import jQuery from 'jquery-ts'
import * as _ from 'lodash'
var dict: _.Dictionary<string>
_.toSafeInteger(3.2); // Fixed
Example use 4 (target ES6)
import toSafeInteger = require('lodash/toSafeInteger') // [ts] Import assignment cannot be used when targeting ECMAScript 2015 modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
import * as toSafeInteger from 'lodash/toSafeInteger' // [ts] Module '"c:/Temp/test/node_modules/@types/lodash/toSafeInteger/index"' resolves to a non-module entity and cannot be imported using this construct.
import toSafeInteger from 'lodash/toSafeInteger' // [ts] Module '"c:/Temp/test/node_modules/@types/lodash/toSafeInteger/index"' has no default export.
import { toSafeInteger } from 'lodash/toSafeInteger' // [ts] Module '"c:/Temp/test/node_modules/@types/lodash/toSafeInteger/index"' resolves to a non-module entity and cannot be imported using this construct.
Oops. Maybe this approach is not ES6 compatible?
Hi —
I was wondering if there'd be interest in adding out-of-the-box support for JSX. I'm happy to submit some initial code to get this going and improve support over time.
Thanks and let me know.
I noticed you had issues with typing components, so I decided to take a look at your typings. Here's a couple suggestions of mine:
This m(component, attrs?, ...children)
line should probably be two declarations, with a constrained generic parameter. That, I believe, should solve the parameter issue.
// Old
<A,S>(component: TComponent<A,S>, a?: (A & Lifecycle<A,S>) | Children, ...children: Children[]): Vnode<A,S>;
// New
(component: TComponent<any, any>, ...children: Children[]): Vnode<any, any>;
<A extends Lifecycle<A, S>, S>(component: TComponent<A,S>, a: A, ...children: Children[]): Vnode<A,S>;
Additionally, that constraint should probably be propagated everywhere else for the vnode, too, just for correctness.
It might be a good idea to introduce an interface Attrs<S> extends Lifecycle<this, S> {}
to simplify the above common constraint, in the case of components especially.
I feel Component
should be renamed AnyComponent
and TComponent<A, S>
renamed Component<A, S>
, just to clarify which is which.
Most of Static
's members should honestly be inlined - it'll make for a much better debugging experience.
Here's a few issues I found as well:
Stream#ap
should really be this (although MithrilJS/mithril.js#1352 will likely change this later):
// Incorrect
ap(f: Functor<T>): Functor<T>;
// Correct
ap: <U, V>(this: Stream<(value: U) => V>, value: U) => Stream<V>;
// After #1352 is resolved
ap<U>(f: Stream<(value: T) => U>): Stream<U>;
m.stream.combine
only accepts an array now. Also, it might be worth adding a few typed overloads.
m.withAttr
only needs the second overload. Stream<T>
already satisfies it, so there's no need to duplicate it. Also, the last argument should be Event
, not something that reduces to {[key: string]: any}
.
Hello, I see in this issue that you suggested adding this directly to mithril.js, too bad that didn't happen :(
But I see you suggested adding it to DT, but I'd like to suggest instead of that, or maybe in addition to that, you add it to types so typings
can be used for managing.
In this case:
class C1 implements ClassComponent<{}> {
foo = 1;
view() {
return m('p', 'foo = ' + this.foo);
}
}
class C2 implements ClassComponent<{}> {
view() {
return m(C1, {
onremove(vnode) {
const n: number = vnode.state.foo; // <-- thinks state type is {}
}
});
}
}
@andraaspar: It looks like Mithril 1.1 will be released on Monday, so I'd like to prepare a PR for DefinitelyTyped that's ready to go when mithril releases.
Stream layout has changed a bit - scan
and scanMerge
functions are now included in stream
rather than separate modules.
I'm going to rename tests
to test
. Convention seems to be either put all tests in module-tests.ts
or to include a test
folder.
To do this I'm going to fork improved
(which should remain compatible with the current mithril 1.0.1 release) to dt-umd
.
The deserialize
method of m.request
should use type any
and not infer a string
type.
We could leverage csstype and provide typings on the vnode inline style
attribute. Mithril supports hyphen and camelCase CSS properties both of which are covered in csstype.
Basic Example
import { StandardProperties, StandardPropertiesHyphen } from "csstype";
interface IStyle extends StandardProperties, StandardPropertiesHyphen {}
interface Attributes extends Lifecycle<any, any> {
// ...
style?: IStyle;
}
Personally feels little much for some intellisense features like completions and checks. I would hypothesize users are not readily reaching for style attributes but given developers tend to expect this type of thing nowadays, might be a discussion point at the very least.
I propose changing the file structure, so this type declaration, also supports global usage.
Benefits:
Changes:
For the new usage you can take a look at this minimal example
P.S. I am using GoLand, so some feedback on other IDEs would be appreciated, though I expect it to work anywhere.
Edit: The namespace can be renamed Mithril -> m, so you can still write m.Vnode instead of Mithril.Vnode.
When using m.request(options)
, the RequestOtions.url
property is required. When using m.request(url, options)
the url property is not required. Similarly for jsonp.
When I define a stream type before using it, I regularly need to typecast the value when using stream()
:
interface MyType {
[index: string]: string | undefined,
}
let s: Stream<MyType>;
s = Stream({
hello: "world",
});
This will error, saying it's not assignable to MyType. Typecasting the object (adding } as MyType
inside the stream) fixes it.
This works though:
interface MyType {
[index: string]: string | undefined,
}
const s: Stream<MyType> = Stream({
hello: "world",
});
This may be a typescript quirk that I'm not aware of. I'm using TypeScript 4.9.5.
Hi,
I noticed that mithril.d.ts relies on Promise definition provided by system definition files. Unfortunately, this breaks compilation if project targets ES5. With this target no Promise definition is provided and the compilation fails with error TS2693: 'Promise' only refers to a type, but is being used as a value here.
error.
The problem can be solved in tsconfig.json with lib option as below:
{
"compilerOptions": {
...
"target": "ES5",
"lib": [
"dom",
"es2015.promise",
"es5",
"scripthost"
]
...
},
...
}
"dom", "es5" and "scripthost" are default values for the lib when target is "ES5". Adding "es2015.promise" solves compilation errors.
I believe a notice about that change in readme.md will benefit newcomers trying to use Mithril with TypeScript.
In v1.0.1 the onmatch
signature was changed to only allow a Promise
to be returned if it was a Promise<Component<any, any>>
. I believe this is too restrictive - see the mithril docs at http://mithril.js.org/route.html#preloading-data for an example of using a Promise
returned by m.request
to defer rendering a component until data is loaded.
I think this is as simple as changing the onmatch
signature to allow Promise<any>
, like so:
onmatch?: (args: any, requestedPath: string) => Mithril.Component<any,any> | Promise<any> | void;
Let me know what you think. I can do it myself and raise a PR if you like.
@isiahmeadows, @andraaspar I've added the branch ts2.4
to experiment with solutions. Running npm i
will install TS2.4.
I've added @andraaspar's most recent updates currently in PR at DT. (Failing tests commented out because I haven't setup DT's linting system for this repo.)
This branch also contains default generic parameters that I'd like to submit in a followup PR. (See here for example.)
Currently the Lifecycle
type has an additional member to get around the new "weak types" checking feature in TS2.4. This leaves us with slightly weaker component types. Using a numeric key improves the situation but it's not quite perfect. (You could write component[0]
.)
There is also this outstanding problem with ClassComponent
: #21
config
in request
should probably look something like this:
config?(xhr: XMLHttpRequest, args: RequestOptions<T>): XMLHttpRequest | void;
instead of this
config?(xhr: XMLHttpRequest): any;
See:
if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
Hi, @spacejack
In mithril.d.ts#1.0.2 RequestOptions.extract
has function which return type seems to be not correct:
extract?: (xhr: XMLHttpRequest, options: RequestOptions<T>) => string;
according to mithril's documentation example it can be not only a string
but an object as well:
extract: function(xhr) {return {status: xhr.status, body: xhr.responseText}}
Cheers,
Babur
Coming in rc7?
Any reason as to why the lifecycle method vnode types for ClassComponents are m.Vnode/m.VnodeDOM
and not m.CVnode/m.CVnodeDOM
?
I can submit a PR if need be.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.