-
Notifications
You must be signed in to change notification settings - Fork 781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Hyperapp 2.0 #672
Comments
Sounds exciting ! However, in the code example for the "Dynamic Imports" section you use |
@icylace Yes, that's mistake and I just fixed it. Thanks for spotting it! 🙇 |
Will there be a way to provide your own custom managed fx? I'd also like to see a managed fx version of lifecycle events and ideally |
Yes.
I need your help here! 😉
Me too! But we're going to need a webpack/parcel guru to help with it; because of how they work by default.
Absolutely. 💯 |
Executing existing actions/fx from lifecycle events should be trivial to support, this is already handled by To make the DOM element available for interop like in the existing |
So where does this leave Ultradom? EOL? |
By the way, cudos on the rad redesign plan. Sounds like Hyperapp going into overdrive. 😉 |
@rbiggs Good question!
Fine. In good standing. I can maintain two similar projects. Now three was too many. Having said that, we can continue exploring using ultradom in HA 2.0. A few things I want to add: the new HA will be purer, and this may drastically change how lifecycle events are used — also components are going to be lazy by default (a good thing!) and I want to see how much we can improve areas like inline CSS, CSS in JS, concatenating classes using Some of these changes should make it to ultradom anyway, but maybe some will be "conceptually" incompatible with it. |
Sound good to me. By the way, about inline CSS. You could support string and object values with just a check like this: function updateAttribute(element, name, value, oldValue, isSvg) {
...
} else if (name === "style" && typeof value !== 'string') { That would let you have your cake and eat it to 🍰🍴You could then use normal inline CSS or a JavaScript object in case you need to make some style calculations based on a dynamic value. Not being able to using string-based inline styles has been a major pain point for me because I use a lot of SVG images. These invariably have tons of inline styles scatter around in all their various parts. The first time I used a bunch of SVG images with Hyperapp I spent hours trying to figure out why they weren't rendering. And then quite a bit of time digging through their code to convert the inline styles into JavaScript objects. It's so much easier to just support both. Please? All it requires is the above type check to have both ways of supporting inline CSS styles. |
@rbiggs Definitely. Just a simple |
Wow, Do Also, would you mind elaborating on the decision to remove the |
No, they definitely don't. I'll update the examples to better reflect what I have in mind.
It seems you misunderstood
|
That would be much appreciated!
I think I do understand, but feel free to correct me here: To provide actions - in 1.0 you have the To call actions externally - in 1.0 the Basically the code goes from: const actions = {
up: state => ({ count: state.count + 1 }),
};
const myApp = app(state, actions, view, document.body);
myApp.up(); to const up = state => ({ count: state.count + 1 });
const myApp = app(state, view, document.body); // Or is this now all one parameter?
myApp.dispatch(up); Without fully understanding the decision, my personal preference would be to continue using the 1.0 technique as I feel it's cleaner and doesn't introduce new API for other programs to use. |
@developerdizzle In 2.0, dispatch will only serve to interop, so to call functions externally you will definitely use dispatch as you showed above. Now, because in 1.0 you often do interop (since there are no subscriptions) this sounds inconvenient, but in 2.0 you will rarely do interop, because there will be a subscription API. For example, in 1.0 you would use interop to implement the following code. In 2.0 you don't need to. import { h, app } from "hyperapp"
import Mouse from "@hyperapp/mouse"
const positionChanged = (state, mouse) => ({ x: mouse.x, y: mouse.y })
const main = app({
init: {
x: 0,
y: 0
},
view: state => `${state.x}, ${state.y}`,
subscriptions: state => [Mouse.move(positionChanged)],
container: document.body
}) |
Can I ask about the rationale behind this one? I really like the way this works now, it makes nested actions a lot less verbose than if I would have to merge myself in each action. I guess the current behaviour might be surprising if you haven’t read the docs carefully though. EDIT: just to not come out as purely negative: I really enjoy using HyperApp and love the fact that it is under heavy development! The ideas you have for 2.0 are really interesting, so I am in fact looking forward to it ;) |
If custom fx are passed the current state tree then you could make a (this is how |
@bendiksolheim I am still open to debate those little details. One thing I want to remove in 2.0 is slices, and for that reason, a shallow merge is not really that helpful. |
I'm glad to see subscriptions coming back. I was sad to see them go way back when. You mention the things you want to work on and the last item is types. Do you mean something like React's PropTypes? |
@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. |
Not really sure what you mean about slices, @jorgebucaran , I am not completely into all the concepts of Hyperapp yet :) But thinking about it a bit more, it makes sense to return the full state when actions no longer follow the same structure that the state does. I don’t see any obvious way around it, actually. Maybe an alternative is a helper function to easily update nested state? Something like a more powerful version of |
This all looks neat! The main things still missing for me is reuse: components. For example, what if it were possible to have managed, auto-mounted app instances as components, by not initially specifying a container?
Component attributes would merely override any state properties set by init. Component reuse is by far the biggest missing thing missing for me in any of the micro frameworks - and the techniques I've seen where lifecycle hooks are being used to create and mount more apps is not user-friendly enough for productive daily use. |
@mindplay-dk Have you given up on the single state tree architecture? 🤔😉 |
It doesn’t provide the convenience, modularity or encapsulation that components provide - all of those factors are critical for efficient daily authoring and reuse. I still work with a single application state model for self-contained applications - but components, in practice, have internal state details that are of no relevance to the application; the application shouldn’t have to manage the life-cycle of a date-picker, for example, anymore than it has to manage the life-cycle of a regular DOM input. (Custom Elements may address this need in the future, but we’re not quite there yet.) |
@mindplay-dk Your opinion and comments are very valuable to me but have you considered that maybe the problem is that you are too conditioned to think in terms of "reusable components"?
I don't know of any other micro-frameworks embracing the single state tree architecture. Sounds like Hyperapp to me. I know I haven't properly solved this problem yet in 1.0, but I want to with 2.0. I love this quote from @wintvelt:
|
This comment has been minimized.
This comment has been minimized.
@mindplay-dk Perfect. You spoke exactly what I am also facing and expect something smart and out of the box. I am with you to have some mechanism to promote abstraction via state full component as well as something simple to get the state management done right. I am just wondering if we think in direction of Mobx for state management of entire app and stateful component for abstraction of complex component like datepicker, etc. I just read this https://medium.com/@botverse/enjoying-mobx-jsx-and-virtual-dom-621dcc2a2bd5 and though of sharing. I am not sure how much relevant it is but it take care of one part of the problem. Refer this too https://github.com/francesco-strazzullo/mobx-virtual-dom-example. Its old repo just for the sake of discussion. |
To share some input in the other direction - I like hyperapp because it's essentially a minimalist React + Redux in a single framework with less magic (no providers, connect functions, etc). If you remove the single state tree and add component state what would make hyperapp any different than React or VueJS? Why not just use those (or if you want something smaller Preact) instead? I'm very interested in a lot of the 2.0 changes, but worry a little hyperapp will turn into just another React clone like Preact or Inferno. |
How about a What do you think of something like this? const logger = dispatch => action => {
const getState = () => dispatch(state => state)
console.log('before state: ', getState())
console.log('action: ', action)
dispatch(action)
console.log('next state: ', getState())
}
app({
enhancer: logger
}) |
@okwolf TBH I'm still not sure. I don't want to give users too much power, but I do want a way to "hook into" the dispatch function, so that you can, e.g., record actions for later replay. |
@jorgebucaran so you will not take part in fxapp? |
@jorgebucaran has reviewed some of the work I've done on |
I've been thinking about the local state problem and I think the following user-space plugin would get around rebuilding of virtual dom everytime: export interface IWarpBubble {
warp(
BubbleConstructor: WarpBubbleConstructor,
...constructorParams: any[]
): WarpBubble;
}
export interface WarpBubbleConstructor {
new(...args: any[]): WarpBubble;
}
export class BubbleMap extends WeakMap<any, any> {
}
export class WarpBubble implements IWarpBubble {
childBubbleMap: BubbleMap | null = null;
warp(
BubbleConstructor: WarpBubbleConstructor,
...constructorParams: any[]
): WarpBubble {
if (!this.childBubbleMap) {
this.childBubbleMap = new BubbleMap();
}
if (!constructorParams.length) {
throw new Error('Cannot engage new warp bubbles without keys');
}
let bubbleMap = this.childBubbleMap;
for (let i = 0; i < constructorParams.length - 1; i++) {
let key = constructorParams[i];
let nextBubbleMap = bubbleMap.get(key);
if (nextBubbleMap && nextBubbleMap instanceof Map) {
bubbleMap = nextBubbleMap;
} else {
nextBubbleMap = new Map();
bubbleMap.set(key, nextBubbleMap);
bubbleMap = nextBubbleMap;
}
}
const lastKey = constructorParams[constructorParams.length - 1];
let childBubble = bubbleMap.get(lastKey);
if (!childBubble) {
childBubble = new BubbleConstructor(...constructorParams);
bubbleMap.set(lastKey, childBubble);
}
setTimeout(() => {
this.sweep();
});
return childBubble;
}
} Then it could be used like: class RootWarpBubble extends WarpBubble {
//...
}
class ChildWarpBubble extends WarpBubble {
constructor(
private typeInScope,
private index
) {}
//...
}
const root= new RootWarpBubble();
export const view = (state, actions) => (
<div>
<h1>Todo</h1>
<ul>
{state.todos.map(({ id, value, done }) => (
<TodoItem
bubble={root.warp(ChildWarpBubble, "TodoItem", id)}
id={id}
value={value}
done={done}
someDerivedValue={someDerivedValueSelector(state)}
toggle={actions.toggle}
/>
))}
</ul>
</div>
) It seems that OO approach would be appropriate for local state. In case this is useful I started a ts project for this (with more conservative gc since I don't know how aggressive vms are with WeakMap). https://github.com/russoturisto/warp-bubbles It compiles but unfortunately I'm too swamped to actually test it, but it seems like this should work just fine. It is a constrained but strait forward approach (I think). Hopefully this helps someone. Thanks, :) |
I'm very fond of Redux Devtools (http://extension.remotedev.io/) - save's me lots of time when debugging. So, here is yet another beginner question: Would 2.0 be able support a user space plugin (possibly though middleware) where I wrap each action and record a unique type for it (probably the name of the function being called)? If I understand correctly, it would then be theoretically possible to hook the HA store into the Redux Devtools plugin (not unlike what ngrx guys did - https://github.com/ngrx/platform/blob/master/docs/store-devtools/README.md). Thanks, :) |
@Russoturisto You are looking for #120! :) tl;dr: Yes. We just need to decide what the middleware API is going to look like. |
Awesome, thank you! :) |
On a related devtools topic, will it be possible to inspect/manipulate the VDOM from the browser like the React devtools in 2.0? I guess we would need to provide an API for this or attach the vnode data to the actual DOM nodes. This could become a dev build only thing. (See #417) |
Am I the only one who think that this I haven't used |
EDIT: Chances are this little feature will not make it to 2.0, two reasons why:
@emil14 Actions are usually a function, but when I wrote that example I was thinking they could be an object too. I'm still not sure whether that feature will make it to the 2.0 cut or not, but I'll explain what I had in mind when I originally wrote it. For example: const reset = { count: 0 }
const increment = state => ({ count: state.count + 1 }) then use them like so: <button onclick={reset}>Reset</button>
<button onclick={increment}>Increment</button> Let's see another example. In the typical ToDo list app you usually have a way to filter items by completion, todo, etc. import { filter } from "./actions"
//...
<div>
{Object.keys(Filters)
.filter(key => Filters[key] !== state.filter)
.map(key => (
<a href="#" onclick={actions.filter({ value: Filters[key] })}>
{key}
</a>
))}
</div> The relevant part is the onclick handler: onclick={filter({
value: Filters[key]
})}
const filter = ({ filter }) => ({ filter }) API details are still subject to change as I finish wrapping 2.0. So take this with a grain of salt! 🌊 |
Hi @jorgebucaran, |
Hey @etylsarin! Thank you for your interest. I'm releasing the 2.0 alpha on the first week of July! :) |
#726 🎉 |
TL;DRI've created a series of issues to document the decision process behind the upcoming V2 API changes rendering this issue now obsolete. Thanks to everyone who contributed their feedback. If you stumbled upon this issue today, refer to the ones below to get the most up-to-date and accurate information, otherwise enjoy the discussion above!
👉 V2 Branch #726
|
Background
After 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.
Predictable as it may be, my plan was to leave things the way they are here and create a new 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!
Breaking 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.
What will change?
With 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:
Hyperapp 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:
dynamic(import("./future-component"))
. Fix Dynamic actions: How to add new actions at runtime? #533.Show me the money!
The Simple Counter
Let's begin with a simple counter and kick it up a notch afterward.
The 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.
How to pass data into the action?
Just use JavaScript.This looks very similar to 1.0, the difference is that the curried function is completely justified now — not built-in or forced upon you.
Here's an interesting way you will be able to reset the count.
Yes, you just put the value { count: 0 } there and you're done.
Side Effects
Ok, time to cut to the chase. How are going to do async stuff now?
What 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, but in JavaScript we only have arrays. So, let's use them.
Notice that creating a function for actions and effects is A Good Practice, but nothing prevents you from doing this:
What about a full example that fetches some information on initialization? The following example is ported from Hyperapp + Hyperapp FX here.
Handling data from DOM events?
DOM events, like effects, produce a result or have data associated with them (http fetch response, DOM
event
, etc).Interoperability
Absolutely. The app function will now return a
dispatch
function (instead of "wired actions") so you can execute actions or effects at will.Dynamic Imports
What about dynamic imported components out of the box?
Subscriptions
There's one more important new feature: Subscriptions. Aptly ripped off Elm's subscriptions, this is how we are going to listen for external input now.
What's breaking?
#1to2
channel in Slack, please join! 🎉Other
Middleware
Some things are still undecided. Should Hyperapp export all side effects? Perhaps we can reuse
@hyperapp/fx
for that?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
.Actions return a new state (not a partial state), so you need to merge the current state with whatever you return, similar to redux.
Lifecycle events? I am still not sure how to handle these. For now, I'll keep them the way they are.
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.
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.
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.JSX, @hyperapp/html, hyperx, h. Nothing is changing about here.
The router will continue to be an external module.
I'm still working hard to improve the performance and 2.0 will not affect Rewrite patch algo / improve diffing performance #499 or Keyed children diff optimization #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.
When is this going to be available?
Very soon. I'll be pushing all the stuff to the
fringe
branch and publish as[email protected]
as soon as I can so we all can start playing with this.Related
The text was updated successfully, but these errors were encountered: