v0.23 introduces Drivers: a plugin-like API which allows you to build a conversation between your Cycle.js app and any side-effectful target or targets.
Previously, Cycle's abstraction was a circular interaction between the app and the user on the DOM. The core function applyToDOM()
meant that all Cycle apps were tied to the DOM. Drivers generalize this idea and allow you to target not only the DOM, but any other interactive target, such as a server with WebSocket, iOS renderers, etc.
Before, the user()
function was embedded inside applyToDOM()
. With drivers, you create the user()
function and provide it to Cycle.run()
. Compare these:
BEFORE
Cycle.applyToDOM('.js-container', appFn);
AFTER
var userFn = Cycle.makeDOMDriver('.js-container');
Cycle.run(appFn, { DOM: userFn });
Cycle comes with makeDOMDriver()
, a factory function which generates a "DOM user" function. Cycle.run(appFn, drivers)
circularly connects appFn with a group of driver functions specified by drivers
object.
The advantage of this is you can now externalize side effects from the app function. Potential drivers could be "XHR Driver", "WebSocket Driver", "Canvas Driver", etc. Each driver is just a function taking an Observable as input and producing an Observable as output (or a collection of Observables).
This is last major API development before v1.0.
Migration guide
Drivers imply some breaking change, but only with small boilerplate at some interfaces. This isn't as radical breaking change as, for instance, v0.21.
If your code is built with MVI architecture, this migration should only affect some boilerplate on Views and Intents. Models should stay intact.
The contract of your appFn()
or main()
has changed: it should take one single "queryable collection of Observables" as input and output one "collection of Observables".
BEFORE
function computer(interactions) {
return interactions.get(selector, type).flatMap( /* to vtree observable */ );
}
AFTER
function main(drivers) {
var vtree$ = drivers.get('DOM', selector, type).flatMap( /* to vtree observable */ );
return {
DOM: vtree$,
driverFoo: // some observable...
driverBar: // another observable...
};
}
The drivers
argument is a queryable collection of Observables. drivers.get(driverName, ...params)
will get an Observable returned by the driver named driverName
, given ...params
. Each driver can have its separate API for ...params
. For instance, for the DOM driver, the API is drivers.get('DOM', selector, eventType)
.
The returned object should have Observables, and the keys should match the driver name. You give the driver name to the run()
function:
Cycle.run(main, {
DOM: Cycle.makeDOMDriver('.js-container'),
driverFoo: // a driver function for Foo
driverBar: // a driver function for Bar
});
Migration steps:
- Replace
interactions.get(...params)
with drivers.get('DOM', ...params)
- Replace the return of the app function from
return vtree$
to return {DOM: vtree$}
- The dollar sign convention is no longer required by any Cycle.js feature. It is still recommended for applications, but not enforced by the framework, ever.
renderAsHTML()
became a driver of its own: makeHTMLDriver()
. Use the HTML Driver like any other driver. See the isomorphic example for more details.
Custom elements
Registering custom elements is not anymore a global mutation function. Instead, when building the DOM Driver function with Cycle.makeDOMDriver()
, you provide an object where keys are the custom element tag names, and values are the definition functions for those custom elements.
EXAMPLE
Cycle.run(app, {
DOM: Cycle.makeDOMDriver('.js-container', {
'my-element': myElementFn
})
});
The definition function contract has changed slightly, but follows the same idea of the main()
function contract: takes a queryable collection of Observables, and should output a collection (object) of Observables.
BEFORE
function myComponent(interactions, props) {
var destroy$ = interactions.get('.remove-btn', 'click');
var id$ = props.get('itemid');
// ...
return {
vtree$: vtree$,
destroy$: destroy$,
changeColor$: changeColor$,
changeWidth$: changeWidth$
};
}
AFTER
function myComponent(drivers) {
var destroy$ = drivers.get('DOM', '.remove-btn', 'click');
var id$ = drivers.get('props', 'itemid');
// ...
return {
DOM: vtree$,
events: {
destroy: destroy$,
changeColor: changeColor$,
changeWidth: changeWidth$
}
};
}
Migration steps:
- Replace function signature from
function (interaction, props)
to function (drivers)
- Replace
interactions.get(...params)
with drivers.get('DOM', ...params)
- Replace
props.get(...params)
with drivers.get('props', ...params)
- Replace the return of the definition function from
return {vtree$, myEvent$, ...}
to return {DOM: vtree$, events: ...}
, where events: ...
is an object where keys are the event name (notice no more dollar sign $
convention!) and values are Observables.
Source code(tar.gz)
Source code(zip)