4kb
framework for creating sturdy frontend applications
Website | Handbook | Ecosystem | Contributing | Chat
- minimal size: weighing
4kb
,choo
is a tiny little framework - event based: our performant event system makes writing apps easy
- small api: with only 6 methods there's not much to learn
- minimal tooling: built for the cutting edge
browserify
compiler - isomorphic: renders seamlessly in both Node and browsers
- very cute: choo choo!
var html = require('choo/html')
var choo = require('choo')
var app = choo()
app.use(logger)
app.use(countStore)
app.route('/', mainView)
app.mount('body')
function mainView (state, emit) {
return html`
<body>
<h1>count is ${state.count}</h1>
<button onclick=${onclick}>Increment</button>
</body>
`
function onclick () {
emit('increment', 1)
}
}
function logger (state, emitter) {
emitter.on('*', function (messageName, data) {
console.log('event', messageName, data)
})
}
function countStore (state, emitter) {
state.count = 0
emitter.on('increment', function (count) {
state.count += count
emitter.emit('render')
})
}
Want to see more examples? Check out the Choo handbook.
We believe programming should be fun and light, not stern and stressful. It's cool to be cute; using serious words without explaining them doesn't make for better results - if anything it scares people off. We don't want to be scary, we want to be nice and fun, and then casually be the best choice around. Real casually.
We believe frameworks should be disposable, and components recyclable. We don't
want a web where walled gardens jealously compete with one another. By making
the DOM the lowest common denominator, switching from one framework to another
becomes frictionless. choo
is modest in its design; we don't believe it will
be top of the class forever, so we've made it as easy to toss out as it is to
pick up.
We don't believe that bigger is better. Big APIs, large complexities, long files - we see them as omens of impending userland complexity. We want everyone on a team, no matter the size, to fully understand how an application is laid out. And once an application is built, we want it to be small, performant and easy to reason about. All of which makes for easy to debug code, better results and super smiley faces.
Choo is reasonably fast out of the box. But sometimes you might hit a scenario where a particular part of the UI slows down the application, and you want to speed it up. Here are some optimizations that are possible.
To be implemented. (See yoshuawuyts/nanomorph#8)
Sometimes we want to tell the algorithm to not evaluate certain nodes (and its
children). This can be because we're sure they haven't changed, or perhaps
because another piece of code is managing that part of the DOM tree. To achieve
this nanomorph
evaluates the .isSameNode()
method on nodes to determine if
they should be updated or not.
var el = html`<div>node</div>`
// tell nanomorph to not compare the DOM tree if they're both divs
el.isSameNode = function (target) {
return (target && target.nodeName && target.nodeName === 'DIV')
}
We use the require('assert')
module from Node core to provide helpful error
messages in development. In production you probably want to strip this using
unassertify.
To convert inlined HTML to valid DOM nodes we use require('bel')
. This has
overhead during runtime, so for production environments we should unwrap this
using yo-yoify.
Setting up browserify transforms can sometimes be a bit of hassle; to make this
more convenient we recommend using bankai with --optimize
to
compile your assets for production.
Because I thought it sounded cute. All these programs talk about being
"performant", "rigid", "robust" - I like programming to be light, fun and
non-scary. choo
embraces that.
Also imagine telling some business people you chose to rewrite something critical for serious bizcorp using a train themed framework. :steam_locomotive::train::train::train:
It's called "choo", though we're fine if you call it "choo-choo" or "chugga-chugga-choo-choo" too. The only time "choo.js" is tolerated is if / when you shimmy like you're a locomotive.
choo
uses nanomorph, which diffs real DOM nodes instead of
virtual nodes. It turns out that browsers are actually ridiculously good at
dealing with DOM nodes, and it has the added benefit of
working with any library that produces valid DOM nodes. So to put a long
answer short: we're using something even better.
Template strings aren't supported in all browsers, and parsing them creates
significant overhead. To optimize we recommend running browserify
with
yo-yoify as a global transform or using bankai directly.
$ browserify -g yo-yoify
Sure.
This section provides documentation on how each function in choo
works. It's
intended to be a technical reference. If you're interested in learning choo for
the first time, consider reading through the handbook first
:sparkles:
Initialize a new choo
instance. opts
can also contain the following values:
- opts.history: default:
true
. Listen for url changes through the history API. - opts.href: default:
true
. Handle all relative<a href="<location>"></a>
clicks and callemit('render')
- opts.timing: default:
true
. Enables calls to the window.performance timing API. Timing calls will not run in browsers that don't support it out of the box. The timing marks arechoo:renderStart
,choo:renderEnd
. The resulting diff is stored aschoo:render
.
Call a function and pass it a state
and emitter
. emitter
is an instance
of nanobus. You can listen to
messages by calling emitter.on()
and emit messages by calling emitter.emit()
.
Choo fires messages when certain events happen:
.on('DOMContentLoaded')
: when the DOM has succesfully finished loading.on('render')
: when the DOM re-renders.on('pushState')
: when the history API is triggered
The render
event should be emitted (emitter.emit('render')
) whenever you want the app to re-render the DOM - it won't happen on its own except when you navigate between routes.
The pushState
can be emitted to navigate between routes: emitted.emit('pushState', '/some/route')
.
Both render
and pushState
will only have an effect once the DOMContentLoaded
event has been fired.
Register a route on the router. Uses nanorouter under the hood.
Params can be registered by prepending the routename with :routename
, e.g.
/foo/:bar/:baz
. The value of the param will be saved on state.params
(e.g.
state.params.bar
). Wildcard routes can be registered with *
, e.g. /foo/*
.
The value of the wildcard will be saved under state.params.wildcard
.
Using hashes to delimit routes is supported out of the box (e.g. /foo#bar
).
When a hash is found we also check if there's an available anchor on the same
page, and will scroll the screen to the position. Using both hashes in URLs and
anchor links on the page is generally not recommended.
New routes can be triggered through emitter.emit('pushState', <routename>)
.
By default we also catch and match all <a href="">
clicks against the router.
This can be disabled by setting opts.href
to false
in the constructor.
Routing via pushState
will not work until the DOMContentLoaded
event has been fired.
Querystrings (?foo=bar
) are ignored when matching routes. They should be
extracted from the window.location
object on render events, from either a
custom event listener or the matched views.
Start the application and mount it on the given querySelector
. Uses
nanomount under the hood. This will replace the selector provided
with the tree returned from app.start()
. If you want to add the app as a child
to an element, use app.start()
to obtain the tree and manually append it.
Start the application. Returns a tree of DOM nodes that can be mounted using
document.body.appendChild()
.
Render the application to a string. Useful for rendering on the server.
Create DOM nodes from template string literals. Exposes bel. Can be optimized using yo-yoify.
$ npm install choo
- bankai - streaming asset compiler
- stack.gl - open software ecosystem for WebGL
- yo-yo - tiny library for modular UI
- bel - composable DOM elements using template strings
- tachyons - functional CSS for humans
- sheetify - modular CSS bundler for
browserify
Creating a quality framework takes a lot of time. Unlike others frameworks, Choo is completely independently funded. We fight for our users. This does mean however that we also have to spend time working contracts to pay the bills. This is where you can help: by chipping in you can ensure more time is spent improving Choo rather than dealing with distractions.
Become a sponsor and help ensure the development of independent quality software. You can help us keep the lights on, bellies full and work days sharp and focused on improving the state of the web. Become a sponsor
Become a backer, and buy us a coffee (or perhaps lunch?) every month or so. Become a backer