API¶
Rendering a Template¶
Because khufu tries to be non-opionated to what implementation of stores you have, you need to set some parameters to To use khufu, you just need to import a root template function and render it inside the element (note template function is called, so you can pass parameters to it):
import {attach} from 'khufu-runtime'
import {main} from './template.khufu'
import {createStore, applyMiddleware} from 'redux'
attach(document.getElementById('app'), main(), {
store(reducer, middleware, state) {
return createStore(reducer, state, applyMiddleware(...middleware));
}
})
Note
Example uses redux 3.1 and ES2015
Adding support for hot reload is straightforward:
import {attach} from 'khufu-runtime'
import {main} from './template.khufu'
import {createStore, applyMiddleware} from 'redux'
attach(document.getElementById('app'), main(), {
store(reducer, middleware, state) {
return createStore(reducer, state, applyMiddleware(...middleware));
}
})
if(module.hot) {
module.hot.accept()
}
Khufu Object¶
The object that is returned by attach
has the following methods:
-
khufu_obj.
queue_redraw
()¶ Schedules next redraw of the view with
requestAnimationFrame
. You don’t have to call it manually whenever the stores in the view change. But you may need it if you have some external shared state which is changing.For example, if you have time displayed in the view:
var khufu_obj = attach(el, main()) setTimeout(khufu_obj.queue_redraw, 1000)
Runtime Settings¶
These are set on object passed as the third argument to
attach(element, template, settings)
.
-
store
(reducer, middleware, state)¶ A function that is used to create a store. The most common one is:
import {createStore, applyMiddleware} from 'redux' function store(reducer, middleware, state) { return createStore(reducer, state, applyMiddleware(...middleware)); }
Warning
The examples here use redux >= 3.1. Older redux can also be used. For example, here is how the code above can be modified for older redux:
import {createStore, applyMiddleware} from 'redux' function store(reducer, middleware, state) { return applyMiddleware(...middleware)(createStore)(reducer, state) }
You may add middleware and/or enhancers that must be used for every store:
import {createStore, applyMiddleware, compose} from 'redux' import {DevTools} from './devtools' import createLogger from 'redux-logger' import thunk from 'redux-thunk' import promise from 'redux-promise' function store(reducer, middleware, state) { return createStore(reducer, state, compose( applyMiddleware(...middleware, thunk, promise, logger), DevTools.instrument() )); }
Or using chrome DevTools extension:
import {createStore, applyMiddleware, compose} from 'redux' import createLogger from 'redux-logger' import thunk from 'redux-thunk' import promise from 'redux-promise' function store(reducer, middleware, state) { return createStore(reducer, state, compose( applyMiddleware(...middleware, thunk, promise, logger), window.devToolsExtension ? window.devToolsExtension() : f => f )); }
You don’t have to treat everything in middleware list as redux middleware. A lame example would be to allow actions to be used to seed some state in the store:
function store(reducer, middleware, state) { let actions = middleware.filter(x => !!x.type); let functions = middleware.filter(x => !x.type); let store = createStore(reducer, state, applyMiddleware(...functions)); for(let action of actions) { store.dispatch(action) } return store })
Or you could determine if some things should actually be middleware or enhancer, to allow both middleware and enhancers in the template:
function store(reducer, middleware, state) { let enhancers = middleware.filter(is_enhancer) let middleware = middleware.filter(x => !is_enhancer(x)) return createStore(reducer, state, compose( // redux docs say middleware should be first enhancer applyMiddleware(...middleware), ...enhancers)); }
Or just treat everything as enhancer:
attach(element, main(), { store: (r, m, s) => createStore(r, s, compose(...m)), })
In the case you use something other than redux, you may use a wrapper that uses redux protocol (namely methods
dispatch
,getState
,subscribe
). For example, here is how you could use RxJS streams as stores (untested):function store(reducer, state, middleware) { let current_state = state let subj = Rx.Subject() let stream = compose(...middleware)(subj) let store = stream.scan(reducer, state) store.subscribe(x => { current_state = x }) return { dispatch: subj.onNext, getState: () => current_state, subscribe: store.subscribe, } }
Compilation With Webpack¶
Compilation of templates in webpack is just a matter of adding a loader. You need to feed the output of the compilation to the babel:
loaders: [{
test: /\.khufu$/,
loaders: ['babel', 'khufu'],
exclude: /node_modules/,
}]
For hot reload you need to turn off generation of static attributes. This is a valuable optimization for production code, so do this only for development:
khufu: {
static_attrs: process.env['NODE_ENV'] != 'production',
}
There is a full example with hot-reload in the sources (just note that khufu loader path is local there, you need a package name instead).
Note
As of webpack 2.0.7-beta you need to put the following into the config, we’re not sure if this is a bug or a feature (the first item in the list needs to be added, others are by default):
resolveLoader: {
mainFields: ["webpackLoader", "main", "browser"],
}
Compilation Settings¶
Settings are put into the khufu
key in the webpack config, in
webpack >= 2.0, you need a LoaderOptionsPlugin
to set them:
- static_attrs
- If
true
(default) means that all attribute values that compiler thinks are constant are put into the external array which is only used for element creatio and is optimized out on diff process. See incremental-dom documentation for more info - additional_class
It’s a string or a function that returns string. The value is a class name that is added to all the things:
- Every CSS selector in the
style
section - Every element having at least one class name
- Every element that has no classes but is mentioned without classes in the
style
section too
The last rule looks a little bit cumbersome, but it allows selectors with bare elements like
div
work as expected.If the setting is a function a full webpack loader context is passed as the first argument. Default function extracts base filename and prepends it with
b-
, for example for/work/templates/menu.khufu
the default name will beb-menu
.- Every CSS selector in the