cyclejs

A functional and reactive JavaScript framework for predictable code

MIT License

Downloads
222.9K
Stars
10.2K
Committers
134

Bot releases are hidden (Show)

cyclejs - Cycle Unified Latest Release

Published by staltz over 7 years ago

cyclejs - Cycle Diversity

Published by staltz over 8 years ago

cyclejs - v6.0.0 - Cycle Nested

Published by staltz almost 9 years ago

cyclejs - Make Rx a peer dependency

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';
cyclejs - RxJS updated to v4.0

Published by staltz about 9 years ago

New RxJS version 4.0 used as dependency https://github.com/Reactive-Extensions/RxJS

cyclejs - Update RxJS to v3.1

Published by staltz about 9 years ago

cyclejs - Update RxJS to v3

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.

cyclejs - Not a major release, just a tiny breaking change

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.

cyclejs - Release candidate for v1.0.0

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;
cyclejs - v0.24.1 - Introduce props.getAll() for custom elements

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

cyclejs - v0.24 - Cosmetic API improvement

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 (getable) 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

cyclejs - Drivers - new possibilities, breaking changes, but no breaking news

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.


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:

  1. Replace interactions.get(...params) with drivers.get('DOM', ...params)
  2. Replace the return of the app function from return vtree$ to return {DOM: vtree$}
  3. 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.
  4. 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:

  1. Replace function signature from function (interaction, props) to function (drivers)
  2. Replace interactions.get(...params) with drivers.get('DOM', ...params)
  3. Replace props.get(...params) with drivers.get('props', ...params)
  4. 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.
cyclejs - v0.22 Tiny breaking change

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.

cyclejs - v0.21.2 - Tiny improvement: include svg() helper

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.

cyclejs - v0.21.1 - Adds feature props.get('*')

Published by staltz over 9 years ago

props.get('*') in custom elements returns an Observable of the entire properties object

Not a breaking change.

See a test of this feature, for more details.

cyclejs - v0.21 - This changes everything: one simple computer() function

Published by staltz over 9 years ago

This version is a major breaking change that would require rewriting your apps entirely.

Read the README

Oops, I did it again.
Oops I did it again


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.

cyclejs - v0.20.4 - Server-side (isomorphic) rendering

Published by staltz over 9 years ago

Introducing 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.

cyclejs - v0.20.3 - Fixes bug #106

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.

cyclejs - v0.20.2 - Update RxJS to 2.5.2

Published by staltz over 9 years ago

cyclejs - v0.20.1 - Fix important and embarrasing bug

Published by staltz over 9 years ago

Polyfill for ES6 Map was missing as a dependency.