Maybe the worlds smallest Application State Manager written in Javascript (less than 300 bytes ).
This project is just to checkout the limits on optimization. The aim is to squeeze out the last bytes and create the worlds smallest application state manager written in Javascript.
Besides all that fun, it is meant to be fully functional and usable for real-world applications.
As NPM module (larger version due to module overhead --- see below)
npm i -S stappo
As plain script (smallest build possible) in your web application
<script type="text/javascript" src="https://cdn.rawgit.com/ohager/stappo/master/dist/stappo.web.min.js"></script>
- Generic Bundle
./dist/stappo.bundle.js
- A browserified bundle usable either on client- (Browser) or server-side (NodeJS). The bundles supports AMD/UMD/CommonJS/ES6 module importation. - Generic
.dist/stappo.min.js
- Usable on client or server-side, but without any module support (plain ES5 class) - Web-Only Bundle
./dist/stappo.web.bundle.js
- Only for browsers, supports AMD/UMD/CommonJS/ES6 module - Web-Only
./dist/stappo.web.min.js
- Plain class, for browsers only...no overhead at all!
The generic version implements its own observer (aka pub/sub) pattern, and therefore doesn't rely on specific platform. The web-only version uses custom events of the browsers event system (addEventListener). The web-only version is smaller than the generic version.
Nowadays, web applications can be quite complex and the big challenge of such complex (web) applications how to manage their complexity. State-of-the-art is component based development; Libraries/Frameworks like ReactJS, Angular2, Aurelia, VueJS, RiotJS and others applies this concept. This allows the devs to break down everything in more or less decoupled and (hopefully) reusable parts, which can be put together like Lego(tm).
Besides the composition feature a web application needs to communicate with the backend to store or fetch persistent data. The common approach to interoperate with the backend is a REST-like/-ful API, or even more recent techniques like GraphQL (Relay). Such backend-state must be handled somehow by the client application, especially because the requests are asynchronous. This makes data arrival unpredictable and adds even more complexity.
Each application of a certain complexity (usually the complexity doesn't need to be high, as you may see in the demo) needs to maintain some shared data among its components. This data represents a specific situation at a specific moment of that application while a user interacts with it. This is considered as application state. That state should be
- accessible by all components of an application
- ideally, stored in a single place (Single Source of Truth)
- mutable by a well-defined interface only
Definition: An application state is a deterministic situation at a certain moment of an application within the context of user interaction.
As one can see in the ONLINE DEMO, even very simple applications must share states among its components. The demo consists of very few components (<10), but has to share a item list and a search term:
On adding a new item via the single input field, the item list must be updated. Additionally, the search bar allows text-based filtering; the item list is being updated on changed search term and changed item. This is still a very simple scenario, but it shows that three components interoperate with two shared states.
The overall concept is deadly simple. Stappo maintains an Application State, where interested modules/components can listen to, while others may update the state. On state updates the subscribed listeners are called, receiving the new state. The state is immutable.
// stappo must be instantiated
// this could be useful to manage large states (e.g one instance per domain)
const StappoClass = require('stappo');
const stappo = new StappoClass()
//
const listener = stappo.listen( state => {
console.log("Updated State", JSON.stringify(state, " "));
});
// update the state --- it'll be merged into the application state
stappo.update(() => ({
foo: {
bar : {
a: 42,
b : [0,1,2]
}
}}));
// stop listen to state changes
stappo.unlisten(listener);
The listen function accepts a functionObj
of the following signature function(state){}
. The state is the updated state and it is immutable.
The functionObj
is being called on each update of the application state. The second optional argument is the calling context, also referred as the thisArg.
It is used to give the called functionObj its calling context (where this
references to -- similar to Object.prototype.call
)
The function returns an id, which can/should be used on stappo.unlisten
.
Revokes the subscription established on stappo.listen
. The listenerId
is the return value from stappo.listen
.
Updates the application state, i.e. calls the passed function that must return a JSON and merges it into the application state. On update all listeners are notified.
fn
is of type (currentState) => { return newStateObj }
Returns the current state
The web only version uses the browser's event system, while the generic version implements its own observer.
Therefore, no listen
and unlisten
method exist. Listening to the event works like this:
window.addEventListener('stappo', e => { console.log('state updated', e.detail)} )
const stappo = new Stappo();
stappo.update(() => ({poo: 'fart'})); // emits 'stappo' event
window.removeEventListener('stappo');
Creates a new Stappo instance. When using more than one instance (e.g. for differnt domains/contexts) you need to define an custom event type name.
Example for multiple instances:
const stappoProducts = new Stappo('stappo.products');
const stappoOrders = new Stappo('stappo.orders');
window.addEventListener('stappo.products', e => { console.log('state updated', e.detail)} )
window.addEventListener('stappo.orders', e => { console.log('state updated', e.detail)} )
stappoProducts.update(() => ({products: newProducts})) // emits 'stappo.products'
stappoOrders.update(() => ({orders: newOrders})) // emits 'stappo.orders'
Updates the application state, i.e. calls the passed function that must return a JSON and merges it into the application state. On update all listeners are notified.
fn
is of type (currentState) => { return newStateObj }
Returns the current state
- Cleanup unused dev dependencies
- Pimp up the demo
- Try to reduce sizes even more!!!