Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is property, or make html clean again #81

Closed
dy opened this issue Apr 7, 2019 · 9 comments
Closed

is property, or make html clean again #81

dy opened this issue Apr 7, 2019 · 9 comments
Labels
discussion Discussions and proposals wontfix This will not be worked on

Comments

@dy
Copy link

dy commented Apr 7, 2019

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

<nav is=${NavBar()}/>
<main is=${App(props)}>
  <header is=${Header(props)} />
  <section ${Content()}>...</section>
  <footer is=${Footer(props)} />
</main>

Compared to current code:

<${NavBar}/>
<${App} ...${props}>
  <${Header} ...${props}/>
  <${Content}>...<//>
  <${Footer} ...${props} />
<//>

That idea would allow collapsing HOCs/Providers as well:

<main is=${App()} provider=${[Lang, Store, Theme]}>
<aside context=${Lang} compose=${[withPagination, reduxForm()]}/>
</main>

Just wonder - if that's worth of separate package and what's possible way to introduce that to htm.

@developit
Copy link
Owner

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.

@developit developit added discussion Discussions and proposals wontfix This will not be worked on labels May 22, 2019
@dy
Copy link
Author

dy commented May 25, 2019

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>

@developit
Copy link
Owner

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>

@dy
Copy link
Author

dy commented May 31, 2019

@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.

@developit
Copy link
Owner

woah. that is bloody clever...........

@developit
Copy link
Owner

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);
}

@dy
Copy link
Author

dy commented Jun 17, 2019

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})

@dy
Copy link
Author

dy commented Jul 2, 2019

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.

@dy dy closed this as completed Jul 2, 2019
@Pyrolistical
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Discussions and proposals wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

3 participants