{"action":"created","issue":{"url":"https://api.github.com/repos/hyperapp/hyperapp/issues/672","repository_url":"https://api.github.com/repos/hyperapp/hyperapp","labels_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/672/labels{/name}","comments_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/672/comments","events_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/672/events","html_url":"https://github.com/hyperapp/hyperapp/issues/672","id":310299155,"number":672,"title":"Hyperapp 2.0","user":{"login":"jorgebucaran","id":56996,"avatar_url":"https://avatars1.githubusercontent.com/u/56996?v=4","gravatar_id":"","url":"https://api.github.com/users/jorgebucaran","html_url":"https://github.com/jorgebucaran","followers_url":"https://api.github.com/users/jorgebucaran/followers","following_url":"https://api.github.com/users/jorgebucaran/following{/other_user}","gists_url":"https://api.github.com/users/jorgebucaran/gists{/gist_id}","starred_url":"https://api.github.com/users/jorgebucaran/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jorgebucaran/subscriptions","organizations_url":"https://api.github.com/users/jorgebucaran/orgs","repos_url":"https://api.github.com/users/jorgebucaran/repos","events_url":"https://api.github.com/users/jorgebucaran/events{/privacy}","received_events_url":"https://api.github.com/users/jorgebucaran/received_events","type":"User","site_admin":false},"labels":[{"id":522275188,"url":"https://api.github.com/repos/hyperapp/hyperapp/labels/DISCUSSION","name":"DISCUSSION","color":"fbca04","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":18,"created_at":"2018-04-01T06:00:37Z","updated_at":"2018-04-02T04:49:56Z","closed_at":null,"author_association":"OWNER","body":"> **Note**: This is not an April Fool's Day prank! 😄  \r\n\r\n## Background\r\n\r\nAfter a lot of debate and enduring weeks of inner turmoil, I've decided to move forward with a series of drastic changes that will be eventually released as Hyperapp 2.0.\r\n\r\n[Predictable as it may be](https://www.quora.com/Why-some-programmers-are-obsessed-with-rewriting-the-same-piece-of-code-again-and-again-in-order-to-make-a-better-code-even-when-it-is-already-working), my plan was to leave things the way they are here and [create a new](https://blog.codinghorror.com/when-understanding-means-rewriting/) JavaScript framework. A direct competitor to Hyperapp, but pretty much the same under the hood. I am calling that off and going to focus on making a better Hyperapp instead! \r\n\r\nBreaking changes again? I am afraid so. I'm unsatisfied with how some things work in Hyperapp and want to fix them to the bone, not start a brand new project. I don't think I will be able to fairly concentrate on two projects that have exactly the same goal when what differentiates them are just some subtle (but important) API differences.\r\n\r\n## What will change?\r\n\r\nWith 2.0 I intend to fix several pain points I've experienced with Hyperapp due to its extreme DIY-ness. In the words of @okwolf:\r\n\r\n> **Hyperapp** holds firm on the functional programming front when managing your state, but takes a pragmatic approach to allowing for side effects, asynchronous actions, and DOM manipulations.\r\n\r\nHyperapp is minimal and pragmatic and I don't want to change it but in order to improve, we need to do more for the user. So, this is what I am going to focus on:\r\n\r\n* **Easy to test** — By making actions and effects pure, testing will be a piece of cake.\r\n* **All Things Dynamic** — First class support for code splitting and dynamically loading actions and views using [import()](https://github.com/tc39/proposal-dynamic-import), e.g., `dynamic(import(\"./future-component\"))`. Fix https://github.com/hyperapp/hyperapp/issues/533.\r\n* **Cleaner Action API** — Eliminate the confusing concept of \"wired actions\". The new actions will be regular, unwired & untapped JavaScript functions.\r\n* **Subscriptions** — Introduce a subscriptions API inspired by Elm.\r\n* Make Hyperapp more suitable for multi-app design (running multiple apps on a single page).\r\n* **Types** — Make Hyperapp typing simpler and easier to get right.\r\n\r\n## Show me the money!\r\n\r\n### The Simple Counter\r\n\r\nLet's begin with a simple counter and kick it up a notch afterward.\r\n\r\n```jsx\r\nimport { h, app } from \"hyperapp\"\r\n\r\nconst down = state => ({ count: state.count - 1 })\r\nconst up = state => ({ count: state.count + 1 })\r\n\r\napp({\r\n  init: { count: 0 },\r\n  view: state => (\r\n    <div>\r\n      <h1>{state.count}</h1>\r\n      <button onclick={down}>-1</button>\r\n      <button onclick={up}>+1</button>\r\n    </div>\r\n  ),\r\n  container: document.body\r\n})\r\n```\r\n\r\nThe biggest surprise here is that you no longer need to pass the actions to the app() call, wait for them to be wired to state changes and receive them inside the view or any of that wacky stuff. It just works.\r\n\r\nHow to pass data into the action? Just use JavaScript.\r\n\r\n```jsx\r\nconst downBy = num => state => ({ count: state.count - num })\r\n\r\nconst view = state => (\r\n  <div>\r\n    <h1>{state.count}</h1>\r\n    <button onclick={down}>-</button>\r\n    <button onclick={up}>+1</button>\r\n    <button onclick={downBy(10)}>-10</button>\r\n  </div>\r\n)\r\n```\r\n\r\nThis looks very similar to 1.0, the difference is that the curried function is completely justified now — not built-in or forced upon you.\r\n\r\nHere's an interesting way you will be able to reset the count.\r\n\r\n```jsx\r\nconst view = state => (\r\n  <div>\r\n    <h1>{state.count}</h1>\r\n    <button onclick={{ count: 0 }}>Reset</button>\r\n    <button onclick={down}>-1</button>\r\n    <button onclick={up}>+1</button>\r\n  </div>\r\n)\r\n```\r\n\r\nYes, you just put the value <samp>{ count: 0 }</samp> there and you're done.\r\n\r\n### Side Effects\r\n\r\nOk, time to cut to the chase. How are going to do async stuff now?\r\n\r\n```jsx\r\nimport { h, app } from \"hyperapp\"\r\nimport { delay } from \"@hyperapp/fx\"\r\n\r\nconst up = state => ({ count: state.count + 1 })\r\n\r\n// This is how we define an effect (Elm calls it commands).\r\nconst delayedUp = delay(1000, up)\r\n\r\napp({\r\n  init: { count: 0 },\r\n  view: state => (\r\n    <div>\r\n      <h1>{state.count}</h1>\r\n      <button onclick={up}>+1</button>\r\n      <button onclick={delayedUp}>+1 with delay</button>\r\n    </div>\r\n  ),\r\n  container: document.body\r\n})\r\n```\r\n\r\nWhat if I want to set the state to something and cause a side effect to happen at the same time? I have you covered (almost). In Elm, they have [_tuples_](http://package.elm-lang.org/packages/elm-lang/core/latest/Tuple), but in JavaScript we only have arrays. So, let's use them.\r\n\r\n```js\r\nconst down = state => ({ count: state.count - 1 })\r\nconst up = state => ({ count: state.count + 1 })\r\nconst delayedUp = delay(1000, up)\r\nconst eventuallyDidNothing = state => [down(state), delayedUp]\r\n```\r\n\r\nNotice that creating a function for actions and effects is A Good Practice, but nothing prevents you from doing this:\r\n\r\n```jsx\r\nconst eventuallyDidNothing = state => [\r\n  { count: state.count - 1 },\r\n  delay(1000, state => ({\r\n    count: state.count + 1\r\n  }))\r\n]\r\n```\r\n\r\nWhat about a full example that fetches some information on initialization? The following example is ported from Hyperapp + Hyperapp FX [here](https://github.com/hyperapp/fx#getting-started).\r\n\r\n```jsx\r\nimport { h, app } from \"hyperapp\"\r\nimport { http } from \"@hyperapp/fx\"\r\nimport { url } from \"./utils\"\r\n\r\nconst quoteFetched = (state, [{ content }]) => ({ quote: content })\r\nconst getNewQuote = http(url, quoteFetched)\r\n\r\napp({\r\n  init: () => [{ quote: \"Loading...\" }, getNewQuote],\r\n  view: state => <h1 onclick={getNewQuote} innerHTML={state.quote} />,\r\n  container: document.body\r\n})\r\n```\r\n\r\n### Handling data from DOM events?\r\n\r\nDOM events, like effects, produce a result or have data associated with them (http fetch response, DOM `event`, etc). \r\n\r\n```jsx\r\nconst textChanged = (state, event) => ({ text: e.target.value })\r\n\r\napp({\r\n  init: { text: \"Hello!\" },\r\n  view: state => (\r\n    <main>\r\n      <h1>{state.text}</h1>\r\n      <input value={state.text} oninput={textChanged} />\r\n    </main>\r\n  )\r\n})\r\n```\r\n\r\n### Interoperability\r\n\r\nAbsolutely. The app function will now return a `dispatch` function (instead of \"wired actions\") so you can execute actions or effects at will.\r\n\r\n```jsx\r\nconst { dispatch } = app(...)\r\n\r\n// And then later from another app or program...\r\n\r\ndispatch(action)\r\n// or\r\ndispatch(effect) // Time.out, Http.fetch, etc.\r\n```\r\n\r\n### Dynamic Imports\r\n\r\nWhat about dynamic imported components out of the box?\r\n\r\n```jsx\r\nimport { h, app, dynamic } from \"hyperapp\"\r\n\r\nconst Hello = dynamic({\r\n  loader: () => import(\"./Hello.js\"),\r\n  loading: <h1>Loading...</h1>\r\n})\r\n\r\napp({\r\n  init: { name: \"Bender\" },\r\n  view: state => <Hello name={state.name} />,\r\n  container: document.body\r\n})\r\n```\r\n\r\n### Subscriptions\r\n\r\nThere's one more important **new** feature: Subscriptions. Aptly ripped off Elm's [subscriptions](https://www.elm-tutorial.org/en/03-subs-cmds/01-subs.html), this is how we are going to listen for external input now.\r\n\r\n```jsx\r\nimport { h, app } from \"hyperapp\"\r\nimport { Mouse } from \"@hyperapp/subscriptions\" // I am open to a shorter name.\r\n\r\nconst positionChanged = (state, mouse) => ({ x: mouse.x, y: mouse.y })\r\n\r\nconst main = app({\r\n  init: {\r\n    x: 0,\r\n    y: 0\r\n  },\r\n  view: state => `${state.x}, ${state.y}`,\r\n  subscriptions: state => [Mouse.move(positionChanged)],\r\n  container: document.body\r\n})\r\n```\r\n\r\n### Middleware?\r\n\r\n```jsx\r\nimport { appWithLogger as app } from \"hyperapp\"\r\n\r\napp({ /* Your app here... */ })\r\n```\r\n\r\n### Other\r\n\r\n- Some things are still undecided. Should Hyperapp export all side effects? Perhaps we can reuse `@hyperapp/fx` for that?\r\n\r\n- I am going to remove slices! But now that actions are **decoupled** from the state update mechanism, we should be able to come up with similar patterns doing something similar to Redux's `combineReducers`.\r\n\r\n- Actions return a new state (not a partial state), so you need to merge the current state with whatever you return, similar to redux.\r\n\r\n- Lifecycle events? I am still not sure how to handle these. For now, I'll keep them the way they are.\r\n\r\n- Bundle size? All the core stuff is, in fact, _less_ than current Hyperapp, but adding effects to that will makes us closer to 2 kB. \r\n\r\n- My interests have shifted to care more code readability and less about code golfing. A tiny code base is still a big priority for me, but not at the expense of making the code unreadable.\r\n\r\n- Server side rendering? I'm torn on this one. Should it be available out of the box or should we continue using `@hyperapp/render`? I am going to need @frenzzy to chime on this one.\r\n\r\n- JSX, @hyperapp/html, hyperx, <samp>h</samp>. Nothing is changing about here.\r\n\r\n- The router will continue to be an external module.\r\n\r\n- I'm still working hard to improve the performance and 2.0 will not affect https://github.com/hyperapp/hyperapp/issues/499 or https://github.com/hyperapp/hyperapp/pull/663. I suspect 2.0 will have slightly better performance out of the box because of how DOM events are going to be handled now.\r\n\r\n## When is this going to be available?\r\n\r\n**Very soon**. I'll be pushing all the stuff to the [`fringe`](https://github.com/hyperapp/hyperapp/tree/fringe) branch and publish as `hyperapp@2.0-beta` as soon as I can so we all can start playing with this.\r\n\r\n## Related\r\n\r\n* https://github.com/jorgebucaran/ultradom/issues/108\r\n* https://github.com/hyperapp/hyperapp/issues/641\r\n"},"comment":{"url":"https://api.github.com/repos/hyperapp/hyperapp/issues/comments/377853985","html_url":"https://github.com/hyperapp/hyperapp/issues/672#issuecomment-377853985","issue_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/672","id":377853985,"user":{"login":"jorgebucaran","id":56996,"avatar_url":"https://avatars1.githubusercontent.com/u/56996?v=4","gravatar_id":"","url":"https://api.github.com/users/jorgebucaran","html_url":"https://github.com/jorgebucaran","followers_url":"https://api.github.com/users/jorgebucaran/followers","following_url":"https://api.github.com/users/jorgebucaran/following{/other_user}","gists_url":"https://api.github.com/users/jorgebucaran/gists{/gist_id}","starred_url":"https://api.github.com/users/jorgebucaran/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jorgebucaran/subscriptions","organizations_url":"https://api.github.com/users/jorgebucaran/orgs","repos_url":"https://api.github.com/users/jorgebucaran/repos","events_url":"https://api.github.com/users/jorgebucaran/events{/privacy}","received_events_url":"https://api.github.com/users/jorgebucaran/received_events","type":"User","site_admin":false},"created_at":"2018-04-02T04:49:55Z","updated_at":"2018-04-02T04:49:55Z","author_association":"OWNER","body":"@rbiggs Fixed it. I am saying our current typings are that very good and the reason: slices and wired actions. With that gone, getting the types right (while at the same time keeping your sanity intact) will be achievable."}}
{"actor":{"display_login":"jorgebucaran"}}