Closed dy closed 5 years ago
Hi @dy. The issue with this approach is that invoking a function is fundamentally different than passing a component reference as the tag name. Component references can be classes or functions, and their calling signature is actually much more nuanced than simply fn(props)
and varies from library to library.
Also, the tag name used when is=
is provided is simply ignored, which seems really confusing.
FWIW, in prior versions of HTM, there was an optional syntax that's sortof similar to what you showed:
html`
<@c ${NavBar} />
<@c ${App}>
.. etc
</@c>
`
However, it didn't make sense to preserve this in the 2.0 parser since it was just a workaround intermediary to allow complex tag names in HTML.
Actually is
is a custom elements attribute, so that if we create custom element class that extends some tag, is
is used natively (ref):
class ExpandableList extends HTMLUListElement { ... }
customElements.define('expandable-list', ExpandableList, {extends: 'ul'})
<ul is="expandable-list">
</ul>
That would be genuinely strong improvement for some framework to abandon jsx components in favor of native custom elements. Just using hooks to create native custom elements.
function CustomComponent(props) {
// hooks-based code (fx is shorthand for useEffect)
fx(() => {
// attachedCallback
return () => {
// detachedCallback
}
}, [])
}
htm`
<ul is=${CustomComponent}></ul>
`
Melding that approach with htm would allow to register custom elements on the fly, because in DOM creating custom element takes redundant customElements.define(..., {extends: tagName})
option as well as not elegant class CustomElement extends HTMLTargetElement
. htm has access to both tagName
and component, so it could just internally register components as native custom elements when the code is up to render.
And most importantly - this would be strong step off the bundling.
<link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<script>
import htm from 'https://unpkg.com/htm'
// Register MaterialUI custom elements
function Button(element, props) {
element.mdcButton = new mdc.button.MDCButton
element.mdcRipple = new mdc.ripple.MDCRipple
}
function App () {
return htm`<button is=${Button} class="mdc-button">Button</button>`
}
</script>
Just so I'm following along properly, what's the difference between your example and this one?
<link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<script>
import htm from 'https://unpkg.com/htm'
// Register MaterialUI custom elements
class Button extends HTMLButtonElement {
static toString(){ return 'mcw-button'; }
constructor() {
super();
this.mdcButton = new mdc.button.MDCButton
this.mdcRipple = new mdc.ripple.MDCRipple
}
}
customElements.define(Button+'', Button);
function App () {
return htm`<${Button} class="mdc-button">Button<//>`
}
</script>
@developit good point, I overlooked that, there's no difference for custom elements to be passed directly as tags. Personally I prefer is
attribute though - that keeps html clean/standard.
Just found an Easter egg of htm. Some special h
function can accept non-strings as tags:
html`
<${document.body}>
<div>Our app code to render into body</div>
<//>
<${document.querySelector('.dialogs-container')}>
<section is=${Dialog}>Portal code</section>
<//>
`
that's not possible with JSX. That enables quite elegantly portals, side-effects, hydration and rids of render
method in API.
woah. that is bloody clever...........
For the curious, here's the implementation:
import { createElement } from 'preact';
import { createPortal } from 'preact/compat';
export function h(tag, props, child) {
if (typeof tag === 'object' && tag && 'ownerDocument' in tag) {
return createPortal(child, tag);
}
return createElement.apply(this, arguments);
}
More I play around with possible syntax of hypothetical framework, more I discover how brilliant htm is. A couple more Easter eggs:
// react fragments are well-supported
html`<></>`
// seems that we don't need second slash for closing tag
html`<a></>`
//direct properties are possible
html`<mount=${target}></mount>`
// ↑ meaning literally a `mount` property of a fragment
// calls h({mount: target})
Ok, the only questionable potential aspect of htm is anonymous props:
htm`<a ${disabled}></a>`
That could be useful in some situations, but the implementation isn't clear.
The rest addressed by this issue can be solved with external framework.
Just found an Easter egg of htm. Some special
h
function can accept non-strings as tags:html` <${document.body}> <div>Our app code to render into body</div> <//> ... `
that is cursed...so i had to implement it https://stackblitz.com/edit/node-wup3qi?file=index.html
htm looks brilliant so far. The only thing that does not seem elegant is components. Both having tag name as variable and having closing tag seems external to html syntax. Besides, trying to couple components with semantic HTML oftentimes brings unnecessary wrapping and long nesting, also creates shallow tags like Contexs, Providers, HOCs etc.
A possible elegant way to bring clean html to htm would be making components just modifiers for actual tags, like
Compared to current code:
That idea would allow collapsing HOCs/Providers as well:
Just wonder - if that's worth of separate package and what's possible way to introduce that to htm.