jorgebucaran / hyperapp

1kB-ish JavaScript framework for building hypertext applications
MIT License
19.07k stars 781 forks source link

Create a new tagged template string vnode factory function: Codename: hypertag #153

Closed jorgebucaran closed 7 years ago

jorgebucaran commented 7 years ago

I think we could benefit from another hyperx-like template literal. Perhaps I should look into https://github.com/hyperapp/hyperapp/issues/88 before I do this, or maybe not, programming is fun.

1

import { html, app } from "hyperapp"

const Hello = ({ name }) => html`<h1>{name}</h1>`

app({ 
  view: model => html`
    <Hello name="world" />
  `
})

2

import { h, app } from "hyperapp"
import { html } from "hypertag"

const Hello = ({ name }) => html`<h1>{name}</h1>`

app({ 
  view: model => html`
    <Hello name="world" />
  `
})

About JSX/Hyperx and some things that I'd like to explore:

Of course, if anyone wants to go ahead and work on this, just do it.

See Also:

chasm commented 7 years ago

Something like this would be lovely.

ngryman commented 7 years ago

I have a slightly different point of view on this. Creating and maintaining a new template function is a load of work. Already proven implementations already exist, we should leverage them instead.

The only thing that bother us for the moment is more syntax-related than feature-related. So I propose that hypertag (nice name btw!) would be a wrapper to hyperx (or equivalent) and bring syntactic stuff on the table. And nothing prevents to implement parsing later.

Example

I think there is a typo in your example as template functions only allow to do this:

app({ 
  view: model => html`
-    <Hello name="world" />
+   ${Hello("world")}
  `
})

But it highlights the fact that template functions have function purity in mind and don't care about templates composition (i.e. components). Which is perfectly ok because it fits any use cases. Still in a real world use case, referencing other components like jsx allows you to do is something really appreciable.

So a good first use case for hypertag would be to add this feature and be able to do something like this:

import { app } from "hyperapp"
import { tag, html } from "hypertag"

tag('Hello', ({ name }) => html`<h1>{name}</h1>`)

app({ 
  view: model => html`
    <Hello name="world" />
  `
})

Other use cases could be treated like arguments passing cascade that purity imposes. We could automatically pass model and actions to view by default for example.

To sump up, I would propose hypertag = hyperx + syntactic sugar than yet another template function.

jorgebucaran commented 7 years ago

@ngryman Nice writeup! 👍

EDIT: I'll still try to write the template function just for fun :)

jorgebucaran commented 7 years ago

Your example should be modified because any classic template function only allows to do this:

I'm not sure I'm following. If it doesn't support writing JSX-style components out of the box like this:

html`<MyComponent />`

I'm not sure it would be much fun to use over JSX. 🤔

ngryman commented 7 years ago

@jbucaran It's because it works on any es6 environment without any tooling involved. es6 support is getting momentum and in a couple of years template literals will just work for any browser: http://kangax.github.io/compat-table/es6/#test-template_literals.

jorgebucaran commented 7 years ago

@ngryman I think we are talking about two different things.

If I ever build a template literal like hyperx, it must support:

html`<MyComponent />`

out of the box, otherwise it's a no-no.

With regards to:

works on any es6 environment without any tooling involved.

