cuppa v0.1.3
cuppa
A fun little frontend application framework. A wee cuppa: only 2.6kb gzipped.
Install
npm i cuppa --save
Simple setup
Initialize an instance with routes:
const cuppa = require('cuppa')
const h = require('cuppa/html')
const app = cuppa([
['/', () => {
return h`<h1>Hello world</h1>`
}]
])
Mount instance to DOM with initial state object:
app(document.getElementById('root'), {
count: 0
}, () => {
console.info('cuppa initialized ☕️')
})
Enjoy while it's hot!
Full usage
Below is a functioning front-end app with async routing.
// App.js
const h = require('cuppa/html')
module.exports = function App (children) {
return h`
<div class='app'>
<nav>
<ul>
<li><a href='/'>Home</a></li>
<li><a href='/about'>About</a></li>
</ul>
</nav>
${children}
</div>
`
}
// Page.js
const h = require('cuppa/html')
module.exports = function Page (data) {
return h`
<h1>You are on page: ${data.title}</h1>
`
}
// index.js
const cuppa = require('cuppa')
const Page = require('./Page.js')
const app = cuppa([
['/', function Home () {
return Promise.resolve({ title: 'Home' })
.then(Page)
}]
['/about', function About () {
return Promise.resolve({ title: 'About' })
.then(Page)
}]
])(document.getElementById('root'), {
count: 0
})
The cuppa instance emits an event when a link is clicked:
app.on('navigate', pathname => {})
And right after rendering has occurred:
app.on('render', markup => {})
State management
Initial state was set when you called cuppa(routes, initialState)
in index.js
. cuppa/state
then exports a higher-order function connect
that you can use to read and update state.
// app/Counter.js
const h = require('cuppa/html')
const { connect } = require('cuppa/state')
module.exports = connect(state => ({
count: state.count
}))(function Counter(props, state) {
return h`
<div>
<h1>The count is ${state.count}</h1>
<button onclick=${e => state.update(state => ({
count: state.count - 1
}))}>Down</button>
<button onclick=${e => state.update(state => ({
count: state.count + 1
}))}>Up</button>
</div>
`
})
Since state is global, it can be controlled via any component
// app/ExternalTrigger.js
const h = require('cuppa/html')
const { connect } = require('./store.js')
module.exports = connect()(function ExternalTrigger (props, state) {
return h`
<div>
In this component, state.count is set to 12.
<button onclick=${e => state.update(state => ({ count: 12 }))}
</div>
`
})
Server side rendering
Server-side is simple, just don't mount the application to the DOM
// app/index.js
const cuppa = require('cuppa')
const Page = require('./Page.js')
const app = cuppa([
['/', function Home () {
return Promise.resolve({ title: 'Home' })
.then(Page)
}]
['/about', function About () {
return Promise.resolve({ title: 'About' })
.then(Page)
}]
])
// server/template.js
module.exports = function Template (markup, state) {
return `
<!doctype html>
<html>
<head>
</head>
<body>
${markup}
<script>window.initialState = ${JSON.stringify(state)}</script>
</body>
</html>
`
}
On the server, the cuppa
instance accepts a route path and initial state object. Use this to send data from your server to the rendered markup.
// server/index.js
const app = require('express')()
const template = require()
const program = require('../app/index.js')
const template = require('./template.js')
app.get('*', (req, res) => {
const state = { count: 0 }
program(req.originalUrl, state)
.then(markup => {
res.send(template(markup, state))
})
})
app.listen(8080)
Then on the client, mount to the DOM and hydrate state.
const app = require('express')()
const state = Object.assign({ count: 0 }, window.initialState)
app(document.getElementById('root'), state)
Extending the template parser
The h
export from cuppa/html
can be modified by the other export of that file: the addTransform
method. Think of this like a higher-order function that can extend the template parser.
const { addTransform } = require('cuppa/html')
const cxs = require('cxs')
addTransform(props => {
if (props.css && typeof props.css === 'object') {
props.className = [props.className || '', cxs(props.css)].join(' ').replace(/^\s/, '')
}
return props
})
With the above, you can now use a css
attribute on your components and it will be translated to classes in the browser:
const h = require('cuppa/html')
module.exports = function Component (props) {
return h`
<div css=${{
display: 'block',
position: 'relative',
color: ${props.color}
}}></div>
`
}
MIT License