Hyperapp: Road to 1.0

Created on 25 Jun 2017  Β·  66Comments  Β·  Source: jorgebucaran/hyperapp

  • [x] Components #238 (See https://github.com/hyperapp/hyperapp/issues/238#issuecomment-312455621)
  • [x] Use requestAnimationFrame to throttle render/patch #90
  • [x] Add SSR DOM hydration
  • [x] Improve documentation

    • [x] Explain what types are allowed in the state prop

  • [x] Homepage #146 @lukejacksonn
  • [x] DTD by @Swizz
  • [ ] Flow by @andyrj? #323 #311
  • [ ] Optimize patch? Post-1.0
Discussion

Most helpful comment

Hey there!!! Sean from the webpack team. Please let me know if we can help support any AoT compilation needs via a webpack loader. :-D There are lots of great examples out there already but we are here to support ya'll!!!!

Also, not sure what your lazy loading story looks like, but as long as your api accepts a Function -> Promise<YourComponentPrimative> then it can support webpack code splitting for syntaxes like

import();

All 66 comments

  • [ ] Recomposing components (like lodash) for expressing state updates
  • [ ] Recycling of DOM nodes (performance benefits)
  • [ ] Controlled Components
  • [ ] SSR
  • [ ] Lifecycle hooks

Although all these may increase the lib size but they can be dispatched through separate packages like InfernoJS does ?

@nitin42

  • Lifecycle events already exist. Maybe they can be improved? Do you have any suggestions?

  • ~SSR. There are several ad-hoc solutions. See also: https://github.com/hyperapp/hyperapp-server for a WIP. Not sure if I should wait for SSR to release 1.0. I think not, but what do you think?~

  • Controlled Components. You can create your own ~custom tags~ controlled components already.

  • ~Recycling of DOM nodes and recomposing components. How do you mean? lol~ We are doing this one! I've updated the todo list. /cc @ngryman

Oh! I really wanted to work on hyperapp and right now I am going through the Elm Architecture doc. Where can I start ?

From recycling of DOM nodes I meant to say that re-using the dom nodes upon their disposal.

Or I can work on the website ? This will also led me to understand the core concepts and the design principles of the lib by writing about the api and their functionality. Let me know what you think !

Where can I start ?

I think here is a good place to start.

Or I can work on the website?

Be my guest! πŸ‘

Ok so I will be rendering the docs on web. Meanwhile I will go through the link you provided and will start contributing !

Is there something on (HyperApp core) which I can hack? Other discussions are already on πŸ”₯ ! I am interested in investing my time by working on HyperApp but don't know where to start. Like cli tool discussion does not have an answer and I feel growing that discussion further is unneeded. Same goes for other discussions. Anything ?

@nitin42 you could try build an app with HyperApp; find a limitation and try improve it or find something that you find writing over and over again and try abstract that process out to a custom element or mixin?

Or help build out functionality of example applications such as the Hacker News PWA. So we can get it up on https://hnpwa.com/

HNPWA seems a good idea!!

I could implement :

  • PWA
  • Comparing lighthouse scores
  • Offline caching
  • Cross browser
  • Web page test
  • App shell architecture

I think I should start working!

Should we use Github's "milestone" feature for 1.0? Not sure how it works, but this seems like the sort of use case they made that feature for.

@jbucaran What's the plan for the relationship between picodom and hyperapp? If hyperapp wants to eventually consume picodom as a dependency, should that be part of 1.0 milestone?

@andrewiggins At this point both vdom engines are the same, but I presume they might eventually begin to differ since both have different "core values". HyperApp is opinionated about state management, while picodom isn't.

I also don't want to consume picodom in hyperapp, but it might happen. The probability is very low though.

Why do we need both actions and events?

Take this really vanilla app:

app({
  state: {
    images: []
  },
  view: function(state) {
    return h('div', {}, state.images.map(function(image) {
      return h('img', {src: 'images/' + image.url});
    }));
  },
  actions: {
    updateImages: function(state, actions, data) {
      state.images = data;

      return state;
    }
  },
  events: {
    updateImages: function(state, actions, images) {
      actions.updateImages(images);
    }
  }
});

setTimeout(function() {
  emit('updateImages', []);
}, 5000);

