Giter Site home page Giter Site logo

webreflection / hyperhtml Goto Github PK

View Code? Open in Web Editor NEW
3.1K 83.0 111.0 4.69 MB

A Fast & Light Virtual DOM Alternative

License: ISC License

JavaScript 33.72% HTML 66.00% CSS 0.28%
performance virtualdom alternative lightweight template-literals template vanilla js dom manipulation

hyperhtml's Introduction

hyper(HTML)

Maintainance Only

This module is great, works great, and served me greatly, but there's a pletora of modern, faster, more capable alternatives me, among many other OSS developers, offer so that if obvious bugs are proven to exist, these will be fixed, but there won't be a major release and I won't remove legacy support for stuff that, as previously mentioned, works just fine and it's battle-tested from IE to the latest Chrome.

Removing that legacy support brings pretty much nothing in terms of size too: this module is already smaller than 90% of alternatives out there, dropping 0.xK so that there's less code that, behind feature detection, is not even used in modern browsers, won't benefit anyone.

Thansk for your understanding and for not opening PRs which goal is to drop a check for legacy browsers ... these won't likely be merged ever as that'd be a major release update and I don't think anyone is interested in that.

📣 Community Announcement

Please ask questions in the dedicated discussions repository, to help the community around this project grow ♥


hyperHTML logo

A Fast & Light Virtual DOM Alternative.


donate Backers on Open Collective Sponsors on Open Collective WebReflection status

Coverage Status Build Status License: ISC Greenkeeper badge Blazing Fast


Following an overview of projects related, or inspired by, hyperHTML. For a deep comparison of current libraries, feel free to check this gist out.

µhtml

The latest, smallest, iteration of all best concept from this library since 2017, have been packaged in ~2.5K. If it's extreme minimalism and great DX that you are after, check uhtml out.

hypersimple

If you've just started with template literals based projects and you like components, or you'd like to understand what's hyperHTML capable of, give hypersimple a try 🎉

lighterhtml 💡

This little brother is "showing off" these days, claiming better performance and unprecedented ease of use.

GitHub Repository

Neverland 🌈🦄

If you like React hooks concept, don't miss this little wrap that adds 0.something overhead to the already lightweight hyperHTML, bringing in very similar concepts.

Blog Post

GitHub Repository

Haunted 🦇 🎃

If you also like React hooks mechanism and you'd like to combine these via hyperHTML or HyperHTMLElement, try haunted out!

Bundlers

You can require or import hyperHTML with any bundler and in different ways.

If requiring or importing from "hyperhtml" doesn't work, try requiring from "hyperhtml/cjs" for CommonJS friendly bundlers (WebPack), or "hyperhtml/esm" for ESM compatible bundlers (Rollup).

See HELPERS.md for a list of additional tools which can be helpful for building hyperHTML based web applications.


Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

Backers

Thank you to all our backers! 🙏 [Become a backer]

Contributors

This project exists thanks to all the people who contribute. [Contribute].


2.34 Highlights

  • the new ?boolean=${value} syntax from µhtml has landed in hyperHTML too. Feel free to read this long discussion to better understand why this syntax is necessary.

V2.5 Highlights

  • <self-closing /> tags for both custom elements and any other as well 🎉

V2 Highlights

Following most important changes in version 2:

  • fully rewritten, and consumable, as ES2015 Module
  • usable via CDN as bundled global hyperHTML variable
  • restructured in modules, utilities, helpers, and commented all over for simplified contribution
  • removed .escape and .adopt, either useless or unstable. hyperHTML.adopt will be implemented as module a part
  • added support for objects as style attribute, fully compatible with Preact implementation
  • improved performance in numerous ways
  • custom elements V0 and V1 are now fully, and properly, supported through document.importNode and/or regular cloneNode tested against common polyfills
  • back to 4.6K thanks to rollup and its ability to merge all the things together like it was already in V1

Documentation

A proper documentation full of examples can be found in viperhtml.js.org.

Basic Example

The easiest way to describe hyperHTML is through an example.

// this is hyperHTML
function tick(render) {
  render`
    <div>
      <h1>Hello, world!</h1>
      <h2>It is ${new Date().toLocaleTimeString()}.</h2>
    </div>
  `;
}
setInterval(tick, 1000,
  hyperHTML(document.getElementById('root'))
);

Features

  • Zero dependencies, no polyfills needed, and it fits in about 4.6KB (minified + brotli)
  • Uses directly native DOM, no virtual DOM involved
  • Designed for template literals, a templating feature built in to JS
  • Compatible with plain DOM elements and plain JS data structures
  • Also compatible with Babel transpiled output, hence suitable for every browser you can think of

Compatibility

IE9+ , iOS8+ , Android 4+ and every modern Mobile or Desktop Browser. You can verify directly through the following links:

Weakmap error on ie < 11

'@ungap/weakmap': object is not extensible

Babel freezes the template literals by spec but that causes problems with the weakmap polyfill. To fix this error add the fix explained on ungap/weakmap

HTML Syntax Highlight

If you are using Visual Studio Code you can install literally-html to highlight all literals handled by hyperHTML and others.

literally-html example

Prettier Templates