It doesn't really matter if they are natively supported or not, I don't want to ship code to production (or anywhere) that uses a template function XML parser (template literals for strings are okay maybe). In other words, you always want to compile your templates to native function calls (like in hyperapp's case your views to native h calls).

ngryman commented 7 years ago

If I ever build a template literal like hyperx, it must support: ...`

html`<MyComponent />`

As it's not possible with hyperx out-of-the-box (or any template function I know, or I'm missing something), I thought that was a typo. In fact I don't know how you can make this work if you don't name you component somewhere. Does hyperxify allow to achieve this?

It doesn't really matter if they are natively supported or not, I don't want to ship code to production (or anywhere) that uses a template function XML parser

Well I think we diverge here. For me it's totally ok to ship this to production when you target specific environments and you don't care about the performance cost of parsing. For example if I develop a Chrome addon or an Electron app with just some basic interactions, I can live with shipping es6 template literals and do the parsing at runtime.

ngryman commented 7 years ago

To illustrate my misunderstanding on the first point: https://github.com/trueadm/t7#components.

jorgebucaran commented 7 years ago

@ngryman Well I think we diverge here. For me it's totally ok to ship this to production when you target specific environments and you don't care about the performance cost of parsing.

Nope, I'm sure we don't diverge here. I wouldn't care either in that specific environment. Here is the thing though, I don't know if that environment exists for sure, or at least they are extremely rare. You usually do care about the cost of parsing, specially in the case of JSX/Hyperx.

Run js-framework-benchmarks or DBMonster, etc., but don't compile Hyperx into native function calls and see for yourself. We're not talking about 20% slower, it's probably more like 200% to 500% slower.

jorgebucaran commented 7 years ago

To illustrate my misunderstanding on the first point: https://github.com/trueadm/t7#components.

😞

rbiggs commented 7 years ago

So, @jbucaran, you want to create from scratch a template library like Hyperx or t7? Those are both kinda large. I'd hope it would just be an alternative to use like everything else. t7 inspired me to try my own hand at an ES6 template solution for components, that was before I stubbled upon Hyperapp on Reddit. Here's a pen of what I came up with: http://codepen.io/rbiggs/pen/pePajZ I used a loading mechanism to notify a component about child components so that I could use them as tags inside the parent. Because I had React users in mind, I based it off of a Component class. This solution allows you to create sibling elements and append them to a container without having to wrap them in an enclosing tag. I also created a DOM patcher in 1KB that is similar to Morphdom so I could update the DOM using the result for a template literal - no virtual DOM objects necessary. But that's not in this pen. I also created a style module so I could create virtual stylesheets scoped to the component, like JSX does, but for template literals. I just never came up with a model/update solution for it.

jorgebucaran commented 7 years ago

I'd hope it would just be an alternative to use like everything else.

Yes, obviously. For real projects I'd probably use only JSX.

jorgebucaran commented 7 years ago

Awesome, thanks, I've forked it and will use it while I learn how to do this.

I'm new to this too, but I made a similar thing here.

jorgebucaran commented 7 years ago

I also created a style module so I could create virtual stylesheets scoped to the component, like JSX does, but for template literals. I just never came up with a model/update solution for it.

Can you expand on this? 🤔

rbiggs commented 7 years ago

yeah, you just pass it an object describing your styles, and it uses the component as the scope for the style rules and then appends the stylesheet to the document. But if a stylesheet's already there, then it just adds the new rules. Was trying to be efficient at that ;-) I can make you a pen to see it in action if you want. No biggie.

rbiggs commented 7 years ago

By the way, with t7, if you cut out all the code for React and Mythril and just leave the universal part you can reduce it by half.

jorgebucaran commented 7 years ago

By the way, with t7, if you cut out all the code for React and Mythril and just leave the universal part you can reduce it by half.

Yeah, you're right. Maybe I should rather look into #88. I'm just leaving this open issue to see if I get around this someday. Totally not a priority.

rbiggs commented 7 years ago

Actually, #88 would be great. t7 is far more advanced than Hyperx. I love how t7 handles components. Maybe you could come up with a separate plugin to make t7 work with your h function. That way i'd be a separate load from hyperapp, not affecting its overall size.

jorgebucaran commented 7 years ago

Maybe you could come up with a separate plugin to make t7 work with your h function.

Yup, yup, that's idea exactly.

t7 is far more advanced than Hyperx.

Yeah, but it's holding me back a bit. Need to study more, etc.

That way i'd be a separate load from hyperapp, not affecting its overall size.

No worries, we've changed how we deal with this from the early days (3 weeks ago lol) when hyperx was a dependency and we're not going back to that.

rbiggs commented 7 years ago

On a totally different note, I see you're always talking about using Codemirror for projects. I've also done some major projects with it in the past. However, I hated it, hard to customize, etc. Then I discovered Ace. Loved it. Easier to customize. By fav is Ace with Tern for code completion and Intellisense like how Visual Studio Code works.

jorgebucaran commented 7 years ago

@rbiggs I tried using Ace before CodeMirror, but couldn't get it to work (this was before HyperApp). I'd love to give it another shot.

rbiggs commented 7 years ago

Odd, I found it so much easier to use. I was using it with RiotJS. I started out with Codemirror, because that's what the client had. But they wanted so much customization. The code base was hard to sort out, the project lead was not friendly. Took a look at Ace, big open source group, lots of participation, easier to understand how all the parts work. Ace was easy to customize popups, etc. whereas with Coremirror you're stuck with whatever Marijn decided you needed.

Maybe I'll fork your CodeMirror example and convert it to Ace. And then add some other features. I'll put it together sometime later next week when I have time. I'll keep you informed.

jorgebucaran commented 7 years ago

Maybe I'll fork your CodeMirror example and convert it to Ace. And then add some other features. I'll put it together sometime later next week when I have time. I'll keep you informed.

Awesome! Please do :) 🙏

dschep commented 6 years ago

I got hyperx to work as desired(afaict) pretty easily:

import { h, app } from 'hyperapp';
import hyperx from 'hyperx';

const hx = hyperx((t, a, c) => h(t.match(/^[A-Z]/) ? eval(t) : t, a, c));

const state = {
  count: 0
}

const actions = {
  down: value => state => ({ count: state.count - value }),
  up: value => state => ({ count: state.count + value })
}

const Component = ({count}) => {
  return hx`<h1>${count}</h1>`;
}

const view = (state, actions) => hx`
  <div>
    <Component count=${state.count}></Component>
    <button onclick=${() => actions.down(1)}>-</button>
    <button onclick=${() => actions.up(1)}>+</button>
  </div>
`;

app(state, actions, view, document.body);
jorgebucaran commented 6 years ago

That's a cool & "evil" trick @dschep! 😃

rbiggs commented 6 years ago

That is neat and evil, sadly. I'd be afraid to use eval in production. Would be so nice to have a Hyperx type template literal solution that didn't require Hyperxify, it just converted to what you needed at the end automatically, sigh.

jorgebucaran commented 6 years ago

100% agree with @rbiggs.

dschep commented 6 years ago

Yeah. Being evil is fun sometimes :smiling_imp: but of course only for unimportant things. Could of course do a t7 style component lib, but then you have to register, components:

import { h, app } from 'hyperapp';
import hyperx from 'hyperx';

const hax = (() => {
  const registry = new Map()
  const hx = hyperx((t, a, c) => h(registry.get(t) || t, a, c));
  hx.register = component => registry.set(component.name, component);
  return hx;
})();

const state = {
  count: 0
}

const actions = {
  down: value => state => ({ count: state.count - value }),
  up: value => state => ({ count: state.count + value })
}

const Component = ({count}) => {
  return hax`<h1>${count}</h1>`;
}
hax.register(Component);

const view = (state, actions) => hax`
  <div>
    <Component count=${state.count}></Component>
    <button onclick=${() => actions.down(1)}>-</button>
    <button onclick=${() => actions.up(1)}>+</button>
  </div>
`;

app(state, actions, view, document.body);

BTW, I'm mostly interested in this so I can use hyperapp for simple 100% client-side projects without a build step. For example I have the above as a codepen with no build step, including hyperapp & hyperx from unpkg.com & wzrd.in respectively.

jorgebucaran commented 6 years ago

@dschep Also check https://github.com/hyperapp/html.