Skip to content
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

Hyperapp V2 #726

Merged
merged 82 commits into from
May 4, 2019
Merged

Hyperapp V2 #726

merged 82 commits into from
May 4, 2019

Conversation

jorgebucaran
Copy link
Owner

@jorgebucaran jorgebucaran commented Jul 9, 2018

Summary

👋 V2 is the pet name for the next version (2.0.0) of Hyperapp. Be sure to subscribe to this PR if you want to be informed of the progress towards the release. I'm still open to feedback and ideas, so share your feedback freely!

To learn more about the upcoming architecture changes check the issue list below.

Status Progress

Try it!

npm i hyperapp@beta

@jorgebucaran jorgebucaran added the breaking This will break things label Jul 9, 2018
@jorgebucaran jorgebucaran added this to the 2.0.0 milestone Jul 9, 2018
@codecov
Copy link

codecov bot commented Jul 9, 2018

Codecov Report

Merging #726 into master will decrease coverage by 83.69%.
The diff coverage is 16.3%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master    #726      +/-   ##
=========================================
- Coverage     100%   16.3%   -83.7%     
=========================================
  Files           1       1              
  Lines         166     233      +67     
  Branches       52      69      +17     
=========================================
- Hits          166      38     -128     
- Misses          0     130     +130     
- Partials        0      65      +65
Impacted Files Coverage Δ
src/index.js 16.3% <16.3%> (-83.7%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ebe173a...df89651. Read the comment docs.

@jorgebucaran jorgebucaran mentioned this pull request Jul 9, 2018
@Swizz Swizz self-requested a review July 9, 2018 08:50
Copy link

@mindplay-dk mindplay-dk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typescript definitions appear to be out of date? I copy/pasted the example from the README, and it fails because the .d.ts has no exported member app. (looks like a lot of the types were deleted?)

@mindplay-dk
Copy link

I also noticed there's a block of 3 commented-out functions left behind in the source.

@SteveALee
Copy link

REAME seems unchanged. As I've not been part of the V2.0 discussion I need a way get context and understand the goals. At least I prefer that before reading code.

@okwolf
Copy link
Contributor

okwolf commented Jul 9, 2018

Wow, still single file source. Is that the plan to keep that going forward?

@jorgebucaran
Copy link
Owner Author

@okwolf For starters, yeah. I had everything in its own file until the last moment.

@lukejacksonn
Copy link
Contributor

lukejacksonn commented Jul 10, 2018

I was really looking forward to seeing it all split up.. I feel the one file things obfuscates different functions reliance on the various globals. You can't get away with that when everything is split up because you have to pass a function everything it needs. Still nice code though. Much more comprehensible that the last one, great job.

@jorgebucaran
Copy link
Owner Author

I feel the one file things obfuscates different functions reliance on the various globals.

We'd just have a constants.js file and export the vnode types and every function required by any given function would need to import it at the top of the file.

@lukejacksonn
Copy link
Contributor

Yeh.. I know how it would work. I was looking forward to that structure, that is all. Thought it would make it more clear to people what is going on and what relies on what. It is ok though, I will do it myself sometime as a learning exercise.

@jorgebucaran
Copy link
Owner Author

@lukejacksonn Do you think it would be better to split up the code then?

@jorgebucaran
Copy link
Owner Author

@lukejacksonn Yeh.. I know how it would work.

For sure, but then I misunderstood this:

You can't get away with that when everything is split up because you have to pass a function everything it needs


My point is, while everything is crammed in one file, every function is independent and already expects as input everything it needs to work on and it doesn't rely on stuff which is in the global scope, other than the stuff you'd import to the top-level scope of the module in which the function is declared if it was in a file.

In other words, the amount of refactoring needed to put everything into files is minimal, that's what I meant. Sorry, if I said something confusing! 😄

@lukejacksonn
Copy link
Contributor

You didn't say anything confusing 😅 and I can see that it would easily refactor. I love the single file thing too, but I am starting to wonder if it would be easier to grok in individual files.

@Swizz
Copy link
Contributor

Swizz commented Jul 10, 2018

I love the one function per file too.
But it could be confusing too, you will need to dive into an src folder with multitude of files, to find the entrypoint, mostly index then to jump from file to file to catch all the function calls.

Here, it is like reading a story, you learn how the function works, then, when it is later called, you already know the function and how it works.

I was for the individual function files, but now I am thinking about it, I am not sure anymore.

Copy link
Contributor

@Swizz Swizz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General thoughts :

  • Still not sure about individual files, but I like the way it is in one file.
  • Would love the use of const for constantes but it is not ES3/5 compliant 🤔
  • Did not read so much the patch part (I am not a vdom scientist)

src/index.js Outdated
for (length = node.length; length--; ) {
rest.push(node[length])
var EMPTY_OBJECT = {}
var EMPTY_ARRAY = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small optimization, mainly used for text vnodes that don't have children, but still use the same VNode shape.

src/index.js Outdated
return target
}

var resolved = typeof Promise === "function" && Promise.resolve()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now Promises are used in core ? (if supported)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is just a trick to call your callback as quickly as possible.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why still use var in 2019? Please use const. No need to be ES3/5 compliant

Copy link
Owner Author

@jorgebucaran jorgebucaran Jan 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kristianmandrup Check out CONTRIBUTION.md—it's written there. Long story short: because we're not compiling Hyperapp, it's handwritten.

src/index.js Outdated
var oldChildren = oldNode.children
var children = node.children
return typeof name === "function"
? name(props, (props.children = children))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Component will received children property and children argument ?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. It's an implementation detail. If you need children for some reason, use props.children. I am still open to change this.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props.children definitely cooler then the second argument, see pros and cons here: #569
I am worried only about performance and memoization:
https://github.com/hyperapp/hyperapp/blob/2b3d85625321038ed2369ddb4ecc320a9e20984e/src/index.js#L458

If everything is ok with props.children would be nice to remove children from the second argument IMO.

Copy link
Contributor

@frenzzy frenzzy Jul 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const Component = (props) => <div>{props.children}</div>

app({
  init: { children: 'Hello' },
  view: (state) => (
    <main>
      <Component {...state}>World</Component>
      <div>{state.children}</div>
    </main>
  ),
  container: document.body,
})

Can you guess what this code will render without opening the demo?

  1. <main><div>World</div><div>Hello</div></main>
  2. <main><div>Hello</div><div>World</div></main>
  3. <main><div>Hello</div><div>Hello</div></main>
  4. <main><div>World</div><div>World</div></main>
  5. throws an error

Demo: https://codepen.io/frenzzy/pen/Zjbqpq/left/?editors=0010

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any alternatives? Why can preact get away with the same hack? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purity forever! Say NO to mutations :D

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so to clarify, we're back to (props, children)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

  • no mutations
  • no pitfalls for children key
  • simple memoization with ===

src/index.js Outdated
}
var dispatch = function(obj, data) {
typeof obj === "function"
? dispatch(obj(state, data))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever.

But the whole codeblock is pretty unreadable 🤔

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I'm working on a new implementation. 👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chaining ternaries is not so bad if you change the formatting a little. I know this is unorthodox, but it definitely helps with clarity.

typeof obj === "function" ?
    dispatch(obj(state, data))
: isArray(obj) ?
    obj[1].effect(obj[1].props, dispatch, setState(obj[0]))
: obj != null && typeof obj.effect === "function" ?
    dispatch([state, obj])
: setState(obj)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No can do, we're using prettier.

}