If you'd like to make your templates prettier than usual, don't miss this plugin: https://github.com/sgtpep/prettier-plugin-html-template-literals

Questions ?

Please ask anything you'd like to know in StackOverflow using the tag hyperhtml so that others can benefit from answers and examples.

hyper or lit ?

You can read more on this hyperHTML vs lit-html comparison.

installation?

npm install hyperhtml

If your bundler does not work with the following:

// ES6
import hyperHTML from 'hyperhtml';

// CJS
const hyperHTML = require('hyperhtml');

You can try any of these other options.

import hyperHTML from 'hyperhtml/esm';
// or
import {hyper, wire, bind, Component} from 'hyperhtml/esm';
// or
import hyperHTML from 'https://unpkg.com/hyperhtml?module';


const hyperHTML = require('hyperhtml/cjs').default;
// or
const {hyper, wire, bind, Component} = require('hyperhtml/cjs');

In alternative, there is a pre-bundled require("hyperhtml/umd") or via unpkg as UMD module.

hyperhtml's People

Contributors

albertosantini avatar asapach avatar benjamingr avatar bigopon avatar chesterhow avatar christianmurphy avatar coreyfarrell avatar esetnik avatar fgribreau avatar greenkeeper[bot] avatar guilhem-metroworks avatar ivancuric avatar janat08 avatar liming avatar lonniebiz avatar marcoscaceres avatar medikoo avatar mikesamuel avatar mixed avatar monkeywithacupcake avatar nuragic avatar pinguxx avatar rikuba avatar sourcegr avatar thibautre avatar webreflection avatar yuretz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hyperhtml's Issues

How to add the selected attribute to an option?

Correctly this doesn't work, getting Uncaught TypeError: updates[(i - 1)] is not a function:

            <select onchange="${events}">${
                state.items.map(item => hyperHTML.wire(item, ":option")`
                    <option value="${item.id}" 
                        ${state.selectedItem.id === item.id && "selected"}>${item.text}</option>
            `)}</select>

Also I successfully used a conditional statement in the literal templates, wrapped in a function, but it is ugly.

Any magic hint?

P.S.: auto label this issue as help wanted. :)

Animations?

How to best deal with animations, ex. post render?

wires in the middle of a list always re-render

Sorry for the awkward issue title, I wasn't entirely sure how to describe it best. Feel free to update if there's something more appropriate.

Anyways, for this I feel like the code speaks for itself. In the following example the hyperHTML.wire(model, ':ul') wire, will always re-render.

import hyperHTML from 'hyperhtml'

function render (model) {
  return hyperHTML.wire(model)`
    <div>
      <h1>Heading</h1>${hyperHTML.wire(model, ':ul')`
      <ul>${
        model.links.map((link) => hyperHTML.wire(link)`
        <li>
          <a href="${link.href}"> ${link.text} </a>
        </li>`)
      }</ul>`
      }<div>
        ${model.content}
      </div>
    </div>
  `
}

const model = {
  links: [
    {
      text: 'Link 1',
      href: '#link1'
    },
    {
      text: 'Link 2',
      href: '#link2'
    }
  ],
  content: 'Static content'
}

hyperHTML.bind(document.body)`${render(model)}`

setTimeout(render, 5000, model)

Note the above yields the expected results (no re-rendering of any node) if hyperHTML.wire(model, ':ul') is wrapped in a parent node, eg. <div>hyperHTML.wire(model, ':ul')...</div>.

IE11 id and class attributes swapped

I just ran into a bug in IE11 where my class and id attributes and their values are swapped.

This isn't a problem if I declare the class before the id.

const id = 'yolo-id'
const classname = 'yolo-class'

const el = hyperHTML.wire()`<div id="${id}" class="${classname}">Yolo</div>`
const el2 = hyperHTML.wire()`<div class="${classname}" id="${id}">Yolo2</div>`

document.body.appendChild(el)
document.body.appendChild(el2)

This results in:

<div class="yolo-id" id="yolo-class">Yolo</div> <!-- Wrong! -->
<div class="yolo-class" id="yolo-id">Yolo2</div>

Thanks!

infinite lists

The current sameList related logic works well for finite lists but re-append same nodes when the length changes.

While it's not too relevant to cover less items than before, the infinite scrolling might be quite a use case.

TODO

  • sameList should return different values if the operation is substitute, same, or similar, where latter means that up to N live nodes lists are same and the new fragment should contain less nodes
  • update the content accordingly, dropping nodes in the lower range, adding nodes if needed after
  • profit

Potential issue with img srcset

Sorry, I've not had time to fully investigate this wanted to file it before I forget:

const srcset = ["foo.png 200w"]; 
function toImage(srcset, alt) {
  return hyperHTML.wire()
  `<img
      role="button"
      alt="${alt}"
      srcset="${srcset.join(" ")}"
      width="195" height="80"/>`;
}

And then attaching that to a section, Chrome says:

screenshot 2017-03-21 12 41 24

conditional blocks

trying to build a markup like this (which is basic tabs widget), all the code generated inside the ternary operator is rendered as text string and not as html:

