You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
i think hyperscript is great, it's a simple functional way to compose html trees, which has led to a vibrant ecosystem. but in this vibrant ecosystem, each module has a slightly different API.
tagName is a string that specifies the type of element to be created, which is passed into document.createElement.
properties is an object with the following:
attributes: an object mapping attribute names to values, which is passed into element.setAttribute(name, value).
events: an object mapping event names to listeners. which is passed into element.addEventListener(name, listener) or element.on${name} = listener
anything that's not attributes or events is set with element[name] = value
children is either an Array, Node, Element, or String. an Array may contained nested Arrays.
element returned is an instanceofNode or Element.
open questions:
do we want to require supporting special tag names (with classes and ids) out of the box? or should it be a re-usable module that can be added later: parse-classmutant/lib/parse-tag
hooks? i like how mutant does this (based on virtual-hyperscript): an array of functions ("hooks") called when element is added to the dom. if hook returns function, that will be called when the element is removed from the dom.
style? the obvious one is the style property as a string, but do we support objects and if so, in what format? (camelCase like react or css names or)
how to handle svg namespacing?
data properties?
anyways, there's my brain dump. what do other people think?
This is probably a good idea. I think this wants a test suite that all those listed modules can pass.
Can you link to documentation about how hooks work/are used? (this feels like a special feature, I wouldn't expect all the above to handle it, and I'm also not sure how it would be implemented reliably)
Perhaps it would be good to have the opinions of @paldepind, creator of Snabbdom and @staltz, creator of CycleJS that uses Snabbdom, who is also a top contributor to Snabbdom? And also of @lhorie, creator of MithrilJS with its own hyperscript-like library?
To make things tangible, I would like to propose to have (1) a standard "strict API" that libraries have to conform to, and (2) "lax APIs" that allows for more flexibility to the user, which can differ from library to library.
The more flexible lax APi can be more convenient for the user to code the view manually, but can be a pain to fully implement and properly test and can easily become a source of bugs. Also likely various libraries will have their own opinions on different flexibility options.
For the strict API, I would propose the closest one to the HTML structure:
h( tag : String, props : Object, children : Array ),
with all 3 parameters mandatory or, equivalently, a single object parameter, called here the "Abstract Node Tree (ANT)", of the signature:
h( {tag: tag, props: props, children : children} )
with values of the same corresponding types - String, Object, Array, again with all the 3 properties mandatory. Any other properties are allowed on the Abstract Nodes but must be ignored by the h. The elements of the children array can be either String or another ANT. The h should be applied recursively to every Abstract Node until only Strings are left over.
That will serve uniformity and consistency and will require the minimum number of manual checks needed from the libraries implementing the spec. It would not be meant to be used manually by the user due to its verbosity.
Further, again for simplicity and consistency, all values of the corresponding type should be allowed. No validation checks need to be performed at the stage of the virtual node creation.
That is e.g. in line with how Snabbdom's Virtual Node is implemented, with all the checks and adjustments delegated to the individual modules. It is then up the library to enforce of not more specific rules.
Example
As simple example,
<ahref="https://github.com/hyperhype/hyperscript">
I love
<strong>Hyperscript</strong></a>
for context, my motivation for this issue is more than just standardizing h(tag, properties, children), i also want to standardize some common behaviors in the properties, namely:
attributes: an object mapping attribute names to values, which is passed into element.setAttribute(name, value) or similar.
events: an object mapping event names to listeners, which is passed into element.addEventListener(name, listener) or element.on${name} = listener
and maybe also class, values (element[name] = value), style, data, hook, as described above or from snabbdom. but opinions here vary widely, so wonder what is most common.
Thanks @ahdinosaur, I see what you mean.
I am trying to follow the path of maximal simplicity and be as close as possible to HTML,
where the attributes and events are treated the same way:
That way both attributes and events are treated equally in a simple flat object, and their names will never clash by the mere HTML design, so there will never be any confusion.
That would also apply to class, style, data-*, as these are just attributes, so again, would never clash. I would also treat props equally, like ReactJS does if I understand correctly (but I am no React expert, so open for corrections if I am wrong).
React's vDom goes a bit further by introducing some non-native conventions that sometimes feel a bit like solutions to their problems 😈. For instance, their famous className instead of class is unnecessary in HyperScript, where the class object property is perfectly ok.
Snabbdom treats events slightly differently by grouping them inside the on prop (for which I would like to know the reason) but still the standard strict API can be remapped into their via a transformer similar to this.
I am trying to follow the path of maximal simplicity and be as close as possible to HTML,
where the attributes and events are treated the same way:
i'm not sure how these are treated the same way... the problem is that hypescript is the DOM, not HTML, and there are many differences between the DOM and HTML.
why would events need to be seperate? they'd just be anything with an on prefix... right?
that's how a many hyperscript implementations do events, so yes this is a legitimate approach. however i think it adds complexity because then you have many special cases to handle.
so maybe a good way to describe my proposal is that it acknowledges the difference in behavior between keys in properties and then reduces special cases (since each behavior is in a dedicated sub-object). maybe this isn't what others want, but it's definitely what i want, in my mind special cases are the source of unnecessary complexity. 😸
i'm not sure how these are treated the same way... the problem is that hypescript is the DOM, not HTML, and there are many differences between the DOM and HTML.
True, the props are slightly more painful but I understand that e.g. React gets away treating them the same way? If the attributes are merely the initial values of the same-named props, do we really need to store them separately on every node? Are there use cases?
@Michael-Zapata Mithril is one popular framework that keeps attributes, props and events together.
Would be interesting to hear the opinion from @lhorie, Mithril's creator.
It seems most libraries keep their virtual node events directly on the props, notably React (and hence Preact, Inferno etc.). Snabbdom is one of the few doing it, where you have to write
h('button',{on: {click: ()=>pipeToActions(increment)}},`Modify score by ${increment}`)
instead of
h('button',{onclick: ()=>pipeToActions(increment)},`Modify score by ${increment}`)
where the latter feels less cluttered and without the annoying double braces. It might be slightly more work for the library writer (but then also can be less work without having to manage the nested structure), but at the end the library is only written once.
On the other side, the shorter way will benefit the user and result in considerable code reduction, given how many times it is used.
Hi, Mithril author here. Sorry I'm a bit late to the party.
IMHO, the base API should support at a minimum what JSX compiles to, as it makes it super easy for anyone to plug it into babel via a pragma, and it's how a lot of people use Mithril and Preact. Personally, I'm not a JSX person, but I've witnessed a number of people express in the past that they would not use a hyperscript without JSX.
In terms of API variations, the landscape is a bit all over the place. As was already pointed out, Snabbdom has a nested map of events instead of inline in the props/attrs object. If you consider React.createElement a hyperscript, it doesn't support CSS selectors, and for that matter, even optional props/attrs. The level of CSS selector support also varies across other implementations (Mithril, for example, supports[href=javascript:void(0)] and every permutation of id, class, attrs thereof)
There are variations in how attributes are handled, with some implementations allowing class/readonly/contenteditable instead of className/readOnly/contentEditable and some not. There are also spec deviations (e.g. React's onClick vs Mithril/HTML/JS's onclick). In Mithril, the recommendation is to stay close to HTML spec, but it also supports the JS API (i.e. both readonly and readOnly)
The variations go further in regards to support for HTML namespacing and SVG/MathML, React being one that has unusual naming edge cases. Mithril uses the xlink:href convention, which I feel is the most guessable.
Mithril also includes support for properties such as innerHTML and select.value/select.selectedIndex, as well as attribute normalization (i.e. h("input", {disabled: false}) does attribute removal, instead of treating it as a conventional but confusing boolean-to-string cast). Mithril also does normalization of event declarations (notably, it ensures ontouchstart/ontouchend/friends work)
Another aspect where hyperscript implementations vary is in terms of varargs support, i.e. h(tag, attrs, child1, child2, ...), as well as whether an array is a valid top level tree (both in and out of components).
Many implementations have special vnode types. Mithril has a fragment type (basically, a range of elements can be keyed and have lifecycle hooks) and a trusted HTML type (i.e. m.trust("<b>This is some markdown/bbcode output</b>")). Snabbdom has thunks.
Another significant aspect is component support. Mithril, React, Preact and friends support components as first args (again, because of JSX), but some systems don't have the concept of components at all (e.g. Snabbdom). Component hooks have different names, and their APIs vary very significantly, especially when it comes to APIs related to state and children.
Mithril and inferno support hooks at the vnode level. If you're going to support it, I'd recommend baking it in. The onbeforeremove hook in particular is useful for animations (it defers DOM removal until a callback), but has a very deep impact on diffing algorithms that is hard to pull out of core.
For class, both React and Mithril decided to defer to the classnames project, so I'd follow along with that convention. For style, both allow objects in camel case. React, however, does not accept a string, whereas Mithril does (including in the CSS selector syntax). The string syntax can be used (and was in at least one occasion) deliberately to squeeze performance, so there's that to consider.
For data-* attributes, Mithril supports arbitrary attributes, as that feels in line with what one would expect out of HTML.
With regards to attrs vs props, as far as DOM elements are concerned in Mithril, attrs are typically always attrs and data usually get into hook functions via closures. Components are where things get a bit murky, where props are usually data-oriented but they might also include HTML attributes (typically class or onchange) and where children can be abused to do strange things. Personally I think people should think of a component as a opaque element vnode that quacks like regular HTML (and in Mithril, it's generally expected that nesting anything inside of anything would behave as HTML does), but that's certainly not the only way to go about it. React components can flat out forbid all expando props via PropTypes (and that's considered a good thing in the places where such a policy is in place). React Router is another example where arbitrary child text nodes are not allowed for the sake of keeping their DSL clean. YMMV.
Reacted by Mikey, fent, Michael Geary, Dmitry Iv. and Bagus
hey, should the .outerHTML method be part of this standard API?
hyperscript :: outerHTML
hyperx :: toString()
react :: renderToString() ?
Reasoning: if you're using some little framework designed for hyperx -for example-, but want to use hyperscript instead, then the framework calls toString() when it server renders and you get [object Object]
I very much like the idea of a single object to define the element
h(opts: HyperscriptOptions={}, ...children: HTMLElement[])// use this overload if the first parameter is an HTMLElement, default options.tag to 'div'h(...children: HTMLElement[])
constel=h({tag: 'a',attr: {href: 'https://gogogo.com','data-whatever': 'sweet!'},styles: {// this is of type: ElementCSSInlineStyle},events: {onclick: console.log}innerHTML: '<strong>Ok</strong>'},h())document.body.appendChild(el)
Activity
dominictarr commentedon Mar 30, 2017
This is probably a good idea. I think this wants a test suite that all those listed modules can pass.
Can you link to documentation about how hooks work/are used? (this feels like a special feature, I wouldn't expect all the above to handle it, and I'm also not sure how it would be implemented reliably)
reqshark commentedon Mar 30, 2017
wow great idea!
+1 to that! :)
I would have a use-case to plug
virtual-hyperscriptinto the standard like a levelup orhyperupbalupton commentedon Mar 30, 2017
Perhaps we should cc the creators of the other libraries into this as well?
dmitriz commentedon Apr 22, 2017
Perhaps it would be good to have the opinions of @paldepind, creator of Snabbdom and @staltz, creator of CycleJS that uses Snabbdom, who is also a top contributor to Snabbdom? And also of @lhorie, creator of MithrilJS with its own hyperscript-like library?
dmitriz commentedon Apr 23, 2017
To make things tangible, I would like to propose to have (1) a standard "strict API" that libraries have to conform to, and (2) "lax APIs" that allows for more flexibility to the user, which can differ from library to library.
The more flexible lax APi can be more convenient for the user to code the view manually, but can be a pain to fully implement and properly test and can easily become a source of bugs. Also likely various libraries will have their own opinions on different flexibility options.
For the strict API, I would propose the closest one to the HTML structure:
h( tag : String, props : Object, children : Array ),with all 3 parameters mandatory or, equivalently, a single object parameter, called here the "Abstract Node Tree (ANT)", of the signature:
h( {tag: tag, props: props, children : children} )with values of the same corresponding types -
String,Object,Array, again with all the 3 properties mandatory. Any other properties are allowed on the Abstract Nodes but must be ignored by theh. The elements of thechildrenarray can be eitherStringor another ANT. Thehshould be applied recursively to every Abstract Node until onlyStringsare left over.That will serve uniformity and consistency and will require the minimum number of manual checks needed from the libraries implementing the spec. It would not be meant to be used manually by the user due to its verbosity.
Further, again for simplicity and consistency, all values of the corresponding type should be allowed. No validation checks need to be performed at the stage of the virtual node creation.
That is e.g. in line with how Snabbdom's Virtual Node is implemented, with all the checks and adjustments delegated to the individual modules. It is then up the library to enforce of not more specific rules.
Example
As simple example,
in the strict API would look like
and the corresponding ANT would be
which would be a pure library-agnostic JSON that is incredibly convenient to pass around, serialise and transform via abstract pure functions.
Let me know what you think.
staltz commentedon Apr 23, 2017
Good idea. Just a note, as a snabbdom (a virtual DOM) library user, this assumption does not apply:
because
h()returns a VNode in snabbdom, so a child can also be a VNode.dmitriz commentedon Apr 23, 2017
Here is the proof of concept with very simple implementation of the extension of
hto the Abstract Node Tree arguments: https://github.com/dmitriz/hyperscript-strictahdinosaur commentedon Apr 24, 2017
hey @dmitriz, awesome!
for context, my motivation for this issue is more than just standardizing
h(tag, properties, children), i also want to standardize some common behaviors in theproperties, namely:attributes: an object mapping attribute names to values, which is passed intoelement.setAttribute(name, value)or similar.events: an object mapping event names to listeners, which is passed intoelement.addEventListener(name, listener)orelement.on${name} = listenerand maybe also
class,values(element[name] = value),style,data,hook, as described above or from snabbdom. but opinions here vary widely, so wonder what is most common.what do you think about this?
balupton commentedon Apr 24, 2017
why would events need to be seperate? they'd just be anything with an
onprefix... right?dmitriz commentedon Apr 24, 2017
Thanks @ahdinosaur, I see what you mean.
I am trying to follow the path of maximal simplicity and be as close as possible to HTML,
where the attributes and events are treated the same way:
So the closest API in the HyperScript would be
or in the single object (Abstract Node) notation
That way both attributes and events are treated equally in a simple flat object, and their names will never clash by the mere HTML design, so there will never be any confusion.
That would also apply to
class,style,data-*, as these are just attributes, so again, would never clash. I would also treatpropsequally, like ReactJS does if I understand correctly (but I am no React expert, so open for corrections if I am wrong).React's vDom goes a bit further by introducing some non-native conventions that sometimes feel a bit like solutions to their problems 😈. For instance, their famous
classNameinstead ofclassis unnecessary in HyperScript, where theclassobject property is perfectly ok.Snabbdom treats events slightly differently by grouping them inside the
onprop (for which I would like to know the reason) but still the standard strict API can be remapped into their via a transformer similar to this.Just my few cents.
ahdinosaur commentedon Apr 24, 2017
i'm not sure how these are treated the same way... the problem is that hypescript is the DOM, not HTML, and there are many differences between the DOM and HTML.
that's how a many hyperscript implementations do events, so yes this is a legitimate approach. however i think it adds complexity because then you have many special cases to handle.
so maybe a good way to describe my proposal is that it acknowledges the difference in behavior between keys in
propertiesand then reduces special cases (since each behavior is in a dedicated sub-object). maybe this isn't what others want, but it's definitely what i want, in my mind special cases are the source of unnecessary complexity. 😸dmitriz commentedon Apr 24, 2017
True, the
propsare slightly more painful but I understand that e.g. React gets away treating them the same way? If the attributes are merely the initial values of the same-named props, do we really need to store them separately on every node? Are there use cases?dmitriz commentedon Apr 25, 2017
@Michael-Zapata
Mithril is one popular framework that keeps attributes, props and events together.
Would be interesting to hear the opinion from @lhorie, Mithril's creator.
It seems most libraries keep their virtual node events directly on the props, notably React (and hence Preact, Inferno etc.). Snabbdom is one of the few doing it, where you have to write
instead of
where the latter feels less cluttered and without the annoying double braces. It might be slightly more work for the library writer (but then also can be less work without having to manage the nested structure), but at the end the library is only written once.
On the other side, the shorter way will benefit the user and result in considerable code reduction, given how many times it is used.
lhorie commentedon Jul 8, 2017
Hi, Mithril author here. Sorry I'm a bit late to the party.
IMHO, the base API should support at a minimum what JSX compiles to, as it makes it super easy for anyone to plug it into babel via a pragma, and it's how a lot of people use Mithril and Preact. Personally, I'm not a JSX person, but I've witnessed a number of people express in the past that they would not use a hyperscript without JSX.
In terms of API variations, the landscape is a bit all over the place. As was already pointed out, Snabbdom has a nested map of events instead of inline in the props/attrs object. If you consider React.createElement a hyperscript, it doesn't support CSS selectors, and for that matter, even optional props/attrs. The level of CSS selector support also varies across other implementations (Mithril, for example, supports
[href=javascript:void(0)]and every permutation of id, class, attrs thereof)There are variations in how attributes are handled, with some implementations allowing
class/readonly/contenteditableinstead ofclassName/readOnly/contentEditableand some not. There are also spec deviations (e.g. React'sonClickvs Mithril/HTML/JS'sonclick). In Mithril, the recommendation is to stay close to HTML spec, but it also supports the JS API (i.e. bothreadonlyandreadOnly)The variations go further in regards to support for HTML namespacing and SVG/MathML, React being one that has unusual naming edge cases. Mithril uses the
xlink:hrefconvention, which I feel is the most guessable.Mithril also includes support for properties such as
innerHTMLandselect.value/select.selectedIndex, as well as attribute normalization (i.e.h("input", {disabled: false})does attribute removal, instead of treating it as a conventional but confusing boolean-to-string cast). Mithril also does normalization of event declarations (notably, it ensuresontouchstart/ontouchend/friends work)Another aspect where hyperscript implementations vary is in terms of varargs support, i.e.
h(tag, attrs, child1, child2, ...), as well as whether an array is a valid top level tree (both in and out of components).Many implementations have special vnode types. Mithril has a fragment type (basically, a range of elements can be keyed and have lifecycle hooks) and a trusted HTML type (i.e.
m.trust("<b>This is some markdown/bbcode output</b>")). Snabbdom has thunks.Another significant aspect is component support. Mithril, React, Preact and friends support components as first args (again, because of JSX), but some systems don't have the concept of components at all (e.g. Snabbdom). Component hooks have different names, and their APIs vary very significantly, especially when it comes to APIs related to state and children.
Mithril and inferno support hooks at the vnode level. If you're going to support it, I'd recommend baking it in. The
onbeforeremovehook in particular is useful for animations (it defers DOM removal until a callback), but has a very deep impact on diffing algorithms that is hard to pull out of core.For
class, both React and Mithril decided to defer to theclassnamesproject, so I'd follow along with that convention. Forstyle, both allow objects in camel case. React, however, does not accept a string, whereas Mithril does (including in the CSS selector syntax). The string syntax can be used (and was in at least one occasion) deliberately to squeeze performance, so there's that to consider.For
data-*attributes, Mithril supports arbitrary attributes, as that feels in line with what one would expect out of HTML.With regards to attrs vs props, as far as DOM elements are concerned in Mithril, attrs are typically always attrs and data usually get into hook functions via closures. Components are where things get a bit murky, where props are usually data-oriented but they might also include HTML attributes (typically
classoronchange) and where children can be abused to do strange things. Personally I think people should think of a component as a opaque element vnode that quacks like regular HTML (and in Mithril, it's generally expected that nesting anything inside of anything would behave as HTML does), but that's certainly not the only way to go about it. React components can flat out forbid all expando props via PropTypes (and that's considered a good thing in the places where such a policy is in place). React Router is another example where arbitrary child text nodes are not allowed for the sake of keeping their DSL clean. YMMV.laduke commentedon Feb 23, 2018
hey, should the .outerHTML method be part of this standard API?
Reasoning: if you're using some little framework designed for hyperx -for example-, but want to use hyperscript instead, then the framework calls toString() when it server renders and you get [object Object]
(outerHTML is the right choice)
alshdavid commentedon Jun 15, 2019
I very much like the idea of a single object to define the element
makes