diffhtml-middleware-logger
Version:
Logs out render transactions to the console
297 lines (240 loc) • 6.7 kB
JavaScript
const { assign } = Object;
const humanize = ms => {
if (ms >= 1000) {
return `${ms / 1000}s`;
}
return `${ms}ms`;
};
const stringToRGB = string => {
const code = string.split('').reduce((
(hash, _, i) => `${string.charCodeAt(i) + (hash << 5) - hash}`
), 0);
const hex = (code & 0x00FFFFFF).toString(16);
return `#${'00000'.slice(0, 6 - hex.length) + hex}`;
};
const cloneTree = tree => assign({}, tree, {
attributes: assign({}, tree.attributes),
childNodes: tree.childNodes.map(vTree => cloneTree(vTree))
});
let Internals = null;
/**
* @returns {any}
*/
const format = (patches) => {
if (!Internals) {
return {};
}
const { decodeEntities, PATCH_TYPE } = Internals;
const newPatches = {
'Node (Insert)': [],
'Node (Remove)': [],
'Node (Replace)': [],
'Node value (Set)': [],
'Attribute (Set)': [],
'Attribute (Remove)': [],
};
const { length } = patches;
let i = 0;
let uniquePatches = 0;
while (true) {
const patchType = patches[i];
if (i === length) {
break;
}
uniquePatches += 1;
switch(patchType) {
case PATCH_TYPE.SET_ATTRIBUTE: {
const vTree = patches[i + 1];
const name = patches[i + 2];
const value = decodeEntities(patches[i + 3]);
newPatches['Attribute (Set)'].push({
'Node': Internals.NodeCache.get(vTree),
'Name': name,
'Value': value,
});
i += 4;
break;
}
case PATCH_TYPE.REMOVE_ATTRIBUTE: {
const vTree = patches[i + 1];
const name = patches[i + 2];
newPatches['Attribute (Remove)'].push({
'Node': Internals.NodeCache.get(vTree),
'Name': name,
});
i += 3;
break;
}
case PATCH_TYPE.NODE_VALUE: {
const vTree = patches[i + 1];
const nodeValue = patches[i + 2];
const oldValue = patches[i + 3];
newPatches['Node value (Set)'].push({
'Node': Internals.NodeCache.get(vTree),
'Value': nodeValue,
'Old value': oldValue,
});
i += 4;
break;
}
case PATCH_TYPE.INSERT_BEFORE: {
const vTree = patches[i + 1];
const newTree = patches[i + 2];
const refTree = patches[i + 3];
newPatches['Node (Insert)'].push({
'Container': Internals.NodeCache.get(vTree),
'Node': Internals.NodeCache.get(newTree),
'Insert Before': Internals.NodeCache.get(refTree),
});
i += 4;
break;
}
case PATCH_TYPE.REPLACE_CHILD: {
const newTree = patches[i + 1];
const oldTree = patches[i + 2];
newPatches['Node (Replace)'].push({
'New node': Internals.NodeCache.get(newTree),
'New tree': newTree,
'Old tree': oldTree,
});
i += 3;
break;
}
case PATCH_TYPE.REMOVE_CHILD: {
const vTree = patches[i + 1];
newPatches['Node (Remove)'].push({
'Tree': vTree,
});
i += 2;
break;
}
}
}
return [newPatches, uniquePatches];
};
/**
* Re-usable log function. Used for during render and after render.
*
* @param {string} message - Prefix for the console output.
* @param {string} method - Which console method to call
* @param {string} color - Which color styles to use
* @param {Date} _date - A date object to render
* @param {import('diffhtml/lib/transaction').default} transaction - Contains: domNode, oldTree, newTree, patches, promises
* @param {any=} completed - Middleware options
*/
const log = (message, method, color, _date, transaction, completed) => {
const {
mount,
newTree,
patches,
promises,
config,
input,
state,
} = transaction;
// Shadow DOM rendering...
if (mount.host) {
const { name: ctorName } = mount.host.constructor;
console[method](
`%c${ctorName} render ${completed ? 'ended' : 'started' }`,
`color: ${stringToRGB(ctorName)}${completed ? '; opacity: 0.5' : ''}`,
completed ? completed : ''
);
}
else {
console[method](message, color, completed ? completed : '');
}
if (!completed && mount) {
console.log('%cmount %O', 'font-weight: bold; color: #868686', mount);
}
if (!completed && input) {
console.log('%cmarkup %O', 'font-weight: bold; color: #868686', input);
}
if (!completed && config) {
console.log(
'%coptions',
'font-weight: bold; color: #868686',
config
);
}
if (state) {
console.log(
'%cstate %O',
'font-weight: bold; color: #868686',
state,
);
}
if (transaction._cloneOldTree || newTree) {
console.log(
'%coldTree %O newTree %O',
'font-weight: bold; color: #868686',
transaction._cloneOldTree,
cloneTree(newTree)
);
}
if (patches && patches.length) {
const [ formattedPatches, uniqueCount ] = format(patches);
console.log(
'%cpatches (%d) %O', 'font-weight: bold; color: #868686',
uniqueCount,
formattedPatches,
);
}
// Don't clutter the output if there aren't any promises.
if (promises && promises.length) {
console.log(
'%ctransition promises %O', 'font-weight: bold; color: #868686', promises
);
}
};
const logger = ({ minimize = false }) => assign(function loggerTask(transaction) {
const start = new Date();
const { mount } = transaction;
const name = mount.name || mount.displayName || mount.nodeName;
log(
`%c<${transaction.mount.nodeName}> render started `,
'groupCollapsed',
'color: #FF0066',
start,
transaction
);
const { oldTree } = transaction;
if (transaction.state.isRendering) {
console.groupEnd();
log(
`%c<${name}> render aborted `,
minimize ? 'groupCollapsed' : 'group',
'color: #FF78B2',
new Date(),
transaction
);
console.groupEnd();
return;
}
transaction._cloneOldTree = oldTree && cloneTree(oldTree);
/**
* Transaction is effectively done, but we need to listen for it to actually
* be finished.
*/
return () => {
// Transaction has fully completed.
transaction.onceEnded(() => {
console.groupEnd();
log(
`%c<${name}> render ended `,
minimize ? 'groupCollapsed' : 'group',
'color: #FF78B2',
new Date(),
transaction,
' >> Completed in: ' + humanize(Date.now() - Number(start))
);
console.groupEnd();
});
};
}, {
displayName: 'loggerTask',
subscribe(_Internals) {
Internals = _Internals;
},
});
export default (opts = {}) => logger(opts);