this.html`<section>
      <h1>Title</h1>
      <ul>
        <li onclick="${this._switch.bind(this)}" data-index="0">Modules</li>
        <li onclick="${this._switch.bind(this)}" data-index="1">Network</li>
        <li onclick="${this._switch.bind(this)}" data-index="2">Follow us</li>
      </ul>
      ${state.active === 0 ? `<div>section 1</div>
        <ul>${
          state.links.applications.map(e => `<li><a href="">${e.name}</a></li>`)
        }</ul>` : null}
      ${state.active === 1 ? `<div>section 2</div>` : null}
      ${state.active === 2 ? `<div>section 3</div>` : null}
    </section>`; 

the elements rendered are exactly what I expect but not the output :-/
This is a beahavior related to the template strings functionality itself or more an HyperHTML issue (in this case I'm sure I'm doing something wrong 😃 )
thanks

Dealing with eventing

If I'm understanding correctly (from the forms example), the eventing depends on stringification ... that means that it's super fragile and things like closures are, obviously, forgotten. It seems like the only way to slightly hack around this is to put functions on the Window object so:

const foo = 123;
const evt = {
  onchange(ev) {
    const onNo = "" + foo; // Aside: double quotes breaks everything
}}

Doesn't really work, because it gets transpiled to:

function onchange(ev){
  const onNo = "" + foo; // double quotes break everything
}

Because it's now just declaring a function, and not calling it.

      <select onchange="${evt.onchange}">
        ${makeOptions(someData)}
      </select>

Would become:

      <select onchange="
function onchange(ev){
    const onNo = "" + foo; // double quotes break everything, foo reference error!
}">
         <option>
      </select>

Maybe there is no way around this?

DOM lifecycle events

At moment hyperHTML creates, updates and removes DOM nodes internally without providing any event hook. @WebReflection would you accept pull requests PR where the events above will be dispatched as custom events?
For example

// updates
function update() {
    for (var
      i = 1,
      length = arguments.length,
      updates = this[EXPANDO].u;
      i < length; i++
    ) {
      updates[i - 1](arguments[i]);
    }
    this.dispatchEvent(new Event('updated'))
    return this;
}

// removes
node.removeChild(child);
walk(child, function(subChild) {
  if (subChild.nodeType === Node.ELEMENT_NODE) {
    subChild.dispatchEvent(new Event('removed'))
  }
})

Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.

Not intentionally, I created a "wrong" html template, render_ko and I got the following exception:

script.js:22 Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
    at appendNodes (unpkg.com/[email protected]:284)
    at updateViaArray (unpkg.com/[email protected]:404)
    at Array.any (unpkg.com/[email protected]:154)
    at HTMLUnknownElement.update (unpkg.com/[email protected]:463)
    at HTMLUnknownElement.upgrade (unpkg.com/[email protected]:487)
    at HTMLUnknownElement.hyperHTML (unpkg.com/[email protected]:18)
    at script.js:22

With the snippet:

const state = Introspected({
  toasts: [
    { date: new Date(), message: "now" },
    { date: new Date(), message: "now" }
  ]
});

const render_ko = hyperHTML.bind(document.querySelector("root"));
const render_ok = hyperHTML.bind(document.querySelector("root2"));

render_ok`
    <table>
        <tbody>${
            state.toasts.map(toast => hyperHTML.wire(toast, ":tr")`<tr>
                <td> ${toast.date} </td>
                <td> ${toast.message} </td>
            </tr>`
        )}</tbody>
    </table>
`;

render_ko`
    <table>
        <tbody>
            <tr>${
                state.toasts.map(toast => hyperHTML.wire(toast, ":tr")`
                    <td> ${toast.date} </td>
                    <td> ${toast.message} </td>
                `)}</tr>
        </tbody>
    </table>
`;

Live demo: https://plnkr.co/edit/XLjGu9Ag2WldQ0Uf7y93?p=preview

Web Components slots implementation

Would it be possible to enhance a hyperHTML template in runtime using for example Web Components slots?
To be clear consider this example:

// my-component.js
function create(root, state) {
  const render = hyperHTML.bind(root)
  // initial render
  template(render, state) 
  return {
    update(state) { 
      template(render, state) 
    }
  }
}

function template(render, state) {
  render`
    <div>
      <slot from="header"></slot>
      <h2>It is me ${state.name}.</h2>
      <slot from="footer"></slot>
    </div>
  `;
}
// app.js
const app = create(document.querySelector('.app'), { name: 'Gian', role: 'dude', age: 84})

setTimeout(() => {
  app.update({ name: 'John', role: 'drugo', age: 77})
}, 2000)
<!-- index.html -->
<div class='app'>
  <slot to='header'>
    <h1>My age is: ${ data.age }</h1>
  </slot>
  <slot to='footer'>
    <h3>My role is: ${ data.role }</h3>
  </slot>
</div>

I am trying to implement hyperHTML in riot.js but I am not able to cover this behavior in a clean way so any hint would be really appreciated.
Thank you

Give an id to wire instead of a type?

What happens if I'm using the same data object for 2 differents div? I understand I can use wire(obj, ':div') new syntax, but if my 2 templates are divs, is that gonna be useful?

Universal hyperHTML / hyperHTML-be ?

I don't see why hyperHTML wouldn't be usable to render on the server side templates of HTML with the following features:

  • automatically html escaped text content
  • automatically sanitized attributes
  • smart inline event handlers assignment (destructured/delegated)
  • automatic raw HTML injection

