WebReflection / uland

A µhtml take at neverland
ISC License
108 stars 2 forks source link

uland components inside of WebComponent / uce don't render #6

Closed bcomnes closed 3 years ago

bcomnes commented 3 years ago

I really like the ergonomics of uland, but I want to deliver a web component as the consumption API of a component I'm writing. This attempt could be misdirected, but I was trying to figure out how to consume a uland component inside of a uce web component wrapper like this:

import uce from 'uce-lib'
import { Component, html, useState } from 'uland'

const Counter = Component((initialState) => {
  const [count, setCount] = useState(initialState)
  return html`
  <button onclick=${() => setCount(count + 1)}>
    Count: ${count}
  </button>`
})

const uceNested = ({ define, render, html, svg, css }) => {
  define('uce-nested', {
    style: selector => css`${selector} {
      font-weight: bold;
      color: blue;
    }`,
    render () {
      this.html`
      <div>
        Hello uce nested
        ${Counter(0)}
      </div>`
    }
  })
}

uce.then(uceNested)

(to quickly expand, uce provides a nice web-component wrapper, but inside the component, I get the nice and simple DX from uland and have access to re-usable hooks.)

This doesn't work, nothing renders for Counter, and from what it looks like, it comes down to a difference in the render functions being used.

Is this something that could be supported? Is there a better way to wrap a uland component in a a webCompoent wrapper similar to uce? Is this whole idea stupid and I should give up?

WebReflection commented 3 years ago

Is this whole idea stupid and I should give up?

no idea is stupid, it's eventually just a matter of understanding the idea value

import uce from 'uce-lib'

I don't even have a repository for that, and I think I should remove/deprecate already that package, but as you can read in npm: https://www.npmjs.com/package/uce-lib

This module does not inject µce in the page, you can still do that as part of the main bundle.

So here what I'd try, before blaming uce

// instead of import uce from 'uce-lib'
import 'uce';

// ... rest of the code ...

// instead of uce.then(uceNested)
customElements.whenDefined('uce-lib').then(() => {
  uceNested(customElements.get('uce-lib'));
});

Anyway, I've written a CodePen and indeed nothing happens, 'cause the uce render is the same used in uhtml, but uland render needs to "unroll" its own hooks, due double pass on regular uhtml "holes".

However, even using explicitly the uland render function, it looks like within the uce render() it doesn't really work, and this is a bit of a bummer.

I'll have a look, but so far I think I'd like these libraries to be somehow interchangeable, hence it might be worth a fix/update where needed.

bcomnes commented 3 years ago

no idea is stupid, it's eventually just a matter of understanding the idea value

Maybe stupid is too harsh, I meant misguided.

This module does not inject µce in the page, you can still do that as part of the main bundle. So here what I'd try, before blaming uce

Oh yeah, sorry, I have a working uce solution in place with uce-lib but its not apparent by the example. My mistake. I'll switch to vanilla WC .get solution.

I'll have a look, but so far I think I'd like these libraries to be somehow interchangeable, hence it might be worth a fix/update where needed.

That would be cool. In the meantime it looks like heresy supports a web component wrapper with hooks if I really need a solution quickly. It would be cool of the u* family could compose. Feel free to close or leave open if you want a reminder to look into this.

WebReflection commented 3 years ago

Well, uce-template has hooks too, but it's more Vue-3-ish than React-ish, but yeah, having uland usable in uce looks like a reasonable request, I just don't know when I'll find the time to do that (soonish though, as I'm curious about why it doesn't work now as I'd expect).

WebReflection commented 3 years ago

I feel stupid ... it's solved: https://codepen.io/WebReflection/pen/QWExMeG?editors=0010

Basically, you need to use the render and html from uland, not the one provided by uce

import 'uce';
import {Component, html, render, useState} from 'uland';

const Counter = Component((initialState) => {
  const [count, setCount] = useState(initialState);
  return html`
  <button onclick=${() => setCount(count + 1)}>
    Count: ${count}
  </button>`;
});

const uceNested = ({define, css}) => {
  define('uce-nested', {
    style: selector => css`${selector} {
      font-weight: bold;
      color: blue;
    }`,
    render() {
      render(this, html`
      <div>
        Hello uce nested
        ${Counter(0)}
      </div>`);
    }
  });
};

customElements.whenDefined('uce-lib').then(() => {
  uceNested(customElements.get('uce-lib'));
});

That's why outside the define(...) everything was working as expected, but using render or html, or even svg, from uce, instead of uland, can't possibly work.

Alternative:

import {define, css} from 'uce';
import {Component, html, render, useState} from 'uland';

const Counter = Component((initialState) => {
  const [count, setCount] = useState(initialState);
  return html`
  <button onclick=${() => setCount(count + 1)}>
    Count: ${count}
  </button>`;
});

define('uce-nested', {
  style: selector => css`${selector} {
    font-weight: bold;
    color: blue;
  }`,
  render() {
    render(this, html`
    <div>
      Hello uce nested
      ${Counter(0)}
    </div>`);
  }
});

Case solved 👋

bcomnes commented 3 years ago

Awesome! That should do it!

WebReflection commented 3 years ago

Another idea, to simplify the pattern:

import 'uce';

import {Component, html, render, useState} from 'uland';
function uland() { return render(this, html.apply(null, arguments)); }
const ulandify = self => Object.defineProperty(
  self,
  'html',
  {value: uland.bind(self.html``)}
);

const Counter = Component((initialState) => {
  const [count, setCount] = useState(initialState);
  return html`
  <button onclick=${() => setCount(count + 1)}>
    Count: ${count}
  </button>`;
});

const uceNested = ({define, render, html, svg, css}) => {
  define('uce-nested', {
    style: selector => css`${selector} {
      font-weight: bold;
      color: blue;
    }`,
    init() {
      ulandify(this).render();
    },
    render() {
      this.html`
      <div>
        Hello uce nested
        ${Counter(0)}
      </div>`;
    }
  });
};

customElements.whenDefined('uce-lib').then(() => {
  uceNested(customElements.get('uce-lib'));
});

This way you can use all uce utilities, when needed, and be sure you are in uland whenever a component passes through ulandify(element) before render() gets call.

WebReflection commented 3 years ago

P.S. above example needs latest uce as I had to make the html property configurable to be able to do that. The key, in this approach, is that you don't need to import uce per each uladn component you define, you can still use the customElements bootstrap procedure and include uce once, while defining components via uland.

bcomnes commented 3 years ago

Even better