phlox
Version:
A frontend architecture that's easy to visualize
410 lines (377 loc) • 12.9 kB
JavaScript
// Generated by CoffeeScript 2.4.1
(function() {
var $, DEV, RE, _, equals, has, hasChanged, isEmpty, isNil, join, map, mapI, match, merge, path, popsiql, prop, props, qq, qqq, sf2, split, subSelectIfDEV, tail, test, type;
equals = require('ramda/src/equals');
has = require('ramda/src/has');
isEmpty = require('ramda/src/isEmpty');
isNil = require('ramda/src/isNil');
join = require('ramda/src/join');
map = require('ramda/src/map');
match = require('ramda/src/match');
merge = require('ramda/src/merge');
path = require('ramda/src/path');
prop = require('ramda/src/prop');
props = require('ramda/src/props');
split = require('ramda/src/split');
tail = require('ramda/src/tail');
test = require('ramda/src/test');
type = require('ramda/src/type');
({mapI, $, sf2} = RE = require('ramda-extras')); //auto_require: ramda-extras
//auto_sugar
[];
qq = function(f) {
return console.log(match(/return (.*);/, f.toString())[1], f());
};
qqq = function(f) {
return console.log(match(/return (.*);/, f.toString())[1], JSON.stringify(f(), null, 2));
};
_ = function(...xs) {
return xs;
};
popsiql = require('popsiql');
DEV = process.env.NODE_ENV === 'development';
// DEV = false
hasChanged = function(lastState, currentState) {
return !equals(lastState, currentState);
};
subSelectIfDEV = function(dataQuery, data) {
var missing, subData;
if (DEV) {
[missing, subData] = popsiql.utils.subSelect(dataQuery, data);
return subData;
} else {
return data;
}
};
module.exports = function(React, app, ops, report) {
var _renderPre, _renderPreMissing, _renderPreMissingOps, _renderPreVMAccess, comp, missings, pushToReportStack, reportStack, useData, withData;
// Save info to print on error (eg. error during render), that will otherwise not be printed because of error
reportStack = [];
missings = {};
pushToReportStack = function(o) {
reportStack.unshift(o);
return reportStack.length = Math.min(100, reportStack.length); // don't let array too big
};
window.addEventListener('error', function() {
var i, len, r, results;
results = [];
for (i = 0, len = reportStack.length; i < len; i++) {
r = reportStack[i];
results.push(report({
...r,
ERROR: true
}));
}
return results;
});
// React hook with state and effect keeping you subscribed to the dataQuery
useData = function(dataQuery, name = void 0) {
var cacheCounter, initialData, setState, state;
if (!dataQuery.UI || !dataQuery.Data || !dataQuery.State) {
throw new Error(`your dataQuery is missing UI, Data or State:\n${sf2(dataQuery)}`);
}
// [uds, commitId] = app.getUDS()
initialData = subSelectIfDEV(dataQuery, app.getUDS());
[state, setState] = React.useState(initialData);
// hasInitialChange = false
// initialUnsub = app.sub dataQuery, (() -> hasInitialChange = true), name + '_INITIAL'
cacheCounter = 0;
React.useEffect(function() {
var data, handleChange, unsub;
// initialUnsub()
handleChange = function(data) {
cacheCounter++;
return setState(merge({cacheCounter}, subSelectIfDEV(dataQuery, data)));
};
// if hasInitialChange
// qq -> 'hasInitialChange'
// currentData = app.getUDS()
// setState subSelectIfDEV dataQuery, currentData
// Do shallow compare to see if data has changed since since initialData and trigger setState if it has
data = app.getUDS();
if (initialData.UI !== data.UI || initialData.Data !== data.Data || initialData.State !== data.State) {
cacheCounter++;
setState(merge({cacheCounter}, subSelectIfDEV(dataQuery, data)));
}
unsub = app.sub(dataQuery, handleChange, name);
return function() {
return unsub();
};
// https://github.com/facebook/react/issues/14476#issuecomment-471199055
// Note: we know dataQueries are small shallow objects anyway so JSON.stringify
// shouldn't put any significant burden. But test it some time to be sure :)
}, [JSON.stringify(dataQuery)]);
return state;
};
// HOC that wraps a component and subscribes it with useData
withData = function(name, f) {
var dataQuery;
if (type(name) !== 'String') {
throw new Error('withData requires name as first argument');
}
[dataQuery] = popsiql.utils.parseArguments(f.toString());
if (dataQuery.UI == null) {
dataQuery.UI = {};
}
if (dataQuery.Data == null) {
dataQuery.Data = {};
}
if (dataQuery.State == null) {
dataQuery.State = {};
}
// fMemoed = React.memo f, equals
return function() {
var dataToRender, renderRes, t0, time;
dataToRender = useData(dataQuery, name);
// dataToRender = subRes
// dataToRender = if isNil res then [uiQuery, dataQuery, stateQuery]
// else [res.UI, res.Data, res.State]
t0 = performance.now();
renderRes = f(dataToRender);
time = {
tot: performance.now() - t0
};
report({
name: name,
kind: 'withData',
dataToRender,
time,
ts: t0
});
return renderRes;
};
};
_renderPre = function(data) {
return React.createElement('pre', {
style: {
width: '100%'
}
}, sf2(data));
};
_renderPreMissing = function(name, missing, data) {
// Not removing this so developer needs to refresh when this happens = good engough
// if !missings[name]
// div = document.createElement 'div'
// missings[name] = div
// document.body.appendChild div
// missDiv = missings[name]
// missDiv.style.position = 'absolute'
// missDiv.style.top = '10px'
// missDiv.style.left = '10px'
// missDiv.style.color = 'blue'
// missDiv.style.zIndex = 9999999999999
// missDiv.style.border = '2px solid red'
// missDiv.style.padding = '20px'
// missDiv.style.background = 'white'
// missDiv.style.display = 'flex'
// missDiv.style.flexDirection = 'column'
// d1 = document.createElement 'div'
// d1.textContent = "'#{name}' missing data!"
// missDiv.appendChild d1
// fmap missing, (ar) ->
// path = join '.', ar
// dp = document.createElement 'div'
// dp.textContent = path
// missDiv.appendChild dp
// pre = document.createElement 'pre'
// pre.style.width = '100%'
// pre.textContent = sf2 data
// missDiv.appendChild pre
return React.createElement('div', {
style: {
color: 'blue',
position: 'absolute',
top: 10,
left: 10,
zIndex: 999999999,
border: '2px solid red',
padding: 20,
background: 'white'
}
}, React.createElement('div', {
style: {
color: 'red',
fontSize: 20
}
}, `'${name}' missing data!`), $(missing, mapI(function(ar, idx) {
path = join('.', ar);
return React.createElement('div', {
key: idx,
style: {
color: 'red',
fontSize: 12
}
}, path);
})), React.createElement('div', {}, 'Result from VM:'), _renderPre(data));
};
_renderPreMissingOps = function(name, missing) {
return React.createElement('div', {
style: {
color: 'blue',
position: 'absolute',
top: 10,
left: 10,
zIndex: 999999999,
border: '2px solid red',
padding: 20,
background: 'white'
}
}, React.createElement('div', {
style: {
color: 'red',
fontSize: 20
}
}, `'${name}' missing operations!`), $(missing, map(function(ar) {
path = join('.', ar);
return React.createElement('div', {
key: path,
style: {
color: 'red',
fontSize: 12
}
}, path);
})));
};
_renderPreVMAccess = function(path) {
return React.createElement('div', {
style: {
color: 'blue',
position: 'absolute',
top: 10,
left: 10,
zIndex: 999999999,
border: '2px solid red',
padding: 20,
background: 'white'
}
}, React.createElement('div', {
style: {
color: 'red',
fontSize: 20
}
}, "Missing VM-dependency"), React.createElement('div', {
style: {
color: 'red',
fontSize: 12
}
}, `vm.${path}`));
};
comp = function(name, vm, deps, renderF) {
var Data, Ops, State, UI, VM, dataQuery, lastCacheCounter, missingOps, prev, vmOps;
if (!deps.VM) {
throw new Error(`Missing VM deps in comp ${name}`);
}
({UI, Data, State, VM, Ops} = popsiql.utils.toDataQuery(deps));
dataQuery = {UI, Data, State};
if (DEV && Ops) {
[missingOps, vmOps] = popsiql.utils.subSelect(Ops, ops);
}
lastCacheCounter = -1;
prev = null;
return function(props) {
var VMandOps, dataForVM, dataForVMarr, dataToRender, dataToRenderOriginal, err, errorCount, getHandler, isMatch, missing, pathInVM, renderRes, res, rf0, t0, tempPrev, time, vm0, vmRes;
res = useData(dataQuery, name);
// TODO: GÖR PROXY FÖR VM SÅ MAN SER OM MAN ACCESSAR NÅGON MAN INTE FRÅGADE EFTER
time = {};
t0 = performance.now();
dataForVMarr = _(res.UI, res.Data, res.State);
dataForVM = {
UI: res.UI,
Data: res.Data,
State: res.State
};
vm0 = performance.now();
vmRes = vm(...dataForVMarr, prev);
time.vm = performance.now() - vm0;
tempPrev = {
UI: res.UI,
Data: res.Data,
State: res.State,
VM: vmRes
};
React.useEffect(function() {
report({
name,
dataForVM,
dataToRender,
time: {
...time,
tot: performance.now() - t0
},
ts: t0,
noCacheChange: isNil(res.cacheCounter) || lastCacheCounter === res.cacheCounter
});
lastCacheCounter = res.cacheCounter;
prev = tempPrev;
return function() {};
});
if (missingOps && !isEmpty(missingOps)) {
return _renderPreMissingOps(name, missingOps);
}
// TODO: turn this on
dataToRender = vmRes;
reportStack.push({name, dataForVM, dataToRender});
if (DEV) {
[missing, dataToRender] = popsiql.utils.subSelect(VM, vmRes);
if (!isEmpty(missing) && vmRes.loading !== true) {
return _renderPreMissing(name, missing, vmRes);
}
}
// else if missings[name]
// document.body.removeChild missings[name]
// delete missings[name]
if (Ops) {
if (DEV) {
dataToRender.Ops = vmOps;
} else {
dataToRender.Ops = ops;
}
}
errorCount = 0;
if (DEV) {
VMandOps = merge(VM, {
Ops: vmOps
});
pathInVM = function(path, o) {
if (isEmpty(path)) {
return true;
} else if (test(/@@/, path[0])) {
return true; // workaround for: vm.records.@@functional/placeholder
} else if (has(path[0], o)) {
return pathInVM(tail(path), o[path[0]]);
} else if (has(path[0] + '〳', o)) {
return pathInVM(tail(path), o[path[0] + '〳']);
} else {
return false;
}
};
dataToRenderOriginal = dataToRender;
getHandler = {
get: function(o, prop, path) {
if (!pathInVM(split('.', path), VMandOps)) {
throw new Error(`vm.${path} VM-dep`);
}
return o[prop];
}
};
dataToRender = RE.recursiveProxy(dataToRender, getHandler);
}
rf0 = performance.now();
try {
renderRes = renderF(dataToRender);
} catch (error) {
err = error;
[isMatch, path] = match(/vm\.(.*?) VM-dep/, err.message);
if (isMatch) {
return _renderPreVMAccess(path);
} else {
throw err;
}
}
time.rf = performance.now() - rf0;
return renderRes;
};
};
return {useData, withData, comp};
};
}).call(this);