A functional and reactive JavaScript framework for predictable code
MIT License
Bot releases are hidden (Show)
Published by staltz over 8 years ago
Published by staltz almost 9 years ago
Published by staltz almost 9 years ago
Breaking change
RxJS is not anymore bundled inside Cycle Core. Instead, it is an npm peer dependency. You need to install both Cycle Core and RxJS: npm install rx @cycle/core
.
Before | After |
---|---|
import {Rx} from '@cycle/core'; |
import Rx from 'rx'; |
Published by staltz about 9 years ago
New RxJS version 4.0 used as dependency https://github.com/Reactive-Extensions/RxJS
Published by staltz about 9 years ago
Published by staltz about 9 years ago
Implies a breaking change to Cycle Core because RxJS v3 is a breaking change. Most noteworthy breaking change is the signature change of scan
operator. Read more here.
Published by staltz about 9 years ago
If you do not use responses.dispose()
, you can safely update to Cycle Core v2 and all your code will still work.
This is not a "v2" as in "everything different". We are just following good old semver. This version fixes the semantics of disposal.
Given
let [requests, responses] = Cycle.run(main, drivers);
There exists responses.dispose()
. Prior to v2, there was no requests.dispose()
, and there were some subscriptions left undisposed if you wanted to dispose everything.
In v2, we have both requests.dispose()
and responses.dispose()
. If you were using dispose(), please carefully update your program. You might need to dispose both requests and responses.
Published by staltz over 9 years ago
This release splits the original cyclejs
npm package into two: @cycle/core
and @cycle/web
.
This is just a split, no functionality added nor removed nor changed. To migrate, you just need to get the right functions from the right packages.
They contain:
let Cycle = require('@cycle/core');
let {run, Rx} = Cycle;
let CycleWeb = require('@cycle/web');
let {makeDOMDriver, makeHTMLDriver, h, svg} = Cycle;
Published by staltz over 9 years ago
props.getAll()
was introduced as an equivalent to props.get('*')
for custom elements. The former allows better integration with TypeScript. See issue #138
Published by staltz over 9 years ago
Small breaking change to improve the API related to driver responses. The getter API was replaced by object properties.
BEFORE
function main(drivers) {
let vtree$ = drivers.get('DOM', '.target', 'click').flatMap( /* ... */ );
// ...
}
AFTER
function main(drivers) {
let vtree$ = drivers.DOM.get('.target', 'click').flatMap( /* ... */ );
// ...
}
This allows using the drivers argument with ES6 destructuring:
function main({DOM, foo, bar}) {
let vtree$ = DOM.get('.target', 'click').flatMap( /* ... */ );
let F$ = foo.delay(1000);
let B$ = bar.map( /* ... */ ).filter( /* ... */ );
// ...
}
Given the argument drivers
to main(), drivers.driverName
will return the output of the driver function denoted by driverName
. As you can see from the example above, the output might be a queryable (get
able) collection of Observables such as DOM.get(selector, eventType)
or simply an Observable, e.g. foo.delay(1000)
.
Changes to custom element internal drivers:
BEFORE: drivers.get('props', 'children')
AFTER: drivers.props.get('children')
To get all properties:
BEFORE: drivers.get('props', '*')
or drivers.get('props')
AFTER: drivers.props.get('*')
only
Published by staltz over 9 years ago
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.
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:
interactions.get(...params)
with drivers.get('DOM', ...params)
return vtree$
to return {DOM: vtree$}
renderAsHTML()
became a driver of its own: makeHTMLDriver()
. Use the HTML Driver like any other driver. See the isomorphic example for more details.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:
function (interaction, props)
to function (drivers)
interactions.get(...params)
with drivers.get('DOM', ...params)
props.get(...params)
with drivers.get('props', ...params)
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.Published by staltz over 9 years ago
Custom events from custom elements now use event.detail
instead of event.data
, to conform with W3C interface for CustomEvent. Read more here #124.
Published by staltz over 9 years ago
Just like there is Cycle.h()
, this version includes Cycle.svg()
helper for making virtual-dom VNodes for SVG.
Published by staltz over 9 years ago
Not a breaking change.
See a test of this feature, for more details.
Published by staltz over 9 years ago
This version is a major breaking change that would require rewriting your apps entirely.
Read the README
Removed Stream, no more inject calls. The cyclic dependency of Observables is now solved internally in the framework. As an app developer, you just need to provide the computer()
function, and Cycle will take care of injection internally. The computer function takes interactions
as input, and should output an Observable of virtual DOM elements. Then call Cycle.applyToDOM(container, computer);
To rewrite your program built with Cycle, you mainly need to delete plenty of boilerplate code related to Streams. Start with the top computer
function and transform observables directly rather than creating Stream and then injecting. Example:
BEFORE
let bar$ = Cycle.createStream(foo$ => foo$.delay(500).map(foo => 'Hello ' + foo));
// ... later
bar$.inject(foo$);
AFTER
let bar$ = foo$.delay(500).map(foo => 'Hello ' + foo);
See some examples
However, Streams and injection might still be an important concept, so they will become a separate helper library for rare cases where you need them.
vdomPropHook
removed. This was a small helper function and specific to virtual-dom. It is very simple to make it yourself in your codebase or make a small library for it.
interaction$ was renamed to interactions since it represents a collection of Observables, but not really an Observable itself.
Published by staltz over 9 years ago
renderAsHTML()
When you need to render a Cycle app from the server and serve it as HTML, you use renderAsHTML(vtree$)
which takes a vtree$
and outputs an Observable of HTML strings, namely html$
. Then just subscribe to html$
to get the html
string and send it as a response.
See the documentation for renderAsHTML()
.
See a complete isomorphic (server-side rendering and client-side rendering) example.
See the tests for more examples, including also custom elements rendered as HTML.
Published by staltz over 9 years ago
Fixes bug #106, where you couldn't provide an (mutating) array as props to a custom element, otherwise it wouldn't be recognized as changed to the custom element's implementation. With v0.20.3, in these cases, you should provide the comparer function that calculates whether the two property values are equal or not.
Example:
Cycle.registerCustomElement('x-element', function (rootElem$, props) {
let vtree$ = props.get('list$', (x, y) => _.isEqual(x, y)).map((list) =>
h('div', [
h('ol', list.map((value) => h('li', value)))
]));
rootElem$.inject(vtree$);
});
Notice props.get('list$', (x, y) => _.isEqual(x, y))
using Lo-Dash's isEqual method as the comparer. You can still use the props getter with just the name props.get('list$')
and it will use Rx's default comparer, as it was previously to this version.
Published by staltz over 9 years ago
Published by staltz over 9 years ago
Polyfill for ES6 Map was missing as a dependency.