Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

🚂🚋 - sturdy 4kb frontend framework

License

NotificationsYou must be signed in to change notification settings

choojs/choo

Repository files navigation

🚂🚋🚋🚋🚋🚋
Fun functional programming
A4kb framework for creating sturdy frontend applications

API stabilityNPM versionBuild StatusTest CoverageDownloadStandard
The little framework that could. Built with ❤︎ byYoshua Wuyts and contributors

Table of Contents

Features

  • minimal size: weighing4kb, 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 edgebrowserify compiler
  • isomorphic: renders seamlessly in both Node and browsers
  • very cute: choo choo!

Example

varhtml=require('choo/html')vardevtools=require('choo-devtools')varchoo=require('choo')varapp=choo()app.use(devtools())app.use(countStore)app.route('/',mainView)app.mount('body')functionmainView(state,emit){returnhtml`<body><h1>count is${state.count}</h1><buttononclick=${onclick}>Increment</button></body>  `functiononclick(){emit('increment',1)}}functioncountStore(state,emitter){state.count=0emitter.on('increment',function(count){state.count+=countemitter.emit('render')})}

Want to see more examples? Check out theChoo handbook.

Philosophy

We believe programming should be fun and light, not stern and stressful. It'scool to be cute; using serious words without explaining them doesn't make forbetter results - if anything it scares people off. We don't want to be scary,we want to be nice and fun, and thencasually be the best choice around.Real casually.

We believe frameworks should be disposable, and components recyclable. We don'twant a web where walled gardens jealously compete with one another. By makingthe DOM the lowest common denominator, switching from one framework to anotherbecomes frictionless. Choo is modest in its design; we don't believe it willbe top of the class forever, so we've made it as easy to toss out as it is topick up.

We don't believe that bigger is better. Big APIs, large complexities, longfiles - we see them as omens of impending userland complexity. We want everyoneon a team, no matter the size, to fully understand how an application is laidout. And once an application is built, we want it to be small, performant andeasy to reason about. All of which makes for easy to debug code, better resultsand super smiley faces.

Events

At the core of Choo is an event emitter, which is used for both applicationlogic but also to interface with the framework itself. The package we use forthis isnanobus.

You can access the emitter throughapp.use(state, emitter, app),app.route(route, view(state, emit)) orapp.emitter. Routes only have access to theemitter.emit method to encourage people to separate business logic fromrender logic.

The purpose of the emitter is two-fold: it allows wiring up application codetogether, and splitting it off nicely - but it also allows communicating withthe Choo framework itself. All events can be read as constants fromstate.events. Choo ships with the following events built in:

'DOMContentLoaded'|state.events.DOMCONTENTLOADED

Choo emits this when the DOM is ready. Similar to the DOM's'DOMContentLoaded' event, except it will be emitted even if the listener isaddedafter the DOM became ready. Usesdocument-ready under the hood.

'render'|state.events.RENDER

This event should be emitted to re-render the DOM. A common pattern is toupdate thestate object, and then emit the'render' event straight after.Note that'render' will only have an effect once theDOMContentLoaded eventhas been fired.

'navigate'|state.events.NAVIGATE

Choo emits this event whenever routes change. This is triggered by either'pushState','replaceState' or'popState'.

'pushState'|state.events.PUSHSTATE

This event should be emitted to navigate to a new route. The new route is addedto the browser's history stack, and will emit'navigate' and'render'.Similar tohistory.pushState.

'replaceState'|state.events.REPLACESTATE

This event should be emitted to navigate to a new route. The new route replacesthe current entry in the browser's history stack, and will emit'navigate'and'render'. Similar tohistory.replaceState.

'popState'|state.events.POPSTATE

This event is emitted when the user hits the 'back' button in their browser.The new route will be a previous entry in the browser's history stack, andimmediately afterward the'navigate' and'render'events will be emitted.Similar tohistory.popState. (Notethatemit('popState') willnot cause a popState action - usehistory.go(-1) for that - this is different from the behaviour ofpushStateandreplaceState!)

'DOMTitleChange'|state.events.DOMTITLECHANGE

This event should be emitted whenever thedocument.title needs to be updated.It will set bothdocument.title andstate.title. This value can be usedwhen server rendering to accurately include a<title> tag in the header.This is derived from theDOMTitleChanged event.

State

Choo comes with a shared state object. This object can be mutated freely, andis passed into the view functions whenever'render' is emitted. The stateobject comes with a few properties set.

When initializing the application,window.initialState is used to provisionthe initial state. This is especially useful when combined with serverrendering. Seeserver rendering for more details.

state.events

A mapping of Choo's built in events. It's recommended to extend this objectwith your application's events. By defining your event names once and settingthem onstate.events, it reduces the chance of typos, generally autocompletesbetter, makes refactoring easier and compresses better.

state.params

