Believe it or not, one instance of hyperapp in one of my projects isn't using any view whatsoever. So here I am submitting a bug report to a frontend library that it crashes when no view is given...
app({
state: 'Example',
events: { loaded: () => console.info('Loaded!') }
})
@dodekeract Two things to consider here:
1) Is this a user bug or
2) do we want to support apps without a view?
To me the answer is (1) and this is the kind of bug I hope to catch using a dev build of the project someday. If the answer was (2) then we could simply add a default view of () => "".
Now, I don't see the point of an app without a view. You want a mixin instead.
@jbucaran Thanks for assuming I don't know what I'm doing, but I really don't want a mixin instead. The hyperapp instance I'm talking about is running inside an iframe to take advantage of cross origin iframe policies (and CORS) to protect user privacy. It doesn't show anything and only communicates with an API. However, since it shares a lot of code with other iframes running in the same tab I need to take advantage of the rest of hyperapp's architecture (state, actions, events) to re-use code.
If the answer was (2) then we could simply add a default view of () => "". — @jbucaran
Why do we have to render anything at all? Neither root nor view is specified so the most sane thing is to render nothing.
@dodekeract What should hyperapp do in this case then?
I am not sure I want to provide you a empty view by default when you can simply do it like this:
view: () => ""
or
view: () => <meta />
etc.
@jbucaran I just think that hyperapp shouldn't attempt to render if there is no view. It should just not attempt to render anything if there is no function to call.
Wouldn't "" still render a text node?
Wouldn't "" still render a text node?
Correct.
It should just not attempt to render anything if there is no function to call.
This would complicate render and schedule and I don't know if introducing this complexity is any worth it.
I think that by orchestrating multiple apps you are already walking a thin line, crossing outside the "safe zone". It also seems to me that at that point, view: () => "" should be the least of your worries.
I have one app per frame. That's the same as having multiple web applications. The only place where it makes a difference regarding hyperapp is for my reasoning to use it even though I don't actually need any view. Other than that, the instances exclusively communicate via HTML5 postMessage, which is very similar to a "normal" hyperapp app that uses e.g. WebSockets.
I reported this as a bug not because I think it's a critical feature, but because I think that hyperapp should never crash, if there is no good reason to crash.
@dodekeract I have one app per frame.
That's interesting. I guess this is not exactly what I was thinking above.
I am not exactly sure how to proceed here.
99% of the time you use a view. You may not need a view at this moment inside your iframe app, but you may change your mind later and decide to add it.
If you remove view and render from a hyperapp, then you are essentially left with a state management library and event bus.. which, if worked as expected, could be useful I guess?
I've not tried it out but presumably, this could be made to work by adjusting the update function https://github.com/hyperapp/hyperapp/blob/master/src/app.js#L37 to look something like:
function update(withState) {
if (withState != null) {
state = merge(state, emit("update", withState))
if(view) repaint(state)
}
}
size, perf or the api?@lukejacksonn We call repaint in two places. So, it is either adding another if (view) or inside do prevent calling view inside render, but then we end up scheduling anyway (in the latter approach).
Another implementation option is to make the view ()=>"" by default.
Does it disrupt size, perf or the api?
Size and perf for an extremely little, I guess.
@jbucaran I'm not even sure if it actually affects the performance at all. V8 might be able to optimise it to not even run the check.
@dodekeract Can you share some of the code or a non-working example that helps visualizing the architecture you are working with?
And, if you were using a different library, like React, what do you think you'd do?
I can't share any real code, but here is some pseudocode that basically shows what's going on:
app({
state: { type: 'api' },
// SomeSharedMixin exposes actions.onMessage
mixins: [SomeSharedMixin],
events: {
loaded: (state, actions) => window.addEventListener('message', actions.onMessage)
}
})
As you can see, I don't need a view in this instance, but since SomeSharedMixin also is used for communication in all other instances of hyperapp (running in a different frame though) it doesn't make sense to re-write the mixin just to be "technically correct".
app({
state: { type: 'ui' },
// SomeSharedMixin exposes actions.onMessage
mixins: [SomeSharedMixin],
events: {
loaded: (state, actions) => window.addEventListener('message', actions.onMessage)
},
view: () => <div>Main UI <iframe src="other-hyperapp-instance"/></div>
})
In React, I'd probably use redux directly without using React-DOM.
@dodekeract Good news! I'll add support for view-less apps in the next release. 👍
I'll implement this simply by not calling the render function. An app without a view will not fire a render event either.
EDIT: The new patch event won't fire either, evidently! 😄
@jbucaran Good, it's easy to workaround, but doesn't make sense to crash needlessly.
@lukejacksonn Seems that a hyperapp instance without a view will be very similar to EventHub in Vue but (in my eyes) will be simpler. Something like this I imagine:
_Codepen example_: https://codepen.io/selfup/pen/jLMRjO
JS
/** @jsx h */
const { h, app } = hyperapp;
const HYPERHUB = app({
state: { num: 0 },
actions: {
hello: (state, actions, { num }) => {
console.log(num);
return { num };
},
},
events: {
dispatch: (state, actions, { action, data }) => {
return actions[action]
? actions[action](data)
: null;
},
},
view: (state, actions) =>
<div>
<h1>{state.num}</h1>
</div>,
root: document.querySelector('#app'),
});
const dispatch = (action, data) => HYPERHUB(
'dispatch',
{ action, data },
);
dispatch('hello', { num: 42 });
// not hyperapp stuff calling hyperapp through hyperhub
document.addEventListener('click', ({ target }) => {
switch(target.id) {
case '9000':
dispatch('hello', { num: 9000 });
break;
case '42':
dispatch('hello', { num: 42 });
break;
default:
null;
break;
}
});
HTML
<button id="9000">HyperHub 9000 from regular JS</button>
<button id="42">Back to 42</button>
<div id="app"></div>
<script src="https://unpkg.com/hyperapp"></script>
This way, devs with already existing frontends like React/Vue/Angular/Ember/etc... can start to trickle in hyperapp and share Flux stores with the native hyperapp emit which can enable them to start porting views to hyperapp if need be.
_^ ^ that sentence is kind of a mess :P_
I dunno, either way it is always fun to explore what can be done 😂
@selfup nice one! This is how I saw it being useful 👍
@dodekeract Implemented in #317.
Most helpful comment
@dodekeract Good news! I'll add support for view-less apps in the next release. 👍
I'll implement this simply by not calling the
renderfunction. An app without a view will not fire arenderevent either.EDIT: The new
patchevent won't fire either, evidently! 😄