A functional and reactive JavaScript framework for predictable code

Overview

Cycle.js

logo
A functional and reactive JavaScript framework for predictable code

Welcome

Question Answer
"I want to learn Cycle.js" Read the official documentation
"I have a question" Create a StackOverflow question
Or
Join the chat
Or
Open an issue
Please note all discussion-like issues are labeled discussion and immediately closed. This doesn't mean we unconsidered your discussion. We only leave actual issues open.
"I found a bug" Open an issue
"I want to help build Cycle.js" Read the Contributing guides
Then
Choose an issue marked "help wanted"

Packages

Cycle.js is comprised of many specialized packages. This repository contains all these packages, e.g., the npm package @cycle/run lives in the directory run. Below you will find a summary of each package.

Package Version Dependencies DevDependencies
@cycle/dom npm (scoped) Dependency Status devDependency Status
@cycle/history npm (scoped) Dependency Status devDependency Status
@cycle/html npm (scoped) Dependency Status devDependency Status
@cycle/http npm (scoped) Dependency Status devDependency Status
@cycle/isolate npm (scoped) Dependency Status devDependency Status
@cycle/most-run npm (scoped) Dependency Status devDependency Status
@cycle/run npm (scoped) Dependency Status devDependency Status
@cycle/rxjs-run npm (scoped) Dependency Status devDependency Status

Globally: Build Status devDependency Status

Stream libraries

The following packages are not under Cycle.js, but are important dependencies, so we display their latest versions for convenience.

Package Version
most npm version
rxjs npm version
xstream npm version

Support OpenCollective OpenCollective

Sponsors

Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor]

Backers

Support us with a monthly donation and help us continue our activities. [Become a backer]

Thanks

Browserstack

Browserstack for providing access to their great cross-browser testing tools.

LICENSE

The MIT License


