WebReflection / hyperHTML

A Fast & Light Virtual DOM Alternative
ISC License
3.07k stars 112 forks source link

Road to V2 #107

Closed WebReflection closed 6 years ago

WebReflection commented 7 years ago

While latest API and logic is mature and battle-tested enough, there are few things that worked well but others that didn't shine in V1.

Following a list of things I'd like to improve for the next release:

This will be a big effort in terms of code refactoring, it will partially change/break current adoptability (an experimental feature not many are using so far anyway), but it will not change the API as we know it.

However, adopt will be not available by default (breaking change) and because of that I think V2 makes sense (as also previously suggested).


If you have anything in particular you'd like to see in a V2 please add a comment to this future-plan issue, thank you.

asapach commented 7 years ago

How about a build tool/optimizer (or babel plugin), along the lines of yo-yoify? Just an idea: is there some performance to be gained by modifying the AST to make run-time faster?

WebReflection commented 7 years ago

Actually I've just read what is yo-yoify about, basically it's exactly what huperHTML is out of the box.

So no, nothing like that is needed. It's already how the core works.

asapach commented 7 years ago

OK, another idea: is there a way to improve developer experience when using wire() with the same object, especially in a loop (https://viperhtml.js.org/hyperhtml/documentation/#api-1-2)? Should I always be using an id when iterating over elements? What if I mistakenly use the same id twice? What if I write one loop (without adding an id) and then my teammate creates another loop without realizing that we'll run into performance problems? Is it possible to catch these scenarios? Or better yet re-design the api to avoid them altogether.

joshgillies commented 7 years ago

tbh - I feel like wire() has been covered extensively, and it's actually not that difficult to understand once you've actually used hyperHTML for more than a day.

The api in my mind is sound, though potentially introducing environment based messaging could help direct users away from non-performant patterns eg.

if (env === 'development') {
  if (doingSomethingWeirdWithWire) {
    console.warn("You're potentially doing something weird with wire, have you considered...")
  }
}

This kind of messaging can be easily removed by uglify for production builds, but then again how much messaging is too much when there's already ample documentation covering wire().


Something I'd like to raise again for consideration is lifecycle hooks for hyper.Component. The quintessential example in my mind is:

class Timer extends hyper.Component {
  get defaultState() {
    return {
      secondsElapsed: 0
    }
  }
  tick () {
    this.setState({
      secondsElapsed: this.state.secondsElapsed + 1
    })
  }
  connectedCallback () {
    this.interval = setInterval(() => this.tick(), 1000)
  }
  disconnectedCallback () {
    clearInterval(this.interval)
  }
  render () {
    return this.html`
      <div>Seconds Elapsed: ${this.state.secondsElapsed}</div>
    `
  }
}

I wonder is there an approach where we can determine whether a component has been rendered and inserted into the DOM without having to resort to MutationObserver + polyfill.

Looking over the likes of patrick-steele-idem/morphdom you can see they have hooks for onBeforeNodeAdded, onNodeAdded, onBeforeNodeDiscarded, and onNodeDiscarded. Which they manage to implement without MutationObserver. I'd have to look closer at how they manage to pull it off, but if similar could be applied to hyper.Component I think that would help to bring hyper.Component inline with the expectations of developers coming from the React world.

marcoscaceres commented 7 years ago

Um, I still like AMD support (I know... but I'm using it with a legacy project)... otherwise, I need to put hyperHTML into the global scope, which makes me a bit sad.

WebReflection commented 7 years ago

@joshgillies

I feel like wire() has been covered extensively, and it's actually not that difficult to understand once you've actually used hyperHTML for more than a day.

Thank you.

is there an approach where we can determine whether a component has been rendered

as soon as you invoke this.render() the component is resolved and rendered

and inserted into the DOM

I could easily invoke a "connected" the first time it's rendered, but I cannot easily grant a "disconnected" without bloating the code and slowing it down per each splice operation.

On top of this, hyper components are compatible with the DOM right away. The only thing the "bind/render" does here is to invoke comp.render() if you pass a component instead of a node but technically you can have hyperHTML components that work out of the box on any other project simply explicitly invoking such .render() method.

Having hooks that work only when components are rendered inside hyperHTML is a functionality lock-in and I happily leave lit-html lock itself in its own classes and logic, keeping hyperHTML open to wider adoption outside its own domain.

without having to resort to MutationObserver + polyfill

I will check what/how they do that, but MutationObserver is the only way to go here, IMO. Remember: hyperHTML promotes the platform as much as it can.

@marcoscaceres

I still like AMD support

here I'm planning to move away from CommonJS and old syntax and go all-in ES2015 modules: AMD ain't gonna happen unless I export a file dedicated for that use case.

Would it work? Also ... if you had an ES2015 export could your bundler use it and wrap it as AMD or you need a file that's AMD out of the box?

Last, but not least, I also would like to see hyperHTML used in modern projects :stuck_out_tongue:

marcoscaceres commented 7 years ago

D'uh! Of course. I'll get it for free when I transpile! 🤠🥁👏

WebReflection commented 7 years ago

then you have already the .mjs version these days ... index.mjs and min.mjs are exporting as ES2015 module. Do these work already?

asapach commented 7 years ago

tbh - I feel like wire() has been covered extensively, and it's actually not that difficult to understand once you've actually used hyperHTML for more than a day.

IMHO, the fact that you can get used to it doesn't necessarily mean it's a good api.

WebReflection commented 7 years ago

I think he meant you should actually use hyperHTML before complaining about it.

Also happy to analize your proposal.

joshgillies commented 7 years ago

@asapach apologies of I came off as rude. My comment wasn't so much around the API itself but the reality that with some new concepts it actually takes building something in order for it to make sense.

A comparison could be made between wire() and closures/scope in JavaScript. In that you can do all the reading in the world but true understanding comes from actually using the technology before the concepts can really take hold.

For me the realization that wire() was a weakly held reference between an Object and a Document Fragment was the spark. But I feel as though calling it weak() would be even more confusing.

Again, if you have an alternative API in mind please share your thoughts. :)

asapach commented 7 years ago

OK, here's my problem with the wire API: why do I need to pass the id when I want to render different content based on the same object? Like wire(user, ':avatar') and wire(user, ':profile') in the docs. If I forget to do that, I'm shooting myself in the foot because it won't be able to track state. A more contrived example is when I'm using this.html in a hyper.Component (which in turn uses wire(this) under the hood): I want to render content more than once, e.g. as a helper method (I don't say it's a good practice, but nevertheless):

render() {
  var snippet = this.renderSnippet();
  return this.html`Info: ${snippet}`;
}

renderSnippet() {
  return this.html`...`;
}

So here's my proposal: it should be possible to distinguish between wire(obj)`foo` and wire(obj)`bar`, since they use different TLs, so it should be clear that the intent of the developer is to use two different wires. Is it possible to cache wires based on TL rather than id? Perhaps it could be a new/different API to avoid breaking existing functionality.

joshgillies commented 7 years ago

@asapach

Is it possible to cache wires based on TL rather than id?

Sure, but you still need a way to handle the following scenarios:

// svgs
wire(obj, 'svg')`...`

// lists
items.map((item, i) => wire(item, `:item-${i}`)`...`

// verbose equivalent of above
wire(obj)`<div>${[
  wire(obj, ':span-1')`<span>${'foo'}</span>`,
  wire(obj, ':span-2')`<span>${'bar'}</span>`
]}</div>`

For these cases (there may be others!) the only real way to handle them is by passing in an :id that you as the developer control. Personally I feel as though if there are cases where you're still required to manually assign an :id, we may as well make that the default for all cases.

Edit: https://github.com/WebReflection/hyperHTML/issues/107#issuecomment-329330612

WebReflection commented 7 years ago

@asapach the wire is a weak relation between an object and a DOM representation.

If I forget to do that, I'm shooting myself in the foot because it won't be able to track state.

It's the same of having a weakmap. If you wm.set(obj, value1) and then wm.set(obj, value2) you inevitably lose the obj <=> value1 relationship and JavaScript wouldn't warn you: it's your code, it's your choice.

The wire gives you the ability to address ids like in a WeakMap relationship such:

// wire(myobj, ':id1')
wm.get(myobj).id1;

// wire(myobj, ':id2')
wm.get(myobj).id2;

// wire(myobj, ':somethingElse')
wm.get(myobj).somethingElse;

Since it'd be inconvenient to write code in any other way, you'll find that wires give you out of the box a very simple mechanism to reuse an object to represent same TL, like @joshgillies showed already, or various TLs for various cases.

This is not going to change and I don't see any proposal that would make it more robust, any better, less confusing, or more flexible.

asapach commented 7 years ago

@joshgillies I totally get that you need different content for SVG and HTML, but that's type, not id.

items.map((item, i) => wire(item, `:item-${i}`)`...`

This is a bad example because you're wiring different objects, and you probably don't want to rely on the index, in case you change the order of the elements in the array.

  wire(obj, ':span-1')`<span>${'foo'}</span>`,
  wire(obj, ':span-2')`<span>${'bar'}</span>`

This is where my proposal might break down, since you're using the same TL on the same object multiple times. But in this case you could opt in and use id if you have to, but in 99% it should not be necessary.

@WebReflection I get that explanation and understand the rationale behind it, but it would be good if wireContent() would not just throw everything away if I use it with a different TL (source) and instead maybe maintain its own TL => wire WeakMap.

WebReflection commented 7 years ago

You basically want to use the TL as :id by default.

This has the following implications:

WebReflection commented 7 years ago

P.S. I will try to benchmark & test the TL as default :id ... I just want to be sure people suggesting changes actually used the API and really had problems hard to solve or figure out otherwise.

WebReflection commented 7 years ago

update

I actually forgot that the wire is created before the TL so there's no way a wire can be lazily evaluated, 'cause the moment a wire is executed, it's relationship has already been made.

var wm = new WeakMap;
var createWire = obj => {
  wm.set(obj, TL => console.log('hyper'));
  return wm.get(obj);
};
var wire = createWire({any: 'object'});
wire`Hello World!`; // hyper

I'm not sure a massive core change would benefit much anyone that used wires and ids correctly here, the lazy bit doesn't look too core friendly.

I'll think about it

joshgillies commented 7 years ago

@asapach

This is a bad example because you're wiring different objects

Haha, you're right! I think what I was attempting to illustrate was more along the lines of:

wire(items)`<ul>${
  items.map((item) => wire(item)`<li>${
    wire(item, `:${item.id}`)`<span>${item.content}</span>`
  }</li>`)
}</ul>`

Which is equivalent to the third scenario in https://github.com/WebReflection/hyperHTML/issues/107#issuecomment-329036523.


@WebReflection

I'm not sure a massive core change would benefit much anyone that used wires and ids correctly here, the lazy bit doesn't look too core friendly.

Technically this type of change would fall inline with a semver major release. I guess is it something we'd want to address with v2, or roadmap to maybe v3?

Thinking on this more, I do wonder whether there's scope to push this out into user-land? @asapach could the following meet your requirement at least initially?

// ⚠️ untested, likely full of bugs! ⚠️ 
const autoWire = (obj) => (TL, ...args) => wire(obj, `:${TL.raw.join('')}`)(TL, ...args)

autoWire(obj)`<div>${autoWire(obj)`<span>...</span>`}</div>`
asapach commented 7 years ago

@joshgillies, yes, that does seem to work: https://codepen.io/asapach/pen/aLzWdq?editors=0010 (no extra dom updates)

TL.raw.join('')

It would probably be a good idea to use a UID here to avoid collisions:

TL.raw.join(UID)
joshgillies commented 7 years ago

@asapach

It would probably be a good idea to use a UID here to avoid collisions:

Remember, hyperHTML does a really good job of ensuring node reuse, and minimum set of changes to the DOM. I'd worry adding some UID into the above would cause more wire variants to be created where viable variants already exist. I guess this is the balancing act we need to perform, managing developer experience with out of the box performance of hyperHTML which is why I'm all for experimenting on things like this in user-land. Glad my little POC worked for you 👍

WebReflection commented 7 years ago

this one:

const autoWire = (obj) => (TL, ...args) => wire(obj, `:${TL.raw.join('')}`)(TL, ...args)

beside potentially accessing big IDs each time, this would create O(n) callbacks so that we optimized the DOM but still penalized the GC.

What I had in mind is different:

This is all fine for a V2 indeed but I don't want to rush conclusions neither.

If I have to make the wire lazy, I end up in "lit-html" or current hyper adopt land: it's incompatible with the outer world and one of the point of hyperHTML is playing nice out of the box with the regular DOM:

// already possible
document.body.appendChild(hyper`<p>hello</p>`);

That is basically just an empty wire that, having a single element, can be appended right away. The concept works for hyper.Component classes with a single root node, you just invoke .render() and you can use it whenever you want, not only in hyperHTML rendered nodes.

I believe this is a strength of the library, avoiding lock-in with itself like in lit-html, and I'd like to keep it this way.

As summary, I will experiment with various ways to optimize the single object as wire argument case but I'm not planning to refactor wires as always lazy wraps of the current functionality.

asapach commented 7 years ago

@joshgillies, what I mean is that foo and foo${bar} would produce the same key in your case, so you need a better separator than ''

@WebReflection

create a bound version of the function once per object, instead of N callbacks per each reference

Yes, that is similar to what I had in mind, too.

Thanks.

WebReflection commented 7 years ago

foo and foo${bar} would produce the same key in your case, so you need a better separator than ''

correct. The TL is parsed once regardless and the string with internal UIDs is also created before parsing it.

Accordingly, here I could also create once a UID per template via something like:

function hash(str) {
  var hval = 0x811c9dc5, i = 0, length = str.length;
  while (i < length) {
    hval ^= str.charCodeAt(i++);
    hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
  }
  return hval >>> 0;
}

hoping there won't be collisions (hardly probable that both the object reference and few TL used would collide though).

At that point I'll have a :_${hash(TL)} reference to use as ID and I can lazy create wires once per reference.

It's a bit of a hell but doable

WebReflection commented 7 years ago

@asapach this is an experimental branch, beside V2, with the wire functionality you suggested: https://github.com/WebReflection/hyperHTML/blob/8bba50bd4f6a9395221213964d1eca74343a6cf8/index.js

Please let me know how it works.

FWIW I'm not too happy right now because going too implicit here might result in footguns when it comes to SVG vs HTML as types while specifying the type would be a step close to just also add an ID to the wire. I will play around with it

asapach commented 7 years ago

@WebReflection I gave it a go, and for the most part it works as expected. And as expected this doesn't work:

wire(obj)`foo${bar}`
wire(obj)`foo${baz}`

A couple of issues, though:

  1. I think we also need to update Component, because it seems to work the old way (non-lazy):
    class Test extends hyperHTML.Component {
    render() {
    return this.html`test ${this.html`<b>passed</b>`}`;
    }
    }

But once I add this to the constructor it works fine:

this.html = hyperHTML.wire(this);
  1. Ran into this error (https://codepen.io/asapach/pen/YrXzYw?editors=1010):
    render() {
    return this.html`test ${{
      any: this.html`<b>passed</b>`,
      placeholder: '<i>failed</i>'
    }}`;
    }
    Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
    at Aura.splice (test.js:167:10)
    at Array.hodor (test.js:1291:35)
    at performOperations (test.js:1419:21)
    at majinbuu (test.js:1275:5)
    at Array.anyContent (test.js:362:19)
    at Array.update (test.js:1103:18)
    at HTMLDivElement.render (test.js:199:12)

    Again, when changing this.html to lazy works fine.

WebReflection commented 7 years ago

And as expected this doesn't work:

What do you mean there? It's the same TL, only the interpolation should update there and there's no way I can/should change that.

Did I misunderstand your comment?

About the Component, I don't think using this.html more than once makes sense there. You need to return the result so it's way too confusing to have it in the wild inside its own templates.

What you need there is just the hyper function like in lit-html ... and since you need hyper.Component there, you have hyper already to use hyper(this) wherever you need.

TL;DR I think you went too far with the Component example. It's ambiguous and hard to document. Plus there is SVG too, and it'd be a mess combined. I don't think I want to implement that. It's visually and logically disturbing, IMO

asapach commented 7 years ago

What do you mean there?

This was one of the counter-examples raised by @joshgillies. I don't expect it to work, since as you said, there's no way to tell them apart. Let's leave it as a caveat for now.

I don't think using this.html more than once makes sense there.

Perhaps it's worth adding this to the docs?

WebReflection commented 7 years ago

Let's leave it as a caveat for now.

It's not a caveat. The whole project is about updating interpolations on re-renders and this will always be the case . You need a different reference if you want a new node.

asapach commented 7 years ago

I agree. And this hasn't changed (works the same before/after). The only concern is that it might be confusing for beginners, hence my comment about a caveat.

gdorsi commented 7 years ago

What about to move to a Lerna monorepo?

WebReflection commented 7 years ago

@gdorsi what would be the benefit/advantage ? What would that solve ?

gdorsi commented 7 years ago
gdorsi commented 7 years ago

You can move the UMD version & the adopt API in different npm packages and versioning them in the same repo of hyper/viperHTML.

WebReflection commented 7 years ago

Just to be clear, viperHTML has a completely different core and only the API is similar. The two will never ship as one, but you could use just viperHTML for both client and server, although that's just a development convenience.

I think this is not what's needed in V2, there are higher priorities

ematipico commented 7 years ago

As far as I understand, to bump to 2.0 will make sure that contributors would be able to piggyback more easily. As plus, it would be nice to have a small CONTRIBUTING.md file with some bullet point for newcomers.

asapach commented 7 years ago

I propose adding until() helper function borrowed from lit-html (as previously suggested here) that is more performant than placeholder (as discussed in #113).

Here's a naive implementation (I'm sure you can do better):

var map = new WeakMap();

function until(promise, defaultContent) {
  if (!promise) return;

  var temp = map.get(promise);
  if (!temp) {
    temp = hyperHTML.wire(promise, ':until')`${defaultContent}`;
    map.set(promise, temp);
    promise.then(content => {
      hyperHTML.wire(promise, ':until')`${content}`; // I'm intentionally using the same key
    });
  }
  return temp;
}

Here's a demo: https://codepen.io/asapach/pen/JrdVyX?editors=0010

WebReflection commented 7 years ago

It is not more performant than placeholder and it's fully redundant with placeholder.

WebReflection commented 7 years ago

also let's be clear on this:

I'm intentionally using the same key

that's your choice, you don't need to do that. Actually, that's an anti pattern.

Your example is also wrong, if I click twice the button I have the resolved content.

Bringing up a discussion where nobody agreed with you on performance issues over non existent, non real-world, use cases just to prove your point on something you should probably try to understand a bit more was not a nice move.

As written in that discussion: please move on, the placeholder was born to provide exact same functionality until does already, there's no way we are going back here: it's a utility, use it when/if you need it, create your own utility through define otherwise.

If you won't give up until this library will be like lit-html and need scope pollution to work, you are more than free to use lit-html instead.

asapach commented 7 years ago

need scope pollution to work

Could you please explain what you mean? You keep bringing that up and I'm not sure I'm on the same page. until() is in a separate module: https://unpkg.com/lit-html@0.6.0/lib/until.js, while define will change the behavior globally throughout the application, which sounds more like global pollution to me.

If you won't give up until this library will be like lit-html ...

That's not my goal -- I hope that one day your feud with lit-html gets resolved and everybody wins at the end. I think you guys are the future. I don't care who got here first, all I want is that developers community benefits from the work you've done.

asapach commented 7 years ago

P.S. Sorry, I should have created a separate thread instead of dumping it here.

Your example is also wrong, if I click twice the button I have the resolved content. ... performance issues over non existent, non real-world, use cases ...

I'm sorry that I wasn't able to convince you that this is indeed a real use case I ran into while trying to solve a real-world problem. The examples I used are intentionally simplified: I don't want to load content multiple times, I want to cache and re-use the promise, while showing the placeholder until it is resolved. If you think it's not worth supporting this scenario out of the box, you're the boss.

Actually, that's an anti pattern.

I know it's a hack. I don't like it, but it worked for the illustration purposes.

WebReflection commented 7 years ago

This is not the place to discuss any of this. This is about the core of the library. I will no longer discuss placeholder which has been introduced to solve exactly what until solves, hence it's there to stay.

Any external utility can be written by users, meaning also hyperHTML is good enough as primitive for other scenarios.

asapach commented 7 years ago

@WebReflection, I'm genuinely interested to hear your explanation about scope pollution.

WebReflection commented 7 years ago

hyperHTML templates are portable and usable as module too, consumable via viperHTML and others (nativeHTML, etc).

This is why you can define extensions without needing functions in the scope, like your until function before, simply defining in either client, server, or native, behaviors, including the placeholder.

The viperHTML Hacker News is a simple example of template reusability. https://github.com/WebReflection/viper-news/blob/master/shared/view/item.js

On the backend, the until makes no sense because you stream content. You can eventually use a placeholder which is a behavior in core, and you won't need to pollute the scope with utilities like until, that are not portable, requires scope pollution.

Every extension in hyperHTML should be done via define which is the way to extend it. Whatever you want to do can be defined, so that you can use {myBehavior: ...} as defined extension, and eventually use placeholder there too or pass to myBehavior all you need do whatever you want.

hyperHTML will keep the core as small as possible and extensions will be created outside this repository.

placeholder is here to stay, extensions are made through define, everything that can be an extension or a third part utility most likely won't ever land in core, there's already enough to let you do whatever you want.

asapach commented 7 years ago

Thanks. A follow-up question if you don't mind: you think that this implicit dependency on plugins is better than explicit one? Is this the way you see people using it?

import {hyper} from 'hyperhtml';
import 'my-behavior-plugin'; // don't forget to import it every time you use it

...
hyper`foo ${{ myBehavior: 'bar' }}`

Versus explicit:

import {hyper} from 'hyperhtml';
import myBehavior from 'my-behavior-plugin';

...
hyper`foo ${ myBehavior('bar') }`
obedm503 commented 7 years ago

I also agree with the underlying idea that function-based explicit plugins are better. Honestly, the way plugins currently work makes it seems like custom "intents" follow the concept of global plugins (sort of like jQuery plugins)


on another note: why not separate out rendering from template creation/composition? I don't have any concrete use case right now, but when I first found out about hyperHTML I really wasn't sure where the template was being rendered and where the template was being constructed. This separation might lead to an easier to understand, and more extensible api as these constructed templates could be passed around freely before reaching a place where they will be rendered.

In the case of hyper.Component and HyperHTMLElement, that means that the user-defined render(){} method returns a constructed template and the rendering would happen internally as an implementation detail of such components.

Just an idea

WebReflection commented 7 years ago

Is this the way you see people using it?

no. the way is documented, you define behaviors through define.

That means you have portable templates, like I've mentioned and showed already.

module.exports = (render, model) => render`
  <section class="my-component">
    <h1>${model.title}</h1>
    <ul>
      ${withSpecialHandler: model.items}
    </ul>
  </section>
`;

Above module is portable on both client and server side, or even native-script side.

Above module doesn't force you to define the withSpecialHandler behavior at all, that is left to you. In this way you can ignore special handler and simply return the list synchronously if you are using viperHTML on the backend, or do something, even asynchronous, on the client before rendering such list. It's up to your implementation.

The module, as view, can be tested in isolation, and the behavior mocked too. This is what I mean by "no scope pollution here", and it's more powerful and more recyclable and flexible than any explicit, scoped, dependency.

WebReflection commented 7 years ago

I also agree with the underlying idea that function-based explicit plugins are better

good. So here you can do whatever you want, you can write explicit functions that returns whatever primitive you want, or you can use the define way, which is how you natively define custom elements too so ... nothing really new as a concept to learn.

on another note: why not separate out rendering from template creation/composition? I don't have any concrete use case right now,

I do, and I've linked it. The fastest Haker News PWA out there uses already this pattern. It shares same views with both client and server, since it's 100% SSR.

In the case of hyper.Component and HyperHTMLElement, that means that ...

... HyperHTMLElement is incompatible with SSR because Shadow DOM is incompatible with SSR, and if you don't use Shadow DOM you are probably better off with regular hyper.Component, although you won't have the created/connected/attributeChanged/disconnected callbacks provided by the platform, yet you can have a component that requires a view that can be shared with both client, server, and, eventually, native.

TL;DR what's in core already, beside the experimental adopt, works already, it has already use cases, it has been discussed already too before V1 went out, and this post is to discuss only missing bits. If you can achieve what you need by yourself, that means that the core exposes already the right primitives. Accordingly, unless I read, see, and understand real-world use cases that cannot be solved with the current core or are really needed, instead of speculating about the need or the performance implication, not much will change. That was never the intent of this thread. Thanks cfor your collaboration.

kidwm commented 7 years ago

I've found that hyperHTML.define is working like global object, it may to be overwrite in other modules after I defined the intent in my module.

It seems using hyperHTML.Component to encapsulate my intent into a module would be better than hyperHTML.define.

Will there be any improvement on hyperHTML.define?

WebReflection commented 7 years ago

I've found that hyperHTML.define is working like global object

it's rather like customElements.define except it doesn't throw. Namespaces or good names matter, and there's no way to improve the semantics, keeping it portable (as previously discussed).

it may to be overwrite in other modules after I defined the intent in my module.

would you prefer it to throw instead, if already defined ?

It seems using hyperHTML.Component to encapsulate my intent into a module would be better than hyperHTML.define.

you are comparing apples and oranges. define is for utilities that can be shared across components. Components is for ... components. If you need components, use components. If you need utilities, use utilities.

Last, but not least, if you have real-world use cases that cannot be solved with current primitives, please show them, thanks.