Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

nuclear-js

optimizely2.3kMIT1.4.0TypeScript support: definitely-typed

Immutable, reactive Flux architecture. UI Agnostic.

flux, nuclear, immutable, react, vue, vuejs, functional, stateless

readme

NuclearJS

Build Status Coverage Status Join the chat at https://gitter.im/optimizely/nuclear-js

Sauce Test Status

Traditional Flux architecture built with ImmutableJS data structures.

Documentation

https://optimizely.github.io/nuclear-js/

Design Philosophy

  • Simple Over Easy - The purpose of NuclearJS isn't to write the most expressive TodoMVC anyone's ever seen. The goal of NuclearJS is to provide a way to model data that is easy to reason about and decouple at very large scale.

  • Immutable - A means for less defensive programming, more predictability and better performance.

  • Functional - The framework should be implemented functionally wherever appropriate. This reduces incidental complexity and pairs well with Immutability.

  • Smallest Amount of State Possible - Using NuclearJS should encourage the modeling of your application state in the most minimal way possible.

  • Decoupled - A NuclearJS system should be able to function without any sort of UI or frontend. It should be backend/frontend agnostic and be able to run on a NodeJS server.

Installation

NuclearJS can be downloaded from npm.

npm install nuclear-js

Examples

  • Shopping Cart Example - Provides a general overview of basic NuclearJS concepts: actions, stores and getters with ReactJS.
  • Flux Chat Example - A classic Facebook flux chat example written in NuclearJS.
  • Rest API Example - Shows how to deal with fetching data from an API using NuclearJS conventions.
  • Hot reloadable stores - Shows how to setup stores to be hot reloadable using webpack hot module replacement.

How NuclearJS differs from other Flux implementations

  1. All app state is in a singular immutable map, like Om. In development you can see your entire application state at every point in time thanks to awesome debugging tools built into NuclearJS.

  2. State is not spread out through stores, instead stores are a declarative way of describing some top-level domain of your app state. For each key in the app state map a store declares the initial state of that key and how that piece of the app state reacts over time to actions dispatched on the flux system.

  3. Stores are not reference-able nor have any getX methods on them. Instead NuclearJS uses a functional lens concept called getters. In fact, the use of getters obviates the need for any store to know about another store, eliminating the confusing store.waitsFor method found in other flux implementations.

  4. NuclearJS is insanely efficient - change detection granularity is infinitesimal, you can even observe computed state where several pieces of the state map are combined together and run through a transform function. NuclearJS is smart enough to know when the value of any computed changes and only call its observer if and only if its value changed in a way that is orders of magnitude more efficient than traditional dirty checking. It does this by leveraging ImmutableJS data structure and using a state1 !== state2 reference comparison which runs in constant time.

  5. Automatic data observation / rendering -- automatic re-rendering is built in for React in the form of a very lightweight mixin. It is also easily possible to build the same functionality for any UI framework such as VueJS, AngularJS and even Backbone.

  6. NuclearJS is not a side-project, it's used as the default Flux implementation that powers all of Optimizely. It is well tested and will continue to be maintained for the foreseeable future. Our current codebase has over dozens of stores, actions and getters, we even share our prescribed method of large scale code organization and testing strategies.

Performance

