by

Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Jul 14 14:21
    benash closed #120
  • Jul 11 17:15
    smalluban closed #121
  • Jul 11 16:15
    1ntEgr8 opened #121
  • Jul 06 20:13
    smalluban labeled #120
  • Jul 06 20:13
    smalluban labeled #120
  • Jul 06 19:21
    benash opened #120
  • Jun 30 15:17
    smalluban synchronize #85
  • Jun 28 10:47
    smalluban synchronize #85
  • Jun 26 07:33
    smalluban synchronize #85
  • Jun 26 06:05
    smalluban synchronize #85
  • Jun 26 05:57
    smalluban labeled #119
  • Jun 26 05:57
    smalluban labeled #119
  • Jun 26 05:46
    smalluban edited #119
  • Jun 25 15:28
    zunwebs edited #119
  • Jun 25 15:22
    zunwebs opened #119
  • Jun 25 14:54
    smalluban synchronize #85
  • Jun 25 14:49
    smalluban synchronize #85
  • Jun 25 14:29
    smalluban synchronize #85
  • Jun 19 17:40
    smalluban closed #118
  • Jun 07 15:19
    smalluban synchronize #85
Riovir
@riovir_gitlab
In the immediate future, yes. In production my company uses Vue.js (mostly). However, there is a design system we are working on, where we already decided to use webcomponents. This is where Hybrids comes in, standing toe-to-toe with LitElement (maybe vanilla, here and there). Personally, Hybrids by far uses my favorite kind of approach in its API. I'm planning on building my next pet project with it. (Last one was Divinity: Original Sin 2 planner made with Vue.js). Finally through Pika CDN I'm also using it in workshops involving, but not focusing on webcomponents due to it's easy to read, no-nonsense syntax.
Alistair MacDonald
@F1LT3R
Cool :)
I’m evaluating Hybrids for use for an AV startup. So far I like the approach more than LitElement, mainly because it's functional rather than class based and seems closer to the metal than LitElements in some ways, eg: the string templating is closer to Vanilla JS.
My intention is to build 100% un-bundled.
Alistair MacDonald
@F1LT3R
A pure CSS and MJS approach. <— And this is probably the biggest reasons I like Hybrids so far. It can be used without transpilation. It abstracts away just enough to make things quick to build, without trying to give you the world on a stick.
Dominik Lubański
@smalluban
@riovir_gitlab I have good news! I played with the experimental syntax of ES modules in node, but it is still too early to adopt it (using type: "module"breaks webpack and karma tools). However, I figure out, that commonjs target can be not transpiled, as all bundlers, that I checked, respect "browser" field of the package.json. Because of that, we can still transpile code (for older browser support) for the browser environment (but with ES modules syntax) and have commonjs syntax without transpilation. It is rather a fix than a feature, and it should not bring any breaking changes. In the future, I plan to drop IE11 support, so then package.json will point just the source code (yeah!). Please, check it out. You can find link to working example on the PR. If it is good to go, you can approve the PR : #100 :)
Riovir
@riovir_gitlab

@riovir_gitlab @smalluban Not only does it work with JSDOM alone, but spinning up a new jest + babel-jest + jest-environment-jsdom-sixteen with a careful global.requestAnimationFrame = (fn) => Promise.resolve().then(fn); added a setupfile, the following test runs nice and green:

const { define, html } = require('hybrids');

function increment(host) {
    host.counter++;
}

const MyElement = {
    counter: 0,
    button: ref('button'),
    render: ({ counter }) => html`<button onclick="${increment}">${counter}</button>`,
};

test('works', async () => {
    const el = await mount(MyElement);
    expect(el.counter).toBe(0);
    el.button.click();
    await Promise.resolve(); // AKA nextTick
    expect(el.counter).toBe(1);
    console.log('Light DOM:', el.outerHTML);
    console.log('Shadow DOM:', el.shadowRoot.innerHTML);
});

