jorgebucaran / superfine

Absolutely minimal view layer for building web interfaces
https://git.io/super
MIT License
1.56k stars 78 forks source link

Discussion about patching shadow-root nodes? Feature request? #188

Closed whaaaley closed 1 year ago

whaaaley commented 2 years ago

I just want to preface this issue by saying this is just an idea I had. I'm not sure that this belongs in Superfine but I do think it's interesting. I'm not aware of other vdom libraries doing this. If it's not within scope of Superfine I totally understand.

Lately I've been experimenting with using shadow dom to create scopes for styles to avoid an unnecessarily complicated build step to prefix styles. I can do this already with a strategy that looks like this. I create a "root" dom node and using a node reference (ref) from the parent I attachShadow using that "root" dom node. Then patch the "root" node. Then I can pass styles into the shadowDom using props. The styles inside cannot leak.

import { patch } from 'superfine'

export default function () {
  let ref = null
  const root = document.createElement('div')

  function handler () {
    ref.node.attachShadow({ mode: 'open' }).appendChild(root)
  }

  return function (props, children) {
    const style = props.style

    if (ref === null) {
      requestAnimationFrame(handler)
    }

    patch(root, <div id={props.id}>
      {style != null && <style>{style}</style>}
      {children}
    </div>)

    // eslint-disable-next-line no-return-assign
    return ref = <div></div>
  }
}

This works great but there are a few less than ideal things. One of which is that it recreates the root node and forces re-runing the patch function inside this "component" on every update from the parent. Perhaps this isn't actually a problem. Maybe this has no performance impact at all. But, maybe there's an opportunity to optimize?

Another issue is the use of requestAnimationFrame. This is just how it has to happen in order to grab the node reference. So, upon first mount the shadowDom innards are one frame behind. Subsequent patches should be fine and not lag because there's no RAF-ing. Not a big deal but wouldn't be needed if integrated into the lib.

Anyway, my thought was that superfine could potentially expose a function to create a shadow context that is "updatable" by the parent. It could potentially look something like this. Please don't mind my use of JSX syntax 😅. Or maybe a property similar to key would be better. <div shadow={true}>

import { patch, shadowRoot } from 'superfine'
import style from './style.css'

const view = <div>
  <h1>I am an application</h1>
  <shadowRoot>
    <style>{style}</style>
    <h1>I am using styles that are inaccessible to the parent</h1>
  </shadowRoot>
</div>

const root = document.createElement('div')
patch(root, view)

Anyway. I'm curious what your thoughts are. Like I said it was just an idea I had. I've been going all-in on shadow dom components lately and I was thinking this might be better to be more deeply integrated into the vdom.