JS.ORG ComVer
Comments
  • Router Drivers

    Router Drivers

    I've been toying with routers inside of cycle for a little bit, and there has been some good discussion and work over at https://github.com/router5/router5/issues/5

    But I think there is some discussion needed on what an ideal router driver API should look like.

    My initial thinking was the following:

    makeRouterDriver(routes, options) {
      // create router
      return routerDriver(namedRoute$) {
        // magic
        return context$
      }
    }
    

    where namedRoute$ could be an observable of string values like: "home" and context$ could be an obserable of a context object:

    {
      path: '/users/4',
      name: 'users',
      params: {'userID': 4},
      etc.
    }
    

    I also assumed that the routes setup should be passed to the routerDriver constructor, however, as @staltz commented in the aforementioned router5 issue, that this might not be the optimal solution.

    discussion 
    opened by bobfp 199
  • Push-Pull proposal: Signals to complement Streams

    Push-Pull proposal: Signals to complement Streams

    TL;DR: create a Signals library besides xstream, use it as the expected sink in some drivers


    Problem

    Over the weekend in CycleConf 17 we started discussing how to better accommodate usage of inherently pull values like Math.random() or window.outerHeight into Cycle.js apps in a way that follows the framework's way of working.

    Currently you would have to do something nasty like click$.map(() => Math.random()) which is impure but most importantly, makes testing a bit worse since it's non deterministic and we're not into doing invasive mocking of globals.

    I noticed a similarity between these values, is that they all look like () => T, e.g. Math.random(): number. Then I drafted this on a paper:

    • PURE: f(x: A): B
    • PUSH: f(x: A): void (used for sending events e.g. observer.next(ev): void)
    • PULL: f(): B (used for reading values)
    • SINK-ONLY DRIVER: f(x$: Stream<A>): void
    • SOURCE-ONLY DRIVER: f(): Stream<B>
    • MAIN (or what @TylorS calls component): f(x$: Stream<A>): Stream<B>

    Most of these bullet points are covered in Cycle.js, except PULL. That's how I noticed it was a missing piece. After discussing with others, watching a talk by Erik Meijer, revising the Push-pull FRP paper by Conal Elliott, I noticed we just need to have a functional ES6 Iterable library. For a while I investigated Dominic Tarr's pull streams and came to the conclusion it's basically AsyncIterable as per Erik Meijer. For a while we also got quite worried about polling given what Conal Elliott described as the biggest problem with pull collections to represent continuous values over time. We thought if there should be a push-and-pull collection (e.g. to represent the virtual DOM over time, which is a "continuous" value which emits updates), and noticed that perhaps Derivable.js by David Sheldrick or Menrva could fill that gap.

    Then, @Widdershin suggested we might not even need a push-and-pull hybrid for the DOM Driver if we have an entirely pull-driven approach with requestAnimationFrame polling. Nick suspected it would be even more better than the current reactive approach.

    We started hacking and came with a proof of concept here: https://github.com/staltz/experiment-push-pull-cyclejs

    function main(sources: Sources): Sinks {
      const vdomS = sources.windowHeight.map(height =>
        div('.foo', 'Height: ' + height),
      );
    
      return {
        DOM: vdomS,
      };
    }
    
    run(main, {
      windowHeight: () => Signal.from(() => window.outerHeight),
      DOM: makeDOMDriver('#main-container'),
    });
    

    Notice the new convention x$ is a Stream and xS is a Signal.

    The example works with rAF and demonstrates that we could essentially model values-over-time as signals, with the same type of functional composability with operators as in xstream or rx.

    What next

    Having Streams and Signals in Cycle.js would be a revolutionary change that requires a lot of careful planning. We don't want to find a show-stopper and be embarrassed after launching this change. For now we have no guarantee whatsoever that this will actually be a thing in Cyclejs.

    Publish the signal library as a standalone ysignal in release candidate or v0.x for wild experimentation. ysignal should be "hot" in the same sense as xstream because we need the assumption of "one execution per stream" and "one execution per signal" so that the DevTools dataflow graph displays something that makes sense in people's mind. A hot Signal (implements Iterable) would mean there is a shared Iterator which is lazily created and destroyed with refCounting.

    We need to consider should we remove MemoryStreams from xstream because so far it has been used to represent values over time, and with Signals we wouldn't need MemoryStreams anymore. Currently MemoryStreams are pull on subscribe, push afterwards, so they have a pull ability. With Signals dedicated for pull, we might not need to mix pull and push. We can have Streams only for push, and Signals only for pull.

    xstream and ysignal could be tightly interrelated "sibling" libraries if they have conversion operators from one to the other and vice-versa. Not sure if they should do that, or if they should just assume conversion with Iterables and ES Observables.

    On the topic of conversion, I noticed Push-to-Pull then Pull-to-Push is not an isomorphism. I don't know what to call that in category theory but all I'm saying is that when you go from Push to Pull, the Pull producer can choose what and how much data to replay, and when you go from Pull to Push, the Push producer can choose when to emit data. This liberty of choice means information is lost during conversion.

    We need to investigate further if we need a hybrid type in Menrva/DerivableJS style. Related to that, since those libraries do intelligent computation of derived values in the signal graph, we might need that ranking algorithm (maybe we don't need the Push part of being hybrid).

    We could map what drivers would expect Signal sinks and what drivers would expect Stream sinks.

    DOM Driver isolation and event delegator event Streams based on Element Signals may be a challenging problem.

    Need to also consider how to support stream lib Diversity.

    Nick Johnstone could comment on his ideas how to use this with cycle/time.

    issue is breaking suggestion priority 3 (should) size L 
    opened by staltz 147
  • Why are interactions and custom elements global?

    Why are interactions and custom elements global?

    I just started playing with Cycle and it looks really cool!

    To me, two things immediately surprised me, so I was wondering if I'm missing something.

    1. interactions.get(selector, eventName) in all examples I've seen pass in a selector (not a dom element) which means those selectors are queried globally in the entire DOM. Surely this is really impractical, you want to scope it all down to specific components, the way you can easily do with React's inline handlers? Wouldn't it be better if say you handle events in the view and then emit to the interactions stream the low level event or a higher level intent?
    2. registerCustomElement seems to register elements globally. Again, this is, imho, mush inferior to how something like React does it where you require components and use them by reference. Is there a way to do that in Cycle? `var Button = require("./button"); Button({}). Sorry if this is documented somewhere, I couldn't find an example/docs.
    opened by KidkArolis 111
  • Automatic scoping of nested dialogues

    Automatic scoping of nested dialogues

    This idea: https://gist.github.com/axefrog/e53b915b1ddd37ce1eac

    Need to let cool down, experiment with it, and evaluate whether 'benefits minus magic minus boilerplate' is positive.

    opened by staltz 110
  • Component encapsulation by default

    Component encapsulation by default

    My main issue with the current Cycle architecture is the way drivers are actually globally scoped:

    • DOM.select('.field') looks for an element in the whole DOM subtree managed by Cycle
    • HTTP is the stream of all HTTP requests/responses going through the driver

    The developer has to be aware of this and take extra precautions to filter out other inputs, such as other parts of the DOM also matching the selector, or other requests issued by other bits of the app.

    The HTTP and fetch drivers encourage filtering the stream of requests by URL or name, but that solution is suboptimal because it could easily suffer from clashes if other components requested the same URL or used the same identifying name. Put otherwise, there is no strong guarantee of isolation.

    The better, driver-agnostic solution to this problem is isolate (provided the driver implements isolateSource/isolateSink -- the fetch-driver doesn't for example).

    It could be argued the implementation of isolateSource/isolateSink is somewhat inelegant compared to the rest of Cycle, with the HTTP driver patching a _namespace property on the request, and the DOM driver writing class names that end up in the DOM (see https://github.com/cyclejs/core/issues/226), but I guess these are mostly hidden away from users.

    However, shouldn't isolation be default behaviour, possibly with an opt-out, rather than being opt-in?

    Anecdotally, of all the people I talked to this about, no-one had realised that sources.DOM.select(_) was not scoped to the virtual-dom subtree returned by that component.

    I really love the concept of fractal architecture @staltz is striving for, but I would argue one of the key benefits is to reduce cognitive load and complexity for the developer by allowing local reasoning, without side-effects. Without isolation, there's a persistent risk of global "side-effect" (in the broad, non-FP sense) and hard-to-debug collisions between distinct parts of the system.

    Another way to phrase this is: when is isolation not desirable? Surely, components should always be isolated, unless they rely on side-effects (watching DOM they don't own, catching requests they didn't issue), which is antithetical to the whole concept.

    These practical concerns aside, I'm also curious about the performance and scalability implications of having such centralised management of streams. Are there benchmarks of how this scales up to thousands of components, with hundreds of subscribers to the HTTP driver requests stream and thousands to the DOM driver events streams, recursively calling the isolateSource and matchesSelector for each level of the component tree?

    I'd like to make it clear that I still think Cycle is a fantastic idea, and the reason I raise this is because I'm worried this could get in the way of its success.

    To make this a more constructive criticism, I've also played with an alternative DOM driver API that keeps effects local. I've put it up in a repo on GitHub, in the hope it can spur discussions on alternative solutions to this issue.

    I admit I was also trying to make observed events more explicit in the component sinks, rather than relying on .events() on the sources and manually matching class names in the .select(_) and outputs. However I'm interested to hear what people think, what principle I have violated doing so, and what may be wrong with this approach :smile:

    /cc @jamesgorrie @kenoir @phamann @oliverjash @jackfranklin who I discussed this with in the past few weeks.

    discussion 
    opened by theefer 89
  • Cycle Diversity: generalize for any stream library

    Cycle Diversity: generalize for any stream library

    @TylorS and I started discussing how to migrate to RxJS 5, and/or support most.js streams without the need for a fork (Motorcycle.js) which mostly has copy-pasted code from some Cycle.js libraries.

    We arrived at a proposal: Build a "base" run() function which is generic and assumes no stream library. It uses "stream adapters" to agree on an interface for each stream library. We need to build an adapter for each stream library. We also need to build "rx run()", "most run()" which basically utilizes the common "base run()" but specify which stream library adapter is used. Drivers are given the stream adapter object from run(), and can written using whatever stream library.

    @cycle/rx-run

    import baseRun from '@cycle/base'
    import streamAdapter from '@cycle/rx-adapter'
    
    const Cycle = {
      run: (main, drivers) => baseRun(main, drivers, {streamAdapter})
    }
    
    export default Cycle
    

    @cycle/dom

    function domDriver(sink$) {...}
    
    domDriver.streamAdapter = rxAdapter // mostAdapter 
    

    Tasks to do

    • [x] Create @cycle/base repo (essentially this PR #198 there) https://github.com/cyclejs/base
    • [x] Write test for @cycle/base ensuring one-liner drivers work properly with default adapter.
    • [x] Build https://github.com/cyclejs/rx5-adapter with tests, using StreamAdapter interface from base
    • [x] Build https://github.com/cyclejs/rx4-adapter with tests, using StreamAdapter interface from base
    • [x] PR on Cycle Core to implement it as @cycle/base + @cycle/rx-adapter, with #200 in mind
    • [x] Make canary version for core
    • [x] Make @cycle/rx-run npm package alias for @cycle/core
    • [x] Rewrite Cycle DOM Driver in TypeScript with transposition off by default
    • [x] Update base to export CycleSetup interface
    • [x] Fix Cycle DOM diversity to adapt the out-facing streams for the app's stream library
    • [x] Convert some cycle-examples to use the canary version of dom with RxJS 5
    • [x] Cycle HTTP driver in TypeScript and using driverFn.streamAdapter = rxAdapter
    • [x] Make canary versions for http, test it around
    • [x] Update power-ui to Diversity
    • [x] xstream-adapter
    • [x] xstream-run
    • [x] Fix Cycle DOM last bugs with DOMSource.events() and isolate
    • [x] Soft release core, dom, *-run, http, jsonp RC versions
    • [x] Build https://github.com/cyclejs/most-adapter in JS with tests, using StreamAdapter interface from base
    • [x] create @cycle/most-run https://github.com/cyclejs/most-run with #200 in mind
    • [x] Unnecessary: Update motorcycle drivers to use driverFn.streamAdapter = mostAdapter
    • [x] Update examples and TodoMVC to Diversity & xstream
    • [x] Make bmi-typescript example, and fix Cycle DOM TypeScript problems, issue #307
    • [x] Write http-random-user in TypeScript to make sure HTTP Driver is also working well
    • [x] Change HoldSubject to Subject in stream adapters. It's not needed.
    • [x] Rework makeHTMLDriver(callback)
    • [x] mockDOMSource should return a stream of the app's own stream library.
    • [x] DOMSource.select().elements should return WhateverStream interface, not type any
    • [x] foo-adapter and foo-run: remove dispose(), add remember()
    • [x] Update cycle history to use stream adapter remember()
    • [x] Update TodoMVC using cycle history
    • [x] Fix Cycle DOM documentation generation
    • [x] Update cycle.js.org
    • [x] cycle.js.org documentation page
    • [x] Write Release notes and migration guide
    • [x] Merge branches diversity to master, and publish non-rc versions
      • [x] Cycle Core / Rx Run
      • [x] DOM
      • [x] HTTP
      • [x] JSONP
      • [x] examples
      • [x] cyclejs.github.io
      • [x] others
    • [x] npm deprecate @cycle/core, use rx-run
    • [x] Cycle DOM rxjs-typings, rx-typings, most-typings
    • [x] Cycle HTTP rxjs-typings, rx-typings, most-typings

    Breaking changes so far

    • DOMSource.select().observable => DOMSource.select().elements()
    • new Cycle.run API for those cases where sources and sinks are wanted: #200
    • Snabbdom instead of virtual-dom
      • breaking changes with hyperscript way of using attrs
    • top-level <svg> element is created with hyperscript-helpers but children elements created with hyperscript
    • mockDOMSource(sa, config) now takes a Stream Adapter as the first argument
    • makeHTMLDriver() => makeHTMLDriver(effectFn, options)
    • No more onError callback as an option to makeDOMDriver()
    • No more sinks.dispose and sources.dispose
    • HTML Driver returns and HTMLSource mimicking DOMSource. HTMLSource.element$ is the stream of HTML strings
    • HTTP Source is not response$$ but contains it. It's an object with select(category: string): Observable<Observable<Response>>, filter(predicate: (res$: ResponseStream) => boolean): HTTPSource and response$$ property.
    closed because: fixed priority 4 (must) issue is feature suggestion scope: docs scope: dom scope: run scope: http 
    opened by staltz 74
  • Babel plugin for circular dependencies

    Babel plugin for circular dependencies

    As per discussion, sometimes the way that dialogues are developed can introduce an awkward feedback loop.

    Per @staltz' example

      const proxyTextFilterSinks = {value$: new Rx.Subject()};
      const actions = intent(proxyTextFilterSinks);
      const state$ = model(peoplePageHTTP.response$, sources.props$, actions);
      const textFilter = TextFilterWrapper(state$, sources.DOM);
      replicateStream(textFilter.value$, proxyTextFilterSinks.value$);
    

    The development of a babel plugin could potentially ease this pain, and doesn't require the writing of an entirely new language.

    closed because: fixed priority 2 (could) issue is feature suggestion scope: run 
    opened by TylorS 66
  • Handling lists in Cycle.js

    Handling lists in Cycle.js

    Handling lists in Cycle is hard. It's the only sad thing about Cycle in my opinion so far, specially because lists are so common. Otherwise, Cycle is beautiful. Because it keeps on popping up so often, let's take it a bit more seriously. This issue is to track progress being done to update docs (cycle.js.org), Nick's library Cycle Collections, and other initiatives that may help people feel comfortable building lists in Cycle.js.

    There are 4 solutions, here are their pros & cons:

    1. Manually build the dataflow, which will contain circular dependencies. This is what we do e.g. in TodoMVC.
      • Pros: explicit, no magic; follows Cycle.js principles of dataflow and purity; is being improved if people use xstream, it's easier to do that in xstream than it is with RxJS.
      • Cons: hard to understand and be comfortable with; probably a bit verbose.
    2. Wrap the Cycle.js subapps as Web Components https://github.com/staltz/cycle-custom-elementify
      • Pros: easier for most web developers to understand; explicit, no magic; enables Cycle.js subapps to be used outside Cycle.js;
      • Cons: subapp cannot have effects besides DOM-related; Diversity-support is annoying; Web Components spec is still in flux or not official yet.
    3. Global state at the top-most component (main).
      • Pros: a bit simpler and familiar mental model (like Redux); still aligned with Cycle.js principles of dataflow and purity.
      • Cons: still some circular dependency machinery to set up; not fractal, but if you know the tradeoffs, this may not be a problem at all.
    4. Stanga and similars.
      • Pros: Like the above, but circular dep machinery is hidden and automatically managed; is fractal; lenses API as a plus.
      • Cons: not aligned with Cycle.js principles of dataflow and purity with regard to drivers, harder to reason about what should be in a driver and what should not be.
    5. Cycle Collections, the library Nick is working on.
      • Pros: aligned with Cycle.js principles, easier to understand the API and reuse, not so much boilerplate/verbosity.
      • Cons: "magic", you give it X and Y and "things just work".
    6. Cycle Onionify, https://github.com/staltz/cycle-onionify
      • Pros: fractal with "lenses/cursors"-like API, only one state tree, hides away both fold and imitate, removes props vs state dichotomy.
      • Cons: currently only xstream support. No props vs state may lead to undesireable concurrent state modifications between parent and children.

    CC @Widdershin @laszlokorte @milankinen @TylorS

    priority 3 (should) issue is feature suggestion scope: docs scope: misc 
    opened by staltz 58
  • Idea: externalize all side-effects just like user interaction is

    Idea: externalize all side-effects just like user interaction is

    We could generalize interactions with external systems, and make the user interaction to be just one of those interactions. For instance, human-computer interaction happening simultaneously with server-computer interaction.

    Proposed API:

    function computer(interactions) {
      var userClicks = interactions.get('dom://.target/click');
      var responses = interactions.get('mongodb://foods/3/best-before');
      // ...
      return {
        vtree$, // as usual
        request$ // for mongodb
      };
    }
    
    Cycle.apply(computer, {
      dom: '#app',
      mongodb: someMongoDriverHereWithConnectionConfig
    });
    
    issue is breaking suggestion 
    opened by staltz 56
  • isolate proposal: a different scope for each driver

    isolate proposal: a different scope for each driver

    How about letting isolate accept multiple scopes? So instead of:

    isolate(Child, 'child')({onion, HTTP})
    

    one could also do:

    isolate(Child, {onion: 'child', HTTP: 'xyz'})({onion, HTTP})
    

    This would solve for instance staltz/cycle-onionify#8:

    isolate(Child, {onion: 'child', HTTP: 'xyz'})({onion, HTTP})
    isolate(AnotherChild, {onion: 'child', HTTP: 'uvw'})({onion, HTTP}) 
    

    When the scope for a driver is omitted, one would be internally generated, as usual:

    isolate(Child, {onion: 'child'})({onion, HTTP})
    isolate(AnotherChild, {onion: 'child'})({onion, HTTP}) 
    

    I've already implemented this in a branch.

    In addition to this change I also removed the conversion of scopes into strings (e.g. when you provide a number) because I need that for an other experiment with onionify. This can break isolation in some drivers, e.g. in the DOM driver when you pass a number as scope.

    closed because: fixed priority 2 (could) issue is feature suggestion scope: isolate 
    opened by abaco 53
  • Implement hot reloading

    Implement hot reloading

    I implemented react-hot-loader for React (with core being extracted into react-hot-api) and I think it should be possible to implement the same for Cycle, especially considering its this-lessness.

    Would you be interested in helping out with this?

    priority 2 (could) issue is feature suggestion scope: misc size L 
    opened by gaearon 46
  • fix(history): state not replaced

    fix(history): state not replaced

    ISSUES CLOSED: #989

    • [ ] There exists an issue discussing the need for this PR
    • [ ] I added new tests for the issue I fixed or built
    • [ ] I used pnpm run commit instead of git commit
    opened by ZZITE 0
  • History Driver replace problem

    History Driver replace problem

    Having issues after upgrading history driver to latest version (state not replaced).

    Looking to code I see that now input is passed as object to underling history lib

    if (input.type === 'replace') {
          history.replace({...input});
    }
    

    But remix-run/history still require separate params:

      /**
       * Replaces the current location in the history stack with a new one.  The
       * location that was replaced will no longer be available.
       *
       * @param to - The new URL
       * @param state - Data to associate with the new location
       *
       * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#history.replace
       */
      replace(to: To, state?: any): void;
    
    opened by FbN 2
  • Returned value of makeHTTPDriver() doesn't typecheck when passed two arguments

    Returned value of makeHTTPDriver() doesn't typecheck when passed two arguments

    Code to reproduce the issue:

    for @cycle/http:

    http = makeHTTPDriver()(request$, 'fooHttp')

    Expected behavior:

    This should typecheck.

    Actual behavior:

    This doesn't typecheck as makeHTTPDriver() returns a function expecting exactly one argument but passed two instead.

    Versions of packages used:

    @cycle/[email protected]

    For anyone come across this, my current workaround is to give drive a more accurate type:

     const driver: ((stream: Stream<RequestInput>, name?: string) => HTTPSource) =
          (makeHTTPDriver() as any)
     http = driver(request$, 'fooHttp')
    
    opened by Javran 3
  • Example build fails

    Example build fails

    It looks to me like some of the dependencies for (at least) one of the examples are out of date. Attempting to run npm start in "cyclejs/examples/intermediate/tsx-seconds-elapsed" fails with TypeScript errors starting with:

    "node_modules/@types/node/index.d.ts(20,1): error TS1084: Invalid 'reference' directive syntax."

    I updated several dependencies by deleting dependency and devDependency lines from "tsx-seconds-elapsed/package.json", and then reinstalling the associated libraries with npm, and the example now builds and runs as expected. I'm relatively new to JavaScript and node.js development, and very new to poking around in Cycle, so I'm not sure—first—how to choose and maintain the versions of dependencies for this example, and—second—what is the best way to manage the dependency listings in "package.json". That said, I've attached the diff to "package.json", if it helps provide some information.

    opened by Juanc1to 3
  • Consider renaming MainDOMSource to DOMSource

    Consider renaming MainDOMSource to DOMSource

    There was a PR that introduced MainDOMSource, BodyDOMSource or DocumentDOMSource as variants of DOMSource union type (https://github.com/cyclejs/cyclejs/blob/master/dom/src/DOMSource.ts#L16)

    Usually in the code, we use MainDOMSource as type of DOM source. And document or body sources are available from any normal DOM source are used as: DOM.select('document').events(...).

    Explicit use of BodyDOMSource or DocumentDOMSource types is a very rare case only for specific components, and probably even more rare case would be expecting DOM source to be MainDOMSource | BodyDOMSource | DocumentDOMSource (which DOMSource now), even if so explicit statement of BodyDOMSource | DocumentDOMSource would be even more appropriate.

    So why this common DOM source it is called MainDOMSource, why not CommonDOMSource or ElementDOMSource, prefix Main probably the shortest option and has some sense but still seems unnatural and contrived here.

    So I propose to remove/deprecate MainDOMSource and rename it to DOMSource, removing union type, the rationale for this is to make reference to commonly used DOM source type more simple and conventional, as there is no real practical reason to have a union type for all kind of sources (DOMSource union type itself actually is not used in the driver's code anywhere). So DOMSource would mean normal commonly used dom source which supports isolation and etc., and there will be specific two types BodyDOMSource and DocumentDOMSource which will be explicitly used when needed.

    So this is my current perspective on this situation with quite ugly/contrived name MainDOMSource, but maybe I missed something.

    issue is breaking suggestion scope: dom 
    opened by wclr 1
Releases(unified-tag)
  • v5.0.0(Oct 19, 2015)

    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'; |

    Source code(tar.gz)
    Source code(zip)
  • v4.0.0(Oct 14, 2015)

  • v3.0.0(Aug 17, 2015)

  • v2.0.0(Aug 8, 2015)

    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.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0-rc1(Jun 24, 2015)

    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;
    
    Source code(tar.gz)
    Source code(zip)
  • v0.24.1(Jun 22, 2015)

    props.getAll() was introduced as an equivalent to props.get('*') for custom elements. The former allows better integration with TypeScript. See issue #138

    Source code(tar.gz)
    Source code(zip)
  • v0.24.0(Jun 15, 2015)

    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

    Source code(tar.gz)
    Source code(zip)
  • v0.23.0(Jun 10, 2015)

    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.
    Source code(tar.gz)
    Source code(zip)
  • v0.22.0(May 26, 2015)

    Custom events from custom elements now use event.detail instead of event.data, to conform with W3C interface for CustomEvent. Read more here #124.

    Source code(tar.gz)
    Source code(zip)
  • v0.21.2(May 12, 2015)

  • v0.21.1(May 8, 2015)

  • v0.21.0(May 7, 2015)

    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.

    Source code(tar.gz)
    Source code(zip)
  • v0.20.4(Apr 25, 2015)

    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.

    Source code(tar.gz)
    Source code(zip)
  • v0.20.3(Apr 23, 2015)

    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.

    Source code(tar.gz)
    Source code(zip)
  • v0.20.2(Apr 12, 2015)

  • v0.20.1(Apr 11, 2015)

  • v0.20.0(Apr 10, 2015)

    v0.20 is unfortunately or fortunately a "this changes everything" version. We removed: DataFlowNode, DataFlowSink, DataFlowSource, Model, View, Intent, DOMUser. New API is just: render(), createStream(), and the familiar registerCustomElement().

    Streams are a core part of Cycle now, and everything you need to know about them are explained here.

    The rationale is: where DataFlowNode supported multiple output Observables, a Stream supports only one output Observable because a Stream is an Observable. The benefit with this new API is the removal of getters, and a rather simpler abstraction since you only need to think of how to compose Streams.

    A single-output DataFlowNode is replaced by a Stream. A single-input DataFlowSink is replaced by just a subscriber to an Observable. A single-output DataFlowSource is replaced by a normal Rx.Observable.

    BEFORE

    let FooNode = Cycle.createDataFlowNode(function (BarNode) {
      return {
        foo$: BarNode.get('bar$').map(bar => 'This is bar: ' + bar)
      };
    });
    

    AFTER

    let foo$ = Cycle.createStream(function (bar$) {
      return bar$.map(bar => 'This is bar: ' + bar);
    });
    

    The DOMUser was a DataFlowNode, hence it was "function-like". Now the user is just a normal JS function implemented by the developer. Cycle as a framework provides render(vtree$, container), which just outputs rootElem$, an Observable emitting the DOM element rendered from vtree$ into the given container. This rootElem$ is special in that it contains a reference to another Observable: rootElem$.interaction$, representing all possible events from all children elements under the root element. You filter on interaction$ by calling choose(selector, eventName). This is how a user is represented in Cycle v0.20:

    let interaction$ = Cycle.createStream(function user(vtree$) {
      return Cycle.render(vtree$, '.js-container').interaction$;
    });
    

    Custom elements are still registered globally with registerCustomElement(tagName, definitionFn), but the definitionFn is expected to have a different signature. definitionFn(rootElem$, props) is the signature, where rootElem$ is a Cycle Stream which should be injected a vtree$ that you should create inside the definitionFn. Properties are accessible using their dollar name on the props object. Unfortunately you must use a getter with the props object: props.get('color$'), while ES6 proxies are still unfeasible. See this example of a custom element.

    Read the (short!) API documentation, check out examples and open issues or join the gitter channel for questions and feedback.

    Source code(tar.gz)
    Source code(zip)
  • v0.18.2(Apr 2, 2015)

    Implements #97. This is small yet nice addition to Custom elements. You can now provide children vtrees to a custom element as such:

    h('my-element', [
      h('h3', 'Hello World')
    ])
    

    You access these in the custom element's implementation like this:

    Cycle.registerCustomElement('my-element', function (user, props) {
      var view = Cycle.createView(function (props) {
        return {
          vtree$: props.get('children$').map(children =>
            h('div.wrapper', children)
          )
        };
      });
    
      // ...
    })
    

    Check the example here.

    Source code(tar.gz)
    Source code(zip)
  • v0.18.1(Mar 31, 2015)

  • v0.18.0(Mar 31, 2015)

    The only breaking change in this version is very small: DataFlowSink.inject() now returns the input it was given, instead of returning the Rx.Disposable that the definitionFn of DataFlowSink returns. This makes DataFlowSink consistent with DataFlowNode and DataFlowSource.

    BEFORE

    sink.inject(node1).inject(node2); // THROWS ERROR because sink.inject(node1) returns Rx.Disposable
    

    AFTER

    sink.inject(node1).inject(node2); // works fine, because sink.inject(node1) returns node1
    

    dispose() function on DataFlowNode and DataFlowSink. These classes contain Rx.Disposable internally, and in the rare use case that you need to dispose them, this function allows you to do that. Normally you don't need to do this since DataFlowNodes are created once on application bootstrap and intended to live forever during the application run. Use dispose() in case you are dynamically creating and destroying DataFlowNodes.


    get() function added to DataFlowSource. It was missing and should exist to stay consistent with DataFlowNode.


    These are the functions that each type contains.

    | contains? | DataFlowSource | DataFlowNode | DataFlowSink |
    |-----------|----------------|--------------|--------------|
    | get()     | YES            | YES          | no           |
    | inject()  | no             | YES          | YES          |
    | dispose() | no             | YES          | YES          |
    
    Source code(tar.gz)
    Source code(zip)
  • v0.17.1(Mar 28, 2015)

  • v0.17.0(Mar 25, 2015)

    This version changes the contract in Cycle so that the DOMUser throws an error if it is injected a View that has a Cycle custom element at the root.

    THIS IS NOW REJECTED:

    let View = Cycle.createView(Model => ({
      vtree$: Model.get("active$").map(active =>
        h('mycustomelement', {active: active}) // <== here
      }),
    }));
    

    YOU NEED TO WRAP IT:

    let View = Cycle.createView(Model => ({
      vtree$: Model.get("active$").map(active =>
        h('div', [ // <== here
          h('mycustomelement', {active: active})
        ])
      }),
    }));
    

    The rationale behind this is two-fold:

    1. This isn't a priority as a feature at the moment. Custom element at the root was considered a bug (#89), and it was partially fixed. More problems related to this recently appeared, and the solution was getting complicated since it's deep into virtual-dom's diff and patch, Widgets, custom elements, etc. I prefer to explicitly not support this use case, then to either have it buggy or to spend a lot of time fixing it. The good news is that later on (maybe post-1.0) it is easy to add support for custom element at the root, while staying backwards compatible (you will also be able to keep on using the div wrapper). In other words, rather do a breaking change now.
    2. A custom element alone in the View is a code smell. If you only have the custom element, then why do you need the View wrapping the custom element? I currently can't see how custom element as the single element in a View is a meaningful pattern. But anyway, if someone discovers this is genuinely important use case, then read argument (1) above where we can add in the future support for this without introducing a breaking change.

    Additional small feature: You can listen to DOMUser errors on user.get('error$').

    Source code(tar.gz)
    Source code(zip)
  • v0.16.3(Mar 24, 2015)

  • v0.16.2(Mar 22, 2015)

  • v0.16.0(Mar 16, 2015)

    clone() removed from all DataFlowNodes, including Model, View, Intent, and DOMUser. This is in order to reduce API surface, and clone() isn't essential, and is easily replaceable.

    BEFORE

    var node = Cycle.createDataFlowNode(function (input) {
      return {out$: input.delay(100)};
    });
    var cloned = node.clone();
    

    AFTER

    function definitionFn(input) {
      return {out$: input.delay(100)};
    }
    var node = Cycle.createDataFlowNode(definitionFn);
    var cloned = Cycle.createDataFlowNode(definitionFn);
    

    v0.16 also adds a type property to each DataFlowNode and other entities. For instance

    var node = Cycle.createDataFlowNode(function (input) {
      return {out$: input.delay(100)};
    });
    console.log(node.type); // 'DataFlowNode'
    
    var view = Cycle.createView(function (input) {
      return {vtree$: input.delay(100)};
    });
    console.log(view.type); // 'View'
    
    Source code(tar.gz)
    Source code(zip)
  • v0.15.3(Mar 11, 2015)

Owner
Cycle.js
A fully reactive JavaScript framework for Human-Computer Interaction
Cycle.js
🍼 650B Virtual DOM - Use reactive JSX with minimal overhead

?? little-vdom Forked from developit's little-vdom gist. npm: npm i @luwes/little-vdom cdn: unpkg.com/@luwes/little-vdom 650B Virtual DOM Components S

wesley luyten 87 Nov 12, 2022
🌙 The minimal & fast library for functional user interfaces

Moon The minimal & fast library for functional user interfaces Summary ?? Small file size (2kb minified + gzip) ⚡ Blazing fast view rendering ?? Purel

Kabir Shah 6k Jan 2, 2023
Dojo Framework. A Progressive Framework for Modern Web Apps

@dojo/framework Dojo is a progressive framework for modern web applications built with TypeScript. Visit us at dojo.io for documentation, tutorials, c

Dojo 549 Dec 25, 2022
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Supporting Vue.js Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome ba

vuejs 201.6k Jan 7, 2023
Ember.js - A JavaScript framework for creating ambitious web applications

Ember.js is a JavaScript framework that greatly reduces the time, effort and resources needed to build any web application. It is focused on making yo

Ember.js 22.4k Jan 4, 2023
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

vue-next This is the repository for Vue 3.0. Quickstart Via CDN: <script src="https://unpkg.com/vue@next"></script> In-browser playground on Codepen S

vuejs 34.6k Jan 4, 2023
Relay is a JavaScript framework for building data-driven React applications.

Relay · Relay is a JavaScript framework for building data-driven React applications. Declarative: Never again communicate with your data store using a

Facebook 17.5k Jan 1, 2023
A rugged, minimal framework for composing JavaScript behavior in your markup.

Alpine.js Alpine.js offers you the reactive and declarative nature of big frameworks like Vue or React at a much lower cost. You get to keep your DOM,

Alpine.js 22.5k Jan 2, 2023
A JavaScript Framework for Building Brilliant Applications

mithril.js What is Mithril? Installation Documentation Getting Help Contributing What is Mithril? A modern client-side JavaScript framework for buildi

null 13.5k Dec 28, 2022
A modest JavaScript framework for the HTML you already have

Stimulus A modest JavaScript framework for the HTML you already have Stimulus is a JavaScript framework with modest ambitions. It doesn't seek to take

Hotwire 11.7k Dec 29, 2022
CrossUI is a free Cross-Browser Javascript framework with cutting-edge functionality for rich web application

CrossUI is a free Cross-Browser Javascript framework with cutting-edge functionality for rich web application

Jack Li 1.4k Jan 3, 2023
🌱 React and redux based, lightweight and elm-style framework. (Inspired by elm and choo)

English | 简体中文 dva Lightweight front-end framework based on redux, redux-saga and react-router. (Inspired by elm and choo) Features Easy to learn, eas

null 16.1k Jan 4, 2023
One framework. Mobile & desktop.

Angular - One framework. Mobile & desktop. Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScr

Angular 85.6k Dec 31, 2022
The tiny framework for building hypertext applications.

Hyperapp The tiny framework for building hypertext applications. Do more with less—We have minimized the concepts you need to learn to get stuff done.

Jorge Bucaran 18.9k Jan 1, 2023
The AMP web component framework.

AMP ⚡ ⚡ ⚡ ⚡ Metrics Tooling AMP is a web component framework for easily creating user-first websites, stories, ads, emails and more. AMP is an open so

AMP 14.9k Jan 4, 2023
Front End Cross-Frameworks Framework - 前端跨框架跨平台框架

English | 简体中文 Omi - Front End Cross-Frameworks Framework Merge Web Components, JSX, Virtual DOM, Functional style, observe or Proxy into one framewor

Tencent 12.5k Dec 31, 2022
The Aurelia 1 framework entry point, bringing together all the required sub-modules of Aurelia.

aurelia-framework Aurelia is a modern, front-end JavaScript framework for building browser, mobile, and desktop applications. It focuses on aligning c

aurelia 11.7k Jan 7, 2023
🐰 Rax is a progressive React framework for building universal application. https://rax.js.org

Rax is a progressive React framework for building universal applications. ?? Write Once, Run Anywhere: write one codebase, run with Web, Weex, Node.js

Alibaba 7.8k Dec 31, 2022
:steam_locomotive::train: - sturdy 4kb frontend framework

Choo ?? ?? ?? ?? ?? ?? Fun functional programming A 4kb framework for creating sturdy frontend applications Website | Handbook | Ecosystem | Contribut

choo 6.7k Jan 4, 2023