diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..22bd7d1
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,10 @@
+## Thanks for contributing to Choo!
+:steam_locomotive::train::train::train::train::train:
+
+### Running tests
+Browser tests can be run with the right credentials via the `npm run
+test:browser` command. This will be run automatically when `npm version` is
+executed.
+
+You may skip the tests by providing `SKIP_TEST=true` when running the version
+command.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..a1d3d67
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,8 @@
+### Expected behavior
+Write here.
+
+### Actual behavior
+Write here.
+
+### Steps to reproduce behavior
+Write here.
diff --git a/README.md b/README.md
index f3829df..8a671f8 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,19 @@
-
+
+
## Features
- __minimal size:__ weighing `5kb`, `choo` is a tiny little framework
@@ -111,67 +109,16 @@
_note: If you've built something cool using `choo` or are using it in
production, we'd love to hear from you!_
-## Getting started
+## Example
Let's create an input box that changes the content of a textbox in real time.
-[Click here to see the final app](http://requirebin.com/?gist=e589473373b3100a6ace29f7bbee3186).
-
-First we import `choo` and create a new instance:
-```js
-const choo = require('choo')
-const html = require('choo/html')
-const app = choo()
-```
-
-Then we define a model. We set an initial value of `state` and a `reducer` that
-can be called to modify it:
-```js
-app.model({
- state: { title: 'Set the title' },
- reducers: {
- update: (data, state) => ({ title: data.value })
- }
-})
-```
-
-Then we create a new view. It has an `h1` tag which displays the current title,
-and an `` field which sends the current value of the text box on every
-input:
-```js
-const mainView = (params, state, send) => html`
-
-
${state.title}
- send('update', { value: e.target.value })}>
-
-`
-```
-
-_Note_: if an `id` property is defined on the outer-most element it will be
-replaced.
-
-We then bind the view to the `/` route on our application
-```js
-app.router((route) => [
- route('/', mainView)
-])
-```
-
-And then start the app and append it to the DOM. You can now run it and [see it
-in action!](http://requirebin.com/?gist=e589473373b3100a6ace29f7bbee3186)
-```js
-const tree = app.start()
-document.body.appendChild(tree)
-```
-
-And all together now:
+[Click here to see the app running](http://requirebin.com/?gist=e589473373b3100a6ace29f7bbee3186).
```js
const choo = require('choo')
const html = require('choo/html')
const app = choo()
app.model({
- state: { title: 'Set the title' },
+ state: { title: 'Not quite set yet' },
reducers: {
update: (data, state) => ({ title: data.value })
}
@@ -179,7 +126,7 @@ app.model({
const mainView = (params, state, send) => html`
-
${state.title}
+
Title: ${state.title}
send('update', { value: e.target.value })}>
@@ -194,6 +141,44 @@ const tree = app.start()
document.body.appendChild(tree)
```
+And to run it, save it as `client.js` and run with [budo][budo] and
+[es2020][es2020]:
+```sh
+$ budo 'client.js' -p 8080 --open -- -t es2020
+```
+
+And to save to static files for deployment, open a new terminal and do:
+```bash
+$ mkdir -p 'dist/'
+$ curl 'localhost:8080' > 'dist/index.html'
+$ curl 'localhost:8080/bundle.js' > 'dist/bundle.js'
+```
+All just a couple of shell commands and `.js` files, no grandiose boilerplate
+needed.
+
+## Philosophy
+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
+like the current state of web development where walled gardens jealously
+compete with one another. We want you to be free, not shackled to a damp
+dungeon wall. By making the DOM the lowest common denominator, switching from
+one framework to another becomes frictionless. Components should run anywhere
+that has a DOM, regardless of the framework. `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, big dependencies, large file
+sizes - 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.
+
## Concepts
`choo` cleanly structures internal data flow, so that all pieces of logic can
be combined into a nice, cohesive machine. Internally all logic lives within
@@ -238,7 +223,7 @@ application logic. This is the _unidirectional_ architecture of `choo`.
- __views:__ take `state` and returns a new `DOM tree` that is rendered in the
browser
-## Models
+### Models
`models` are objects that contain initial `state`, `subscriptions`, `effects`
and `reducers`. They're generally grouped around a theme (or domain, if you
like). To provide some sturdiness to your `models`, they can either be
@@ -273,7 +258,7 @@ and has access to the full application state. Try and keep the logic in these
bulk of your logic will safely shielded, with only a few points touching every
part of your application.
-## Effects
+### Effects
Side effects are done through `effects` declared in `app.model()`. Unlike
`reducers` they cannot modify the state by returning objects, but get a
callback passed which is used to emit `actions` to handle results. Use effects
@@ -288,7 +273,7 @@ A typical `effect` flow looks like:
4. When the async call is done, either a success or error action is emitted
5. A reducer catches the action and updates the state
-## Subscriptions
+### Subscriptions
Subscriptions are a way of receiving data from a source. For example when
listening for events from a server using `SSE` or `Websockets` for a
chat app, or when catching keyboard input for a videogame.
@@ -307,7 +292,7 @@ app.model({
})
```
-## Router
+### Router
The `router` manages which `views` are rendered at any given time. It also
supports rendering a default `view` if no routes match.
@@ -333,7 +318,7 @@ using `send('app:location', { location: href })`. This will not work from
within namespaced `models`, and usage should preferably be kept to a minimum.
Changing views all over the place tends to lead to messiness.
-## Views
+### Views
Views are pure functions that return a DOM tree for the router to render. They’re passed the current state, and any time the state changes they’re run again with the new state.
Views are also passed the `send` function, which they can use to dispatch actions that can update the state. For example, the DOM tree can have an `onclick` handler that dispatches an `add` action.
@@ -349,316 +334,75 @@ const view = (params, state, send) => {
```
In this example, when the `Add` button is clicked, the view will dispatch an `add` action that the model’s `add` reducer will receive. [As seen above](#models), the reducer will add an item to the state’s `todos` array. The state change will cause this view to be run again with the new state, and the resulting DOM tree will be used to [efficiently patch the DOM](#does-choo-use-a-virtual-dom).
-## Common Actions
-### HTTP
-`choo` ships with a built-in [`http` module](https://github.com/Raynos/xhr)
-that weighs only `2.4kb`:
-```js
-const http = require('choo/http')
-const choo = require('choo')
-const app = choo()
-
-app.model({
- effects: {
- 'app:error': (state, event) => console.error(`error: ${event.payload}`),
- 'app:print': (state, event) => console.log(`http: ${event.payload}`),
- 'http:get_json': getJson,
- 'http:post_json': postJson,
- 'http:delete': httpDelete
- }
-})
-
-function getJson (state, data, send) {
- http.get('/my-endpoint', { json: true }, function (err, res, body) {
- if (err) return send('app:error', { payload: err.message })
- if (res.statusCode !== 200 || !body) {
- return send('app:error', { payload:'something went wrong' })
- }
- send('app:print', { payload: body })
- })
-}
-
-function postJson (state, data, send) {
- const body = { foo: 'bar' }
- http.post('/my-endpoint', { json: body }, function (err, res, body) {
- if (err) return send('app:error', { payload: err.message })
- if (res.statusCode !== 200 || !body) {
- return send('app:error', { payload:'something went wrong' })
- }
- send('app:print', { payload: body })
- })
-}
-
-function httpDelete (state, data, send) {
- const body = { foo: 'bar' }
- http.del('/my-endpoint', { json: body }, function (err, res, body) {
- if (err) return send('app:error', { payload: err.message })
- if (res.statusCode !== 200) {
- return send('app:error', { payload:'something went wrong' })
- }
- })
-}
-```
-Note that `http` only runs in the browser to prevent accidental requests when
-rendering in Node. For more details view the [`raynos/xhr`
-documentation](https://github.com/Raynos/xhr).
-
-### Server Sent Events (SSE)
-[Server Sent Events (SSE)][sse] allow servers to push data to the browser.
-They're the unidirectional cousin of `websockets` and compliment `HTTP`
-brilliantly. To enable `SSE`, create a new `EventSource`, point it at a local
-uri (generally `/sse`) and setup a `subscription`:
-```js
-const stream = new document.EventSource('/sse')
-
-app.model({
- subscriptions: [
- function (send) {
- stream.onerror = (e) => send('app:error', { payload: JSON.stringify(e) })
- stream.onmessage = (e) => send('app:print', { payload: e.data })
- }
- ],
- effects: {
- 'sse:close': () => stream.close(),
- 'app:error': (state, event) => console.error(`error: ${event.payload}`),
- 'app:print': (state, event) => console.log(`sse: ${event.payload}`)
- }
-})
-```
-This code does not handle reconnects, server timeouts, exponential backoff and
-queueing data. You might want to use a package from `npm` or [write your
-own][sse-reconnect] if you're building something for production.
-
-### Keyboard
-Most browsers have [basic support for keyboard events][keyboard-support]. To
-capture keyboard events, setup a `subscription`:
-```js
-app.model({
- namespace: 'input',
- subscriptions: [
- function (send) {
- document.addEventListener(
- 'keypress',
- (e) => send('input:print', { payload: e.keyCode })
- )
- }
- ],
- effects: {
- print: (state) => console.log(`pressed key: ${state.payload}`)
- }
-})
-```
-
-### WebSockets
-[WebSockets][ws] allow for bidirectional communication between servers and
-browsers:
-```js
-const socket = new document.WebSocket('ws://localhost:8081')
-
-app.model({
- subscriptions: [
- function (send) {
- socket.onerror = (e) => send('app:error', { payload: JSON.stringify(e) })
- socket.onmessage = (e) => send('app:print', { payload: e.data })
- }
- ],
- effects: {
- 'ws:close': () => socket.close(),
- 'ws:send': (state, event) => socket.send(JSON.stringify(event.payload)),
- 'app:error': (state, event) => console.error(`error: ${event.payload}`),
- 'app:print': (state, event) => console.log(`ws: ${event.payload}`)
- }
-})
-```
-This code does not handle reconnects, server timeouts, exponential backoff and
-queueing data. You might want to use a package from `npm` or [write your
-own][ws-reconnect] if you're building something for production.
-
-### Forms
-Forms and lists are probably the most used concepts on any page. Together with
-links they comprise most of what can be done on web pages.
-```js
-const choo = require('choo')
-const http = require('choo/http')
-const html = require('choo/html')
-const app = choo()
-
-function view (params, state, send) {
- return html`
-
- `
-
- function onSubmit (event) {
- send('login', new FormData(event.target))
- event.preventDefault()
- }
-}
-
-app.model({
- effects: {
- login: (data, state, send) => {
- http.post('/login', { body: data }, (err, res, body) => {
- send('authorize', { payload: body })
- })
- }
- }
-})
-
-app.router((route) => [
- route('/', view)
-])
-
-app.start()
-```
-
-If you want a form element to be selected when it's loaded, add the
-[`autofocus`][html-input] property.
-```js
-const view = html`
-
-`
-```
-
-### Links
-In HTML links are represented with the `` tag. By
-default `choo` enables a `subscription` for all `a` tags on a page. When a link
-is clicked, the click event is caught, and the value of `href` is passed into
-the router causing a state change. If you want to disable this behavior, set
-`app.start({ href: false })`.
-```js
-const nav = html`
- home
- first link
- second link
-`
-```
-
-### Rendering in Node
-Sometimes it's necessary to render code inside of Node; for serving hyper fast
-first requests, testing or other purposes. Applications that are capable of
-being rendered in both Node and the browser are called
-_[isomorphic][isomorphic]_.
-
-Rendering in Node is slightly different than in the browser. First off, to
-maintain performance all calls to `subscriptions`, `effects`, and `reducers`
-are disabled. That means you need to know what the state of your application is
-going to be _before_ you render it - no cheating!
-
-Secondly, the `send()` method inside `router` and `view` has been disabled. If
-you call it your program will crash. Disabling all these things means that your
-program will render [`O(n)`][big-o], which is super neat. Off to [10.000
-QPS][qps] we go!
-
-To render in Node call the `.toString()` method instead of `.start()`. The
-first argument is the path that should be rendered, the second is the state:
-```js
-const http = require('http')
-const client = require('./client') // path to client entry point
-http.createServer(function (req, res) {
- const html = client.toString('/', { message: 'hello server!' })
- res.setHeader('Content-Type', 'text/html; charset=utf-8')
- res.end(html)
-})
-```
-
-In order to make our `choo` app call `app.start()` in the browser and be
-`require()`-able in Node, we check if [`module.parent`][module-parent] exists:
-```js
-const choo = require('choo')
-const html = require('choo/html')
-const app = choo()
-
-app.router((route) => [
- route('/', (params, state, send) => html`
-
${state.message}
- `)
-])
-
-if (module.parent) module.exports = app
-else document.body.appendChild(app.start())
-```
-
-#### Rehydration
-Now that your application is successfully rendering in Node, the next step would
-be to make it load a JavaScript bundle once has loaded the HTML. To do this we
-will use a technique called _rehydration_.
-
-_Rehydration_ is when you take the static, server-rendered version of your
-application (static HTML, _dehydrated_ because it has no logic) and _rehydrate_
-it by booting up the JS and attaching event handlers on the DOM to make it
-dynamic again. It's like restoring flavor to cup noodles by adding hot water.
-
-Because we're using something called `morphdom` under the hood, all we need is
-point at an `id` at the root of the application. The syntax for this is
-slightly different from what we've seen so far, because we're _updating_ a
-dehydrated DOM nodes to make them dynamic, rather than a new DOM tree and
-attaching it to the DOM.
-```js
-const choo = require('choo')
-const html = require('choo/html')
-const app = choo()
-
-app.router((route) => [
- route('/', (params, state, send) => html`
-
${state.message}
- `)
-])
-
-if (module.parent) module.exports = app
-else app.start('#app-root'))
-```
-
-When the JS is booted on top of the dehydrated application, it will look for
-the `#app-root` id and load on top of it. You can choose any name you like for
-the id, but __make sure it's the same on every possible top level DOM node__,
-or else things might break. Furthermore to ensure things go smoothly, try and
-keep the initial state identical on both the server and the client.
-
-And that's it! If you want to go down the route of mad performance, consider
-make all first request static and caching them using something like [bl][bl],
-[nginx][nginx], [varnish][varnish] or a global CDN.
-
## API
-### app = choo()
-Create a new `choo` app
+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][handbook] or
+[concepts](#concepts) first :sparkles:
+
+### app = choo(opts)
+Initialize a new `choo` app. Takes an optional object of handlers. Handlers can
+be:
+- __onError(err, state, createSend):__ called when an `effect` or
+ `subscription` emit an error. If no handler is passed, the default handler
+ will `throw` on each error.
+- __onAction(action, state, name, caller, createSend):__ called when an
+ `action` is fired.
+- __onStateChange(action, state, prev, caller, createSend):__ called after a
+ reducer changes the `state`.
+
+`createSend()` is a special function that allows the creation of a new named
+`send()` function. The first argument should be a string which is the name, the
+second argument is a boolean `callOnError` which can be set to `true` to call
+the `onError` hook istead of a provided callback. It then returns a
+`send(actionName, data?)` function.
+
+Handlers should be used with care, as they're the most powerful interface into
+the state. For application level code it's generally recommended to delegate to
+actions inside models using the `send()` call, and only shape the actions
+inside the handlers.
### app.model(obj)
Create a new model. Models modify data and perform IO. Takes the following
arguments:
-- __namespace:__ optional namespace that prefixes the keys in `state`,
- `reducers` and `effects`. Also limits `actions` called by `send()` to
- in-namespace only.
-- __state:__ object. Key value store of initial values
-- __reducers:__ object. Syncronous functions that modify state. Each function
- has a signature of `(data, state)`
-- __effects:__ object. Asyncronous functions that perform IO. Each function has
- a signature of `(data, state, send)` where `send` is a reference to
- `app.send()`
+- __namespace:__ namespace the model so that it cannot access any properties
+ and handlers in other models
+- __state:__ initial values of `state` inside the model
+- __reducers:__ synchronous operations that modify state. Triggered by
+ `actions`. Signature of `(actionData, state)`.
+- __effects:__ asynchronous operations that don't modify state directly.
+ Triggered by `actions`, can call `actions`. Signature of `(actionData, state,
+ send, done)`
+- __subscriptions:__ asynchronous read-only operations that don't modify state
+ directly. Can call `actions`. Signature of `(state, send, done)`.
-### app.router(params, state, send)
-Creates a new router. See
+#### send()
+Send a new action to the models with optional data attached. Namespaced models
+can be accessed by prefixing the name with the namespace separated with a `:`,
+e.g. `namespace:name`.
+
+#### done()
+When an `effect` or `subscription` is done executing, or encounters an error,
+it should call the final `done(err)` callback. If an `effect` was called by
+another `effect` it will call the callback of the caller. When an error
+propegates all the way to the top, the `onError` handler will be called,
+registered in `barracks(handlers)`. If no callback is registered, errors will
+`throw`.
+
+### app.router((route) => [routes])
+Creates a new router. Takes a function that exposes a single `route` function,
+and that expects a tree of `routes` to be returned. See
[`sheet-router`](https://github.com/yoshuawuyts/sheet-router) for full
-documentation. Registered views have a signature of `(params, state, send)`,
-where `params` is URI partials.
+documentation. Registered views have a signature of `(state, prev, send)`,
+where `state` is the current `state`, `prev` is the last state, `state.params`
+is URI partials and `send()` can be called to trigger actions.
-### html = app.toString(route, state)
+### html = app.toString(route, state?)
Render the application to a string of HTML. Useful for rendering on the server.
-First argument is a path that's passed to the router. Second argument is the
-state object. When calling `.toString()` instead of `.start()`, all calls to
-`send()` are disabled, and `subscriptions`, `effects` and `reducers` aren't
-loaded. See [rendering in Node](#rendering-in-node) for an in-depth guide.
+First argument is a path that's passed to the router. Second argument is an
+optional state object. When calling `.toString()` instead of `.start()`, all
+calls to `send()` are disabled, and `subscriptions`, `effects` and `reducers`
+aren't loaded.
### tree = app.start(rootId?, opts)
Start the application. Returns a tree of DOM nodes that can be mounted using
@@ -677,45 +421,39 @@ following values:
changes (eg `localhost/#posts/123`). Enabling this option automatically
disables `opts.history` and `opts.href`.
-### choo/html\`html\`
-Tagged template string HTML builder. See
-[`yo-yo`](https://github.com/maxogden/yo-yo) for full documentation. Views
-should be passed to `app.router()`
+### view = choo/html\`html\`
+Tagged template string HTML builder. Built on top of [yo-yo][bel], [bel][bel]
+and [hyperx][hyperx]. To register a view on the `router` it should be wrapped
+in a function with the signature of `(state, prev, send)` where `state` is the
+current `state`, `prev` is the last state, `state.params` is URI partials and
+`send()` can be called to trigger actions.
-## Errors
-### Could not find DOM node (#id) to update
-This means that a re-render of the DOM was triggered before the first render
-was done. This is usually the case when `send()` is called inside a
-`subscription` before the DOM is done rendering. Instead try listening for a
-`'DOMContentLoaded'` event:
+To create listeners for events, create interpolated attributes on elements.
```js
-document.addEventListener('DOMContentLoaded', (e) => send('init'))
+const html = require('choo/html')
+html`
+
+`
```
+Example listeners include: `onclick`, `onsubmit`, `oninput`, `onkeydown`,
+`onkeyup`. When creating listeners always remember to call `e.preventDefault()`
+on the event so it doesn't bubble up and do stuff like refreshing the full page
+or the like.
-### send() cannot be called on the server
-This means a `send()` event was triggered in Node. In Node, `reducers`,
-`effects` and `subscriptions` are disabled for performance reasons, so if
-`send()` was called to trigger an action it wouldn't work. Try finding where in
-the DOM tree `send()` is called, and disable it when called from within Node.
+To trigger lifecycle events on any part of a view, set the `onload=${(el) =>
+{}}` and `onunload=${() => {}}` attributes. These parameters are useful when
+creating self-contained widgets that take care of their own state and lifecycle
+(e.g. a maps widget) or to trigger animations. Most elements shouldn't have a
+need for these hooks though.
## FAQ
-### Why did you build this?
-`choo` is nothing but a formalization of how I've been building my applications
-for the past year. I originally used `virtual-dom` with `virtual-app` and
-`wayfarer` where now it's `yo-yo` with `send-action` and `sheet-router`. The
-main benefit of using `choo` over these technologies separately is that it
-becomes easier for teams to pick up and gather around. The code base for `choo`
-itself is super petite (`~200` LOC) and mostly acts to enforce structure around
-some excellent npm packages. This is my take on modular frameworks; I hope
-you'll find it pleasant.
-
### 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 and
+_"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 to the company using the `choo` framework.
+critical to the company using `choo`.
:steam_locomotive::train::train::train:
### Why is it a framework, and not a library?
@@ -723,7 +461,13 @@ I love small libraries that do one thing well, but when working in a team,
having an undocumented combination of packages often isn't great. `choo()` is a
small set of packages that work well together, wrapped in an an architectural
pattern. This means you get all the benefits of small packages, but get to be
-productive right from the start.
+productive right from the start without needing to plough through layers of
+boilerplate.
+
+### 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.
### How does choo compare to X?
Ah, so this is where I get to rant. `choo` (_chugga-chugga-chugga-choo-choo!_)
@@ -768,9 +512,16 @@ give you my opinions directly. Ready? Here goes:
provides framework lock in, and additionally doesn't have a clean enough
architecture. I appreciate what it does, but don't think it's the answer.
+### Why can't send() be called on the server?
+In Node, `reducers`, `effects` and `subscriptions` are disabled for performance
+reasons, so if `send()` was called to trigger an action it wouldn't work. Try
+finding where in the DOM tree `send()` is called, and disable it when called
+from within Node.
+
### Which packages was choo built on?
-- __views:__ [`yo-yo`](https://github.com/maxogden/yo-yo)
-- __models:__ [`send-action`](https://github.com/sethvincent/send-action),
+- __views:__ [`yo-yo`](https://github.com/maxogden/yo-yo),
+ [`bel`](https://github.com/shama/bel)
+- __models:__ [`barracks`](https://github.com/yoshuawuyts/barracks),
[`xtend`](https://github.com/raynos/xtend)
- __routes:__ [`sheet-router`](https://github.com/yoshuawuyts/sheet-router)
- __http:__ [`xhr`](https://github.com/Raynos/xhr)
@@ -782,13 +533,6 @@ with DOM nodes][morphdom-bench], 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.
-### What packages do you recommend to pair with choo?
-- [tachyons](https://github.com/tachyons-css/tachyons) - functional CSS for
- humans
-- [sheetify](https://github.com/stackcss/sheetify) - modular CSS bundler for
- browserify
-- [pull-stream](https://github.com/pull-stream/pull-stream) - minimal streams
-
### How can I optimize choo?
`choo` really shines when coupled with `browserify` transforms. They can do
things like reduce file size, prune dependencies and clean up boilerplate code.
@@ -796,7 +540,8 @@ Consider running some of the following:
- [unassertify](https://github.com/twada/unassertify) - remove `assert()`
statements which reduces file size. Use as a `--global` transform
- [es2020](https://github.com/yoshuawuyts/es2020) - backport `const`,
- `fat-arrows` and `template strings` to older browsers
+ `fat-arrows` and `template strings` to older browsers. Should be run as a
+ `--global` transform
- [yo-yoify](https://github.com/shama/yo-yoify) - replace the internal `hyperx`
dependency with `document.createElement` calls; greatly speeds up performance
too
@@ -850,20 +595,22 @@ Sure.
$ npm install choo
```
-## Contributing
-Browser tests can be run with the right credentials via the `npm run test:browser`
-command. This will be run automatically when `npm version` is executed.
-
-You may skip the tests by providing `SKIP_TEST=true` when running the version
-command.
-
## See Also
+- [choo-handbook](https://github.com/yoshuawuyts/choo-handbook) - the little
+ `choo` guide
- [budo](https://github.com/mattdesl/budo) - quick prototyping tool for
`browserify`
- [stack.gl](http://stack.gl/) - open software ecosystem for WebGL
- [yo-yo](https://github.com/maxogden/yo-yo) - tiny library for modular UI
- [bel](https://github.com/shama/bel) - composable DOM elements using template
strings
+- [tachyons](https://github.com/tachyons-css/tachyons) - functional CSS for
+ humans
+- [sheetify](https://github.com/stackcss/sheetify) - modular CSS bundler for
+ `browserify`
+- [pull-stream](https://github.com/pull-stream/pull-stream) - minimal streams
+- [es2020](https://github.com/yoshuawuyts/es2020) - because in hindsight we
+ don't need most of ES6
## License
[MIT](https://tldrlegal.com/license/mit-license)
@@ -887,3 +634,8 @@ command.
[sheet-router]: https://github.com/yoshuawuyts/sheet-router
[html-input]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
[inu]: https://github.com/ahdinosaur/inu
+[yo-yo]: https://github.com/maxogden/yo-yo
+[bel]: https://github.com/shama/bel
+[hyperx]: https://github.com/substack/hyperx
+[budo]: https://github.com/mattdesl/budo
+[es2020]: https://github.com/yoshuawuyts/es2020