As most of you may be well aware of, Hyperapp 2.0 is coming #672. π
Here's my todo list before I am ready to publish its beta test version.
clone.js, patch.js, h.js, app.js, etc.)defer function. Instead of setTimeout, use Promise.resolve (faster).dispatch, setState, runCommand, render.patch, createElement, updateAttribute) to accept resolveNode and eventProxy).resolveNode to support children as a prop.dispatch as the second argument to every component or similar.updateAttribute to compute a Element.className from an object, similar to classcat, but built-in.Your comments and feedback, please! π
Glad there is a thought out plan! Looking forward to the changes π
Is there a task for middleware missing? Or will that be part of another task like dispatch?
The topic we were all awaiting π
Is there some things we can help on ?
@Swizz Tests π and testing the beta release when it's published, also documentation ideas and the website, but notice I didn't include that on the todo list. Hyperapp 2.0 is coming first.
Great to see a todo list!
A few questions:
If you're going to use Promise.resolve, what does this mean for your intended browser support?
I'm sure most apps that support IE11 will already be bundling a Promise polyfill, but for those who weren't, this will need some documentation.
What is the "targeted merges"? A utility function to manage updating nested state, or something like a vdom optimization?
Will you be maintaining a 1.x branch and 2.x branch in parallel? I ask, because 1.x is already an extremely useful and nice framework, with some features that you seem to be dropping (state slices, "context-like" functionality of the lazy components).
Could be an opportunity to make 2.x take advantage of modern language & platform features on modern browsers, and 1.x be maintained for older browsers and people who aren't interested in the managed effects system. Food for thought :shallow_pan_of_food:
setTimeout if typeof Promise === "undefined".One way or another, 2.x is the way forward and how I plan to invest most of my Hyperapp time in the future.
@SkaterDad Targeted/aimed merges (or targeted/aimed updates) work like this:
<button onclick={{
foo: {
bar: state => {
// state === globalState.foo.bar
return { justLikeSlices: true }
// globalState = shallowMerge(
// immutablySet(globalState, "foo.bar", {
// justLikeSlices: true
// })
// )
}
}
}} />
With a graceful 1.x to 2.x path, the adoption will be easier.
Hyperapp 1.x is not here since years, and I am pretty sure, Hyperapp 2.x will allow all this features to live in userland :+1:
@jorgebucaran 3.1. There may be lazy components, after all, I'm not too sure about it. How are you using its context-like functionality? I'd really like to hear that.
My main app codebase was mostly in place by the time lazy components landed, so I'm not using them very much yet. There's one or two places where I used them to access state, but those components weren't deeply nested, so I mostly did it because I could. I'm sure some other people have used/abused it more than I have :laughing:
The biggest benefit my app had from lazy components was being able to access the global actions object, especially for my router's Link component. With 2.0's version of actions, this use case goes away.
Yeah access to global state and actions are somewhat like the new context API in React.
But maybe lazy components not only allow for things like a more elegant way of injecting dynamic actions without rewriting the actions, but could provide us with a more elegant alternative to things like Redux as well.
And maybe we could write components like
// This is only possible because Connector is a lazy component
// that knows the underlying state
// Maybe mutations should know the state whole, but I donβt know
var Component = function (props) {
return (<Connector render={function (stateSlice, mutations, operations) {
return (<Selector from={stateSlice} render={function (selectedState) {
// Actual rendering goes here.
}} />);
}} />);
};
In the React community, there has been a recommended practice that
In general, the shape of the state is something only reducers should know about. The moment it leaks out of the store, code becomes structurally coupled.
Since reducers decide whatβs the shape because they write it. Itβs just common sense to make the βreadβ happen close to them with selectors.
As a rule of thumb, doing
state.is a mistake. The only place where that should be allowed is inside a selector. But only as long as the selector is [collocated] in the reducer which is responsible for that part of the state.
βhttps://hackernoon.com/selectors-in-redux-are-a-must-d6b0637c79b7
So revealing the state to userland is not a good practice. But for library authors, are lazy components a must? I am not so sure.
@infinnie I organized all your comments in the same post. I don't want this issue to go offtopic. Thanks.
@jorgebucaran Never mind & thanks. π
I'm so excited about this, I can't wait to start beta testing. I'd love to render some help though. π (I'm probably good at writing tutorials).
Hi, is there any reason code is not written in ES6 ?
@bardiarastin Other than ES modules, we're not using any modern JavaScript idioms.
The main reason is I want full control over the code I am writing and shipping and don't want to introduce a build step to the project. It's a modest attempt to make a case for simplicity.
π for a new subscription library
π for middleware API (#703)
First of all thanks for Hyperapp 1.x, it was just what I needed and all in a tiny lib.
I have a couple of projects running in 1.x that are good candidates to beta test the 2.0.
Curious if you'll refactor patch algo to improve Hyperapp performance in comparison to other frameworks on Stefan Krause's js-framework-benchmark.
@rbiggs Not for 2.0, but after (when I have time again). FWIW #663 improved those numbers substantially. You are welcome to discuss performance more in #499.
I am absolutely sure that you should use ESNext or maybe even TypeScript in hyperapp's sources.
Modern JavaScript gives you a better performance and much more simple code. Hyperapp's user already has Node.js and npm, probably used React and know something about Babel and Webpack. A lot of new features work in modern browsers out of the box and Hyperapp works on IE10+. There is no need to use ES5.
You, as a library developer, should teach developers to best practice. Build tools are good things and there is nothing bad if someone has to learn a bit more about ecosystem. Especially due to great Parcel which is very simple
@emil14 Hyperapp doesn't need any modern JavaScript features at the moment, so performance is not affected because of this decision. For example, if we wanted to use Set or Map and because of poor browser support shipped our own shims, then you'd be totally right β we'd be better off using the browser's native Set and Map.
Things Hyperapp doesn't use or avoids without affecting its perf:
this, prototypesFWIW, not using prototypes may in fact negatively affect performance. But you don't need any modern JS features or tooling to use them if you wanted to.
@leeoniya
I am not saying I could've used prototypes, but decided not to because I have thisphobia. π
I am saying Hyperapp doesn't make use of prototypes. Hyperapps don't have methods or properties and there are no app instances either. In 1.0, even if you had multiple apps running on the same page (not encouraged either) they are unlikely to share the same implementation of actions, so you'd need to create a different actions object for every app call.
In 2.0 there is _not even_ an actions object, so I've yet to find a use case for prototypes in Hyperapp.
i also keep this and new out of domvm's public apis. however, there are legitimate perf reasons for using prototypes internally.
Thanks, if you find a good reason to use it in Hyperapp let us know.
@jorgebucaran
Thank you for your answer! But I still don't agree with you :)
You can write sources in ESNext and let us to choose, should we transpile it or not, what browser support we do need. You can ship ES5 via CDN and add something like hyperapp.es5.js to the npm-package, don't you?
If you afraid of greater bundle size, generated by Webpack/Parcel/Rollup - do you really think it's so important ? :)
Classes, this, prototypes
I can understand that. These features are not very "functional", but the rest of them are good
You can write sources in ESNext and let us to choose, should we transpile it or not, what browser support we do need.
Nope, sorry, you are confused. Now, let me try to set the record straight.
Features like [].map, that were introduced in the ES5 revision, we _use_ a few times.
Features like classes or arrow functions introduced in the ES6 revision we _do not_ use anywhere. There is one important feature we do use, however, and that is ES modules.
https://github.com/hyperapp/hyperapp/blob/8da3edec673f9b67da03771b8713bda3a2b23519/src/index.js#L1
https://github.com/hyperapp/hyperapp/blob/8da3edec673f9b67da03771b8713bda3a2b23519/src/index.js#L29
This means that you, in fact, need to transpile Hyperapp into actual ES3 or ES5 through Babel (or your compiler of choice) when creating a production bundle.
You can also import { h, app } from "hyperapp" using type=module, for example, see this CodePen.
It's src/index.js. Here is some (mildly useless) trivia β because it's just single file, you don't even need unpkg or a CDN that can serve the raw ES modules code. The following actually works!
import { h, app } from "https://cdn.rawgit.com/hyperapp/hyperapp/8da3edec/src/index.js"
You can ship ES5 via CDN and add something like hyperapp.es5.js to the npm-package, don't you?
We do that!
https://github.com/hyperapp/hyperapp/blob/8da3edec673f9b67da03771b8713bda3a2b23519/package.json#L26-L29
If you afraid of greater bundle size, generated by Webpack/Parcel/Rollup - do you really think it's so important ? :)
We already use rollup to bundle Hyperapp into a UMD. But this is only useful for CDN users.
A few ES 6 features could have been useful like promises. π
I believe subscriptions are already implemented, but are you still planning to include classcat style support?
@okwolf Yes, I want to. What do you think? :)
@jorgebucaran I like the idea. How much bloat are we talking about adding?
Please no, it is not needed in case of using CSS Modules or CSS in JS.
@okwolf Not a lot. See https://github.com/jorgebucaran/classcat
@frenzzy This feature is consistent and harmonious with VDOM and Hyperapp. But you make a point. How do you personally use CSS modules or CSS in JS with Hyperapp? Do you have any examples? Maybe that could help me make up my mind.
EDIT: I clarify. I am quite confident about this feature. I don't strongly believe we ought to have it, but I would not be unconfortable in the slightest if we did introduce it. Treating some attributes as objects is consistent with how V2 is shaping up, e.g., actions.
@frenzzy I don't understand. How does CSS modules or CSS in JS make classcat unnecessary?
With plain css I'd use it like this:
<ul>
{items.map((text, index) => (
<li class={cc({
highlight: index === state.selected,
disabled: state.disabled.indexOf(index) >= 0
})}> {text} </li>
)}
</ul>
with css moduled (with classes imported as the var style), like this:
<ul>
{items.map((text, index) => (
<li class={cc({
[style.highlight]: index === state.selected,
[style.disabled]: state.disabled.indexOf(index) >= 0
})}> {text} </li>
)}
</ul>
sure, the syntax is a little more verbose with css modules, but in both cases classcat (cc in the example) significantly simplifies the code for toggling and concatenating classes. And that's quite a common need. In fact I use it in all but my most trivial projects. So I'm pro including it.
Edit: To illustrate how classcat helps, this is one way I might do it without classcat (better options welcome):
<ul>
{items.map((text, index) => (
<li class={
[
state.selected=== index && style.highlight,
state.disabled.indexOf(index) >=0 && style.disabled,
].filter(x => !!x).join(' ')
}> {text} </li>
)}
</ul>
... do that enough places and you start looking for a library to do it for you, or write your own.
May be classcat (and probably other features) can be separate module because of flexibility? Everyone can include it if wants or not. Also I think that it can be exists in the official doc as the recommended way.
I think it's mostly a question of: how will this feature behave when you don't need to toggle classes. I think these should (and would) all work as expected with cc included:
class={'foo'}
class={'foo bar'}
class={['foo', 'bar']}
class={{foo: isFoo()}
class={['foo', {bar: isBar()}]}
...basically: writing regular class strings should still work. But the power of classcat is there built in when you need it. (Which, again, is quite often)
I would expect the "bloat" to be miniscule and well worth it
@zaceno Yep, as you said, they'd all work.
How do you personally use CSS modules or CSS in JS with Hyperapp?
The way I use it right now is like this
// This helper generates a className and insert a style rule in the DOM.
// It can be for instance emotion-js, CSS modules "import", or a custom helper
const componentClass = generateClass('/* CSS rules here */')
const MyComponent = (props) => (
<div class={componentClass} />
)
With conditional styles, I usually use ES6 string interpolation:
const componentBase = generateClass('/* CSS rules here */')
const isComponentActive = generateClass('')
const themes = {
RED: generateClass('/* red theme */'),
BLUE: generateClass('/* blue theme */'),
}
const MyComponent = ({ active, theme }) => (
<div
class={`
${componentBase}
${active && isComponentActive}
${themes[theme ||Β 'RED']}
`}
/>
)
// By default: inactive, default theme is red
<MyComponent />
// Activates the blue theme
<MyComponent active theme="BLUE" />
For my use cases, a classcat helper is not necessary.
Usually use this simple inline class concat or can extract to a helper if used a lot:
import style from './style.css'
const FooComponent = ({ active }) => (
<div class={[style.foo, active && style.active].filter(Boolean).join(' ')} />
)
@thibautRe @gpoitch Thanks for sharing that.
Well, it seems neither CSS modules or CSS in JS would interfere with classcat-like built-in functionality.
Having classcat-like functionality built-in would be handy sometimes.
I pretty much exclusively use Tailwind.css now, with some sprinkles of custom CSS classes, so the CSS-in-JS stuff is a non-issue.
@jorgebucaran did we drop the idea of middleware? If not, maybe classcat could be vdom middleware that checks props for class, and if it's an object, parses, and if it's a string, passes that through as normal - that keeps it out of everyone's hair, but would make it easy enough to flip a switch and enable it.
@mrozbarry did we drop the idea of middleware?
See #703.
The V2 PR is here #726.
Most helpful comment
@bardiarastin Other than ES modules, we're not using any modern JavaScript idioms.
The main reason is I want full control over the code I am writing and shipping and don't want to introduce a build step to the project. It's a modest attempt to make a case for simplicity.