This is cool, but why not allow this:

app({
  state: {
    images: []
  },
  view: function(state) {
    return h('div', {}, state.images.map(function(image) {
      return h('img', {src: 'images/' + image.url});
    }));
  },
  actions: {
    updateImages: function(state, actions, data) {
      state.images = data;

      return state;
    }
  }
});

setTimeout(function() {
  emit('action', {name: 'updateImages', data: []});
}, 5000);

@brandonros Why do we need both actions and events?

Architecture

I think the separation is important from an architecture point of view.

  • Actions: You call us.

    • We receive your request and update the state on your behalf.

  • Events: We call you.

    • You can attach one or more handlers to the same event.

Now, you can also create custom events, in which case you are responsible for calling yourself, but they also work in the same way as the default events.

Return Values

Actions return a partial state or a thunk, or maybe just call several other actions. Actions can be async functions too.

Events reduce over some data by successively calling each event handler of the specified event.

Consider the following example.

const emit = app({
  events: {
    someEvent: [
      (state, actions, data) => data + 1,
      (state, actions, data) => data + 1,
      (state, actions, data) => data + 1
    ]
  }
})

And this is the result of firing someEvent with some data.

console.log(
  emit("someEvent", 10)
) // => 13

One-to-many

An event can have more than one callback associated with it.

myEvent: [handler1, handler2, handler3]

An action is always a single call.


These are some of my thoughts. I am sure that:

setTimeout(function() {
    emit('action', {name: 'updateImages', data: []});
}, 5000);

Would be interesting, and we can consider the implications of making emit work in both ways, allowing you also to emit default events.

@jbucaran I think it the time it took you to write this response, you could have made emit('action', {name: 'updateImages', data: []}); work. :)

@brandonros It didn't take so long and this is good documentation for subscribers too! :)

So, what about this:

const Sender = emit => ({
  events: {
    send(state, actions, { name, data }) {
      return actions[name](data)
    }
  }
})

const emit = app({ 
  mixins: [Sender]
})

Then use it to call actions directly outside your app.

emit("send", { name: "updateImages", data: [...] })

Making it work for action might be possible too, but would require some "hacking" since action is one of the default events.

@jbucaran Thank you for taking the time to supply that example. I appreciate that and I hope it helps somebody else.

I've got one more "idea" that I came up with using this, just playing around with it.

I really don't like having to set up Browserify or Webpack or all of that crap. I'm trying to avoid Babel + JSX as well, just for exercise.

Back in the jQuery days, I used to do a lot of:

$('body').html('<div>a really big <span>html string</span></div>')

I saw you guys had the ability to use the (html`html string here`), but that requires Browserify.

Is it possible to get compiled/ported so you can just include a page, and then call h(html('long string')) or something similar?

I just spent 15 minutes trying to figure out why the "load" event isn't working.

Your codepad says it is 'init' instead of load. Please update the docs. https://github.com/hyperapp/hyperapp/blob/master/docs/events.md

Load is in 0.11 which should be out anytime now... Master is ahead of npm publish by a bit.

Thunks do not work and when I try to install it to help debug to provide more information (all I can get is a minified version), I get rollup.config.js not found

Edit: I see what you mean about master. Can you put the rollup.config.js in the repo please?

Edit: I was trying to build an old version. Ignore, thanks!

I recall someone else running into that when npm installing the package from master git repo. I have always had to make local copy and create a symlink in node_modules to the local repo to test newest version. Hopefully 0.11 will be out soon.

@brandonros As h implement the normalised hyperscript representation, that allow you to use hyperscript-helpers. That allow you to use an elm like syntax to create your virtual dom node.

import { h, app } from "hyperapp"
import helpers from "hyperscript-helpers"

const { div, h1 } = helpers(h)

app({
  state: {
    title: "Hi.",
  },
  view: state => 
    div(null, [
      h1(null, state.title)
    ])
})

I am also working on my own implementation, to create a more optimised (tree shaking, right h call) experience for Hyperapp and Picodom users.