The current params taken from the route. E.g./foo/:bar becomes available asstate.params.bar If a wildcard route is used (/foo/*) it's available asstate.params.wildcard.

state.query

An object containing the current queryString./foo?bin=baz becomes{ bin: 'baz' }.

state.href

An object containing the current href./foo?bin=baz becomes/foo.

state.route

The current name of the route used in the router (e.g./foo/:bar).

state.title

The current page title. Can be set using theDOMTitleChange event.

state.components

An objectrecommended to use for local component state.

state.cache(Component, id, [...args])

Generic class cache. Will lookup Component instance by id and create one if notfound. Useful for working with statefulcomponents.

Routing

Choo is an application level framework. This means that it takes care ofeverything related to routing and pathnames for you.

Params

Params can be registered by prepending the route name with:routename, e.g./foo/:bar/:baz. The value of the param will be saved onstate.params (e.g.state.params.bar). Wildcard routes can be registered with*, e.g./foo/*.The value of the wildcard will be saved understate.params.wildcard.

Default routes

Sometimes a route doesn't match, and you want to display a page to handle it.You can do this by declaringapp.route('*', handler) to handle all routesthat didn't match anything else.

Querystrings

Querystrings (e.g.?foo=bar) are ignored when matching routes. An objectcontaining the key-value mappings exists asstate.query.

Hash routing

By default, hashes are ignored when routing. When enabling hash routing(choo({ hash: true })) hashes will be treated as part of the url, converting/foo#bar to/foo/bar. This is useful if the application is not mounted atthe website root. Unless hash routing is enabled, if a hash is found we check ifthere's an anchor on the same page, and will scroll the element into view. Usingboth hashes in URLs and anchor links on the page is generally not recommended.

Following links

By default all clicks on<a> tags are handled by the router through thenanohref module. This can bedisabled application-wide by passing{ href: false } to the applicationconstructor. The event is not handled under the following conditions:

  • the click event had.preventDefault() called on it
  • the link has atarget="_blank" attribute withrel="noopener noreferrer"
  • a modifier key is enabled (e.g.ctrl,alt,shift ormeta)
  • the link's href starts with protocol handler such asmailto: ordat:
  • the link points to a different host
  • the link has adownload attribute

:warn: Note that we only handletarget=_blank if they also haverel="noopener noreferrer" on them. This is needed toproperly sandbox webpages.

Navigating programmatically

To navigate routes you can emit'pushState','popState' or'replaceState'. See#events for more details about these events.

Server Rendering

Choo was built with Node in mind. To render on the server call.toString(route, [state]) on yourchoo instance.

varhtml=require('choo/html')varchoo=require('choo')varapp=choo()app.route('/',function(state,emit){returnhtml`<div>Hello${state.name}</div>`})varstate={name:'Node'}varstring=app.toString('/',state)console.log(string)// => '<div>Hello Node</div>'

When starting an application in the browser, it's recommended to provide thesamestate object available aswindow.initialState. When the application isstarted, it'll be used to initialize the application state. The process ofserver rendering, and providing an initial state on the client to create theexact same document is also known as "rehydration".

For security purposes, afterwindow.initialState is used it is deleted fromthewindow object.

<html><head><script>window.initialState={initial:'state'}</script></head><body></body></html>

Components

From time to time there will arise a need to have an element in an applicationhold a self-contained state or to not rerender when the application does. Thisis common when using 3rd party libraries to e.g. display an interactive map or agraph and you rely on this 3rd party library to handle modifications to the DOM.Components come baked in to Choo for these kinds of situations. Seenanocomponent for documentation on the component class.

// map.jsvarhtml=require('choo/html')varmapboxgl=require('mapbox-gl')varComponent=require('choo/component')module.exports=classMapextendsComponent{constructor(id,state,emit){super(id)this.local=state.components[id]={}}load(element){this.map=newmapboxgl.Map({container:element,center:this.local.center})}update(center){if(center.join()!==this.local.center.join()){this.map.setCenter(center)}returnfalse}createElement(center){this.local.center=centerreturnhtml`<div></div>`}}
// index.jsvarchoo=require('choo')varhtml=require('choo/html')varMap=require('./map.js')varapp=choo()app.route('/',mainView)app.mount('body')functionmainView(state,emit){returnhtml`<body><buttononclick=${onclick}>Where am i?</button>${state.cache(Map,'my-map').render(state.center)}</body>  `functiononclick(){emit('locate')}}app.use(function(state,emitter){state.center=[18.0704503,59.3244897]emitter.on('locate',function(){window.navigator.geolocation.getCurrentPosition(function(position){state.center=[position.coords.longitude,position.coords.latitude]emitter.emit('render')})})})

Caching components

When working with stateful components, one will need to keep track of componentinstances –state.cache does just that. The component cache is a functionwhich takes a component class and a unique id (string) as its first twoarguments. Any following arguments will be forwarded to the component constructortogether withstate andemit.

The default class cache is an LRU cache (usingnanolru), meaning itwill only hold on to a fixed amount of class instances (100 by default) beforestarting to evict the least-recently-used instances. This behavior can beoverriden withoptions.

Optimizations

Choo is reasonably fast out of the box. But sometimes you might hit a scenariowhere a particular part of the UI slows down the application, and you want tospeed it up. Here are some optimizations that are possible.

Caching DOM elements

Sometimes we want to tell the algorithm to not evaluate certain nodes (and itschildren). This can be because we're sure they haven't changed, or perhapsbecause another piece of code is managing that part of the DOM tree. To achievethisnanomorph evaluates the.isSameNode() method on nodes to determine ifthey should be updated or not.

varel=html`<div>node</div>`// tell nanomorph to not compare the DOM tree if they're both divsel.isSameNode=function(target){return(target&&target.nodeName&&target.nodeName==='DIV')}

Reordering lists

It's common to work with lists of elements on the DOM. Adding, removing orreordering elements in a list can be rather expensive. To optimize this you canadd anid attribute to a DOM node. When reordering nodes it will comparenodes with the same ID against each other, resulting in far fewer re-renders.This is especially potent when coupled with DOM node caching.

varel=html`<section><divid="first">hello</div><divid="second">world</div></section>`

Pruning dependencies

We use therequire('assert') module from Node core to provide helpful errormessages in development. In production you probably want to strip this usingunassertify.

To convert inlined HTML to valid DOM nodes we userequire('nanohtml'). This hasoverhead during runtime, so for production environments we should unwrap thisusing thenanohtml transform.

Setting up browserify transforms can sometimes be a bit of hassle; to make thismore convenient we recommend usingbankai build to build your assets for production.

FAQ

Why is it called Choo?

Because I thought it sounded cute. All these programs talk about being"performant","rigid","robust" - I like programming to be light, fun andnon-scary. Choo embraces that.

Also imagine telling some business people you chose to rewrite somethingcritical for serious bizcorp using a train themed framework.:steam_locomotive::train::train::train:

Is it called Choo, Choo.js or...?

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.

Does Choo use a virtual-dom?

Choo usesnanomorph, which diffs real DOM nodes instead ofvirtual nodes. It turns out thatbrowsers are actually ridiculously good atdealing with DOM nodes, and it has the added benefit ofworking withany library that produces valid DOM nodes. So to put a longanswer short: we're using something even better.

How can I support older browsers?

Template strings aren't supported in all browsers, and parsing them createssignificant overhead. To optimize we recommend runningbrowserify withnanohtml as a global transform or usingbankai directly.

$ browserify -g nanohtml

Is choo production ready?

Sure.

API

This section provides documentation on how each function in Choo works. It'sintended to be a technical reference. If you're interested in learning choo forthe first time, consider reading through thehandbook first:sparkles:

app = choo([opts])

Initialize a newchoo instance.opts can also contain the following values:

  • opts.history: default:true. Listen for url changes through thehistory API.
  • opts.href: default:true. Handle all relative<a href="<location>"></a> clicks and callemit('render')
  • opts.cache: default:undefined. Override default class cache used bystate.cache. Can be a anumber (maximum number of instances in cache,default100) or anobject with ananolru-compatible API.
  • opts.hash: default:false. Treat hashes in URLs as part of the pathname,transforming/foo#bar to/foo/bar. This is useful if the application isnot mounted at the website root.

app.use(callback(state, emitter, app))

Call a function and pass it astate,emitter andapp.emitter is an instanceofnanobus. You can listen tomessages by callingemitter.on() and emit messages by callingemitter.emit().app is the same Choo instance. Callbacks passed toapp.use() are commonly referred to as'stores'.

If the callback has a.storeName property on it, it will be used to identifythe callback during tracing.

See#events for an overview of all events.

app.route(routeName, handler(state, emit))

Register a route on the router. The handler function is passedapp.stateandapp.emitter.emit as arguments. Usesnanorouter under thehood.

See#routing for an overview of how to use routing efficiently.

app.mount(selector)

Start the application and mount it on the givenquerySelector,the given selector can be a String or a DOM element.

In the browser, this willreplace the selector provided with the tree returned fromapp.start().If you want to add the app as a child to an element, useapp.start() to obtain the tree and manually append it.

On the server, this will save theselector on the app instance.When doing server side rendering, you can then check theapp.selector property to see where the render result should be inserted.

Returnsthis, so you can easily export the application for server side rendering:

module.exports=app.mount('body')

tree = app.start()

Start the application. Returns a tree of DOM nodes that can be mounted usingdocument.body.appendChild().

app.toString(location, [state])

Render the application to a string. Useful for rendering on the server.

choo/html

Create DOM nodes from template string literals. Exposesnanohtml. Can be optimized usingnanohtml.

choo/html/raw

Exposesnanohtml/raw helper for rendering raw HTML content.

Installation

$ npm install choo

See Also

  • bankai - streaming asset compiler
  • stack.gl - open software ecosystem for WebGL
  • yo-yo - tiny library for modular UI
  • tachyons - functional CSS forhumans
  • sheetify - modular CSS bundler forbrowserify

Support

Creating a quality framework takes a lot of time. Unlike others frameworks,Choo is completely independently funded. We fight for our users. This does meanhowever 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 spentimproving Choo rather than dealing with distractions.

Sponsors

Become a sponsor and help ensure the development of independent qualitysoftware. You can help us keep the lights on, bellies full and work days sharpand focused on improving the state of the web.Become asponsor

Backers

Become a backer, and buy us a coffee (or perhaps lunch?) every month or so.Become a backer

License

MIT


[8]ページ先頭

©2009-2025 Movatter.jp