Coder Social home page Coder Social logo

unredux's Introduction

Unredux

An experimental set of tools for cross-platform framework-less development (500 lines of core + libraries!). Framework-less approach assumes you are willing to assemble the final thing yourself, buying more control and flexibility at the cost of additional boilerplate.

Think CycleJS without drivers built on Kefir, Kefir.DB, and React. Unredux is compatible with React ecosystem (except ReactRouter and some other high-level stuff replaced with a stream-based code here).

WIP. Highly unstable. Name is a temporary dummy thing.

Most frameworks allow you to override important stuff with options. Yet this approach is naturally limited as option objects simply ain't expressive enough to compete with a real code. As soon as your app's assumptions are different from that of a framework (a reasonable guess), you start tweaking stuff here and there, adding more and more ugly hacks in the process. In our experience, the best solution is often a framework removal.

If you don't like the imperative, object-oriented approach VueJS and MobX promote. If you are not satisfied with the bundle size, complexity and vendor locks of Angular and GraphQL. If you are sick of Redux ecosystem and willing to concentrate on the business logic – here's a solution.

Unlike those titles, you can read the whole code of Unredux in one sitting.

Features

  • CycleJS-like architecture with sources and sinks but without drivers.
  • Reactive dataflow with proactive insertions.
  • Reactive state management (think Redux with functions instead of actions).
  • Full-scale examples: realistic routing, SSR, etc.
  • Declarative data fetching and auto-persistence (in progress).

Component model is as simple as possible (components are called apps here to differentiate them from React components):

function anyApp(sources, props) {
  sources      // {state$: streamOfStates, DOM: streamOfLocalDOMEvents, ...}
  ...          // -- your code --
  return sinks // {Component: reactComponent, action$: streamOfActions, ...}
}

where sources and sinks are records of streams (Object <KefirStream> speaking TypeScript), where keys are predefined and correspond to event or effect types (a complex sentece but it describes everything about the component model – try to do that for another framework).

Component "composition" is just a function and stream composition, nothing fancy. And as it's not a framework you can mix and swap stuff without a problem. For example, we use complex stateful React components like DnD and some unique jQuery plugins together with this architecture in production.

Docs

Also suggested:

Examples

A basic example.

Component isolation and reuse.

Pages and simple routing.

Advanced routing (wip).

Classic "TodoMVC" app.

Todos with a flexible history management.

Shopping Cart with interactions between "parent" and "child" apps.

CRUD client-server apps showing async data fetching, posting, caching, and more.

Previous example extended with SSR.

Declarative Data Load, lightweight alternative to GraphQL+Relay / Falcor.

Notable differences:

  1. Unlike both (GraphQL / Falcor) it's REST API based (avoiding vendor lock-in on Backend).
  2. It's more reactive and declarative where it makes sense with clear fallback paths.
  3. Unlike GraphQL it relies on plain data to describe queries, instead of extra DSL layer.
  4. Less magic, more boilerplate. Model dependencies are described, not auto-injected.
  5. Much smaller code base and bundle size.
  6. Validations and Querying are decoupled. Use TsIO, PropTypes, Tcomb or any other dynamic typing to describe and validate your models. Avoiding vendor lock-in again.

Usage

Examples are expected to work in Browser and NodeJS environments. The latter will require babel-node (because of ES6 modules).

$ npm install babel-cli -g
$ npm install http-server -g

Browser

  1. Fork and download the repo.
  2. $ npm install in the root to pull common dependencies.
  3. $ cd <example_folder>.
  4. $ npm install to pull local example dependencies and symlink vendor libraries (see below).
  5. $ npm start (check package.json scripts).

NodeJS

  1. Create some.js file in the project root.
  2. Run the file with $ babel-node some.js.
  3. Required babel plugins and presets will be applied automatically.

Prerequisites

  • Functional Programming (basics)
  • Lensing (basics)
  • React (basics)
  • Kefir (basics)

Remarks

Conventions