dispatch(props.init)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return dispatch

?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm still not sure if I should return it or not.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispatch doesn't return anything, so it will be undefined either way, right?

Copy link
Contributor

@Swizz Swizz Jul 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is about returning the dispatch function itself not the dispatch(props.init) result.

Just commented on this line as this is the last line of the app function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to get the state of your application at specific moment of time? For example for HMR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should interop be done with subscribe then?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frenzzy There is currently no way other than using subscriptions. But I am open to exploring alternatives if need be.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@okwolf Bingo.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like Elm. But still, if we can think of an alternative, e.g., app.getState(), I am happy to discuss it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorgebucaran we should start by trying to build things that need interop and find where this approach gets ugly.

src/index.js Outdated
}
*/

export function app(props) {
Copy link
Contributor

@Swizz Swizz Jul 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about instead of inline exports, using the following block at the top or the bottom of the file

export { h, app }

To make clear that h and app will be exported.

I am using this pattern and I found it verry clever. (I learned it from Erlang).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could like that. Let me do some research.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is definitely more clear when all exports goes near to each other, but I found out that for example webpack generate more noise when you export a name instead of an expression.

src/index.js Outdated
var dispatch = function(obj, data) {
typeof obj === "function"
? dispatch(obj(state, data))
: Array.isArray(obj)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be isArray(obj) for the sake of consistency?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! Good catch and thanks. I'll push updated code soon.

src/index.js Outdated
var length = arguments.length
var DEFAULT = 0
var RECYCLED_NODE = 1
var TEXT_NODE = 2

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems misleading.

https://github.com/hyperapp/hyperapp/blob/2b3d85625321038ed2369ddb4ecc320a9e20984e/src/index.js#L420

Should your internal constants match the JS spec, or should this be renamed to avoid confusion?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dangerousdan Haha you do have a point. I'll fix it.

@jorgebucaran
Copy link
Owner Author

@atomiks #726 (comment)

I considered something like it, but I feel now that a mixed approach would break the balance. It's all or nothing. Split the code or not, but not in between is what I mean :)

src/index.js Outdated
eventProxy,
isSvg
) {
if (name === "key") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also be

if(name === "style") {
  ...
} else if (name !== "key") {
  ...
}

To avoid an empty if

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a fan of the "exit early" pattern

if (name === "key") return

// Do other ifs

Copy link
Owner Author

@jorgebucaran jorgebucaran Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is just personal taste or there's something to this. I mean, my brain finds it a lot easier to think of this as: (if something is true; do nothing), instead of (if not something; do something). Not always, though.

src/index.js Outdated
: document.createElement(node.name)

var props = node.props
if (props.onCreate) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So lifecycle events are still supported and use camelCase now? 🤔

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave in to camelCase :)

...lifecycle events

They're in this branch, for now. I still haven't decided whether to remove them or not. I know we don't need them, but having them can be useful. You can help me decide by adding your feedback to #717.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but having them can be useful.

I'm glad the hooks are still there so I don't yet have to use custom elements and allow this in my codebase 🤢

@okwolf
Copy link
Contributor

okwolf commented Jul 11, 2018

Will hyperapp@2.0.0-beta.0 be published from this branch or not until merged to master?

@jorgebucaran
Copy link
Owner Author

@okwolf Published from master after it's merged (with docs). Future V1 releases will be published from the V1 branch, though.

}