async function mount(Hybrids) {
    const Element = define('test-candidate', Hybrids);
    document.body.appendChild(new Element());
    await Promise.resolve();
    const el = document.body.children[0];
    return el;
}

function ref(selector) {
    return ({ render }) => render().querySelector(selector);
}

Awesome! I've just approved the PR. :)

Riovir
@riovir_gitlab
A question about testing the definitions: is there a recommended way for testing event listeners? The docs detail how to approach testing the rendered HTML. I can also easily test what an event handler would do. What I struggle with is testing that I connected the right element to the right handler. Of course I could always use the option hybridsjs/hybrids#100 enabled (if I didn't shoot myself in the foot with constructible-stylesheets not working in JSDOM).
(Context: I recently rewrote a pet project to Hybrids from Vue.)
Riovir
@riovir_gitlab
Update: First, I got out of the hole I dug myself in with constructible-stylesheets by filtering out the blank ones JSDOM would get due to the limits of the polyfill. Then using the method described in #100 using the native approach I tested the behavior directly as a webcomponent. Still, I wonder if there is a simpler approach I could take here.
Dominik Lubański
@smalluban

@riovir_gitlab It depends on how deep you want to test your components. I think we don't have to test if "click" event work from the DOM, but I understand your need to check out if it is correctly connected in the template. You don't have to initialize the whole component to test out if the template is constructed properly (but it might be the fastest way). As you mention docs, they propose using render method directly on DOM element created for testing. This approach separates the template structure from the rest of the component. You can easily then pass values to the function (mock them), so you the only unit test the view.

I am not sure which testing framework you use, but for example, you can do something like this:

import Component from "./component";

let el;
beforeEach(() => { el = document.createElement('div'); });

it('should attach click event', () => {
  const spy = jasmine.createSpy();
  const render = Component.render({ clickHandler: spy });
  render(el);
  el.queryElementById('#button').click();
  expect(spy).toHaveBeenCalledTimes(1);
});

By using that pattern you can cover all of the components features separately - you can test your property factories (if you have some), you can test side effects (the logic behind function called when events occur), and finally, test template itself if its structure and event listeners are attached correctly.

And I would be careful with constructible-stylesheets. Chrome team again did something without consulting, so the implementation might change, or can be not supported at all by other browser vendors. As the idea is great, I decided to not yet support it in the library (It would be possible to extend the style helper of the template engine to support them).
Riovir
@riovir_gitlab
@smalluban Appreciate the feedback! Indeed, adding a mockable indirection between the button and handler allowed a much simpler way of testing (with Jest). About the constructible-stylesheets, I had a hunch that leaving them out of your API was quite intentional. That said, I'm hoping some efficient form of modular style sharing catches on. (At least I'm seeing Firefox prototyping it as well.) For now, I'll quaranteen its usage in my pet projects.
jl0nd0n0
@jl0nd0n0
Hello
please i like create webcomponent form. How i ca
How i can render states and cities in controls select ?
thanks in advanced for your help
Dominik Lubański
@smalluban
Hi @jl0nd0n0 ! I am happy to help you, but I need more detailed information. Can you provide an example code somewhere online? You can create an issue on the github repo as well.
Alistair MacDonald
@F1LT3R
@smalluban - ever think about moving this chat to something like Discord?
Dominik Lubański
@smalluban
@F1LT3R Yes, I even did create an account for hybrids on Slack, but Discord might be a better option. For now, I don't see much conversation here, and personally I like more issues on Github if someone has a problem. They are more discoverable and stick in the repo (not all people use communicators). Also, the channel feed might be hard to scan for a particular problem. However, if the community grow I will move the chat communication to Discord or Slack. Still, you can reach me privately here if you want to talk on specific ideas in a more "live" faction.
justtobetrendy
@justtobetrendy