Getters are only calculated whenever their dependencies change. So if the dependency is a keypath then it will only recalculate when that path in the app state map has changed (which can be done as a simple state.getIn(keyPath) !== oldState.getIn(keyPath) which is an O(log32(n)) operation. The other case is when a getter is dependent on other getters. Since every getter is a pure function, NuclearJS will only recompute the getter if the values if its dependencies change.

API Documentation

API Documentation

For Smaller Applications

NuclearJS was designed first and foremost for large scale production codebases. For a much more lightweight Flux implementation that shares many of the same ideas and design principles check out Microcosm.

Contributing

Contributions are welcome, especially with the documentation website and examples. See CONTRIBUTING.md.

changelog

1.4.0 (September 21, 2015)

  • [NEW] Added ability to switch out the default caching strategy for caching getter values. Also expose an LRU cache that can be swapped in for the basic cache
  • [NEW] Add ability to supply your own logger and override the default console group logger in NuclearJS
  • [UPGRADE] Upgrade immutable to 3.8.1

Cache Configuration

import * as Nuclear from 'nuclear-js';

const MAX_ITEMS = 1000 // (optional, default = 1000) how many items to keep in the LRU cache before evicting
const EVICT_COUNT = 10 // (optional, default = 1) how many items to throw out when the cache fills up

new Nuclear.Reactor({
  debug: false,
  cache: new Nuclear.LRUCache(MAX_ITEMS, EVICT_COUNT),
});

Using your own Logger

import * as Nuclear from 'nuclear-js';

new Nuclear.Reactor({
  logger: {
    dispatchStart(reactorState, actionType, payload) {
      console.log(`dispatch: actionType=${actionTypes}`, payload)
    },
    dispatchError(reactorState, error) {
      // useful if you need to close a console.group if an error is thrown during dispatch
    },
    dispatchEnd(reactorState, state, dirtyStores, previousState) {
      const prevStateChanges = previousState.filter((val, key) => dirtyStores.contains(key)).toJS()
      const stateChanges = state.filter((val, key) => dirtyStores.contains(key)).toJS()

      console.log('prev state: ', prevStateChanges)
      console.log('new state: ', stateChanges)
    },
  },
});

1.3.0 (December 31, 2015)

  • [NEW] Store hot-reloading via reactor.replaceStores(stores) which replaces the implementation of a store without resetting its underlying state value. See hot reloading example.
  • [NEW] Support for more granular options for logging and triggering invariants in the NuclearJS runtime. See API Docs for details.

1.2.1 (November 5, 2015)

  • [FIXED] Observers of the entire app state not triggering on actions from a late registered store

1.2.0 (November 1, 2015)

  • [NEW] Exposed new API methods: batchStart and batchStop.
  • [NEW] Changed the transpiler to Babel.
  • [FIXED] Completely refactored Reactor, Evaluator and ChangeObserver.
  • [FIXED] Fixed all issues related to hash code collisions.
  • [FIXED] Refactored how change observation works to be much more efficient.

1.1.2 (October 5, 2015)

  • [FIXED] Fix for observer iteration when removed during notify. Issue #151

1.1.1 (July 26, 2015)

  • [ADDED] Bowser support via bower.json

1.1.0 (July 23, 2015)

  • [NEW] added Reactor#serialize, Reactor#loadState, Store#serialize and Store#deserialize methods
  • [NEW] added Reactor#batch to allow batch dispatches before notify observers
  • [NEW] throw error when trying to dispatch within a dispatch
  • [FIXED] fix Evaluator locking if getter evaluation errors

API Additions

Reactor#serialize()

Returns a plain JavaScript object representing the application state. By default this maps over all stores and returns toJS(storeState).

reactor.loadState(reactor.serialize())

Reactor#loadState( state )

Takes a plain JavaScript object and merges into the reactor state, using store.deserialize

This can be useful if you need to load data already on the page.

reactor.loadState({
  stringStore: 'bar',
  listStore: [4,5,6],
})

Store#serialize

Serialization method for the store's data, by default its implemented as `Nuclear.toJS' which converts ImmutableJS objects to plain JavaScript. This is overridable for your specific data needs.

// serializing an Immutable map while preserving numerical keys
Nuclear.Store({
  // ...
  serialize(state) {
    if (!state) {
      return state;
    }
    return state.entrySeq().toJS()
  },
  // ...
})

Store#deserialize

Serialization method for the store's data, by default its implemented as `Nuclear.toImmutable' which converts plain JavaScript objects to ImmutableJS data structures. This is overridable for your specific data needs.

// deserializing an array of arrays [[1, 'one'], [2, 'two']] to an Immutable.Map
Nuclear.Store({
  // ...
  deserialize(state) {
    return Immutable.Map(state)
  },
  // ...
})

1.0.5 (June 4, 2015)

  • [NEW] Configured linting using eslint. Linting is now part of the contributing process. Eslint can be run using: grunt eslint
  • [NEW] Implemented new developer docs landing page. This website is still in beta. You can view it here: https://optimizely.github.io/nuclear-js/
  • [FIXED] Removed accidentally checked in node_modules directory.
  • [FIXED] Addressed all the lint warnings and errors in the codebase using the new rules in .eslintrc
  • [FIXED] Updated documentation.

1.0.2 (May 14, 2015)

  • [DEPRECATED] reactor.registerStore will be deprecated in 1.1.x - use reactor.registerStores instead. Added deprecation warning.
  • [FIXED] Now properly observing getters when passing silent=true for registerStores - this option no longer makes sense as it makes future observations unreliable.
  • [FIXED] Support Utils.isFunction in all browsers. #57
  • [FIXED] Evaluator now doesn't evaluate getter args twice when there is a stale value.

1.0.1 (April 27, 2015)

  • [NEW] Expose createReactMixin functionality on NuclearJS singleton.
  • [FIXED] Fix new Store() from throwing error when not passed a config object.

1.0.0 (April 25, 2015)

  • [NEW] Built in support for React. No need for the NuclearReactMixin, simply use reactor.ReactMixin
  • [BREAKING] Reactor.get( ...getters, transformFn ) -> Reactor.evaluate( getter )
  • [BREAKING] Reactor.getJS( ...getters, transformFn ) -> Reactor.evaluateToJS( getter )
  • [BREAKING] Keypaths must always be arrays, no more support for 'foo.bar' style keypaths.
  • [BREAKING] Getters are no longer a constructor, instead they are plain arrays.
  • [FIXED] Huge optimizations for Reactor.evaluate and Reactor.observe - These values are now very efficiently memoized by leveraging that fact that getters are pure functions and are transforming immutable data. This means that complex transformations won't be reevaluated unless its direct dependencies or underlying state change.