Skip to content

Commit d6a6fb1

Browse files
authored
chore: add state logger to improve developer experience (#120)
1 parent eeedb36 commit d6a6fb1

File tree

4 files changed

+146
-1
lines changed

4 files changed

+146
-1
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"dependencies": {
2323
"@testing-library/dom": "^7.11.0",
2424
"codemirror": "5.54.0",
25+
"deep-diff": "^1.0.2",
2526
"dom-accessibility-api": "^0.4.4",
2627
"js-beautify": "^1.11.0",
2728
"lodash.debounce": "4.0.8",

src/hooks/usePlayground.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useReducer, useEffect } from 'react';
22
import parser from '../parser';
33
import { initialValues as defaultValues } from '../constants';
4+
import { withLogging } from '../lib/logger';
45

56
function reducer(state, action) {
67
switch (action.type) {
@@ -59,7 +60,11 @@ function usePlayground(props) {
5960
}
6061

6162
const result = parser.parse({ markup, query, cacheId: instanceId });
62-
const [state, dispatch] = useReducer(reducer, { result, markup, query });
63+
const [state, dispatch] = useReducer(withLogging(reducer), {
64+
result,
65+
markup,
66+
query,
67+
});
6368

6469
useEffect(() => {
6570
if (typeof onChange === 'function') {

src/lib/logger.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import differ from 'deep-diff';
2+
3+
const styles = {
4+
header: 'color: gray; font-weight: lighter;',
5+
type: 'color: black; font-weight: bold;',
6+
prevState: 'color: #9E9E9E; font-weight: bold;',
7+
action: 'color: #03A9F4; font-weight: bold;',
8+
nextState: 'color: #4CAF50; font-weight: bold;',
9+
error: 'color: #F20404; font-weight: bold;',
10+
black: 'color: #000000; font-weight: bold;',
11+
12+
diff: {
13+
E: {
14+
style: `color: #2196F3; font-weight: bold`,
15+
text: 'CHANGED:',
16+
},
17+
N: {
18+
style: `color: #4CAF50; font-weight: bold`,
19+
text: 'ADDED:',
20+
},
21+
D: {
22+
style: `color: #F44336; font-weight: bold`,
23+
text: 'DELETED:',
24+
},
25+
A: {
26+
style: `color: #2196F3; font-weight: bold`,
27+
text: 'ARRAY:',
28+
},
29+
},
30+
};
31+
32+
export const pad = (num, maxLength) => `${num}`.padStart(0, maxLength);
33+
34+
function formatTime(time) {
35+
return `${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(
36+
time.getSeconds(),
37+
2,
38+
)}.${pad(time.getMilliseconds(), 3)}`;
39+
}
40+
41+
// Use performance API if it's available in order to get better precision
42+
const timer = typeof performance?.now === 'function' ? performance : Date;
43+
44+
function renderDiff(diff) {
45+
const { kind, path, lhs, rhs, index, item } = diff;
46+
47+
switch (kind) {
48+
case 'E':
49+
return [path.join('.'), lhs, '→', rhs];
50+
case 'N':
51+
return [path.join('.'), rhs];
52+
case 'D':
53+
return [path.join('.')];
54+
case 'A':
55+
return [`${path.join('.')}[${index}]`, item];
56+
default:
57+
return [];
58+
}
59+
}
60+
61+
const isLoggingEnabled =
62+
process.env.NODE_ENV !== 'production' || !!localStorage.getItem('debug');
63+
64+
export function withLogging(reducerFn) {
65+
if (isLoggingEnabled) {
66+
return reducerFn;
67+
}
68+
69+
const supportsGroups = typeof console.groupCollapsed === 'function';
70+
71+
return (prevState, action) => {
72+
const started = timer.now();
73+
const startedTime = new Date();
74+
75+
const newState = reducerFn(prevState, action);
76+
77+
const took = timer.now() - started;
78+
const diff = differ(prevState, newState);
79+
80+
const header = [
81+
[
82+
`%caction`,
83+
`%c${action.type}`,
84+
`%c@ ${formatTime(startedTime)}`,
85+
`(in ${took.toFixed(2)} ms)`,
86+
].join(' '),
87+
styles.header,
88+
styles.type,
89+
styles.header,
90+
];
91+
92+
if (supportsGroups) {
93+
console.group(...header);
94+
} else {
95+
console.log(...header);
96+
}
97+
98+
console.log('%c prev state %O', styles.prevState, prevState);
99+
console.log('%c action %O', styles.action, action);
100+
console.log('%c new state %O', styles.nextState, newState);
101+
102+
if (!diff) {
103+
console.log(
104+
'%c diff %cno state change!',
105+
styles.prevState,
106+
styles.error,
107+
);
108+
} else {
109+
if (supportsGroups) {
110+
console.groupCollapsed(' diff');
111+
} else {
112+
console.log('diff');
113+
}
114+
115+
diff.forEach((elem) => {
116+
console.log(
117+
`%c ${styles.diff[elem.kind].text}`,
118+
styles.diff[elem.kind].style,
119+
...renderDiff(elem),
120+
);
121+
});
122+
123+
if (supportsGroups) {
124+
console.groupEnd();
125+
}
126+
}
127+
128+
if (supportsGroups) {
129+
console.groupEnd();
130+
}
131+
132+
return newState;
133+
};
134+
}

0 commit comments

Comments
 (0)