RubyLouvre/anu

View on GitHub
lib/devtools.js

Summary

Maintainability
C
1 day
Test Coverage
(function(global, factory) {
    typeof exports === "object" && typeof module !== "undefined"
        ? (module.exports = factory())
        : typeof define === "function" && define.amd
            ? define(factory)
            : (global.DevTools = factory());
})(this, function() {
    var options = React.options || {};
    var roots = {};
    options.roots = roots;
    function findVNodeFromDOM(vnode, dom) {
        if (!vnode) {
            for (var i in roots) {
                var root = roots[i];
                var result = findVNodeFromDOM(root, dom);
                if (result) {
                    return result;
                }
            }
        } else {
            if (vnode.node === dom) {
                //如果是原子虚拟DOM,直接比较 _hostNode === dom
                return vnode;
            }

            var children = vnode._renderedChildren;

            if (children) {
                for (var _i = 0; _i < children.length; _i++) {
                    var child = children[_i];

                    if (child) {
                        var _result = findVNodeFromDOM(child, dom);
                        if (_result) {
                            return _result;
                        }
                    }
                }
            }
        }
    }

    function normalizeChildren(vnode, dom) {
        var ret = [];
        for (var a = vnode.child; a; a = a.sibling) {
            ret.push(updateReactComponent(a, dom));
        }
        return ret;
    }

    //===================================      用于根据一个虚拟DOM找到它的组件实例
    var instanceMap = new Map();

    function getKeyForVNode(vnode) {
        return vnode.stateNode 
    }

    function getInstanceFromVNode(vnode) {
        var key = getKeyForVNode(vnode);

        return instanceMap.get(key);
    }

    /**
 * Create a bridge for exposing Inferno's component tree to React DevTools.
 *
 * It creates implementations of the interfaces that ReactDOM passes to
 * devtools to enable it to query the component tree and hook into component
 * updates.
 *
 * See https://github.com/facebook/react/blob/59ff7749eda0cd858d5ee568315bcba1be75a1ca/src/renderers/dom/ReactDOM.js
 * for how ReactDOM exports its internals for use by the devtools and
 * the `attachRenderer()` function in
 * https://github.com/facebook/react-devtools/blob/e31ec5825342eda570acfc9bcb43a44258fceb28/backend/attachRenderer.js
 * for how the devtools consumes the resulting objects.
 */
    function createDevToolsBridge() {
        console.log("createDevToolsBridge.....");
        var ComponentTree = {
            getNodeFromInstance: function getNodeFromInstance(instance) {
                //createReactDOMComponent生成的实例有vnode对象
                return instance.node;
            },
            getClosestInstanceFromNode: function getClosestInstanceFromNode(dom) {
                var vnode = findVNodeFromDOM(null, dom);
                //转换为ReactCompositeComponent或ReactDOMComponent
                return vnode ? updateReactComponent(vnode, null) : null;
            }
        };
        
        // Map of root ID (the ID is unimportant) to component instance.
        //会根据vnode创建实例,并保存在instanceMap与roots中
        if(document.readyState === "complete"){
            findRoots(document.body, roots);
        }else{
            var id = setInterval(function(){
                if(document.readyState === "complete"){
                    clearInterval(id);
                    findRoots(document.body, roots);
                }
            }, 100);
        }
        

        var Mount = {
            _instancesByReactRootID: roots,
            // tslint:disable-next-line:no-empty
            _renderNewRootComponent: function _renderNewRootComponent() {}
        };

        var Reconciler = {
            // tslint:disable-next-line:no-empty
            mountComponent: function mountComponent() {},

            // tslint:disable-next-line:no-empty
            performUpdateIfNecessary: function performUpdateIfNecessary() {},

            // tslint:disable-next-line:no-empty
            receiveComponent: function receiveComponent() {},

            // tslint:disable-next-line:no-empty
            unmountComponent: function unmountComponent() {}
        };

        //============

        var queuedMountComponents = new Map();
        var queuedReceiveComponents = new Map();
        var queuedUnmountComponents = new Map();

        var queueUpdate = function queueUpdate(updater, map, component) {
            if (!map.has(component)) {
                map.set(component, true);
                requestAnimationFrame(function() {
                    updater(component);
                    map.delete(component);
                });
            }
        };

        var queueMountComponent = function queueMountComponent(component) {
            return queueUpdate(
                Reconciler.mountComponent,
                queuedMountComponents,
                component
            );
        };
        var queueReceiveComponent = function queueReceiveComponent(component) {
            return queueUpdate(
                Reconciler.receiveComponent,
                queuedReceiveComponents,
                component
            );
        };
        var queueUnmountComponent = function queueUnmountComponent(component) {
            return queueUpdate(
                Reconciler.unmountComponent,
                queuedUnmountComponents,
                component
            );
        };

        // 创建 componentAdded, componentUpdated,componentRemoved三个重要钩子
        var componentAdded = function componentAdded(vnode) {
            var instance = updateReactComponent(vnode);
            //将_currentElement代替为ReactCompositeComponent实例
            if (isRootVNode(vnode)) {
                instance._rootID = nextRootKey(roots);
                roots[instance._rootID] = instance;
                Mount._renderNewRootComponent(instance);
            }
            //遍历非实组件的孩子
            visitNonCompositeChildren(instance, function(childInst) {
                if (childInst) {
                    childInst._inDevTools = true;
                    queueMountComponent(childInst);
                }
            });
            queueMountComponent(instance);
        };

        var componentUpdated = function componentUpdated(vnode) {
            var prevRenderedChildren = [];

            //通过anujs instance得到 ReactCompositeComponent实例
            visitNonCompositeChildren(instanceMap.get(vnode), function(
                childInst
            ) {
                prevRenderedChildren.push(childInst);
            });

            var instance = updateReactComponent(vnode);
            queueReceiveComponent(instance);
            visitNonCompositeChildren(instance, function(childInst) {
                if (!childInst._inDevTools) {
                    // New DOM child component
                    childInst._inDevTools = true;
                    queueMountComponent(childInst);
                } else {
                    // Updated DOM child component
                    queueReceiveComponent(childInst);
                }
            });

            prevRenderedChildren.forEach(function(childInst) {
                if (!document.body.contains(childInst.node)) {
                    instanceMap.delete(childInst.node);
                    queueUnmountComponent(childInst);
                }
            });
        };

        var componentRemoved = function componentRemoved(vnode) {
            var instance = updateReactComponent(vnode);

            visitNonCompositeChildren(instance, function(childInst) {
                instanceMap.delete(childInst.node);
                queueUnmountComponent(childInst);
            });
            queueUnmountComponent(instance);
            instanceMap.delete(vnode);
            if (instance._rootID) {
                delete roots[instance._rootID];
            }
        };

        return {
            componentAdded: componentAdded,
            componentUpdated: componentUpdated,
            componentRemoved: componentRemoved,

            ComponentTree: ComponentTree,
            Mount: Mount,
            Reconciler: Reconciler
        };
    }
    //是否为根节点的虚拟DOM
    function isRootVNode(vnode) {
        if(vnode.isTop){
            return vnode;
        }
        // TODO 判断是否为根节点的方法和 preact 流程不一样,这里有可能有问题
        for (var i in roots) {
            if (roots[i] === vnode) {
                return true;
            }
        }
        return false;
    }

    /**
 *roots里面都是经过
 *
 * @param {DOMElement} node
 * @param {[key: string] => ReactDOMComponent|ReactCompositeComponent}
 */
    function findRoots(node, roots) {
        Array.from(node.childNodes || []).forEach(function(child) {
            var vnode = child.__component || child._reactInternalFiber;
            if (vnode) {
                roots[nextRootKey(roots)] = updateReactComponent(vnode);
            } else {
                findRoots(child, roots);
            }
        });
    }

    function updateReactComponent(vnode, parentDom) {
        if (!vnode) {
            return null;
        }
        var newInstance;

        if (vnode.tag < 3) {
            newInstance = createReactCompositeComponent(vnode);
        } else {
            newInstance = createReactDOMComponent(vnode, parentDom);
        }

        var oldInstance = getInstanceFromVNode(vnode);

        if (oldInstance) {
            Object.assign(oldInstance, newInstance);
            return oldInstance;
        }
        var key = getKeyForVNode(vnode);
        if (key) {
            instanceMap.set(key, newInstance);
        }
        //将它存入instanceMap中
        return newInstance;
    }

    function createReactDOMComponent(vnode, parentDom) {
        var type = vnode.type;

        if (
            type === "#comment" ||
      vnode === null ||
      vnode == undefined ||
      vnode === true ||
      vnode === false
        ) {
            return null;
        }
        var props = vnode.props;
        var dom = vnode.stateNode;

        var isText = typeof vnode !== "object" || type === "#text";

        return {
            _currentElement: isText
                ? vnode.text + ""
                : {
                    type: type,
                    props: props
                },
            _inDevTools: false,
            _renderedChildren: !isText && normalizeChildren(vnode, dom),
            _stringText: isText ? vnode.text + "" : null,
            node: dom || parentDom
        };
    }

    /**
 * Return a ReactCompositeComponent-compatible object for a given Inferno
 * component instance.
 *
 * This implements the subset of the ReactCompositeComponent interface that
 * the DevTools requires in order to walk the component tree and inspect the
 * component's properties.
 *
 * See https://github.com/facebook/react-devtools/blob/e31ec5825342eda570acfc9bcb43a44258fceb28/backend/getData.js
 */
    function createReactCompositeComponent(vnode) {
        var type = vnode.type;
        var typeName = type.displayName || type.name;
        var instance = vnode.stateNode 
        var dom = React.findDOMNode(instance);
        return {
            getName: function getName() {
                return typeName;
            },

            type: type,
            _instance: instance,
            state: instance.state,
            node: dom,
            props: instance.props,
            _currentElement: {
                type: type,
                key: normalizeKey(vnode.key),
                props: vnode.props,
                ref: null
            },
            // _renderedComponent: updateReactComponent(instance.updater.rendered, dom),
            _renderedComponent: updateReactComponent(vnode.child, dom),
            forceUpdate: instance.forceUpdate && instance.forceUpdate.bind(instance),
            setState: instance.setState && instance.setState.bind(instance)
        };
    }

    function nextRootKey(roots) {
        return "." + Object.keys(roots).length;
    }

    function normalizeKey(key) {
        if (key && key[0] === ".") {
            return null;
        }
    }

    /**
 * Visit all child instances of a ReactCompositeComponent-like object that are
 * not composite components (ie. they represent DOM elements or text)
 */
    function visitNonCompositeChildren(instance, callback) {
        if(!instance) {
            return;
        }
        if (instance._renderedComponent) {
            callback(instance._renderedComponent);
            visitNonCompositeChildren(instance._renderedComponent, callback);
        } else if (instance._renderedChildren) {
            instance._renderedChildren.forEach(function(child) {
                if (child) {
                    callback(child);
                    visitNonCompositeChildren(child, callback);
                }
            });
        }
    }

    function initDevTools() {
    /* tslint:disable */
        console.log("初始chrome react 调试工具");
        var bridge = createDevToolsBridge();
        var Methods = {
            afterMount: "componentAdded",
            afterUpdate: "componentUpdated",
            beforeUnmount: "componentRemoved"
        };
        for (var name in Methods) {
            (function(key, alias) {
                var oldMethod = options[key];
                //重写anujs原有的方法
                options[key] = function(instance) {
                    var updater = instance.updater;//1.2.8
                    var vnode = updater._reactInternalFiber;
                    bridge[alias](vnode);
                    if (oldMethod) {
                        oldMethod(vnode);
                    }
                };
            })(name, Methods[name]);
        }

        window["__REACT_DEVTOOLS_GLOBAL_HOOK__"].inject(bridge);
    }


    initDevTools();
});