{"id":13394626,"url":"https://github.com/choojs/choo","last_synced_at":"2025-12-15T03:23:20.918Z","repository":{"id":37978810,"uuid":"58482213","full_name":"choojs/choo","owner":"choojs","description":":steam_locomotive::train: - sturdy 4kb frontend framework","archived":false,"fork":false,"pushed_at":"2020-01-23T17:13:46.000Z","size":719,"stargazers_count":6766,"open_issues_count":44,"forks_count":596,"subscribers_count":128,"default_branch":"master","last_synced_at":"2025-05-06T09:24:32.279Z","etag":null,"topics":["choo","dom","interface","minimal","modular","ui","unidirectional","vanilla"],"latest_commit_sha":null,"homepage":"https://choo.io/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/choojs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-10T17:50:35.000Z","updated_at":"2025-05-06T05:34:19.000Z","dependencies_parsed_at":"2022-07-12T17:04:15.590Z","dependency_job_id":null,"html_url":"https://github.com/choojs/choo","commit_stats":null,"previous_names":["yoshuawuyts/choo"],"tags_count":108,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choojs%2Fchoo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choojs%2Fchoo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choojs%2Fchoo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/choojs%2Fchoo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/choojs","download_url":"https://codeload.github.com/choojs/choo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253356253,"owners_count":21895670,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["choo","dom","interface","minimal","modular","ui","unidirectional","vanilla"],"created_at":"2024-07-30T17:01:25.961Z","updated_at":"2025-12-15T03:23:15.849Z","avatar_url":"https://github.com/choojs.png","language":"JavaScript","readme":"\u003ch1 align=\"center\"\u003eChoo\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n  :steam_locomotive::train::train::train::train::train:\n\u003c/div\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003cstrong\u003eFun functional programming\u003c/strong\u003e\n\u003c/div\u003e\n\u003cdiv align=\"center\"\u003e\n  A \u003ccode\u003e4kb\u003c/code\u003e framework for creating sturdy frontend applications\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003c!-- Stability --\u003e\n  \u003ca href=\"https://nodejs.org/api/documentation.html#documentation_stability_index\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square\"\n      alt=\"API stability\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- NPM version --\u003e\n  \u003ca href=\"https://npmjs.org/package/choo\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/choo.svg?style=flat-square\"\n      alt=\"NPM version\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Build Status --\u003e\n  \u003ca href=\"https://travis-ci.org/choojs/choo\"\u003e\n    \u003cimg src=\"https://img.shields.io/travis/choojs/choo/master.svg?style=flat-square\"\n      alt=\"Build Status\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Test Coverage --\u003e\n  \u003ca href=\"https://codecov.io/github/choojs/choo\"\u003e\n    \u003cimg src=\"https://img.shields.io/codecov/c/github/choojs/choo/master.svg?style=flat-square\"\n      alt=\"Test Coverage\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Downloads --\u003e\n  \u003ca href=\"https://npmjs.org/package/choo\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dt/choo.svg?style=flat-square\"\n      alt=\"Download\" /\u003e\n  \u003c/a\u003e\n  \u003c!-- Standard --\u003e\n  \u003ca href=\"https://standardjs.com\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square\"\n      alt=\"Standard\" /\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch3\u003e\n    \u003ca href=\"https://choo.io\"\u003e\n      Website\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://github.com/choojs/choo-handbook\"\u003e\n      Handbook\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://github.com/YerkoPalma/awesome-choo\"\u003e\n      Ecosystem\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003c!-- \u003ca href=\"https://github.com/trainyard/choo-cli\"\u003e --\u003e\n    \u003c!--   CLI --\u003e\n    \u003c!-- \u003c/a\u003e --\u003e\n    \u003c!-- \u003cspan\u003e | \u003c/span\u003e --\u003e\n    \u003ca href=\"https://github.com/choojs/choo/blob/master/.github/CONTRIBUTING.md\"\u003e\n      Contributing\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://www.reddit.com/r/choojs/\"\u003e\n      Reddit\n    \u003c/a\u003e\n    \u003cspan\u003e | \u003c/span\u003e\n    \u003ca href=\"https://webchat.freenode.net/?channels=choo\"\u003e\n      Chat\n    \u003c/a\u003e\n  \u003c/h3\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003csub\u003eThe little framework that could. Built with ❤︎ by\n  \u003ca href=\"https://twitter.com/yoshuawuyts\"\u003eYoshua Wuyts\u003c/a\u003e and\n  \u003ca href=\"https://github.com/choojs/choo/graphs/contributors\"\u003e\n    contributors\n  \u003c/a\u003e\n\u003c/div\u003e\n\n## Table of Contents\n- [Features](#features)\n- [Example](#example)\n- [Philosophy](#philosophy)\n- [Events](#events)\n- [State](#state)\n- [Routing](#routing)\n- [Server Rendering](#server-rendering)\n- [Components](#components)\n- [Optimizations](#optimizations)\n- [FAQ](#faq)\n- [API](#api)\n- [Installation](#installation)\n- [See Also](#see-also)\n- [Support](#support)\n\n## Features\n- __minimal size:__ weighing `4kb`, Choo is a tiny little framework\n- __event based:__ our performant event system makes writing apps easy\n- __small api:__ with only 6 methods there's not much to learn\n- __minimal tooling:__ built for the cutting edge `browserify` compiler\n- __isomorphic:__ renders seamlessly in both Node and browsers\n- __very cute:__ choo choo!\n\n## Example\n```js\nvar html = require('choo/html')\nvar devtools = require('choo-devtools')\nvar choo = require('choo')\n\nvar app = choo()\napp.use(devtools())\napp.use(countStore)\napp.route('/', mainView)\napp.mount('body')\n\nfunction mainView (state, emit) {\n  return html`\n    \u003cbody\u003e\n      \u003ch1\u003ecount is ${state.count}\u003c/h1\u003e\n      \u003cbutton onclick=${onclick}\u003eIncrement\u003c/button\u003e\n    \u003c/body\u003e\n  `\n\n  function onclick () {\n    emit('increment', 1)\n  }\n}\n\nfunction countStore (state, emitter) {\n  state.count = 0\n  emitter.on('increment', function (count) {\n    state.count += count\n    emitter.emit('render')\n  })\n}\n```\nWant to see more examples? Check out the [Choo handbook][handbook].\n\n## Philosophy\nWe believe programming should be fun and light, not stern and stressful. It's\ncool to be cute; using serious words without explaining them doesn't make for\nbetter results - if anything it scares people off. We don't want to be scary,\nwe want to be nice and fun, and then _casually_ be the best choice around.\n_Real casually._\n\nWe believe frameworks should be disposable, and components recyclable. We don't\nwant a web where walled gardens jealously compete with one another. By making\nthe DOM the lowest common denominator, switching from one framework to another\nbecomes frictionless. Choo is modest in its design; we don't believe it will\nbe top of the class forever, so we've made it as easy to toss out as it is to\npick up.\n\nWe don't believe that bigger is better. Big APIs, large complexities, long\nfiles - we see them as omens of impending userland complexity. We want everyone\non a team, no matter the size, to fully understand how an application is laid\nout. And once an application is built, we want it to be small, performant and\neasy to reason about. All of which makes for easy to debug code, better results\nand super smiley faces.\n\n## Events\nAt the core of Choo is an event emitter, which is used for both application\nlogic but also to interface with the framework itself. The package we use for\nthis is [nanobus](https://github.com/choojs/nanobus).\n\nYou can access the emitter through `app.use(state, emitter, app)`, `app.route(route,\nview(state, emit))` or `app.emitter`. Routes only have access to the\n`emitter.emit` method to encourage people to separate business logic from\nrender logic.\n\nThe purpose of the emitter is two-fold: it allows wiring up application code\ntogether, and splitting it off nicely - but it also allows communicating with\nthe Choo framework itself. All events can be read as constants from\n`state.events`. Choo ships with the following events built in:\n\n### `'DOMContentLoaded'`|`state.events.DOMCONTENTLOADED`\nChoo emits this when the DOM is ready. Similar to the DOM's\n`'DOMContentLoaded'` event, except it will be emitted even if the listener is\nadded _after_ the DOM became ready. Uses\n[document-ready](https://github.com/bendrucker/document-ready) under the hood.\n\n### `'render'`|`state.events.RENDER`\nThis event should be emitted to re-render the DOM. A common pattern is to\nupdate the `state` object, and then emit the `'render'` event straight after.\nNote that `'render'` will only have an effect once the `DOMContentLoaded` event\nhas been fired.\n\n### `'navigate'`|`state.events.NAVIGATE`\nChoo emits this event whenever routes change. This is triggered by either\n`'pushState'`, `'replaceState'` or `'popState'`.\n\n### `'pushState'`|`state.events.PUSHSTATE`\nThis event should be emitted to navigate to a new route. The new route is added\nto the browser's history stack, and will emit `'navigate'` and `'render'`.\nSimilar to\n[history.pushState](http://devdocs.io/dom/history_api).\n\n### `'replaceState'`|`state.events.REPLACESTATE`\nThis event should be emitted to navigate to a new route. The new route replaces\nthe current entry in the browser's history stack, and will emit `'navigate'`\nand `'render'`. Similar to\n[history.replaceState](http://devdocs.io/dom/history#history-replacestate).\n\n### `'popState'`|`state.events.POPSTATE`\nThis event is emitted when the user hits the 'back' button in their browser.\nThe new route will be a previous entry in the browser's history stack, and\nimmediately afterward the`'navigate'` and `'render'`events will be emitted.\nSimilar to [history.popState](http://devdocs.io/dom_events/popstate). (Note\nthat `emit('popState')` will _not_ cause a popState action - use\n`history.go(-1)` for that - this is different from the behaviour of `pushState`\nand `replaceState`!)\n\n### `'DOMTitleChange'`|`state.events.DOMTITLECHANGE`\nThis event should be emitted whenever the `document.title` needs to be updated.\nIt will set both `document.title` and `state.title`.  This value can be used\nwhen server rendering to accurately include a `\u003ctitle\u003e` tag in the header.\nThis is derived from the\n[DOMTitleChanged event](https://developer.mozilla.org/en-US/docs/Web/Events/DOMTitleChanged).\n\n## State\nChoo comes with a shared state object. This object can be mutated freely, and\nis passed into the view functions whenever `'render'` is emitted. The state\nobject comes with a few properties set.\n\nWhen initializing the application, `window.initialState` is used to provision\nthe initial state. This is especially useful when combined with server\nrendering. See [server rendering](#server-rendering) for more details.\n\n### `state.events`\nA mapping of Choo's built in events. It's recommended to extend this object\nwith your application's events. By defining your event names once and setting\nthem on `state.events`, it reduces the chance of typos, generally autocompletes\nbetter, makes refactoring easier and compresses better.\n\n### `state.params`\nThe current params taken from the route. E.g. `/foo/:bar` becomes available as\n`state.params.bar` If a wildcard route is used (`/foo/*`) it's available as\n`state.params.wildcard`.\n\n### `state.query`\nAn object containing the current queryString. `/foo?bin=baz` becomes `{ bin:\n'baz' }`.\n\n### `state.href`\nAn object containing the current href. `/foo?bin=baz` becomes `/foo`.\n\n### `state.route`\nThe current name of the route used in the router (e.g. `/foo/:bar`).\n\n### `state.title`\nThe current page title. Can be set using the `DOMTitleChange` event.\n\n### `state.components`\nAn object _recommended_ to use for local component state.\n\n### `state.cache(Component, id, [...args])`\nGeneric class cache. Will lookup Component instance by id and create one if not\nfound. Useful for working with stateful [components](#components).\n\n## Routing\nChoo is an application level framework. This means that it takes care of\neverything related to routing and pathnames for you.\n\n### Params\nParams can be registered by prepending the route name with `:routename`, e.g.\n`/foo/:bar/:baz`. The value of the param will be saved on `state.params` (e.g.\n`state.params.bar`). Wildcard routes can be registered with `*`, e.g. `/foo/*`.\nThe value of the wildcard will be saved under `state.params.wildcard`.\n\n### Default routes\nSometimes a route doesn't match, and you want to display a page to handle it.\nYou can do this by declaring `app.route('*', handler)` to handle all routes\nthat didn't match anything else.\n\n### Querystrings\nQuerystrings (e.g. `?foo=bar`) are ignored when matching routes. An object\ncontaining the key-value mappings exists as `state.query`.\n\n### Hash routing\nBy default, hashes are ignored when routing. When enabling hash routing\n(`choo({ hash: true })`) hashes will be treated as part of the url, converting\n`/foo#bar` to `/foo/bar`. This is useful if the application is not mounted at\nthe website root. Unless hash routing is enabled, if a hash is found we check if\nthere's an anchor on the same page, and will scroll the element into view. Using\nboth hashes in URLs and anchor links on the page is generally not recommended.\n\n### Following links\nBy default all clicks on `\u003ca\u003e` tags are handled by the router through the\n[nanohref](https://github.com/choojs/nanohref) module. This can be\ndisabled application-wide by passing `{ href: false }` to the application\nconstructor. The event is not handled under the following conditions:\n- the click event had `.preventDefault()` called on it\n- the link has a `target=\"_blank\"` attribute with `rel=\"noopener noreferrer\"`\n- a modifier key is enabled (e.g. `ctrl`, `alt`, `shift` or `meta`)\n- the link's href starts with protocol handler such as `mailto:` or `dat:`\n- the link points to a different host\n- the link has a `download` attribute\n\n:warn: Note that we only handle `target=_blank` if they also have\n`rel=\"noopener noreferrer\"` on them. This is needed to [properly sandbox web\npages](https://mathiasbynens.github.io/rel-noopener/).\n\n### Navigating programmatically\nTo navigate routes you can emit `'pushState'`, `'popState'` or\n`'replaceState'`. See [#events](#events) for more details about these events.\n\n## Server Rendering\nChoo was built with Node in mind. To render on the server call\n`.toString(route, [state])` on your `choo` instance.\n\n```js\nvar html = require('choo/html')\nvar choo = require('choo')\n\nvar app = choo()\napp.route('/', function (state, emit) {\n  return html`\u003cdiv\u003eHello ${state.name}\u003c/div\u003e`\n})\n\nvar state = { name: 'Node' }\nvar string = app.toString('/', state)\n\nconsole.log(string)\n// =\u003e '\u003cdiv\u003eHello Node\u003c/div\u003e'\n```\n\nWhen starting an application in the browser, it's recommended to provide the\nsame `state` object available as `window.initialState`. When the application is\nstarted, it'll be used to initialize the application state. The process of\nserver rendering, and providing an initial state on the client to create the\nexact same document is also known as \"rehydration\".\n\nFor security purposes, after `window.initialState` is used it is deleted from\nthe `window` object.\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript\u003ewindow.initialState = { initial: 'state' }\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n## Components\nFrom time to time there will arise a need to have an element in an application\nhold a self-contained state or to not rerender when the application does. This\nis common when using 3rd party libraries to e.g. display an interactive map or a\ngraph and you rely on this 3rd party library to handle modifications to the DOM.\nComponents come baked in to Choo for these kinds of situations. See\n[nanocomponent][nanocomponent] for documentation on the component class.\n\n```javascript\n// map.js\nvar html = require('choo/html')\nvar mapboxgl = require('mapbox-gl')\nvar Component = require('choo/component')\n\nmodule.exports = class Map extends Component {\n  constructor (id, state, emit) {\n    super(id)\n    this.local = state.components[id] = {}\n  }\n\n  load (element) {\n    this.map = new mapboxgl.Map({\n      container: element,\n      center: this.local.center\n    })\n  }\n\n  update (center) {\n    if (center.join() !== this.local.center.join()) {\n      this.map.setCenter(center)\n    }\n    return false\n  }\n\n  createElement (center) {\n    this.local.center = center\n    return html`\u003cdiv\u003e\u003c/div\u003e`\n  }\n}\n```\n\n```javascript\n// index.js\nvar choo = require('choo')\nvar html = require('choo/html')\nvar Map = require('./map.js')\n\nvar app = choo()\napp.route('/', mainView)\napp.mount('body')\n\nfunction mainView (state, emit) {\n  return html`\n    \u003cbody\u003e\n      \u003cbutton onclick=${onclick}\u003eWhere am i?\u003c/button\u003e\n      ${state.cache(Map, 'my-map').render(state.center)}\n    \u003c/body\u003e\n  `\n\n  function onclick () {\n    emit('locate')\n  }\n}\n\napp.use(function (state, emitter) {\n  state.center = [18.0704503, 59.3244897]\n  emitter.on('locate', function () {\n    window.navigator.geolocation.getCurrentPosition(function (position) {\n      state.center = [position.coords.longitude, position.coords.latitude]\n      emitter.emit('render')\n    })\n  })\n})\n```\n\n### Caching components\nWhen working with stateful components, one will need to keep track of component\ninstances – `state.cache` does just that. The component cache is a function\nwhich takes a component class and a unique id (`string`) as its first two\narguments. Any following arguments will be forwarded to the component constructor\ntogether with `state` and `emit`.\n\nThe default class cache is an LRU cache (using [nanolru][nanolru]), meaning it\nwill only hold on to a fixed amount of class instances (`100` by default) before\nstarting to evict the least-recently-used instances. This behavior can be\noverriden with [options](#app--chooopts).\n\n## Optimizations\nChoo is reasonably fast out of the box. But sometimes you might hit a scenario\nwhere a particular part of the UI slows down the application, and you want to\nspeed it up. Here are some optimizations that are possible.\n\n### Caching DOM elements\nSometimes we want to tell the algorithm to not evaluate certain nodes (and its\nchildren). This can be because we're sure they haven't changed, or perhaps\nbecause another piece of code is managing that part of the DOM tree. To achieve\nthis `nanomorph` evaluates the `.isSameNode()` method on nodes to determine if\nthey should be updated or not.\n\n```js\nvar el = html`\u003cdiv\u003enode\u003c/div\u003e`\n\n// tell nanomorph to not compare the DOM tree if they're both divs\nel.isSameNode = function (target) {\n  return (target \u0026\u0026 target.nodeName \u0026\u0026 target.nodeName === 'DIV')\n}\n```\n\n### Reordering lists\nIt's common to work with lists of elements on the DOM. Adding, removing or\nreordering elements in a list can be rather expensive. To optimize this you can\nadd an `id` attribute to a DOM node. When reordering nodes it will compare\nnodes with the same ID against each other, resulting in far fewer re-renders.\nThis is especially potent when coupled with DOM node caching.\n\n```js\nvar el = html`\n  \u003csection\u003e\n    \u003cdiv id=\"first\"\u003ehello\u003c/div\u003e\n    \u003cdiv id=\"second\"\u003eworld\u003c/div\u003e\n  \u003c/section\u003e\n`\n```\n\n### Pruning dependencies\nWe use the `require('assert')` module from Node core to provide helpful error\nmessages in development. In production you probably want to strip this using\n[unassertify][unassertify].\n\nTo convert inlined HTML to valid DOM nodes we use `require('nanohtml')`. This has\noverhead during runtime, so for production environments we should unwrap this\nusing the [nanohtml transform][nanohtml].\n\nSetting up browserify transforms can sometimes be a bit of hassle; to make this\nmore convenient we recommend using [bankai build][bankai] to build your assets for production.\n\n## FAQ\n### Why is it called Choo?\nBecause I thought it sounded cute. All these programs talk about being\n_\"performant\"_, _\"rigid\"_, _\"robust\"_ - I like programming to be light, fun and\nnon-scary. Choo embraces that.\n\nAlso imagine telling some business people you chose to rewrite something\ncritical for serious bizcorp using a train themed framework.\n:steam_locomotive::train::train::train:\n\n### Is it called Choo, Choo.js or...?\nIt's called \"Choo\", though we're fine if you call it \"Choo-choo\" or\n\"Chugga-chugga-choo-choo\" too. The only time \"choo.js\" is tolerated is if /\nwhen you shimmy like you're a locomotive.\n\n### Does Choo use a virtual-dom?\nChoo uses [nanomorph][nanomorph], which diffs real DOM nodes instead of\nvirtual nodes. It turns out that [browsers are actually ridiculously good at\ndealing with DOM nodes][morphdom-bench], and it has the added benefit of\nworking with _any_ library that produces valid DOM nodes. So to put a long\nanswer short: we're using something even better.\n\n### How can I support older browsers?\nTemplate strings aren't supported in all browsers, and parsing them creates\nsignificant overhead. To optimize we recommend running `browserify` with\n[nanohtml][nanohtml] as a global transform or using [bankai][bankai] directly.\n```sh\n$ browserify -g nanohtml\n```\n\n### Is choo production ready?\nSure.\n\n## API\nThis section provides documentation on how each function in Choo works. It's\nintended to be a technical reference. If you're interested in learning choo for\nthe first time, consider reading through the [handbook][handbook] first\n:sparkles:\n\n### `app = choo([opts])`\nInitialize a new `choo` instance. `opts` can also contain the following values:\n- __opts.history:__ default: `true`. Listen for url changes through the\n  history API.\n- __opts.href:__ default: `true`. Handle all relative `\u003ca\n  href=\"\u003clocation\u003e\"\u003e\u003c/a\u003e` clicks and call `emit('render')`\n- __opts.cache:__ default: `undefined`. Override default class cache used by\n  `state.cache`. Can be a a `number` (maximum number of instances in cache,\n  default `100`) or an `object` with a [nanolru][nanolru]-compatible API.\n- __opts.hash:__ default: `false`. Treat hashes in URLs as part of the pathname,\n  transforming `/foo#bar` to `/foo/bar`. This is useful if the application is\n  not mounted at the website root.\n\n### `app.use(callback(state, emitter, app))`\nCall a function and pass it a `state`, `emitter` and `app`. `emitter` is an instance\nof [nanobus](https://github.com/choojs/nanobus/). You can listen to\nmessages by calling `emitter.on()` and emit messages by calling\n`emitter.emit()`. `app` is the same Choo instance. Callbacks passed to `app.use()` are commonly referred to as\n`'stores'`.\n\nIf the callback has a `.storeName` property on it, it will be used to identify\nthe callback during tracing.\n\nSee [#events](#events) for an overview of all events.\n\n### `app.route(routeName, handler(state, emit))`\nRegister a route on the router. The handler function is passed `app.state`\nand `app.emitter.emit` as arguments. Uses [nanorouter][nanorouter] under the\nhood.\n\nSee [#routing](#routing) for an overview of how to use routing efficiently.\n\n### `app.mount(selector)`\nStart the application and mount it on the given `querySelector`,\nthe given selector can be a String or a DOM element.\n\nIn the browser, this will _replace_ the selector provided with the tree returned from `app.start()`.\nIf you want to add the app as a child to an element, use `app.start()` to obtain the tree and manually append it.\n\nOn the server, this will save the `selector` on the app instance.\nWhen doing server side rendering, you can then check the `app.selector` property to see where the render result should be inserted.\n\nReturns `this`, so you can easily export the application for server side rendering:\n\n```js\nmodule.exports = app.mount('body')\n```\n\n### `tree = app.start()`\nStart the application. Returns a tree of DOM nodes that can be mounted using\n`document.body.appendChild()`.\n\n### `app.toString(location, [state])`\nRender the application to a string. Useful for rendering on the server.\n\n### `choo/html`\nCreate DOM nodes from template string literals. Exposes\n[nanohtml](https://github.com/choojs/nanohtml). Can be optimized using\n[nanohtml][nanohtml].\n\n### `choo/html/raw`\nExposes [nanohtml/raw](https://github.com/shama/nanohtml#unescaping) helper for rendering raw HTML content.\n\n## Installation\n```sh\n$ npm install choo\n```\n\n## See Also\n- [bankai](https://github.com/choojs/bankai) - streaming asset compiler\n- [stack.gl](http://stack.gl/) - open software ecosystem for WebGL\n- [yo-yo](https://github.com/maxogden/yo-yo) - tiny library for modular UI\n- [tachyons](https://github.com/tachyons-css/tachyons) - functional CSS for\n  humans\n- [sheetify](https://github.com/stackcss/sheetify) - modular CSS bundler for\n  `browserify`\n\n## Support\nCreating a quality framework takes a lot of time. Unlike others frameworks,\nChoo is completely independently funded. We fight for our users. This does mean\nhowever that we also have to spend time working contracts to pay the bills.\nThis is where you can help: by chipping in you can ensure more time is spent\nimproving Choo rather than dealing with distractions.\n\n### Sponsors\nBecome a sponsor and help ensure the development of independent quality\nsoftware. You can help us keep the lights on, bellies full and work days sharp\nand focused on improving the state of the web. [Become a\nsponsor](https://opencollective.com/choo#sponsor)\n\n\u003ca href=\"https://opencollective.com/choo/sponsor/0/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/0/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/1/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/1/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/2/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/2/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/3/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/3/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/4/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/4/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/5/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/5/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/6/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/6/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/7/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/7/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/8/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/8/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/9/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/9/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/10/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/10/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/11/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/11/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/12/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/12/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/13/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/13/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/14/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/14/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/15/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/15/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/16/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/16/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/17/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/17/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/18/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/18/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/19/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/19/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/20/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/20/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/21/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/21/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/22/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/22/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/23/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/23/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/24/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/24/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/25/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/25/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/26/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/26/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/27/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/27/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/28/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/28/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/sponsor/29/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/sponsor/29/avatar.svg\"\u003e\u003c/a\u003e\n\n### Backers\nBecome a backer, and buy us a coffee (or perhaps lunch?) every month or so.\n[Become a backer](https://opencollective.com/choo#backer)\n\n\u003ca href=\"https://opencollective.com/choo/backer/0/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/0/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/1/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/1/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/2/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/2/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/3/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/3/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/4/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/4/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/5/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/5/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/6/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/6/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/7/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/7/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/8/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/8/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/9/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/9/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/10/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/10/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/11/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/11/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/12/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/12/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/13/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/13/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/14/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/14/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/15/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/15/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/16/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/16/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/17/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/17/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/18/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/18/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/19/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/19/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/20/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/20/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/21/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/21/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/22/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/22/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/23/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/23/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/24/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/24/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/25/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/25/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/26/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/26/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/27/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/27/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/28/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/28/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/choo/backer/29/website\" target=\"_blank\"\u003e\u003cimg src=\"https://opencollective.com/choo/backer/29/avatar.svg\"\u003e\u003c/a\u003e\n\n## License\n[MIT](https://tldrlegal.com/license/mit-license)\n\n[nanocomponent]: https://github.com/choojs/nanocomponent\n[nanolru]: https://github.com/s3ththompson/nanolru\n[bankai]: https://github.com/choojs/bankai\n[nanohtml]: https://github.com/choojs/nanohtml\n[browserify]: https://github.com/substack/node-browserify\n[budo]: https://github.com/mattdesl/budo\n[es2020]: https://github.com/yoshuawuyts/es2020\n[handbook]: https://github.com/yoshuawuyts/choo-handbook\n[hyperx]: https://github.com/substack/hyperx\n[morphdom-bench]: https://github.com/patrick-steele-idem/morphdom#benchmarks\n[nanomorph]: https://github.com/choojs/nanomorph\n[nanorouter]: https://github.com/choojs/nanorouter\n[yo-yo]: https://github.com/maxogden/yo-yo\n[unassertify]: https://github.com/unassert-js/unassertify\n[window-performance]: https://developer.mozilla.org/en-US/docs/Web/API/Performance\n","funding_links":["https://opencollective.com/choo","https://opencollective.com/choo/backer/0/website","https://opencollective.com/choo/backer/1/website","https://opencollective.com/choo/backer/2/website","https://opencollective.com/choo/backer/3/website","https://opencollective.com/choo/backer/4/website","https://opencollective.com/choo/backer/5/website","https://opencollective.com/choo/backer/6/website","https://opencollective.com/choo/backer/7/website","https://opencollective.com/choo/backer/8/website","https://opencollective.com/choo/backer/9/website","https://opencollective.com/choo/backer/10/website","https://opencollective.com/choo/backer/11/website","https://opencollective.com/choo/backer/12/website","https://opencollective.com/choo/backer/13/website","https://opencollective.com/choo/backer/14/website","https://opencollective.com/choo/backer/15/website","https://opencollective.com/choo/backer/16/website","https://opencollective.com/choo/backer/17/website","https://opencollective.com/choo/backer/18/website","https://opencollective.com/choo/backer/19/website","https://opencollective.com/choo/backer/20/website","https://opencollective.com/choo/backer/21/website","https://opencollective.com/choo/backer/22/website","https://opencollective.com/choo/backer/23/website","https://opencollective.com/choo/backer/24/website","https://opencollective.com/choo/backer/25/website","https://opencollective.com/choo/backer/26/website","https://opencollective.com/choo/backer/27/website","https://opencollective.com/choo/backer/28/website","https://opencollective.com/choo/backer/29/website"],"categories":["JavaScript","Badges","Uncategorized","框架","Programming Languages","Examples"],"sub_categories":["Uncategorized","macros","JavaScript"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchoojs%2Fchoo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchoojs%2Fchoo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchoojs%2Fchoo/lists"}