dispatch(props.init)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispatch(props.init || {})

Providing init with an empty state permits to omit it in case of concise example or stateless app. We can write like:

app({
  view: () => (
    <div>
      Lorem ipsum dolor si amet
    </div>
  ),
  container: document.body
})

instead of:

app({
  init: () => ({ }),
  view: () => (
    <div>
      Lorem ipsum dolor si amet
    </div>
  ),
  container: document.body
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props.init is a function, so it should be

dispatch(props.init || function() {})

or in a more concise way

dispatch(props.init || Function())

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, dispatch works with objects too.

(part of the unreadable ternary 🙄 )

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup.

Copy link

@kumarabhishek kumarabhishek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My major comment is to use as much ES6 as possible to make code simpler, smaller and performant.

package.json Outdated
"rollup": "^0.57.1",
"typescript": "2.8.1",
"uglify-js": "3.3.16"
"babel-preset-env": "^1.7.0",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use latest babel 7 packages ?

I have recently tried using rollup and parceljs for bundling one of my library and finally i ennded up using webpack 4.15.x because i wanted to use latest babel packages as descibed below:

    "@babel/cli": "^7.0.0-beta.51",
    "@babel/core": "^7.0.0-beta.51",
    "@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.51",
    "@babel/plugin-syntax-dynamic-import": "^7.0.0-beta.51",
    "@babel/preset-env": "^7.0.0-beta.51",
    "@babel/preset-react": "^7.0.0-beta.51",

If rollup is finalised for V2 then ignore this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorgebucaran I would not anticipate babel 7 leaving beta soon. They have many open issues.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying that! @ggassmann

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/index.js Outdated
if (state !== newState) {
state = newState

if (!updateInProgress) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will happen if updateInProgress is true ? Are we ignoring all setState() call till updateInProgress is true ? Just a wild thought of saving it to tempState(keep overwriting till updateInProgress is true) and apply the latest value of tempState once updateInProgress becomes false.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@k-murakami0609 Are we ignoring all setState() call till updateInProgress is true ?

Nope! We're avoiding patching, that's it. In other words, state updates are pretty much immediate.

cb(element, done)
} else {
done()
export var h = function(name, props) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we are exporting h object here so this source file becomes ES6 module. So why then we are not using all power of ES6. To name a few, using const, Object.assign, spread operator, etc which will make code more readable, smaller (not larger at least) and simple without adding any burden to performance. Making methods will easily become one liners.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this file gets transpiled. Rollup supports specifying UMD, which is how this project is configured, which is probably where the export support comes from.
https://github.com/hyperapp/hyperapp/blob/2b3d85625321038ed2369ddb4ecc320a9e20984e/package.json#L27

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kumarabhishek https://github.com/hyperapp/hyperapp/blob/master/CONTRIBUTING.md#style


FYI: We're not losing any performance from not using more ES6 idioms.

But that could change in the future if we start using things like Maps or WeakMaps and they become faster than regular objects? 🤔

src/index.js Outdated
var listen = function(sub, oldSub, dispatch) {
if (isArray(sub) || isArray(oldSub)) {
var out = []
var subs = isArray(sub) ? sub : [subs]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you are doing duplicate isArray check for sub which is already done in if condition. So the tradeoff is having to variables to store result of isArray for sub and oldSub versus calling isArray two times. I will consider more variables as cheaper than more function call in runtime. My idea is something like this:

const isArraySub = isArray(sub)
const isArrayOldSub = isArray(oldSub)
if (isArraySub || isArrayOldSub) {
  let out = []
  let subs = isArraySub ? sub : [subs];
  ...
}

Copy link

@TheNando TheNando Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that won't work with let, only with var. Var hoists the definition to the top of the scope, so when it's referenced at [subs] it returns undefined. But with let it will throw a ReferenceError, since it is self referencing and won't exist until the line has executed.

I actually think this is a typo and he was intending to ensure that sub is an array. If that's the case, you can just do:

var subs = [].concat(sub)

Which will ensure that subs ends up an array and will work if sub is already an array or just a value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheNando I have already made this suggestions one month ago on Slack
see https://hyperapp.slack.com/archives/C5D86NQ3X/p1529772214000096?thread_ts=1529772214.000096&cid=C5D86NQ3X

If @jorgebucaran made the decision to do not use this implementation, he should have some good reasons.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @Swizz. Basically [].concat is a lot slower.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorgebucaran But : [subs] supposed to be : [sub], right?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheNando Yes, totally, that's a bug! Fixing it now :)

src/index.js Outdated
var subscribe = props.subscribe
var subscriptions = []
var container = props.container
var element = container && container.children[0]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is container.children always defined and thus container.children[0] is always valid ?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kumarabhishek Only if container exists!

container && ...

src/index.js Outdated
updateAttribute(element, name, attributes[name], null, isSvg)
}
}
var createTextVNode = function(text, element) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use ES6 default parameter and organise params like below then createTextVNode is not needed at all:

var createVNode = function(name, element, props={}, children=[],  key=null, type=TEXT_NODE) {
 ...
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please read this regarding which ES spec we follow.

src/index.js Outdated
}
}

var getKey = function(node) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method will not be needed if we ensure that node.key is by default null. This can be done using default parameter in functions related to node.

src/index.js Outdated
) {
for (var name in merge(lastProps, nextProps)) {
if (
(name === "value" || name === "checked"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a matter of taste, i like single quotes for strings in JS and double quotes inside JSX/HTML.

Copy link

@joseluisq joseluisq Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not bad suggestion, in fact I prefer single quotes for strings too.
Even it's supported by prettier https://prettier.io/docs/en/options.html#quotes

Copy link

@joseluisq joseluisq Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in that case, It's Hyperapp @jorgebucaran preference 😅

@TheNando
Copy link

TheNando commented Jul 11, 2018

@kumarabhishek Regarding using ES6 over ES5...

My major comment is to use as much ES6 as possible to make code simpler, smaller and performant.

Simpler, maybe. Smaller, probably not after transpilation. Definitely not more performant.

Ultimately, it's per-browser implementation, but in most cases, ES6 is less performant. Declaring arrow functions are a little slower in Chrome and much slower FF. Destructuring is much slower across the board. Classes are slower except in FF. The new loops, generators, map/set, Object.assign, spread...pretty much all slower than es5 implementations.

http://incaseofstairs.com/six-speed/

Previously, we were using a microtask, but this creates an issue
when toggling an element with an event handler causing the event
to fire twice (unless we force Hyperapp to create a new element).
This is one of several reasons frameworks like React implement a
synthetic event system. Hyperapp V1 didn't have this bug, because
we were using setTimeout to defer rendering.

setTimeout defers callbacks too late, Promise.resolve too early,
requestAnimationFrame just right.

- Prevents firing events twice (without keys, or e.stopPropagation)
- Prevents inputs from going out of sync if typing too fast, or from
  flickering if one changes input's value, e.g., to prevent the user
  from entering numbers in it.
- Doesn't interfere with effects using requestAnimationFrame to run
  animations like setTimeout does.
- Falls back to setTimeout if requestAnimationFrame is not available.

One gotcha about using requestAnimationFrame is that rendering
will be effectively paused in when running in background tabs,
which is a browser measure to improve perf and battery life.

After considering all options, I decided to run with rAF until
I can find a better alternative.
- Diff props before creating new elements
- Use DOM as source of truth for selected attribute
- Add more documentation
src/index.js Outdated
? document.createTextNode(node.name)
: (isSvg = isSvg || node.name === "svg")
? document.createElementNS("http://www.w3.org/2000/svg", node.name)
: document.createElement(node.name)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we support customized built-in elements here? For example:

<p is="word-count">

and

document.createElement("p", { is: "word-count" })

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dennis84 Hyperapp sets attributes/properties after creating the element. This code:

<p is="word-count">

...creates a p element with an is attribute exactly as declared. Is that what you needed?

Screen Shot 2019-04-15 at 21 44 55

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that doesn't work for custom elements. As Far as I know, you have to pass the "is" attribute as options to the createElement function.

If you run this in the conosle nothing is logged:

class WordCount extends HTMLParagraphElement {
  connectedCallback() {
    console.log('AAAA')
  }
}

window.customElements.define('word-count', WordCount, {extends: 'p'})

var p = document.createElement('p')//, {is: 'word-count'})
p.setAttribute('is', 'word-count')
p.appendChild(document.createTextNode('test'))
document.body.appendChild(p)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dennis84 Does React/Inferno/Preact/etc. handle this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dennis84 Awesome, thanks. I'll figure out what's the best way to add this after V2 is out. Thank you.

mrozbarry and others added 6 commits April 25, 2019 17:23
- App takes second HoF for dispatch
To consolidate the custom/default payload into one argument, the
2-tuple action-payload pair now accepts a payload creator where
you'd normally write the payload. A payload creator is a function
that takes the default payload and returns a new payload. You can
use it as follows:

    <input
      type="text"
      value={state.value}
      oninput={[UpdateItem, event => ({
        id: todo.id,
        value: event.target.value
      })]}
    />

Also included with this commit:

- Inline identity function
- Return updated state from setState and dispatch
@okwolf
Copy link
Contributor

okwolf commented Apr 26, 2019

@jorgebucaran is there going to be a new alpha release with decorateDispatch/middleware?

@jorgebucaran
Copy link
Owner Author

@okwolf Yes, working on it! Hang on. :)

@@ -1,4 +1,4 @@
# <img height=24 src=https://cdn.rawgit.com/jorgebucaran/f53d2c00bafcf36e84ffd862f0dc2950/raw/882f20c970ff7d61aa04d44b92fc3530fa758bc0/Hyperapp.svg> Hyperapp
# Hyperapp

[![Travis CI](https://img.shields.io/travis/hyperapp/hyperapp/master.svg)](https://travis-ci.org/hyperapp/hyperapp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broken link, should point to jorgebucaran/hyperapp

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Will fix.

README.md Outdated
## Getting Started
- **Minimal**—We have aggressively minimized the concepts you need to learn to be productive while remaining on par with what other frameworks can do. Dynamic, purely functional frontend applications without the headache.
- **Declarative**–Design your UI as a function of the state, and Hyperapp will efficiently update the DOM as your data changes. Declarative views, managed effects, and subscriptions make your code predictable and easy to debug.
- **Standalone**—Do more with less. Hyperapp includes state management and a [state-of-the-art] Virtual DOM engine that supports keyed updates, functional components & tree memoization—all without dependencies.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyperapp includes state management and a [state-of-the-art] Virtual DOM engine that supports keyed updates, functional components & tree memoization—all without dependencies in one dependency.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyperapp itself is a dependency. Is that what you mean? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively you could say all without additional dependencies. But yes.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with "all without extra dependencies". 😅

README.md Outdated

Instead of returning a partial state object directly, an action can return a function that takes the current state and actions as arguments and returns a partial state object.
When describing the content of a page, we use the `h` function to create a virtual DOM. A virtual DOM is an object representation of how the DOM should look at any point in time. Hyperapp calls your `view` function to get this object and converts it into real DOM nodes in the browser efficiently.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Distinguish between virtual DOM and virtual nodes?

? [b[0], b[1], b[0](b[1], dispatch), a && a[2]()]
: a
: a && a[2]()
var patchSubs = function(oldSubs, newSubs, dispatch) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this code golfed enough? I can almost read it 😆

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@okwolf I just pushed to this branch like a few seconds ago. Are you sure you're looking at the right code? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was referring to the new version of patchSubs being more readable. Hopefully not at the cost of too many bytes 🙃

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@okwolf Which one is more readable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most folks seem to think the nested ternaries are less readable. I don't think it's by much though 🤷‍♂

@jorgebucaran
Copy link
Owner Author

jorgebucaran commented May 2, 2019

Changes

New Features

  • Middleware (Thank you, @mrozbarry)

    const enhance = oldDispatch => newDispatch
    
    app(props, enhance)
  • The root node can be a text node now.

    app({ view: () => "Hello" })
  • Payload creators (Consolidate default and custom payload into one payload #830)

    const targetValue = event => event.target.value
    const NewValue = (state, value) => ({ ...state, value })
    ...
    <input type="text" value={state.value} oninput={[NewValue, targetValue]} />
  • Declarative event options (preventDefault, stopPropagation, passive)

    const preventDefault = props => ({ action: props.action, preventDefault: true })
    ...
    <form onsubmit={preventDefault(SubmitForm)}>
        <label>
            Name:
            <input
            type="text"
            value={state.value}
            oninput={[Change, targetValue])}
            />
        </label>
        <h1>{state.value}</h1>
        <input type="submit" value="Submit" />
    </form>

Size

  • ~1.9 kB 🎉

@jorgebucaran jorgebucaran merged commit b5c3cc5 into master May 4, 2019
@jorgebucaran jorgebucaran deleted the V2 branch May 4, 2019 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking This will break things enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet