The simplest way to create web components from plain objects and pure functions! đŸ’¯

Overview

hybrids - the web components

npm version bundle size types build status coverage status npm gitter twitter Conventional Commits code style: prettier GitHub

🏅 One of the four nominated projects to the "Breakthrough of the year" category of Open Source Award in 2019

hybrids is a UI library for creating web components with unique declarative and functional approach based on plain objects and pure functions.

  • The simplest definition — just plain objects and pure functions - no class and this syntax
  • No global lifecycle — independent properties with own simplified lifecycle methods
  • Composition over inheritance — easy re-use, merge or split property descriptors
  • Super fast recalculation — smart cache and change detection mechanisms
  • Global state management - model definitions with support for external storages
  • Templates without external tooling — template engine based on tagged template literals
  • Developer tools included — HMR support out of the box for a fast and pleasant development

Quick Look

<script type="module">
  import { html, define } from 'https://unpkg.com/hybrids@^5';
  
  function increaseCount(host) {
    host.count += 1;
  }

  const SimpleCounter = {
    count: 0,
    render: ({ count }) => html`
      <button onclick="${increaseCount}">
        Count: ${count}
      </button>
    `,
  };

  define('simple-counter', SimpleCounter);
</script>

<simple-counter count="10"></simple-counter>

Click and play with <simple-counter> example:

Edit <simple-counter> web component built with hybrids library

Documentation

The project documentation is available at the hybrids.js.org site.

License

hybrids is released under the MIT License.