@brandonros If you want to go completely standalone, this works (as long as you're targeting mostly es6 compatible browsers):

<html>
  <head>
    <script src="https://unpkg.com/hyperx"></script>
    <script src="https://unpkg.com/hyperapp"></script>
  </head>
  <body>
    <script>
      const {app, h} = hyperapp
      const html = hyperx(h, {attrToProp: false})

      /*
        now you can produce virtual trees like this:
        html`
          <div class="foo">
            <p>AAA</p>
            <p>BBB</p>
          </div>`
      */

      //initialize your app:
      app({...})
    </script>
  </body>
</html>

The reason this method is not usually mentioned or recommended is because there is a performance penalty to compiling the hyperx strings in browser when it could be handled by a browserify transform or webpack loader.

It's also possible to do something similar for a standalone html page written with JSX, but I can't remember the module name. babel-standalone I think.

@nitin42 Recomposing components (like lodash) for expressing state updates

What is this? I don't use lodash and I am not familiar with the concept "recomposing components" either. But it sounds interesting.

I think @nitin42 is referring to this:

var _ = require('lodash/core');
// Load the FP build for immutable auto-curried iteratee-first data-last methods.
var fp = require('lodash/fp');

Thanks @naugtur, but now I am more confused! πŸ˜„

EDIT: Not sure I understand what this has to do why hyperapp either.

waiting for SSR so much πŸ˜„

@lyquocnam There are ways to hydrate pre-rendered HTML. We already use it at work πŸ’―.

@jbucaran have any examples about Hyperapp with backend and SSR ?

Edit: ~hyperApp~ -> Hyperapp

@lyquocnam Nope, no non-trivial SSR example, but we created hyperapp/server and could use some help. πŸ˜„

/cc @andrewiggins @SkaterDad @corysimmons

Swamped for about the next month then I can work on some SSR solution. Anyone is welcome to beat me to it. 😬

Hey there!!! Sean from the webpack team. Please let me know if we can help support any AoT compilation needs via a webpack loader. :-D There are lots of great examples out there already but we are here to support ya'll!!!!

Also, not sure what your lazy loading story looks like, but as long as your api accepts a Function -> Promise<YourComponentPrimative> then it can support webpack code splitting for syntaxes like

import();

Now that #311 has been merged, what's remaining before achieving 1.0 status?

@okwolf Almost nothing! πŸ„

@TheLarkInn what would it take for hyperapp to have default support for bundle splitting with async imports similarly to what Vue supports?

Seems to me it's just about accepting a promise where a component or custom tag is expected, but I only saw that at a conference ;)

I think that would have to be a Mixin of some sort since we don't ship with polyfills πŸ€”

Just thinking out loud here πŸ˜›

Or we could just check if Promise is a thing and then check if a certain view is a Promise

Webpack is buggy and bundles are huge or require a ton of config (there are literally books on how to use that project.............). It seems almost all "JS fatigue" revolves around that project...

This seems to go against what Hyperapp is trying to do (i.e. not overcomplicate simple things).

I'd vote not doing anything to support webpack and just build a simple bundler from scratch.

@corysimmons Webpack is buggy... It seems almost all "JS fatigue" revolves around that project...

That's not very nice man. JS fatigue is a myth and @TheLarkInn is just offering help. I am not a webpack user _myself_, but I can think of many users who want to bundle their hyperapps with webpack.

@TheLarkInn There are lots of great examples out there already but we are here to support ya'll!!!!

Can you show me an example of this? πŸ˜„

Webpack... bundles are huge

They can get bigger than rollup if you are bundling a ton of modules, but that's because of how webpack works.

[webpack] work by wrapping each module in a function, putting them in a bundle with a browser-friendly implementation of require, and evaluating them one-by-one. That’s great if you need things like on-demand loading, but otherwise it’s a bit of a waste, and it gets worse if you have lots of modules.

and

If you need code-splitting, or you have lots of static assets, or you’re building something with lots of CommonJS dependencies, Webpack is a better choice. If your codebase is ES2015 modules and you’re making something to be used by other people, you probably want Rollup.

Source

Also:

[webpack] ...at runtime, each of those module functions is evaluated in turn to populate the module cache. This architecture has lots of advantages β€” it makes it possible to implement advanced features like code-splitting and on-demand loading, and hot module replacement (HMR).

Source

Webpack is fine ❀️ . I use it and the API is straightforward.

@corysimmons Let's try to be a bit more civil πŸ˜›

Also scope hoisting has helped a ton! My boilerplate counter is only 2kb gzipped using webpack, So bundles being very large might not apply so much anymore.

Let's not blame JS fatigue on a single library πŸ™


Otherwise, I wonder if this should be a Mixin (official Mixin) or in core πŸ€”

Moving lazy loading talk

Separate issue: https://github.com/hyperapp/hyperapp/issues/379

Yeah I'm toxic. Bye guys.

@corysimmons Whoa, I just said: "that's not very nice man" because you went on a little off-topic rant blasting webpack.

And now you left the organization too? I don't get it, were you expecting anyone to support you on this or pretend you didn't say anything?

  • [ ] Optimize patch?

@JorgeBucaran is this referring to what #378 would do to help fix #373?

@okwolf Nope, #373 came up just recently. Optimize patch involves rewriting the patch function.

The objective is to rewrite it in less code and make it faster at the same time. πŸ‡πŸ’₯

The objective is to rewrite it in less code and make it faster at the same time. πŸ‡πŸ’₯

@JorgeBucaran I understand how to measure code size, but what metric are we using to determine performance in this instance?

An interesting project to put on a test, will there be SSR support in 1.0 or this feature is not on a roadmap?

@VladShcherbin Thanks! Hyperapp now provides automatic SSR hydration out of the box since 0.14.0.

@JorgeBucaran yeah, I've seen hydration in the docs.

I mean, when a user requests a page, how do you generate html for this page on server (using node for example), is there any demo/repo of this?

@VladShcherbin Entirely up to you to handle. If I was worried about SEO and time-to-interactive in a large-ish app, I would just send the HTML to the client and let Hyperapp hydrate it.

Is there any demo/repo of this?

SSR is not my cup of tea, so I haven't been interested in making a demo or repo, but we hydrate pre-rendered HTML at work using Hyperapp already, so it works.

Maybe @Andyrj, @SkaterDad or @Zaceno can comment about Hyperapp and SSR in general. πŸ™

Sorry I'm no SSR-er either. Don't know how it works even in principle. But I'm curious to learn :)

One thing I do know works, is: setting up a fake browser environment in node using jsdom or undom (well undom has a bug atm, but anyway), and then running hyperapp server-side, and serialize the fake dom into html.

That would be server-side rendering with hyperapp, in my estimation. But I'm not sure that's what we're talking about.

@zaceno @VladShcherbin - I've been playing with hyperapp/ssr/jsdom recently. I've thrown my discoveries and a working demo into a repo - https://github.com/maxholman/hyperapp-ssr-jsdom-example

@zaceno That would be server-side rendering with hyperapp, in my estimation. But I'm not sure that's what we're talking about.

That's pretty much it! :)

@JorgeBucaran really?! And then what? You just serve up the plain HTML? Or hydrate it into a client side instance (which sounds like a lot of extra work for no extra benefit). I rhink I just haven't understood the point of SSR... Why not just use PHP then? (Not being sarcastic. Just explaining what it is I don't get)

@JorgeBucaran is there a way to Log state updates and action information to the console?

@AlexanderChen1989 there's an official logger, although it's in the shop right now, getting a tune-up for compatibility with > 0.12.1 Hyperapp (post mixins). It will definitely be released before 1.0.

@okwolf how can i implement old events like behavior? i have read new source code, but cannot find events like function

@AlexanderChen1989 What were you trying to do exactly? Depending on what you are doing the solution may be different now that there are no custom events.

im curious how to implement logger?

@AlexanderChen1989 Here.

@JorgeBucaran good idea, thanks

This issue is pretty far out of date with what is actually being worked on for 1.0.0 now.

What should we do?

Hmm, I want to merge #497 before I publish 1.0 and before I publish 1.0 I want to publish 0.18.0 so people that need it right now can start using it.

EDIT: I'll close here when 1.0 is officially out.

Closing in favor of #507! 🎁 πŸŽ‰πŸš€

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dmitrykurmanov picture dmitrykurmanov  Β·  3Comments

jorgebucaran picture jorgebucaran  Β·  4Comments

icylace picture icylace  Β·  3Comments

Mytrill picture Mytrill  Β·  4Comments

jacobtipp picture jacobtipp  Β·  3Comments