Hello, we're in the process of implementing a small PoC using Hybrids and we're having an issue with render (html.resolve) being called in a loop when the component is connected to a redux store and that the service that is used by resolve will dispatch to the store. I have an example up : https://stackblitz.com/edit/hybrids-redux-counter-7upzfc?file=service.js this roughly reproduce how we're conditional rendering according to user auth, to reproduce just uncomment the dispatch in the service.

I'm sure we're doing something wrong that might be obvious to somebody here...

Dominik Lubański
@smalluban
@justtobetrendy I will look at your example in the morning and let you know ;)
justtobetrendy
@justtobetrendy
np it's greatly appreciated @smalluban
Dominik Lubański
@smalluban

@justtobetrendy There are two problems there. The first is how the redux works (by the way thanks for sharing this - I slightly changed the connect factory in the example because of you ;) ). We can say that the redux subscribe mechanism is dumb. The docs say, that subscribed event listener is called if "potentially have changed". Your action in the service does not change the value of the user (it is still the same string), but the state of the redux store has changed. The invalidate() callback in the hybrids is for notifying cache mechanism, that property has a new value. To not recalculate the values of each dependency, the library assumes that if you called invalidate(), the value has changed, so all of the properties, which relay on it have to be recalculated. In your example, it will be the render. And here we are in the second problem - if render property is called (for any reason) the checkAuthStatusAndRender() is called, which eventually updates redux store (here we have the end of the loop). I know that you must put somewhere call for the user, and for now, the best way is to use html.resolve() for promise based results. I've almost finished the store feature, which will be a game-changer, as it will allow define your user model, attach storage to it, and use it as it would be available synchronously (YES!). In the meantime, here it is a solution for the connect method (a smarter version):

function connect(store, mapState) {
  const get = mapState ? () => mapState(store.getState()) : () => store.getState();

  return {
    get, 
    connect: (host, key, invalidate) => store.subscribe(() => {
      // Invalidate must update state of the property,
      // but this callback might be called even though value didn't change
      if (host[key] !== get()) invalidate();
    }),
  };
}

However, when your property value changes (by the reference), it still might create an endless loop if calling dependents change it again.