Comments
  • why stackblits?

    why stackblits?

    Hello.

    The question is why an online platform is chosen for the demos?! There are so many developers each having their own editor, environtment, etc. When downloading an archive from stackblits I cannot run it locally even when installing the packages - it seems it uses some convention around index.html/index.js. Imho, that is quite ambiguous. My personal preference for 'installable' editor is mostly motivated by the fact that I have best font rendering(of the source code), while in-browser ones are not quite good(for which I can present evidence, but that's not the point). Maybe you thought that would enable hybrids rendering correctly, including the polifils, but if the authors would like more devs trying it out, it should be more accessible to play with. Moreover, users that try new webcomponents' tech rarely would have old browsers or be naive not having poliflls' strategy for their products. Please don't be extravagant - let this lovely new lib get traction, because it has real potential.

    Cheers, Kostadin

    feature 
    opened by kostadinnm 19
  • bundlers: process.env.NODE_ENV in rollup breaks build

    bundlers: process.env.NODE_ENV in rollup breaks build

    I have created a repo for demo : https://github.com/vogloblinsky/hybrids-build-rollup

    After building the main js file, i got this error in the html page :

    Uncaught ReferenceError: process is not defined
    
    help 
    opened by vogloblinsky 15
  • html|promise: Rendering property after http request

    html|promise: Rendering property after http request

    I have a property that inside it's connect, I make a http request, but it sometimes render, and sometimes don't. I have to keep refreshing the page until one time the component renders the api response.

    I'm doing like this:

    import { html } from 'hybrids';
    import { getOngs } from '../services/ongsService';
    import materializeStyle from '../styles';
    
    const ongsProperty = ({
      get: (host, lastValue) => lastValue || [],
      set: (host, newValue) => newValue,
      connect: async (host, key, invalidate) => {
        host.ongs = await getOngs();
        return () => {};
      },
    });
    
    const ongsListRender = ({ ongs }) => html`
      ${materializeStyle}
      <h1>Help My.ONG</h1>
      ${JSON.stringify(ongs)}
      <ul class="collapsible">
      ${ongs.map(ong => html`
        <li>
          <div class="collapsible-header">${ong.name} - ${ong.taskTitle}</div>
          <div class="collapsible-body"><span>Lorem ipsum</span></div>
        </li>
      `)}
      </ul>
    `;
    
    
    const ongsList = {
      ongs: ongsProperty,
      render: ongsListRender,
    };
    
    export default ongsList;
    

    I've made it work by putting a console.log(await getOngs()) above host.ongs = await getOngs();, but I guess this is a wrong thing to do.

    render properties 
    opened by DavideCarvalho 15
  • feat(html): scoped elements

    feat(html): scoped elements

    It's rather a naive implementation of the scoped elements definition to check if it is possible to implement similar to https://open-wc.org/scoped-elements/ in hybrids.

    import { html } from "hybrids";
    import ScopedElement from "./ScopedElement";
    
    const MyElement = {
      render: () => html`
        <scoped-element></scoped-element>
      `.scope({ ScopedElement }),
    };
    

    It's naive because of simple replacement of the element's names - it should be smarter, as that substring might be used in different context inside of the template string.

    opened by smalluban 14
  • Timing of connect during instantiation

    Timing of connect during instantiation

    I am running into some sort of race condition during instantiation, that i don't fully understand how hybrids works in the depths. Hoping someone can shed some light.

    So i am wiring up a component to dexie liveQuery observable. It is a simple query that users the rowID attribute to set query subscription that returns all elements that are in that row and sets the result already as the value of one of the other component attributes. It is working quite well except that sometimes the observable uses the default value for rowID instead of the value from the component attributes.

    I don't understand how/why the connect function of one attribute gets the default value instead of the value that is given to the component at "instantiation" via an "html" attribute.

    I don't have a simple reproduction available yet, but can try to create one of it's really needed to understand the question.

    Thanks...

    bug properties template engine 
    opened by gotjoshua 13
  • Styling a nested React component within a Hybrids component

    Styling a nested React component within a Hybrids component

    Hi! I am just beginning with Hybrids and so far enjoying it very much, but I ran across an issue regarding the integration with React. I am trying to use a React component inside my Hybrids layout. The component loads with the reactify method as in the react-counter example. However, I'm not sure what would be the best approach for styling the component. Let's say I get a CSS along with the 3rd party component that I can load or inject anywhere - I couldn't find how to inject this style to the component created by reactifiy. My best bet was to wrap the 'reactified'' component in another Hybrids component that also includes the style, and render the React component with shadowRoot: false. However, this for some reason breaks the handling of events in the React component. I was able to recreate the event issue in a react-counter example fork. Could you tip me on the best direction to proceed? Thank you for the work on this project! /Avner

    help render 
    opened by Avnerus 13
  • render: couple thoughts about why render uses asynchronous queue to update the DOM

    render: couple thoughts about why render uses asynchronous queue to update the DOM

    Hey guys, I've been using Hybrids pretty extensively for a couple months and had a question about one of the design decisions / gotchas that's bitten me a number of times. In the past, there has typically been a good reason for the choices made in Hybrids, so I imagine that may be the case here, too -- just wondering what that reason might be...

    Basically, why do you wait to attach the shadowRoot until the initial render? Most custom element sample code and existing libraries attach the shadowRoot and create the shadow DOM within the element's constructor, so it is available from that component's properties and methods immediately.

    In addition to the shadow DOM not being available until the component is connected, because Hybrids optimizes rendering to 60fps by potentially deferring tasks to subsequent animation frames, the shadow DOM's existence immediately following connection is non-deterministic. It may be available in the next requestAnimationFrame, or -- on slow browsers like IE11 -- it may not be available until much later.

    This can cause timing issues where methods and properties may try to access shadow DOM that is not yet available. Should attempts to access non-existent shadow DOM throw an exception? Should it queue all requests and invoke them when rendering finally occurs? Should it defer the first request until rendering? The most recent request?

    We can avoid these questions entirely by performing an initial render in the constructor and rendering any updates within the rAF queue. Since properties are already set in the constructor, I can't see any immediate logistical reason why the initial render needs to be delayed -- but there's probably something I'm missing.

    Here's an example:

    import { html, define } from 'hybrids';
    
    define('my-custom-dialog', {
      show: (host) => (arg) => {
        console.log('show called with', arg);
        host.shadowRoot.querySelector('dialog').showModal();
      },
      render: () => html`<dialog>hello</dialog>`
    });
    
    const el = document.createElement('my-custom-dialog');
    el.show(1); // throws exception -- shadowRoot is null
    document.body.appendChild(el);
    requestAnimationFrame(() => el.show(2)); // may or may not throw exception (depends on FPS)
    

    Is there any chance you can move the initial render to the constructor? If not, what's the best way to work around the non-deterministic shadow DOM issue?

    As always, thanks for a great library!

    feature 
    opened by amarajs 13
  • Class attribute mix-in _on_ the web-component

    Class attribute mix-in _on_ the web-component

    Not sure if its actually an anti-pattern, but i like to put classes onto the root web-component element itself. I found this to be not so seamless DX.

    I madea sandbox forking the simple counter example

    i got it working via render or content function that includes these lines:

     render: (host) => {
        const { count, className } = host;
        const classDefaults = "static-string works-fine";
        host.classList.add(...classDefaults.split(" "), ...className.split(" "));
    

    results:

    <simple-counter class="from-instance static-string works-fine" count="0">
    

    All fine (as a work around), but i expected this to work:

    {
        class: ({ className }) => `does-not-work ${className}`, 
        // this doesn't work as it defines a getter that is apparently not used for rendering
    }
    

    I understand from the docs that class and style are treated specially, but I am not sure if the lack of mix-in functionality is a feature to be expected, or a bug that needs attention.

    render template engine 
    opened by gotjoshua 12
  • feat(layout): Add support for layout engine

    feat(layout): Add support for layout engine

    Description

    A built-in layout system for the template engine. Its main goal is not to replace existing solutions for styling (ex. preferred .css helper), but to add a fast way for the composition of the components in the "view" layouts. The system focuses only on "invisible" CSS rules - display types, position, sizing, alignment, flexbox, grid, etc... The rest "visual" styles should be created in place in the UI components, like colors, fonts, borders, backgrounds... Then those components can be aligned in the views components, by the layout system.

    It supports styling elements in the content or render property with the same simple API. It has a strong caching mechanism once the template is compiled, all of the instances share the same CSSStyleSheet instance where it is supported, or use a shared style element attached to the root node of the current subtree.

    Usage

    The main template attached to the content or render property of the component must have only a single <template> root element with at least one layout rule. Rules added to the root <template> elements apply to the host element (regardless if it uses Shadow DOM or not).

    The rules are defined in the layout attribute, with space-separated rules. The attribute can take additional selectors and media queries. The rules can take arguments followed by a colon character:

    • layout="block width:50%"
    • layout:first-child="margin:0"
    • layout@768px="column"
    import { define, html } from "hybrids";
     
    define({
      tag: "my-element",
      content: () => html`
        <template layout="row center" layout@1024px="column">
          <div layout="gap:2">Text</div>
          <div layout="grow">Other text</div>
        </template>
      `,
    ;
    

    Rules

    For now, the following rules apply:

    • Layout attributes must not contain expressions (does not throw yet)
    • Dimensions without type - multiplication of 8px
    • Media queries use predefined rules - "portrait" and "landscape", others generate rule: min-width: ${value} - so use it for example, like this: layout@768px="..."
    • Support for CSS variables: layout="margin:--value"

    The current rules can be fined in the source code in the src/template/layout.js file.

    I plan to add a global function, which will allow adding custom rules, spacing, and breakpoints (but not overwrite defaults).

    Mixing

    The layout system can be easily mixed with other styling methods, so it is possible to create UI components using layout and then add some "visual" styles using style helpers:

    import { define, html } from "hybrids";
    
    define({
      tag: "my-anchor",
      href: "",
      render: ({ href }) => html`
        <template layout="row">
          <a layout="grow" href="${href}"><slot></slot></a>
        </template>
      `.css`
        a { text-decoration: none; }
      `,
    };
    
    opened by smalluban 11
  • Benchmark

    Benchmark

    Hi,

    Here is the link to the results for a simple TodoMVC app : https://vogloblinsky.github.io/web-components-benchmark/

    Source code here : https://github.com/vogloblinsky/web-components-benchmark/tree/master/todomvc/hybrids

    opened by vogloblinsky 11
  • Update Store based on previous value

    Update Store based on previous value

    Hi!

    Regarding updates to a Store in quick succession, how do we safely update the value based on the previous state?

    I'm noticing that the value retrieved from the Store factory is not the immediately previous value since the setter is async. I wasn't able to find this in the docs, I might be missing it though.

    Thank you!

    store 
    opened by rubixibuc 10
  • Extension for FLIP / Miotion Animations in planning?

    Extension for FLIP / Miotion Animations in planning?

    Hello,

    we are still convinced of the project and use it diligently. Recently, we finally saw an interesting solution in a similar project "Lit". There animations are cleverly implemented at transitions of the data structure and the generated HTML. The integration effort, as seen there in the Playground example, is minimal. Is something like this also to be expected for HybridsJS or to be implemented directly?

    Best thanks in advance

    • https://github.com/lit/lit/tree/main/packages/labs/motion
    • https://greensock.com/docs/v3/Plugins/Flip/
    • https://lit.dev/playground/#sample=examples/motion-todos
    • https://aerotwist.com/blog/flip-your-animations/
    feature 
    opened by dobexx 3
  • Feature Request: Custom Elements Manifest Analyzer

    Feature Request: Custom Elements Manifest Analyzer

    Hi @smalluban, as mentioned in our Gitter chat, it would be great to extend Hybrids support to custom-elements-manifest with an analyzer plugin. https://github.com/open-wc/custom-elements-manifest

    The plugin generates a manifest JSON document which describes the API of custom elements for documentation, in-editor code hinting, and other tooling. In particular, I'm interested in supporting generative documentation and sandbox tooling for elements. It seems that custom-elements-manifest is the de-facto standard in this area and an analyzer plugin might greatly improve Hybrids' visibility.

    I am happy to help on this feature!

    feature 
    opened by auzmartist 1
Owner
hybrids
Extraordinary developer tools for creating Web Components
hybrids
Write JSX-driven components with functions, promises and generators.

Crank.js Write JSX-driven components with functions, promises and generators. Documentation is available at crank.js.org. Crank.js is in a beta phase,

null 2.5k Jan 1, 2023
A Web Component compiler for building fast, reusable UI components and static site generated Progressive Web Apps

Stencil: A Compiler for Web Components and PWAs npm init stencil Stencil is a simple compiler for generating Web Components and static site generated

Ionic 11.3k Jan 4, 2023
📃 Violet is an open-source discord bot with multiple functions written in TypeScript.

?? About Violet is a JavaScript Discord bot focused on utility and game functions. It have a temporary channels system, minecraft and league of legend

Violet 3 Nov 11, 2022
KioskBoard - A pure JavaScript library for using virtual keyboards.

KioskBoard - Virtual Keyboard A pure JavaScript library for using virtual keyboards. Current Version 2.0.0 * Documentation and Demo https://furcan.git

Furkan MT 177 Dec 29, 2022
📓 The UI component explorer. Develop, document, & test React, Vue, Angular, Web Components, Ember, Svelte & more!

Build bulletproof UI components faster Storybook is a development environment for UI components. It allows you to browse a component library, view the

Storybook 75.9k Jan 9, 2023
Fest - A reuseable UI components builder for the web

fest Fest is actually fast. A reuseable UI components builder for the web. Rerender components or parts of components as users interact with your app.

Bismark Yamoah 3 Jul 6, 2022
Write components once, run everywhere. Compiles to Vue, React, Solid, Angular, Svelte, and more.

Write components once, run everywhere. Compiles to: At a glance Mitosis is inspired by many modern frameworks. You'll see components look like React c

Builder.io 7.7k Jan 1, 2023
Create blazing fast multithreaded Web Apps

Welcome to neo.mjs! neo.mjs enables you to create scalable & high performant Apps using more than just one CPU, without the need to take care of a wor

neo.mjs 2.4k Dec 31, 2022
⚛ī¸ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.

Fast 3kB alternative to React with the same modern API. All the power of Virtual DOM components, without the overhead: Familiar React API & patterns:

Preact 33.6k Jan 8, 2023
jCore - JavaScript library for building UI components

JavaScript library for building UI components

iOnStage 11 Jan 21, 2022
Shoelace - A collection of professionally designed, every day UI components built on a framework-agnostic technology. đŸĨž

Shoelace A forward-thinking library of web components. Works with all frameworks ?? Works with CDNs ?? Fully customizable with CSS ?? Includes an offi

Shoelace 7.7k Dec 26, 2022
Knockout makes it easier to create rich, responsive UIs with JavaScript

Knockout Knockout is a JavaScript MVVM (a modern variant of MVC) library that makes it easier to create rich, desktop-like user interfaces with JavaSc

Knockout.js 10.3k Dec 31, 2022
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Supporting Vue.js Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome ba

vuejs 201.6k Jan 7, 2023
AngularJS - HTML enhanced for web apps!

AngularJS AngularJS lets you write client-side web applications as if you had a smarter browser. It lets you use good old HTML (or HAML, Jade/Pug and

Angular 59.3k Jan 1, 2023
Cybernetically enhanced web apps

What is Svelte? Svelte is a new way to build web applications. It's a compiler that takes your declarative components and converts them into efficient

Svelte 64.3k Dec 31, 2022
Ember.js - A JavaScript framework for creating ambitious web applications

Ember.js is a JavaScript framework that greatly reduces the time, effort and resources needed to build any web application. It is focused on making yo

Ember.js 22.4k Jan 4, 2023
Our original Web Component library.

Polymer ℹī¸ Note: This is the current stable version of the Polymer library. At Google I/O 2018 we announced a new Web Component base class, LitElement

Polymer Project 21.9k Jan 3, 2023
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

vue-next This is the repository for Vue 3.0. Quickstart Via CDN: <script src="https://unpkg.com/vue@next"></script> In-browser playground on Codepen S

vuejs 34.6k Jan 4, 2023
OpenUI5 lets you build enterprise-ready web applications, responsive to all devices, running on almost any browser of your choice.

OpenUI5. Build Once. Run on any device. What is it? OpenUI5 lets you build enterprise-ready web applications, responsive to all devices, running on al

SAP 2.7k Dec 31, 2022