http: add
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
[![downloads][8]][9] [![js-standard-style][10]][11]
|
[![downloads][8]][9] [![js-standard-style][10]][11]
|
||||||
|
|
||||||
A framework for creating sturdy web applications. Built on years of industry
|
A framework for creating sturdy web applications. Built on years of industry
|
||||||
experience and distills the essence of functional architectures into a
|
experience it distills the essence of functional architectures into a
|
||||||
productive package.
|
productive package.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -52,16 +52,44 @@ const tree = app.start()
|
|||||||
document.body.appendChild(tree)
|
document.body.appendChild(tree)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Perform HTTP requests
|
|
||||||
|
|
||||||
## Concepts
|
## Concepts
|
||||||
- __state:__ a single object that contains all application state, should only
|
- __state:__ a single object that contains all application state, should only
|
||||||
ever be modified by `reducers`
|
ever be modified by `reducers`
|
||||||
- __reducers:__ syncronous functions that modify `state`
|
- __reducers:__ syncronous functions that modify `state`
|
||||||
- __effects:__ asyncronous functions that perform IO. Effects should call
|
- __effects:__ asyncronous functions that perform IO. Effects can call
|
||||||
`send()` when done
|
`send()` when done to handle results
|
||||||
- __subscriptions:__ streams of data that can either be written to or read from
|
- __subscriptions:__ streams of data that can either be written to or read from
|
||||||
|
|
||||||
|
## Side effects
|
||||||
|
### 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')
|
||||||
|
|
||||||
|
// GET JSON
|
||||||
|
http.get('/my-endpoint', { json: true }, function (err, res, body) {
|
||||||
|
if (err) throw err
|
||||||
|
if (res.statusCode !== 200 || !body) throw new Error('something went wrong')
|
||||||
|
})
|
||||||
|
|
||||||
|
// POST JSON
|
||||||
|
const body = { foo: 'bar' }
|
||||||
|
http.post('/my-endpoint', { json: body }, function (err, res, body) {
|
||||||
|
if (err) throw err
|
||||||
|
if (res.statusCode !== 200 || !body) throw new Error('something went wrong')
|
||||||
|
})
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
http.del('/my-endpoint', function (err, res) {
|
||||||
|
if (err) throw err
|
||||||
|
if (res.statusCode !== 200) throw new Error('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).
|
||||||
|
|
||||||
## API
|
## API
|
||||||
### app = choo()
|
### app = choo()
|
||||||
Create a new `choo` app
|
Create a new `choo` app
|
||||||
@@ -100,6 +128,7 @@ Start the application. Returns a DOM element that can be mounted using
|
|||||||
- __models:__ [`send-action`](https://github.com/sethvincent/send-action),
|
- __models:__ [`send-action`](https://github.com/sethvincent/send-action),
|
||||||
[`xtend`](https://github.com/raynos/xtend)
|
[`xtend`](https://github.com/raynos/xtend)
|
||||||
- __routes:__ [`sheet-router`](https://github.com/yoshuawuyts/sheet-router)
|
- __routes:__ [`sheet-router`](https://github.com/yoshuawuyts/sheet-router)
|
||||||
|
- __http:__ [`xhr`](https://github.com/Raynos/xhr)
|
||||||
|
|
||||||
## Optimizing
|
## Optimizing
|
||||||
To bring down file size, consider running the following `browserify`
|
To bring down file size, consider running the following `browserify`
|
||||||
@@ -112,9 +141,10 @@ transforms:
|
|||||||
UglifyJS2. Use as a `--global` transform
|
UglifyJS2. Use as a `--global` transform
|
||||||
|
|
||||||
## Packages that work well together
|
## Packages that work well together
|
||||||
- [xhr](https://github.com/Raynos/xhr) - small XHR wrapper
|
|
||||||
- [tachyons](https://github.com/tachyons-css/tachyons) - functional CSS for
|
- [tachyons](https://github.com/tachyons-css/tachyons) - functional CSS for
|
||||||
humans
|
humans
|
||||||
|
- [sheetify](https://github.com/stackcss/sheetify) - modular CSS bundler for
|
||||||
|
browserify
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
const choo = require('../../')
|
||||||
|
|
||||||
|
const mainView = require('./views/main')
|
||||||
|
|
||||||
|
const app = choo()
|
||||||
|
|
||||||
|
app.model(require('./models/error'))
|
||||||
|
app.model('api', require('./models/api'))
|
||||||
|
|
||||||
|
app.router((route) => [
|
||||||
|
route('/', mainView)
|
||||||
|
])
|
||||||
|
|
||||||
|
const tree = app.start()
|
||||||
|
document.body.appendChild(tree)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
const http = require('../../../http')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
state: {
|
||||||
|
title: 'Button pushing machine 3000'
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
set: function (action, state) {
|
||||||
|
return { 'api:title': action.payload }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
good: performGoodRequest,
|
||||||
|
bad: performBadRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function performGoodRequest (action, state, send) {
|
||||||
|
http('/good', { json: true }, function (err, res, body) {
|
||||||
|
if (err) return send('error', { payload: 'HTTP error' })
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
return send('error', { payload: body.payload })
|
||||||
|
}
|
||||||
|
if (!body) {
|
||||||
|
return send('error', { payload: 'fatal: no body received' })
|
||||||
|
}
|
||||||
|
send('api:set', { payload: body.message })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function performBadRequest (action, state, send) {
|
||||||
|
http('/bad', { json: true }, function (err, res, body) {
|
||||||
|
if (err) return send('error', { payload: 'HTTP error' })
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
const message = (body && body.message)
|
||||||
|
? body.message
|
||||||
|
: 'unknown server error'
|
||||||
|
return send('error', { payload: message })
|
||||||
|
}
|
||||||
|
if (!body) {
|
||||||
|
return send('error', { payload: 'fatal: no body received' })
|
||||||
|
}
|
||||||
|
send('api:set', { payload: body.title })
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Create a new error. Errors are pushed into an array so they can be
|
||||||
|
// queued. We're doing a bit of timestamp trickery here so each error
|
||||||
|
// is shown for exactly one second before being dropped. Probably
|
||||||
|
// useful for real world scenarios; especially when coupled to
|
||||||
|
// fancy CSS animation thingies
|
||||||
|
|
||||||
|
// Each error is displayed 1 second
|
||||||
|
const ERROR_TIMEOUT = 1000
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
state: {
|
||||||
|
error: [],
|
||||||
|
errorTimeDone: null,
|
||||||
|
triggerTime: null
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
error: function (action, state) {
|
||||||
|
const now = Date.now()
|
||||||
|
const timeDone = state.errorTimeDone
|
||||||
|
const newTimestamp = (timeDone && timeDone >= now)
|
||||||
|
? timeDone + ERROR_TIMEOUT
|
||||||
|
: now + ERROR_TIMEOUT
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: state.error.concat(action.payload),
|
||||||
|
errorTimeDone: newTimestamp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'error:delete': function (action, state) {
|
||||||
|
state.error.shift()
|
||||||
|
return { error: state.error }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
error: function (action, state, send) {
|
||||||
|
const timeout = state.errorTimeDone - Date.now()
|
||||||
|
setTimeout(function () {
|
||||||
|
send('error:delete')
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"private": true,
|
||||||
|
"main": "client.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "NODE_ENV=development node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bankai": "^2.0.2",
|
||||||
|
"browserify": "^13.0.1",
|
||||||
|
"server-router": "^2.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
const serverRouter = require('server-router')
|
||||||
|
const browserify = require('browserify')
|
||||||
|
const bankai = require('bankai')
|
||||||
|
const http = require('http')
|
||||||
|
|
||||||
|
const PORT = 8080
|
||||||
|
|
||||||
|
const server = http.createServer(createRouter())
|
||||||
|
server.listen(PORT, () => console.log(`listening on port ${PORT}`))
|
||||||
|
|
||||||
|
var index = 0
|
||||||
|
const errors = [
|
||||||
|
'a giant robot invaded robot town!',
|
||||||
|
'something weird crawled out of the swamp',
|
||||||
|
'oh no, the bear people invaded the server!',
|
||||||
|
'the NSA',
|
||||||
|
'12 goats started a band',
|
||||||
|
'we kinda just gave up'
|
||||||
|
]
|
||||||
|
|
||||||
|
function createRouter () {
|
||||||
|
const router = serverRouter('/404')
|
||||||
|
|
||||||
|
const html = bankai.html({ css: false })
|
||||||
|
router.on('/', (req, res) => html(req, res).pipe(res))
|
||||||
|
|
||||||
|
const js = bankai.js(browserify, require.resolve('./client.js'))
|
||||||
|
router.on('/bundle.js', (req, res) => js(req, res).pipe(res))
|
||||||
|
|
||||||
|
router.on('/good', (req, res) => {
|
||||||
|
res.end('{ "message": "all is well in robo town!" }')
|
||||||
|
})
|
||||||
|
|
||||||
|
router.on('/bad', (req, res) => {
|
||||||
|
res.statusCode = 500
|
||||||
|
res.end(`{ "message": "${errors[index++ % (errors.length)]}" }`)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.on('/404', (req, res) => {
|
||||||
|
res.statusCode = 404
|
||||||
|
res.end('{ "message": "the server is confused" }')
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const choo = require('../../../')
|
||||||
|
|
||||||
|
module.exports = function (params, state, send) {
|
||||||
|
return choo.view`
|
||||||
|
<section>
|
||||||
|
<h1>${state['api:title']}</h1>
|
||||||
|
<h2>Latest error: ${state.error[0]}</h2>
|
||||||
|
<button onclick=${(e) => send('api:good')}>OK!</button>
|
||||||
|
<button onclick=${(e) => send('api:bad')}>Naughty</button>
|
||||||
|
</section>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"global": "^4.3.0",
|
"global": "^4.3.0",
|
||||||
"send-action": "^1.1.0",
|
"send-action": "^1.1.0",
|
||||||
"sheet-router": "^2.0.5",
|
"sheet-router": "^2.0.5",
|
||||||
|
"xhr": "^2.2.0",
|
||||||
"xtend": "^4.0.1",
|
"xtend": "^4.0.1",
|
||||||
"yo-yo": "^1.2.0"
|
"yo-yo": "^1.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
# -t uglifyify \
|
# -t uglifyify \
|
||||||
# | discify --open
|
# | discify --open
|
||||||
|
|
||||||
browserify index.js --full-paths \
|
browserify http.js --full-paths \
|
||||||
-g unassertify \
|
-g unassertify \
|
||||||
-g uglifyify \
|
-g uglifyify \
|
||||||
| gzip-size \
|
| gzip-size \
|
||||||
|
|||||||
Reference in New Issue
Block a user