UNPKG

5.94 kBMarkdownView Raw
1# CARMI
2
3[![Build Status](https://travis-ci.org/wix-incubator/carmi.svg?branch=master)](https://travis-ci.org/wix-incubator/carmi)
4
5## Compiler for Automatic Reactive Modelling of Inference
6
7This is a POC of an entirely new approach to modelling inferred state, there are 4 classic methods of handling
8derivation of state
9
101. The naive - compute from scratch every time the state is changed
112. Handle cache invalidation manually with all the world of hurt that entails.
123. Using Immutable data and caching computation based on the identity of the inputs
134. Using Functional Reactive Programming to box fragments of your state with getters&setters, running derivations in a
14 way that logs which fragments were read during the computation, and invalidate when one of their setters is invoked
15
16This project is an attempt at a new approach, a DSL+Compiler which are fed two types of inputs:
17
181. The derivation of state you need
192. The paths in the model you want to write to
20
21The compiler generates JS source code which handles all the reactive cache invalidation automatically
22
23Because the compiler knows in advance all the stuff that can be read/written from/to the model, it can do all sort of
24cool stuff that is nearly impossible to do automatically using other approaches
25
261. Track conditional consumption of parts of the model only if used with zero over head
272. Hoisting shared sub expressions, so they are only calculated once
283. Not track dependencies if there are no setters that can cause the expression to invalidate
294. All computation is incremental
30
31```js
32const { compile, root, arg0, setter, splice } = require("carmi");
33const todosByIdx = root.keyBy("idx");
34const anyTodoNotDone = todosByIdx.anyValues(todo => todo.get("done").not());
35const todosDisplayByIdx = todosByIdx.mapValues(todo =>
36 todo.get("task").plus(todo.get("done").ternary(" - done", " - not done"))
37);
38const todosDisplay = root.map(todo => todosDisplayByIdx.get(todo.get("idx")));
39const model = {
40 todosDisplay,
41 anyTodoNotDone,
42 setTodoDone: setter(arg0, "done"),
43 spliceTodos: splice(),
44};
45
46const todosModel = eval(compile(model));
47const todos = todosModel([
48 { idx: "1", done: false, task: "write a blog post about carmi" },
49 { idx: "2", done: true, task: "publish to npm" },
50 { idx: "3", done: false, task: "write a demo for carmi" },
51]);
52console.log(todos.todosDisplay);
53/*
54[ 'write a demo for carmi - not done',
55'write a blog post about carmi - not done',
56'publish to npm - done' ]
57*/
58todos.setTodoDone(2, true); // only todosDisplayByIdx of the demo is recalculated
59console.log(todos.todosDisplay);
60/*
61[ 'write a blog post about carmi - not done',
62 'publish to npm - done',
63 'write a demo for carmi - done' ]
64*/
65todos.spliceTodos(0, 3, todos.$model[2], todos.$model[1], todos.$model[0]); // todosDisplayByIdx is not called at all
66console.log(todos.todosDisplay);
67/*
68[ 'write a demo for carmi - done',
69 'publish to npm - done',
70 'write a blog post about carmi - not done' ]
71*/
72```
73
74## Usage with Babel Macros
75
76### Using a magic comment
77
78```js
79// @carmi
80
81require('carmi/macro') // Activate the macro!
82
83const { root } = require('carmi')
84module.exports = { first: root.get(0) }
85```
86
87### Using string literals
88
89```js
90const carmi = require('carmi/macro')
91
92const modelBuilder = carmi`
93 const { root } = require('carmi')
94 module.exports = { first: root.get(0) }
95`
96
97const model = modelBuilder(["first", "second"])
98console.log(model.first) // prints "first"!
99```
100
101## Usage with Babel (as a plugin)
102
103Compiles Carmi files (`xxx.carmi.js`) automatically using Babel. It reserves the external modules, required by `require`,
104in order to help bundlers (like webpack) understand dependencies across Carmi models (and to help them watch the files).
105
106```js
107// @carmi
108
109const { root } = require("carmi");
110const { second } = require("./anotherModel.carmi.js");
111
112module.exports = { first: root.get(0), second };
113```
114
115turns to:
116
117```js
118require("carmi");
119require("./anotherModel.camri.js");
120
121module.exports = CARMI_COMPILATION_RESULT;
122```
123
124### Add to your babel configuration
125
126Add this plugin to your `.babelrc`:
127
128```
129{
130 ... babel conf ...,
131 "plugins": ["carmi/babel"]
132}
133```
134
135> Now you're set! :moneybag:
136
137## Usage with Webpack
138
139Compiles Carmi files on the fly with a fly webpack loader.
140
141### Require using webpack configurations
142
143Add this to your webpack configurations:
144
145```js
146module: {
147 rules: [
148 {
149 test: /\.carmi\.js$/,
150 exclude: /(node_modules|bower_components)/,
151 use: {
152 loader: 'carmi/loader',
153 options: {
154 // Carmi options...
155 }
156 }
157 }
158 ]
159}
160```
161
162#### Available carmi options:
163**`debug`**: Add debug function to the generated code.
164**`type-check`**: Add static type checking to the generated code.
165**`no-cache`**: Cache will be ignored. *(It could noticeably affect the build time)*
166**`cache-scenario`** (**`mtime`** *(default)* | **`git-hash`**): Specify which cache scenario you want to use. Each of scenario is collection all dependencies of the entry file and checking if at least one of it was updated. Then cache will be ignored and carmi will compile file from the scratch. The difference is how carmi checks which dependency was updated. **mtime** option will check last modified date of the file. But it won't work for cases when you are going to `git clone` project before each build since git doesn't contain a created/modified file metadata. For this cases **git-hash** option could be useful. It collects state based on git hash of each dependency and invalidate file if some of hash was updated (usually it happens after each git tree modification.) *(Will be ignored if no-cache option is enabled)*
167**`ast`**: Add AST to the output for debug purposes.
168
169
170Then you can just `require('./model.carmi.js')` like a boss
171
172### Require with explicit loader
173
174```js
175const modelBuilder = require("carmi/loader!./model.carmi.js");
176// modelBuilder is the carmi builder function!
177```