diff --git a/config/mallery.config.js b/config/mallery.config.js index 189e2c7a..21515529 100644 --- a/config/mallery.config.js +++ b/config/mallery.config.js @@ -9,12 +9,26 @@ module.exports = { public: "../docs/public" }, colors: { - accent: "#222" + accent: "#37A" }, toc: [ - { title: pkg.name, path: "README.md" }, + { title: "Home", path: "README.md" }, + { path: "introduction.md" }, { path: "getting_started", children: [{ path: "installation.md" }] }, { title: "Changelog", path: "CHANGELOG.md" }, - { title: "Repository", href: pkg.repository.url } + { title: "Repository", href: pkg.repository.url }, + { + path: "legacy", + title: "Legacy (v2)", + children: [ + { title: "Getting Started", path: "README.md" }, + { path: "installation.md" }, + { path: "first_steps.md" }, + { path: "stores.md" }, + { path: "reducers.md" }, + { path: "subscriptions.md" }, + { path: "streams.md" } + ] + } ] }; diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 00000000..0826426b --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,43 @@ +# Introduction + +My goal with alo always was to simplify state management and to improve reusability without requiring immutability. As of today, writing this article, its about 3 years ago since I developed the first couple versions of alo including v2.8.3. After releasing v2.8.3 and working with many alternative libraries, I started to realize several conceptual weaknesses and reached a dead end in its implementation, I had to rethink my strategy. + +I write this introduction as a mix of history-article to share my past experience developing a state management library, and as conceptual-guide into alo. And by the way, please don't hang me for my bare-bones english :). + +My audience for this article is mostly frontend developers that already do state management in some form. But if something is very unclear, just ask me with a [Github Issue](https://github.com/alojs/alo/issues), I am open to extend the article also for a wider audience. + +## TLDR + +TODO: Transactions!!! / Change-propagation + +Reading about state management one mostly hears, that immutability is the best and only way to it, especially coupled with React where the performance of the application is directly depending on fast equality checks. While not often talked about there are alternatives and depending on your use case they might actually work better than the immutability approach. Everything summarized, I recommend a thinking of (Im)-mutability as tools for different jobs, instead of just following one paradigm like a religion. + +While the state management in javascript is mostly filled with solutions based around immutability, I try to provide a library to fill the other gap. Not to replace existing solutions, but to broaden the way we think in our frontend work. Sorry for the "Please not again a new library"-camp :P + +## Mutability and Immutability + +Advantages of immutability: +- "Transactions": Since you don't mutate objects but rather create new ones, references to the old object don't autmatically get updated. This lets you control when, and how state changes are being propagated through your application +- "Equality checks": You can use equality checks "==" on object references. This is especially useful when combined with a object state tree, where after a change somewhere deep in the state, each parent object in the tree is recreated, allowing you to very fast compare state regarding deep changes without the need of recursive equality checks. +- "Easy undo,redo / time travel": Thanks to its characteristics it's comparatively easy and very efficient to implement undo, redo features in your application and gain access to devtools like time travel debugging. + +Advantages of mutability: +- "Comes for free in the language": Most basic constructs in the language are based around mutability. So while experimenting and prototyping your mind stays more focused on the actual business-problem you're trying to solve, instead of always keeping an eye and making sure that your precious objects aren't mutated. +- "One way to do it": Goes hand in hand with the first argument. You don't have to think about if you gonna use some library, object destructuring, Object.freeze, Object.assign. Just use that mighty "=" :) +- "List change performance": Especially if your use case has to do with large lists and frequent item changes, mutability will serve you very well because it doesn't come with the overhead of recreating the whole array on every change. + +I recommend immutability-based state management for mid-large, team based projects where its important that state changes are only propagated at will. In such a scenario its wise to use something like [Redux](https://github.com/reduxjs/redux) and [Immer](https://github.com/immerjs/immer) to access the experience of a very large community. + +For small-mid projects, performance-depending features and prototypes I recommend to spend atleast some time with mutability because it might + +## Why another library? + +## Deepclone approach + +## Command approach + +## Event/Tagging approach + +## Getter/Setter approach + + diff --git a/docs/legacy/README.md b/docs/legacy/README.md new file mode 100644 index 00000000..de25069d --- /dev/null +++ b/docs/legacy/README.md @@ -0,0 +1,68 @@ +What follows is a backup of the original Alo v2.8.3 documentation. + +Alo is a state management library, helping you to organize frontend application state in a productive way, giving you ways to handle side effects, and allowing you, to stay in control of whats going on. There are many libraries to manage state: [Redux](https://github.com/reactjs/redux), [Mobx](https://github.com/mobxjs/mobx), [Vuex](https://github.com/vuejs/vuex), and a countless number more. Although I like the ideas behind them, I wasn't quite happy how they introduce many necessary extra steps to get started, require dozens of extra plugins to get you your job done, or introduce new babel build tools, just to write pragmatic code. + +## Features + +* State managed with [flyd](https://github.com/paldepind/flyd) streams (therefore being able to access the state stream directly if needed) +* All the features of a basic state management library (stores, dispatch, subscriptions, middleware) +* Computed properties with dependencies on the state (only computed if dependencies change) +* Dependency-handling for subscriptions (allowing to call subscriptions only if specific dependencies change) +* Before and after events for subscriptions +* Flexible object oriented classes for stores, subscriptions, reducers, etc. +* Promise Support based on [yaku](https://github.com/ysmood/yaku) for dispatches, dependencies and middleware + +## And how? +The [getting started](http://www.alojs.com/getting_started/first_steps.html) section will provide you with additional information, but here is Alo at first glance: + +([Fiddle](https://jsfiddle.net/katywings/tr1vexgp/)) +```js +var Alo = require('alo'); +// Alo building blocks are always assigned to a specific Alo instance +var alo = new Alo(); + +// Stores hold the state of your app and are the main building block of Alo +var counterStore = alo.createStore({ count: 0 }, 'counter'); + +var increase = function(amount) { return { type: 'increase', payload: amount || 1 }} + +// Reducers define how actions are applied to the state +var reducer = counterStore.createReducer(function(state, action) { + if (action.type === "increase") { + state.count += action.payload; + } + + return state +}); + +// Subscribe to state changes +var subscription = counterStore.subscribe(function(stores) { + console.log('count', stores.counter.state.count); +}); + +// Apply the increase action to the store, you always will get a promise from this +var promise = counterStore.dispatch(increase(3)); + +// -> count 0 +// -> count 3 +``` + +## Roadmap +- Documentation of Todo example +- Writing a changelog +- Extending the documentation +- Promise code refactoring +- Where possible, reduce the library size + +### Long-term +Subject to change and open for discussion! + +- 100% Test coverage +- Dev-tools +- Remove utility dependencies +- Remove flyd streams: they didn't really bring an advantage and just added weight +- Remove subscription members: like with the flyd integration, my experience with them was, that they don't provide a lot of advantages +- Reducers just as functions +- Probably for 4.0: Subscriptions should not be called on addStore / removeStore - one can manually call remember if such behaviour is needed +- Probably for 4.0: Remove polymorphism, extend API with separate functions + diff --git a/docs/legacy/first_steps.md b/docs/legacy/first_steps.md new file mode 100644 index 00000000..00b12ab4 --- /dev/null +++ b/docs/legacy/first_steps.md @@ -0,0 +1,74 @@ +# First steps + +Alo is very similar to the [flux pattern](https://medium.com/hacking-and-gonzo/flux-vs-mvc-design-patterns-57b28c0f71b7). + +The main building blocks are: + +- Stores: Are boxes of data, this data can be anything - from booleans to objects +- Reducers: They describe how an action should be synchronously applied to data +- Dependencies: Computations which are derived from data +- Subscriptions: Are reactions after the data in one or multiple stores has changed +- Middlewares: They can change an action - (even asynchronously), before it will be redirected to reducers + +Except a more object oriented approach, there is one *essential difference* compared implementations like [Redux](http://redux.js.org/): Alo doesn't try to enforce the use of only one central store for your data, therefore subscriptions can be attached to multible stores. That is, you still can use it with a single store model if you want. + +Alo's primary goal is not performance, but to minimize data mutation bugs. Therefore reducers will always get a clone and not the reference of the dispatched action and payload. + +The data flow is as follow: +`dispatch on store -> middlewares -> reducers -> subscriptions` + +## Relations +Thanks to the object oriented nature, the API consists of many helper functions. As an example stores and subscriptions are related [m:n](https://stackoverflow.com/questions/3397349/meaning-of-nm-and-1n-in-database-design#3397384): + +([Fiddle](https://jsfiddle.net/katywings/oogL9bnr/1/)) +```js +var makeLogState = function(storeName) { + return function(stores) { + console.log(stores[storeName].state); + }; +}; + +var alo = new Alo(); +var store = alo.createStore('initial state', 'store1'); +var logStateStore1 = makeLogState('store1'); +var subscription = store.subscribe(logStateStore1); +store.removeSubscription(subscription); +var subscription2 = alo.createSubscription(logStateStore1); +subscription2.addStore(store); + +var store2 = alo.createStore(true, 'store2'); +var logStateStore2 = makeLogState('store2'); +var subscription3 = alo.createSubscription(logStateStore2); +store2.addSubscription(subscription3); +``` + +## Alo instances +As Alo follows a relational model, its building blocks are related to each other but they are also related to a Alo instance! + +```js +// A single Alo instance +var alo = new Alo(); +// This store is related to the alo instance above +var store = alo.createStore('state', 'storeName'); +``` + +**This implementation detail was added, so that [debugging and tooling](https://github.com/alojs/alo/issues/9) of your apps can be achieved at a later moment - currently the alo instances are just here because they are here.** + +If you are creating an app with Alo you should create the Alo instance at one place and use [dependency injection](https://youtu.be/6YBV1cKRqzU), to enable its use in multible parts of your software: + +```js +var createAppStore = function(alo) { + return alo.createStore({}, 'app'); +} +var App = function() { + var alo = new Alo(); + var appStore = createAppStore(alo); +} +``` + +### When to use multible instances +[Debugging and tooling](https://github.com/alojs/alo/issues/9) will be per instance, therefore if you are developing an "App in app" solution you could create an instance per App. Use multible Alo instances when you want to completely separate specific components for debugging. + + +## Utilities +In the example code you probably will find many uses of the `alo.util` property. Use these with precaution! There are plans to remove many of them in a future major release. diff --git a/docs/legacy/installation.md b/docs/legacy/installation.md new file mode 100644 index 00000000..46e4afc2 --- /dev/null +++ b/docs/legacy/installation.md @@ -0,0 +1,32 @@ +# Installation + +## Versions +There are different main files available for use in the [dist](https://github.com/alojs/alo/tree/master/dist) folder. + +The files are constructed with a combination of these postfixes: + +* full: includes extras (helpers like some basic reducers and middleware) +* dev: enables long stacktraces in promises and will (as of 3.0) add the debug console to the Alo class + +## CDN's +You can use Alo with NPM or Bower or even straight from a CDN. There are couple browser builds available in the dist folder + +### Development (unminified and with devtools) (see Versions) + +* Core: https://cdn.rawgit.com/alojs/alo/v2.8.3/dist/alo.dev.js +* Full: https://cdn.rawgit.com/alojs/alo/v2.8.3/dist/alo.full.dev.js + +### Production (minified) + +* Core: https://cdn.rawgit.com/alojs/alo/v2.8.3/dist/alo.min.js +* Full: https://cdn.rawgit.com/alojs/alo/v2.8.3/dist/alo.full.min.js + +### More? +Please have a look at [RawGit](https://rawgit.com). It allows you to use basically any file from Github repos (and therefore also all the files from https://cdn.rawgit.com/alojs/alo/v2.8.3/dist/) + +## Use (CJS) + +* Core: `require('alo/dist/alo.js')` +* Core *Dev*: `require('alo/dist/alo.dev.js')` +* Full: `require('alo')` +* Full *Dev*: `require('alo/dist/alo.full.dev.js')` diff --git a/docs/legacy/middlewares.md b/docs/legacy/middlewares.md new file mode 100644 index 00000000..325563d2 --- /dev/null +++ b/docs/legacy/middlewares.md @@ -0,0 +1,2 @@ +# Middlewares +To be continued... diff --git a/docs/legacy/reducers.md b/docs/legacy/reducers.md new file mode 100644 index 00000000..395526d4 --- /dev/null +++ b/docs/legacy/reducers.md @@ -0,0 +1,46 @@ +# Reducers +Reducers apply actions to store states: + +[Fiddle](https://jsfiddle.net/katywings/z2hheeuj/1/) +```js +var alo = new Alo(); +var store = alo.createStore(0, 'number'); + +// Create a reducer on alo instance level +var reducerPlus = alo.createReducer(function(state, action) { + if (action.type == '+') { + state += 1; + } + + return state; +}); +// Add the reducer to the store +store.addReducer(reducerPlus); + +// This creates and adds a new reducer to the store in one step +var reducerMinus = store.createReducer(function(state, action) { + if (action.type == '-') { + state -= 1; + } + + return state; +}); + +alo.util.Promise.resolve() + .then(function() { + return store.dispatch({ type: '-' }); + }) + .then(function() { + return store.dispatch({ type: '-' }); + }) + .then(function() { + return store.dispatch({ type: '+' }); + }) + .then(function() { + return store.dispatch({ type: '-' }); + }) + .then(function() { + console.log(store.getState()); + // -2 + }) +``` diff --git a/docs/legacy/stores.md b/docs/legacy/stores.md new file mode 100644 index 00000000..42adcf87 --- /dev/null +++ b/docs/legacy/stores.md @@ -0,0 +1,95 @@ +# Stores +As earlier mentioned stores hold the state of your application and redirect actions to reducers. + +They can be created like this: + +```js +var alo = new Alo(); +var store = alo.createStore("state", "storeName"); +``` + +The constructor parameters are: + +1. Initial state of the store, (can be any type but generally will be an object) +2. Optional: An unique name for the store (this name will be used as store identifier in the subscription!) + +## Dispatch +Dispatches are the way to notify a store about a state change: + +```js +var action = { + type: 'changeState', + payload: 'newState' +} +store.dispatch(action); +``` + +*Atleast if you are not using a middleware to change this behaviour, you **always** +need to dispatch action objects atleast with a type field.* + +The dispatch will however not do anything, as long as the store doesn't have a related reducer! +If the store has any reducers, they will decide what should happen with your action. + +## Get the current state +- Only the state: +```js +store.getState(); +``` + +- State and computed properties +```js +store.getData(); +``` + +## Computed properties +Alo has built-in support for computed properties. They are only recalculated, when their dependencies change (plusThree is only called, when plusTwo changes). Look at the following example to get a feel for them: + +[Fiddle](https://jsfiddle.net/katywings/w4q6242q/4/) +```js +var alo = new Alo(); +var store = alo.createStore(1, 'number'); +store.createReducer(function(state, action) { + if (action.type == 'add') { + state += 1; + } + + return state; +}); + +// Creates 3 computed properties, plusThree is especially dependent on plusTwo +store.createComputedProperty({ + 'plusOne': function(store) { + return store + 1; + }, + 'plusTwo': function(store) { + return store + 2; + }, + 'plusThree': [['plusTwo'], function(store, computed) { + return computed.plusTwo + 1; + }] +}); + +store.subscribe(function(stores) { + // Computed + console.log('state', stores.number.state); + console.log('computed properties', stores.number.computed); +}); + +store.dispatch({ type: 'add' }); +``` + +**At the moment computed properties will (as lazy as they are) only be called after a dispatch with state change, as an example you could have a "initialize" action which sets the initial store state**: + +```js +var alo = new Alo(); +var store = alo.createStore({}, 'number'); +store.createReducer(function(state, action) { + if (action.type == 'initialize') { + state = action.payload; + } + + return state; +}); +// store.createComputedProperty... +store.dispatch({ type: 'initialize', payload: 1}); +``` diff --git a/docs/legacy/streams.md b/docs/legacy/streams.md new file mode 100644 index 00000000..88b47ed2 --- /dev/null +++ b/docs/legacy/streams.md @@ -0,0 +1,14 @@ +# Streams +Alo uses [Flyd](https://github.com/paldepind/flyd) streams in the background. The API provides you with a direct way to access underlying streams: + +```js +var alo = new Alo(); +var store = alo.createStore('state', 'storeName'); +var subscription = alo.createSubscription(function() {}); + +// This is the flyd stream and you can use it with the many flyd modules +var stream = store.getStream(); + +// Use this very careful: Subscription streams are overwritten with new streams, each time when the relation between the subscription and its stores is changed. If you only add / remove stores to the subscription at the beginning of your app, then the usage of this should be fine +var subscriptionStream = subscription.getStream(); +``` diff --git a/docs/legacy/subscriptions.md b/docs/legacy/subscriptions.md new file mode 100644 index 00000000..9432f13a --- /dev/null +++ b/docs/legacy/subscriptions.md @@ -0,0 +1,44 @@ +# Subscriptions +Subscriptions get called each time the state on a related store changes. Therefore when a dispatched action doesn't lead to a state change via reducer, that dispatch will not lead to a subscription call either. + +**When a subscription gets related to a new store it will be called!** + +[Fiddle](https://jsfiddle.net/katywings/75fjhrbp/3/) +```js +var alo = new Alo(); +var store = alo.createStore({n: 1}, 'number'); +// This is a nice reducer for prototyping which just replaces store state with the whole dispatched action +store.addReducer(alo.extras.reducers.createUntypedReplace()); + +// Creating a subscription over the alo instance +var subscription1 = alo.createSubscription(function(stores) { + console.log('stores', stores); +}) +store.addSubscription(subscription1); + +// Creating a subscription directly on a store +var subscription2 = store.subscribe(function(stores) { + console.log('hello from subscription 2'); +}); +// or even store.createSubscription(fun...) + +// Ways of removing a subscription: +// 1. Remove all related stores +subscription2.removeStore(); +// 2. Remove a specific store +subscription2.removeStore(store); +// 3. Remove a subscription from a store (does the same as 2.) +store.removeSubscription(subscription2); + +store.dispatch({n: 2}); +``` + +The parameters of the subscription function are: + +1. Stores: Object with every related store (by store name) + - stores.storeName.state: State of the store + - stores.storeName.computed: Computed properties of the store +2. Computed properties: Object with computed properties of this subscription + +## Computed properties +Subscriptions can have own computed properties like [stores](http://www.alojs.com/getting_started/stores.html#computed-properties])! But keep in mind: **As soon as a subscription has its own computed properties, they will be used for the lazy subscription-call comparison and not the related stores of the subscription!** diff --git a/docs/public/CNAME b/docs/public/CNAME new file mode 100644 index 00000000..9f509fd6 --- /dev/null +++ b/docs/public/CNAME @@ -0,0 +1 @@ +www.alojs.com \ No newline at end of file diff --git a/templates/readme/README.md.hop.ejs b/templates/readme/README.md.hop.ejs index 067ee926..a779bebf 100644 --- a/templates/readme/README.md.hop.ejs +++ b/templates/readme/README.md.hop.ejs @@ -12,4 +12,10 @@ <%= input.pkg.description %> -Demo (with devtools): https://codepen.io/katywings/pen/mNzjVd +## Links + +- [Codepen Demo](https://codepen.io/katywings/pen/mNzjVd) +- [Introduction](https://www.alojs.com/introduction.html) + +## Logo +The logo was created in association with Nora Rigo, a freelance designer at http://www.designcrowd.com/.