preact
Version:
Fast 3kb React-compatible Virtual DOM library.
206 lines (173 loc) • 5.53 kB
JavaScript
/* istanbul ignore file */
import { options, Component, Fragment } from 'preact';
import { Renderer } from './renderer';
import { setupOptions } from './10/options';
import { createRenderer } from './10/renderer';
/**
* Wrap function with generic error logging
*
* @param {*} fn
* @returns
*/
function catchErrors(fn) {
return function(...args) {
try {
return fn(...args);
} catch (e) {
/* istanbul ignore next */
console.error('The react devtools encountered an error');
/* istanbul ignore next */
console.error(e); // eslint-disable-line no-console
}
};
}
/* istanbul ignore next */
let noop = () => undefined;
export function initDevTools() {
// This global variable is injected by the devtools
let preactHook = /** @type {*} */ (window).__PREACT_DEVTOOLS__;
// Check for Preact devtools first :)
if (preactHook) {
const renderer = createRenderer(preactHook);
setupOptions(/** @type {*} */ (options), renderer);
preactHook.attach(renderer);
return;
}
/** @type {import('../internal').DevtoolsWindow} */
let hook = (window).__REACT_DEVTOOLS_GLOBAL_HOOK__;
if (hook == null) return;
/** @type {(vnode: import('../internal').VNode) => void} */
let onCommitRoot = noop;
/** @type {(vnode: import('../internal').VNode) => void} */
let onCommitUnmount = noop;
// Initialize our custom renderer
let rid = Math.random()
.toString(16)
.slice(2);
let preactRenderer = new Renderer(hook, rid);
catchErrors(() => {
let isDev = false;
try {
isDev = process.env.NODE_ENV !== 'production';
} catch (e) {}
// Tell devtools which bundle type we run in
window.parent.postMessage(
{
source: 'react-devtools-detector',
reactBuildType: /* istanbul ignore next */ isDev
? 'development'
: 'production'
},
'*'
);
let renderer = {
bundleType: /* istanbul ignore next */ isDev ? 1 : 0,
version: '16.5.2',
rendererPackageName: 'preact',
// We don't need this, but the devtools `attachRenderer` function relies on
// it being there.
findHostInstanceByFiber(vnode) {
return vnode._dom;
},
// We don't need this, but the devtools `attachRenderer` function relies on
// it being there.
findFiberByHostInstance(instance) {
return preactRenderer.inst2vnode.get(instance) || null;
}
};
if (!hook._renderers) {
// eslint-disable-next-line no-console
console.info(
'Preact is not compatible with your version of react-devtools. We ' +
'will address this in future releases.'
);
return;
}
hook._renderers[rid] = renderer;
// We can't bring our own `attachRenderer` function therefore we simply
// prevent the devtools from overwriting our custom renderer by creating
// a noop setter.
Object.defineProperty(hook.helpers, rid, {
get: () => preactRenderer,
set: () => {
if (!preactRenderer.connected) {
helpers.markConnected();
}
}
});
let helpers = hook.helpers[rid];
// Tell the devtools that we are ready to start
hook.emit('renderer-attached', {
id: rid,
renderer,
helpers
});
onCommitRoot = catchErrors(root => {
// Empty root
if (root.type === Fragment && root._children.length == 0) return;
let roots = hook.getFiberRoots(rid);
root = helpers.handleCommitFiberRoot(root);
if (!roots.has(root)) roots.add(root);
});
onCommitUnmount = catchErrors(vnode => {
hook.onCommitFiberUnmount(rid, vnode);
});
})();
// Store (possible) previous hooks so that we don't overwrite them
let prevVNodeHook = options.vnode;
let prevCommitRoot = options._commit;
let prevBeforeUnmount = options.unmount;
let prevBeforeDiff = options._diff;
let prevAfterDiff = options.diffed;
options.vnode = vnode => {
// Tiny performance improvement by initializing fields as doubles
// from the start. `performance.now()` will always return a double.
// See https://github.com/facebook/react/issues/14365
// and https://slidr.io/bmeurer/javascript-engine-fundamentals-the-good-the-bad-and-the-ugly
vnode.startTime = NaN;
vnode.endTime = NaN;
vnode.startTime = 0;
vnode.endTime = -1;
if (prevVNodeHook) prevVNodeHook(vnode);
};
options._diff = vnode => {
vnode.startTime = now();
if (prevBeforeDiff != null) prevBeforeDiff(vnode);
};
options.diffed = vnode => {
vnode.endTime = now();
if (prevAfterDiff != null) prevAfterDiff(vnode);
};
options._commit = catchErrors((vnode, commitQueue) => {
// Call previously defined hook
if (prevCommitRoot != null) prevCommitRoot(vnode, commitQueue);
// These cases are already handled by `unmount`
if (vnode == null) return;
onCommitRoot(vnode);
});
options.unmount = catchErrors(vnode => {
// Call previously defined hook
if (prevBeforeUnmount != null) prevBeforeUnmount(vnode);
onCommitUnmount(vnode);
});
// Inject tracking into setState
const setState = Component.prototype.setState;
Component.prototype.setState = function(update, callback) {
// Duplicated in setState() but doesn't matter due to the guard.
let s =
(this._nextState !== this.state && this._nextState) ||
(this._nextState = Object.assign({}, this.state));
// Needed in order to check if state has changed after the tree has been committed:
this._prevState = Object.assign({}, s);
return setState.call(this, update, callback);
};
}
/**
* Get current timestamp in ms. Used for profiling.
* @returns {number}
*/
export let now = Date.now;
try {
/* istanbul ignore else */
now = performance.now.bind(performance);
} catch (e) {}