-
Notifications
You must be signed in to change notification settings - Fork 595
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
implement choo v5 API #425
Conversation
🎉 Ok, so it looks like with the change to mutable state, we no longer have the view signature of
since prev state is not a thing right? I had one big use case for Sometimes I have a view that displays in response to a route like So on view create, I'd add an xhr request in response to the To sum up what I was doing to solve this- in the render function I would compare the current and previous parts of the Some prior art to solving this issue: In vue, the vue router has a So my first piece of feedback- |
@abradley2 You might want to check out my choo-routehandler complementary route handling framework for Choo. It lets you define loading hooks for routes, which is what you need right? Bear in mind it's written for Choo v4 though... cc: @timwis |
My only suggestion would be to include a nanocomponant in the example, demonstrating the isSameNode method. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! I left some questions in example.js
. Beyond that, I share @abradley2's concern about being able to detect route changes, and I might suggest moving example.js
to an example
directory so that you can have a distinct package.json
. This would remove things like todomvc-css
from choo's package.json
, and also let you show users how to run choo apps with npm start
and npm run build
scripts.
example.js
Outdated
@@ -0,0 +1,353 @@ | |||
var mutate = require('xtend/mutable') | |||
var expose = require('choo-expose') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this library? It's not on npm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Derp, yeah I should publish it, it's still just on my machine - 10 lines tho but yeah lol
example.js
Outdated
css('todomvc-app-css/index.css') | ||
|
||
if (module.parent) { | ||
exports.todoStore = todoStore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might this be overkill for an example app? Or is there some reason to include this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah this is for the tests to run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooooh, right - funny, i did the same thing in an app i'm writing so that tests could run last week. maybe a comment saying "so tests can run"? lol, unless you think most folks wouldn't wonder about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good suggestion
example.js
Outdated
app.route('/', mainView) | ||
app.route('#active', mainView) | ||
app.route('#completed', mainView) | ||
app.mount('body') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there no app.start()
anymore? index.js
suggests it still exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's still there, and should be in the README! - the reason for app.mount()
is that I like writing my apps as:
<body>
<header></header>
<main></main>
<footer></footer>
</body>
app.mount()
is to replace a DOM node. (e.g. document.body
), app.start()
is to append to a DOM node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, interesting. I didn't realise it was an either/or. I assumed you had to mount
and start
. k thx
example.js
Outdated
<footer class="info"> | ||
<p>Double-click to edit a todo</p> | ||
<p>choo by <a href="https://yoshuawuyts.com/">Yoshua Wuyts</a></p> | ||
<p>Created by <a href="http://shuheikagawa.com">Shuhei Kagawa</a></p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, at first read, I thought this person co-authored choo. My guess is that they wrote the original version of the choo todomvc app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
example.js
Outdated
} | ||
|
||
function todoStore () { | ||
return function (state, emitter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first read, this made me think models needed to return functions, which is a bit less..simple, but the readme suggests they don't need to. If the example is meant to illustrate the simplicity, perhaps it's better if this doesn't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point
example.js
Outdated
|
||
function todoStore () { | ||
return function (state, emitter) { | ||
var localState = state.todos |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I'm gathering from this block of localState
modifications is that the way you update state in choo v5
is by modifying the global state
object, which is passed down to "models" (things that are use
d). That pattern is a bit unclear here because it looks like we're modifying a local variable, and not actually persisting that globally anywhere. In fact, because of the way JavaScript variables work by reference with objects, we are modifying a global variable, but you'd have to be familiar with that to pick up on what's going on here. My suggestion would be to make modifications to state.todos.active = []
etc. directly for simplicity's sake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 fair point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its a cool pattern though. Maybe we can note it in the handbook.
example.js
Outdated
localState.idCounter = 0 | ||
} | ||
|
||
emitter.on('DOMContentLoaded', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would we want to wait until the DOM is finished rendering to register our listeners and such?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda copied this pattern from choo 4's subscription
API - the subs were only ever booted after the app was loaded. I guess this also prevents weird things from starting if you ever run the code in Node
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. I mean, most folks put their <script>
tag at the bottom of <body>
these days anyway, so it's already being run after most of the DOM is loaded, but I imagine if you're really trying to optimize, you'd want your JS to run even before your fonts are finished loading. (Though if you're really trying to optimize, you'll probably use system fonts lol, but you get the point)
@abradley2 The We also removed our internal copy of the With var nanocomponent = require('nanocomponent')
var html = require('html')
var myComponent = nanocomponent({
render: function (state) {
return html`<div>hi</div>`
},
onupdate: function (el, location) {
console.log('new location was passed, mutate the DOM node!')
}
})
module.exports = myView
function myView (state, emit) {
return html`
<section>
${myComponent(window.location)}
</section>
`
} @bcomnes I'm thinking of including @timwis fair point about having an example dir; I'm mildly on the fence about it because I was trying to mimick the hypercore directory layout... but yeah maybs we should just move it to a dir |
The handbook is good too. |
index.js
Outdated
} | ||
function toString (location, state) { | ||
state = state || {} | ||
return router(location, state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should return router(location, state).toString()
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah lol, yeah ✨
} | ||
_frame(state, prev) | ||
function route (route, handler) { | ||
router.on(route, function (params) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be function (params, state, emit)
? If so nanorouter
would need to be patched: choojs/nanorouter#1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nah it's ok because we wrap it in the next line, no need to pass all those extra args
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does toString
then passes the state
argument around? Alternatively it could overwrite the state I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh dang, you're right - yeah I was thinking the latter tbh. Given it's all sync it's safe
At first glance, that |
Implemented all feedback! |
Are the recent changes released as a beta? |
anything else which might satisfy this requirement is fair game. | ||
|
||
Generally for production builds you'll want to run: | ||
`choo` uses [nanomorph][nanomorph], which diffs real DOM nodes instead of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix link edit: nm
I found a few issues around input elements. I opened issues at nanomnorph, but I'm thinking maybe they should live here. If you type into an input type=search element, that text is cleared on every render event, even if the text data is unbound to state in any way. If you try to bind an input=range element to state, it doesn't reflect state till you click on it, resize it, or directly set the value property on the dom node. UPDATE: A work around is wrapping input search types with nanocomponent. Not a solution but works. I've not been able to find the source of the bug on either of these. Any ideas? |
@bcomnes yeah, to get the latest pre-release run: $ npm i choo@next |
✨ ✨ ✨ thanks for all the feedback everyone! ✨ ✨ ✨ |
That was fun! |
heaps of changes!
Mostly following what's in https://medium.com/@yoshuawuyts/choo-v5-bc775b007b5e, but we're not including the
component
thing, and we changed the router API slightly. All for more simplicity.All the
lib/
stuff should become their own modules, but yeah it's late and I can't be bothered. Also lol, what are tests even.This version is gonna be released as
v5.0.0-2
so y'all can have a play with it. Check out theexample.js
file for a full example. Probably wanna symlink innanomorph
until choojs/nanomorph#47 has been resolved.Anyway, hope this is somewhat shows the direction I'm feeling for v5; feedback is heaps welcome!
edit: also yay for less API surface - we also dropped down to 4.3kB which is like neat. Huzzah