We use ASCII marble diagrams:

observable: ---v1---v2---> (time)

where v1 may denote a string "v1" or something else, which should be clear from a context.

unredux's People

Contributors

ivan-kleshnin avatar scabbiaza avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unredux's Issues

Better routing

Current routing implementation is somewhat clunky. Need to reconsider it.

Connection

Need to check how connect is implemented and to think if there are any problems with the approach.

Routing

I want something minimalistic. Kinda tired of bloated routing solutions.
Trying the same route-parser I used previously in CycleJS:

import * as R from "ramda"
import {inspect} from "util"
import Route from "route-parser"

// routes :: Array (Route, Payload) ~ Routes
let routes = [
  [new Route("/"), {foo: "foo"}],
  [new Route("/users"), {foo: "foo"}],
  [new Route("/users/create"), {foo: "foo"}],
  [new Route("/users/:id"), {foo: "foo"}],
  [new Route("/users/:id/edit"), {foo: "foo"}],
  [new Route("/*path"), {foo: "foo"}],
]

// makeRouter :: Routes -> {doroute :: Function, unroute :: Function}
let makeRouter = (routes) => {
  // doroute :: String -> {mask :: String, params :: Object, payload :: any}
  let doroute = (url) => {
    for (let [route, payload] of routes) {
      let match = route.match(url)
      if (match) {
        return {mask: route.spec, params: match, payload}
      }
    }
    throw Error(`${inspect(url)} does not match any known route`)
  }

  // unroute :: (String, Params) -> String
  let unroute = (mask, params) => {
    for (let [route, payload] of routes) {
      if (route.spec == mask) {
        return route.reverse(params)
      }
    }
    throw Error(`${inspect(mask)} does not match any known route`)
  }

  return {doroute, unroute}
}

let router = makeRouter(routes)

console.log(router.doroute("/users"))
console.log(router.doroute("/zergjnrekjn"))
console.log(router.unroute("/users/:id", {id: 1}))
console.log(router.unroute("/zergjnrekjn", {}))

Consider Kefir and Most

  1. RxJS is too big.
  2. Throttling in RxJS is severily broken (a huge problem as we depend heavily on throttling).
import * as R from "ramda"
import K from "kefir"
import {Observable as O} from "rxjs"

// Kefir
let source$ = K.sequentially(100, R.range(0, 7))
  .throttle(200, {leading: false, trailing: true})

source$.log()

// 1-2-3-4-5-6|
// 1---3---5-6|
// Good for stateful streams – emits the last "snapshot".

// RxJS
let source$2 = O.interval(100).take(7)
  .throttleTime(200, undefined, {leading: false, trailing: true})

source$2.subscribe(console.log)

// 1-2-3-4-5-6|
// 1---3---5--|
// Bad for stateful streams – drops the last "snapshot".

// MostJS
// throttling is not configurable, leading edge only

Lensing

let actions = {
  increment: Observable.merge(
    intents.increment,
    stateCycle.sample(intents.incrementIfOdd).filter(state => isOdd(state.counter))
  )
    .map(() => (state) => R.assoc("counter", state.counter + 1, state)),
  decrement: intents.decrement
    .map(() => (state) => R.assoc("counter", state.counter - 1, state)),
}

raw approach is fine but too noisy in the update part. Solutions:

  • wrap Ramda lenses (too verbose by default)?
  • use Partial.Lenses (too big)?
  • use Rx-Utils (need reconsideration)?

React warnings

React raises a warning for fields with value but without onChange. It's not applicable for our architecture. The current workaround is:

<input type="text" name="filter.tag" value={index.filter.tag} onChange={R.id}/> Tag

Need to suppress this warning in a single place.


Consider the following.

// Should suppress DevTool warning since React 16.1.0
// plugins: [
//   new Webpack.DefinePlugin({
//     "__REACT_DEVTOOLS_GLOBAL_HOOK__": "({ isDisabled: true })"
//   }),
// ]

React 16.1.1 is released.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.