Basically, a 1:1 representation as string of what hyperHTML would produce at runtime on the client.

Composability

One of the really useful things about React is that components and elements can be created, passed around and composed very easily. Instead of thinking in terms of templates of html, developers are thinking in terms of components, like a custom <Button> or a <List> component that takes other (bare) child components and wraps each of them in an <li> tag.

<List>
  <div>an item</div>
  <div>another item</div>
</List>

producing

<ul>
  <li><div>an item</div></li>
  <li><div>another item</div></li>
</ul>

Does hyperHTML have this capability? Can I create a larger component out of smaller ones? Or would you say that that is a job for WebComponents or something else to be built on top of hyperHTML?

Architecture around "partial static rendering"

This likely isn't the responsibility of hyperHTML/viperHTML in any way, but rather should be some sort of wrapper function or build step, but I've been spending the last few days on trying to wrap my head around the idea of doing partial static rendering on the server, and serving optimized render functions to the client.

My example is a simple card or similar, that renders an element with 3 sources of data: styles, i10n and state.

render`
    <dl class="${styles.wrapper}" data-id="${state.id}">
        <dt class="${styles.field}"> ${i10n.firstName} </dt>
        <dd class="${styles.value}"> ${state.firstName} </dd>

        <dt class="${styles.field}"> ${i10n.emailAddress} </dt>
        <dd class="${styles.value}"> ${state.emailAddress} </dd>
    </dl>
`;

The only piece of data that is dynamic here and would change is state, everything else is basically static and could be determined at build time. We have various different reasons for wanting to keep our actual content separate from template code, ranging from organizational and translation purposes, and ability to substitute data sources or a CMS in the future, and styles would be a list of class names generated by one of the various "CSS in JS" solutions out there, such as CSS Modules. It doesn't seem to make much sense dynamically calculating all of this on the client when we render state and having to send down extra data objects that shouldn't ever need to be used if you are doing server side rendering with viperHTML. Ideally it would be possible to supply our styles and content (with or without state) to the render function, and have the above (or something similar) generate a new render function such as:

render`
    <dl class="css-1cds5y" data-id="${state.id}">
        <dt class="css-jj86g3">First Name</dt>
        <dd class="css-h689hgd"> ${state.firstName} </dd>

        <dt class="css-jj86g3">Email</dt>
        <dd class="css-h689hgd"> ${state.emailAddress} </dd>
    </dl>
`;

Not really an "issue", just some food for thought, and am just curious if you have any thoughts yourself around these issues, or ideas on how to approach it.

Disappearing lists

Trying to track down the cause of this issue, and identify if it's a bug in hyperHTML or an issue with my implementation.

Given the below example I expect a list of buttons to render; initially as:

<button>Hello World!</button>

Then update as:

<button>Hello World!</button><button>Hello Friend!</button>

Instead of the expected result, the elements are removed entirely from the DOM after the initial update.

const hyperHTML = require('hyperhtml')

function Button () {
  const html = hyperHTML.wire()
  return function update (data) {
    return html`
      <button>
        ${data.text}
      </button>
    `
  }
}

function List (component) {
  const html = hyperHTML.wire()
  const items = []
  return function update (list) {
    return html`${
      list.map((item, i) =>
        (items[i] || (items[i] = component()))({ text: item }))
    }`
  }
}

const buttonList = List(Button)

const app = hyperHTML.bind(document.body.appendChild(document.createElement('div')))

app`${buttonList(['Hello World!'])}`

setTimeout(() => buttonList(['Hello World!', 'Hello Friend!']), 1000)

sometimes text, sometimes nodes

I have the following situation.

const render = hyperHTML.bind(aHTMLTableElem);
const items = getItems(); // can be an empty list
renderer `${items.length ? toTR(items) : hyperHTML.wire()`<tr><td colspan="2">None</td></tr>`}
      `;
// Ignore that this will result in an array of arrays; I flatten those... 
function toTR(item) {
  return hyperHTML.wire(item)
  `
    <tr>
      <td>...test...</td>
      <td>...test...</td>
    </tr>
  `;
}

However, I can't get it to work :( The result is "[object HTMLTableRowElement]".

What's the right pattern to use here?

It seems hyperHTML updating nodes unnecessarily?

I have been reviewing multiple UI libraries for my next project. I am very impressed with hyperHTML as it is leveraging already available platform features like Template literals. I am playing around now and I have this quick question. My apologies, if this is a dumb question (I am learning).

In the article test here - https://webreflection.github.io/hyperHTML/test/article.html

  1. Go to this article.html page
  2. In the console, call the update() method

update( hyperHTML.bind(articleElement), { title: 'True story', magic: true, paragraphs: [ {title: 'touching'}, {title: 'incredible'}, {title: 'doge'} ] } );

  1. Notice that h3 tag (which has title) is getting updated unnecessarily. state is not changed from the initial value. Then, why h3 tag is always getting updated.

Currently h3 tag looks like this
<h3>${state.title}</h3>

If I change this to
<h3>some text, ${state.title}</h3>
then title is not getting updated unnecessarily