justtobetrendy
@justtobetrendy
Sorry I meant to answer way sooner. Thanks for looking into this @smalluban this allowed us to keep going thank you.
Doruk Kutlu
@d0ruk
hello
i'm trying to hook my app to a redux store
i am using connect() from examples
export function connect(store, mapState) {
  const get = mapState
    ? () => mapState(store.getState())
    : () => store.getState();

  return {
    get,
    connect: (host, key, invalidate) =>
      store.subscribe(() => {
        // Invalidate must update state of the property,
        // but this callback might be called even though value didn't change
        if (host[key] !== get()) invalidate();
      }),
  };
in my component, i do
export default {
  count: 20,
  flexAttrs: connect(store, state => state.flexbox),
  CSSString: ({ flexAttrs }) => {}
when i dispatch() an action from some other component onn the page, i get
Uncaught Error: Invalidating property in chain of get calls is forbidden: 'flexAttrs'
anyone have an idea what that means?
Dominik Lubański
@smalluban
Hi @d0ruk! Thanks for using the library. The above error protects the observe mechanism from putting into the endless loop of re-renders. In your case, you must invoke dispatch() from the redux inside of the getter of the other component. Why this is wrong? If your dispatch would change the value of that property, it will again dispatch another action, as it will be recalculated, etc.. Your dispatch() actions should be placed in components as side effects - probably as the callbacks of the click events, etc. If you need to call dispatch() in the initial phase, you should place it inside of the connect method of the property.
Can you provide a part of the code of that other component?
Doruk Kutlu
@d0ruk
a <select-group> renders a bunch of <select>, gathers their "selected" values and dispatches an object to the store
<counter>s are verbatim from the hybrids example apps
Doruk Kutlu
@d0ruk
flexAttrs seems to be recalculated even if the only thing changing count
any help in untangling it would be appreciated
Dominik Lubański
@smalluban
Ok, thanks for the code, I will look at this later today and I will let you know.
Dominik Lubański
@smalluban
@d0ruk I've found a moment to look at your code example. There is a missing implementation of the MySelect, but I figured it out from the context ;) The error is correct - you are invoking a side effect inside of the render property - you are updating the value of the redux store. The library protects from that behavior at may lead to endless loops (as I wrote before). You should use the observe() method instead. Create a property, which calculates a map of values from the <my-select> elements, and put the dispatch action inside of the observe(). I have prepared a fully working example on codesandbox using your code from the gist. Here you have it https://codesandbox.io/s/redux-counter-web-component-built-with-hybrids-library-mz497?file=/src/SelectGroup.js. The most important part is in the values property of the SelectGroup element:
{
  values: {
    get: ({ items }) =>
      Object.fromEntries(
        items
          .map(({ key, selected }) => {
            if (!key) {
              console.warn(
                "Required attribute 'key' missing on <select> in <select-group>"
              );
              return [];
            }

            return [key, selected];
          })
          .filter(([k, v]) => k && v)
      ),
    observe({ path }, items) {
      if (Object.keys(items).length) {
        store.dispatch(setObject(items, path));
      }
    }
  },
}
Doruk Kutlu
@d0ruk
that worked like a charm @smalluban
is it OK if I invite you to a repo? there are few things I've tried to get to the kind of app I'm aiming for
I'd love it if you had a look at the places I haven't yet grasped - specifically using get() set() pairs to sync ith redux store
also, I want to use the Router you have at @hybrids/app-tools
Doruk Kutlu
@d0ruk
but I don't use a bundler for the app so I need to either request UMD from a CDN or build the UMD artifact from github source
but npm run build:dist does not work
Dominik Lubański
@smalluban
@d0ruk, sure, you can add me to your repo. However, you can simply create issues with explanation of the places where you need help (ping me by @smalluban). About the app-tools - it is not finished project, so I doubt that there are some parts ready to be used. I have a plan of creating routing management, but with slightly different approach.
Riovir
@riovir_gitlab

Hi @smalluban! I was wondering if there is a way to publish hybrids components that are not sensitive to component version collision on the same page. Until the scoped CustomElementsRegistry is an option https://open-wc.org/scoped-elements/ is seen adopted as a workaround.

The Hybrids API has a similar concept in the Dependencies section. However, I failed to put together a PoC either by duct-taping a Hybrids-defined class with the ScopedElementsMixin or local-scoping a 3rd party webcomponent. Here is some (pseudo) code I've been trying:

export { html, define } from 'https://cdn.pika.dev/hybrids@^4.2.1';
export { ScopedElementsMixin } from 'https://cdn.pika.dev/@open-wc/scoped-elements@^1.1.1';

/** 3rd party webcomponent that doesn't self define by default */
export class ThirdPartyComponent extends HTMLElement {
    constructor() {
        super();
        const template = document.createElement('template');
        template.innerHTML = /* html */`
            <div>Incompatible versions of this component are used on the same page</div>
        `;
        this.attachShadow({ mode: 'open' }).appendChild(template.content.cloneNode(true));
    }
}

/** Registers its own version of ThirdPartyComponent in the global scope, triggering clash when incompatible version is there. */
const UnsafeComponent = {
    render: () => html`
        I'm using a
        <third-party-component></third-party-component>
    `
    .define({ ThirdPartyComponent })
};

/** Attempts to come up with an alias no one else uses. Error prone though */
const WorkaroundComponent = {
    render: () => html`
        I'm using an
        <aliased-third-party-component></aliased-third-party-component>
    `
    .define({ AliasedThirdPartyComponent: ThirdPartyComponent })
};

/** Would prefer Hybrids' spin on ScopedElementsMixin from @open-wc/scoped-elements */
const PreferredComponent = {
    render: () => html`
        I'm using a
        <third-party-component></third-party-component>
    `
    .defineButScopedPlease({ ThirdPartyComponent }) // inspecting the DOM would reveal <third-party-component-[unique number]>
};