Honestly, I don't know if this is issue or not. Just trying to understand all these better. Thank you so much for this awesome library. Can't wait for the "adopt" and refactoring release.

Uncaught TypeError: Invalid value used as weak map key

Just a note, not an issue.

This example raises the error in the issue title:

const items = [1, 2, 3];
const render = hyperHTML.bind(document.body);
render`
    <ul>${items.map(item => hyperHTML.wire(item)`
        <li>${item}</li>
    `)}</ul>
`;

This is due to items is not an array of objects, as in the examples of the docs.

Fixed with hyperHTML.wire({item}), note the curly braces.
Or removing the .wire part like

<ul>${items.map(item => `<li>${item}</li>`...

Do you think it is worthy adding this little note in the docs?

Support IE9+

Just a reminder for whoever will ask.

IE9+ for both Mobile and Desktop landed in version 0.8

setVirtualContent fails with the following example

<!DOCTYPE html>
<html lang="en">

<head>
	<title>HyperHTML</title>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<script src="hyperhtml.js"></script>
</head>

<body>
</body>
<script>
var render = hyperHTML.bind(document.body);
var node1 = document.createElement('p');
var node2 = document.createElement('p');

node1.innerText = 'node1';
node2.innerText = 'node2';

render `<h1>Divs</h1>${[node1, node2]}<h1>end</h1>`;
//                               |----> This node wont be rendered
render `<h1>Divs</h1>${[node1, node1]}<h1>end</h1>`;
</script>

</html>

This results in the following. notice the missing node1

<body>
  <h1>Divs</h1>
    <p>node1</p>
    <!--_hyper_html: 1579249636;-->
  <h1>end</h1>
</body></html>

hyperHTML.wire(weakReference)

It looks like hyperHTML.wire() constantly need a collection companion to work at its best.

I wonder if using a WeakMap to relate renders to data would make it deadly simple to update lists of things.

TODO

Use an internal WeakMap if .wire(...) passes an object, falling back to a dirty expando property in case WeakMap is not available.

This might be just the last extra bit needed to make hyperHTML super friendly 🎉

Events problem

onclick="${function withName() { console.log('whatev'); }}"

produces: SyntaxError: expected expression, got '}'

and markup:

<div onclick="function withName(){console.log(" blah")}"="" >
  </div>

Not sure what am I doing wrong.

List items

Nice framework. I've used straight template literals a lot. I love them. I've also used Yo-yo/Choo, Hyperx with Virtual Dom and Hyperapp. I'm not quite sure how to handle rendering repeating items from an array. I tried creating an example of composable parts, but can't seem to get the list items to render. They come back as strings, not what I was expecting. With normal template literals that would work. What am I doing wrong?

const render = hyperHTML.bind(document.body)
const wire = hyperHTML.wire()

const fruits = ['Apples', 'Oranges', 'Bananas']

const header = render => render`
  <nav>
    <h1>The Title</h1>
  </nav>`;

const footer = render => render`
  <footer>
    <h3>The footer</h13>
  </footer>`

const main = render => render`
  <section>
    <h2>Fruits</h2>
    <ul>
      ${fruits.map(fruit => `<li>${fruit}</li>`).join('')}
    </ul>
  </section>`

const update = render => render`${[header(wire), main(wire), footer(wire)]}`
update(render)

Example of defaults in an input text box

I have a text box which is populated with the value of the object for the user and I want it to update the object with each character that is entered. The problem is that if I type in the middle of the text, as I type it moves the caret to the end of the text box because it is updating the value. I believe in React they have a DefaultValue option. What is a good way to handle this in HyperHTML?

<input oninput="${events.updated.bind(info)}" value="${info.description}">

Example: https://plnkr.co/edit/jGTLn98Ggv76oqd69OTq?p=preview

Having some questions

Hey, first 👏 very nice work!

I have some questions about this project :

  • I did play with bel + morphdom (morphdom author also says DOM is not slow :) and I agree) the drawback being each call to render actually render the view, (re-)creating a new DOM, then I'm using morphdom to patch the actual DOM. It's faster than some older practice and free the user to manually update the view on state change or writing data binding, but still not optimum as you pointed. But, is your way too much in disfavour of memory usage, by keeping a ref of the statics on the node?
  • Would it possible to add some hooks / callback / options to prevent some node to be updated? Use case: using a node as a container for other view, closing / switching / ... I very like this possibility from morphdom.
  • Does it play well with svg?

TypeError undefined is not an object

I'm not sure what to make of this, since I can't replicate it on any device.

We use Bugsnag to report javascript errors on our site, and it's been reporting a TypeError here on L787

It seems to be happening in both Chrome & Safari, desktop & mobile.

Here's the raw error:

TypeError undefined is not an object (evaluating 'e[t[n++]][t[n]]') 
    webpack:///node_modules/hyperhtml/hyperhtml.js:793 
    webpack:///node_modules/hyperhtml/hyperhtml.js:883 createUpdates
    webpack:///node_modules/hyperhtml/hyperhtml.js:931 upgrade
    webpack:///node_modules/hyperhtml/hyperhtml.js:20 hyperHTML

e[t[n++]][t[n]] is parentNode[path[i++]][path[i]] minified.

Not sure what to do here as I can't replicate this, just wondering if you had any thoughts.

Thanks!

DOM diffing in hyperHTML

Not sure if I understand the library correctly. Does it do a DOM diffing including contents? I don't see that happening with the article example. On calling the update function multiple times with the same state, I see that DOM nodes that rely on state are getting re-rendered even though there is no change in state.

Needless re-rendering without update dependant on white-space

I've read the docs with it mentioning that any node with whitespace around it is treated as raw-text and parsed as such, which is perfectly fine, but it also looks like this whitespace controls whether or not a node should be re-rendered, regardless of it there are actually any updates or not.

I've not yet done any performance tests to see what the actual impact is, but in the following example name and each user node in the <ul> is re-rendered, regardless of whether there has been any updates or not.

const update = (render, {name, users = []}) => render`
    <h1>${name}</h1>
    
    <ul>${users.map(user => hyperHTML.wire(user)`
        <li>${user.login}</li>
    `)}</ul>

    <button onclick="${emit(ACTIONS.NAME_CHANGED, 'Harry')}">Harry</button>
    <button onclick="${emit(ACTIONS.NAME_CHANGED, 'Sally')}">Sally</button>
    
    <br />
    
    <button class="button" onclick="${loadGithubFollowers}">Load Followers</button>
`;

Where as this works as expected:

const update = (render, {name, users = []}) => render`
    <h1>
        ${name}
    </h1>
    
    <ul>${users.map(user => hyperHTML.wire(user)`
        <li>
            ${user.login}
        </li>
    `)}</ul>

    <button onclick="${emit(ACTIONS.NAME_CHANGED, 'Harry')}">Harry</button>
    <button onclick="${emit(ACTIONS.NAME_CHANGED, 'Sally')}">Sally</button>
    
    <br />
    
    <button class="button" onclick="${loadGithubFollowers('ryardley')}">Load Followers</button>
`;

Surely this causes an unnecessary performance overhead and could potential cause issues when trying to insert DOM nodes or fragments?

I should note that I found this by noticing chrome highlighting DOM changes in devTools. The behaviour seems rather counter intuitive and something I hesitate to suggest using this with our team with having to outline when and where to wrap variables with a space or not, and with no easy way that comes to mind to write a linter that detects when and when it is not appropriate.

Guidance on boolean attributes

There are attributes in HTML, like "hidden", which don't accept an attribute value. E.g.,:

<div hidden>

It's not clear to me how to deal with those - as they sometimes need to be included conditionally (cheap hack is to use style="" or a CSS class - but not as nice).

So... wondering if you have any idea about how those can be included ... or if they might need to be a special case.

Ability to skip subtree render

There is a skip() method in the Incremental DOM, which tell the renderer to skip node children render. It's good for a "poor man" web component w/o shadow DOM, slots etc... So basically you move responsibility of render node's children to the node itself.

Not sure how good this idea in case of hyperHTML or how easy (if even possible) to implement it, but lets imagine construction like <div>Hello <x-bar value="world"><skip></x-bar></div>. <skip> (or some other marker) should signal renderer to ignore node's children to be touched during update.

May be the whole idea is wrong in the first place or there is better approach to the problem above.

Noob treeview example

I'm learning HyperHTML and the new javascript it is using so I'm sure I'm missing some core concepts. This might also be a good example to have on a recursive template. I have a tree list appearing based on the data but I am having trouble making it interactive. I probably should use hyperHTML.wire instead of return in the renderNode function but I might also have it completely architected wrong. Any assistance would be appreciated.
Here is a plnkr of what I have so far: https://plnkr.co/edit/RaPWEBwvdlLS0PVBmSiV?p=preview
Here is a fork of your code: https://github.com/BentleyDavis/hyperHTML/blob/master/test/tree.html

Uncaught TypeError: Cannot use 'in' operator to search for '_hyper_html: ' in null

([email protected]:15)

For a typo, I passed a null to the bind method and I got the error in the title when the template was rendered.

        const el = document.querySelector("foos"); // the tag is foo
        const render = hyperHTML.bind(el);

Does it make sense checking at least for a truthy value, not, of course, checking the existence, for not doubling the query call?

I don't know if a default binding to the document.body may help.

Standalone Diff Module

Hi.
How about splitting out DOM morphing module from HyperHTML that will have interface compatible with nanomorph (used in choo) and morphdom (used in yo-yo) so:

  1. We will be able to compare morphing modules between each other.
  2. We will be able to swap one for another easily if needed in higher level frameworks.

Thanks for your work,
Ilya.

Rendering <style> elements

Hi Andrea, thanks for another great library.

There's an annoying problem with rendering <style> elements that results in an error. Here's a simple use-case:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="hyperHTML.js"></script>
    <style id="style"></style>
</head>
<body>
    <div class="content"></div>
    <div class="content"></div>
    <script src="main.js"></script>
</body>
</html>

and main.js

function tick(render) {
  render`.content:before {
    content: '${new Date().toLocaleTimeString()}';
  }`;
}
setInterval(tick, 1000,
  hyperHTML.bind(document.getElementById('style'))
);

This results in an error and the output showing

<!--_hyperHTML247232086-->
<!--_hyperHTML247232086-->

Instead of the expected

15:23:31
15:23:31

The cause is that whatever is inside <style> tag is automatically treated as a text node and so, the comment tag is instead treated as part of that text node instead of a separate comment. A possible solution is to create the nodes inside a temporary <div> and then transfer them to the <style> element.

However, to be honest I think this is a very rare use-case and perhaps isn't worth fixing. Still, I wanted to report the issue.

Cheers.

Reversed incremental updates

Right now the logic to boost up incremental updates takes care only of pushed nodes.

[1, 2, 3, 4, 5, 6]
// render [1, 2, 3, 4, 9, 10, 11]
//   => reduce to [1, 2, 3, 4]
//   => append [9, 10, 11]
// render result
[1, 2, 3, 4, 9, 10, 11]

However, taking Twitter as example, the same logic should be applied for prepended nodes.

[1, 2, 3, 4, 5, 6]
// render [0, 1, 3, 4, 5, 6]
//   => reduce to [3, 4, 5, 6]
//   => prepend [0, 1]
// render result
[0, 1, 3, 4, 5, 6]

The function in charge of finding the difference should not give up if the first item is different and it should check if looping backward there is a chance to find a reversed update prepending.

Example:

if (i === 0 && a[0] !== b[0]) {
  return tryToUpdatePrependingOrSubstituteAll(a, b);
} else {
  // ...
}

I'm pretty sure a good algo will make hyperHTML nearly as heavy as 2KB but I feel like this is an important feature to cover all kind of lists updates.

Worth trying to sneak in this smarter algo.

Potential redundant request with img

@WebReflection, sorry, I've not had a chance to investigate this in detail, but it seems what setting img.src via hyperHTML can result in unintended network requests because of the generated ID attribute value.

See 404s here:
https://travis-ci.org/w3c/respec/jobs/243534752

Where, there are things like:

/%3C!--_hyper_html:%20836557996;--%3E

I think they are coming from (simplified test case):

function toLogo(obj) {
  const a = document.createElement("a");
  a.href = obj.href ? obj.href : "";
  a.classList.add("logo");
  return hyperHTML.bind(a)`
    <span id="${obj.id}">
      <img
        alt="${obj.alt}"
        src="${obj.src}"
        width="${obj.width}"
        height="${obj.height}">
    </span>`;
}

const  a = toLogo({ src: "test",  alt: "alt test", width: "123", height: "123", id: "foo"});
document.body.appendChild(a); 

I've not been able to reproduce by pasting into developer console, but can see the GET request initiated in by the image in the network tab in Safari.

See, for example:
https://w3c.github.io/browser-payment-api/

Positioning gotcha

While improving the incremental demo page I've spot a weird issue I need to track down.

The following works without problems:

function update() {
  render`
    <form><input type="submit">${
      '<input type="checkbox">'
    }</form>
  `
}

While the following fails like a charm.

function update() {
  render`
    <form>${
      '<input type="checkbox">'
    }<input type="submit"></form>
  `
}

Fix it!

Use of hyperHTML inside property getters

For a few months, I have been using string literals inside getters in property definitions.

function Templator(){ Object.defineProperties(this,{ "templates":{ enumerable: true, configurable: true, get: function(){ let colors=["red","skyblue", "green", "yellow"]; return { butt:(colors)=>


${colors.map((c, i)=><input type="button" value="${c.toUpperCase()}" id="${1+c.toString()}" onclick="jannew.templates.start(64,${i+1} )" style="${'min-width: 200px; min-height: 70px; background: ' + c.toString() + '; padding: 12px; margin:0 20px 20px 20px;'}">).join(" ")}
, layout:(jindex, index, array)=>hyperHTML.wire()
  • ${jindex}: ${index}
  • , updategrid:(array, jindex)=>hyperHTML.bind(document.body)
      ${array.map(this.templates.layout)}
    ${this.templates.butt(colors)}`,
    start:(number, jindex)=>{
    let array=[];
    for(let j=0;j<number;j++){array[j]=jindex;}
    this.templates.updategrid(array, jindex);
    },

            };
        },
    },
    

    });

    this.templates.start(64,1);

    }

    let jannew =new Templator();`

    the above, without the hyperHTML should work. if you remove the wire, in the layout, and look at the demo page--- you will see that clicking the buttons on the bottom still "changes" the page. But I get the text blowout, I had mentioned before, without using a an insert.

    here are three test pages:

    first works fine: Its hyperHTML in the wild. Just out in a pen, plain js.

    http://codepen.io/jfrazz/pen/JWaBdd

    the second pen, uses the wire and we only see [object HTMLElement],
    http://codepen.io/jfrazz/pen/73ec6ec23a1910291c44a07d8d8286cd
    the third
    I remove the wire, which makes it even more clear that the bind is working, the content is being changed, but the render is an issue...
    http://codepen.io/jfrazz/pen/LWJgPM/

    I could just be miss-using string literals or hyperHTML or both ... I was just hoping i could implement the updating advantages of your work in a larger dashboard project. I do not believe I have done that well in the above examples.

    Partial attribute results in off-by-one application of state

    Hi. I’m playing with hyperHTML and reading the docs too quickly for my own good. :D So I ended up trying to write a partial attribute.

    This might be a wontfix since the docs spells out that partial attributes are forbidden (in Getting Started); but in addition to not working, a partial attribute messes with state application to other parts of the template. Simple test case:

    <main></main>
    <script src="https://unpkg.com/[email protected]/min.js"></script>
    <script>
      const update = (render, state) => {
        render`
        <div class="Teaser ${state.test ? 'Teaser--test' : ''}">
          <h2 class="Teaser-title">
            <a href="${state.url}">${state.title}</a>
          </h2>
          <div class="Teaser-text">${state.text}</div>
        </div>
        `;
      }
      update(hyperHTML.bind(document.querySelector('main')), {
        title: 'This is the title',
        text: '<p>And the main description</p>',
        url: '/this/is/the/url',
        test: Math.random() > 0.5
      })
    </script>

    Renders as:

    <div class="Teaser <!--_hyper_html: -718251426;-->">
      <h2 class="Teaser-title">
        <a href="">/this/is/the/url</a>
      </h2>
      <div class="Teaser-text">This is the title</div>
    </div>

    This is the related error (in Firefox’s console):

    TypeError: updates[(i - 1)] is not a function
    	update https://unpkg.com/[email protected]/hyperhtml.js:599:7
    	upgrade https://unpkg.com/[email protected]/hyperhtml.js:623:31
    	hyperHTML https://unpkg.com/[email protected]/hyperhtml.js:18:15
    	hyperHTML self-hosted:951:17
    	update http://test-hyperhtml.dev:8080/:5:5
    	<anonymous> http://test-hyperhtml.dev:8080/:14:3
    

    If possible, it would be a better result to have the partial attribute fail but not the next content.

    Binding multiple elements

    I will be using HyperHTML to create widgets on a web page within a content management system. I believe I will need to hyperHTML.bind multiple HTML elements. I will be adjusting you code to allow this but I wanted to see if you had any preferences on how to do it.

    //Find all the statements in the document
    var statements = document.getElementsByTagName('statement');
    
    //Render the statements
    for (let s of statements) {
        var render = hyperHTML.bind(s);
        update(render, dict, mainId, events);
    }
    

    https://github.com/BentleyDavis/ReasonRoot/tree/1fbec6a5327decdf78dedba80c2c63ed9625e0f2

    known issues with shadow-dom?

    hi, I'm trying to use HyperHTML with vanilla custom elements using shadow dom, so far almost everything works fine except an issue when trying to re-render an attribute on tag on webcomponent property change... it works on Firefox (which is using the shadow dom polyfill) but does not work on Chrome (with the native implementation).

    I will give you an example repo ASAP, but at the moment I would like to known if you are aware of any issue with shadow-dom or web components in general.

    thanks

    Null attribute causes errors

    Im getting an error if i pass a null attribute

    Uncaught TypeError: Cannot read property 'nodeType' of null
    at populateNode (test-bundle.js:643)
    at Array.any (test-bundle.js:506)

    in a template like this

    <div>
        <span>${data.placeholder}</span>
        more text
    </div>
    

    where placeholder can be null, i can do this

    <div>
        <span>${data.placeholder || ''}</span>
        more text
    </div>
    

    i wonder if you can update hyperhtml where if the value its null or undefined the textcontent will be ''

    maybe add an extra check for nulls/undefined in this function
    https://github.com/WebReflection/hyperHTML/blob/master/hyperhtml.js#L130

    Thanks

    VS Code with js check enabled

    This is not an issue, but a hint how to use hyperHTML (and Introspected) with Visual Studio Code editor.

    If you use the recent version of VS Code editor (rel. 0.12.1 or higher), you are able to use the feature Type checking in JavaScript.

    To avoid message errors like

    message: 'Cannot find name 'hyperHTML'.'
    message: 'Cannot find name 'Introspected'.'

    I suggest to add the following lines to global.d.ts, enabling intellisense and setting off the problems:

    declare namespace hyperHTML {
        function bind(obj: any);
        function wire(obj?: any, id?: String);
    };
    
    declare function Introspected (objectOrArray: any, callback?: Function);
    
    declare namespace Introspected {
        function observe(objectOrArray: any, callback: Function);
        function pathValue(objectOrArray: any, path: String);
    }
    

    Recommend Projects

    • React photo React

      A declarative, efficient, and flexible JavaScript library for building user interfaces.

    • Vue.js photo Vue.js

      🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

    • Typescript photo Typescript

      TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

    • TensorFlow photo TensorFlow

      An Open Source Machine Learning Framework for Everyone

    • Django photo Django

      The Web framework for perfectionists with deadlines.

    • D3 photo D3

      Bring data to life with SVG, Canvas and HTML. 📊📈🎉

    Recommend Topics

    • javascript

      JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

    • web

      Some thing interesting about web. New door for the world.

    • server

      A server is a program made to process requests and deliver data to clients.

    • Machine learning

      Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

    • Game

      Some thing interesting about game, make everyone happy.

    Recommend Org

    • Facebook photo Facebook

      We are working to build community through open source technology. NB: members must have two-factor auth.

    • Microsoft photo Microsoft

      Open source projects and samples from Microsoft.

    • Google photo Google

      Google ❤️ Open Source for everyone.

    • D3 photo D3

      Data-Driven Documents codes.