UNPKG

3.85 kBJavaScriptView Raw
1import { options, Fragment } from 'preact';
2
3/**
4 * Get human readable name of the component/dom node
5 * @param {import('./internal').VNode} vnode
6 * @param {import('./internal').VNode} vnode
7 * @returns {string}
8 */
9export function getDisplayName(vnode) {
10 if (vnode.type === Fragment) {
11 return 'Fragment';
12 } else if (typeof vnode.type == 'function') {
13 return vnode.type.displayName || vnode.type.name;
14 } else if (typeof vnode.type == 'string') {
15 return vnode.type;
16 }
17
18 return '#text';
19}
20
21/**
22 * Used to keep track of the currently rendered `vnode` and print it
23 * in debug messages.
24 */
25let renderStack = [];
26
27/**
28 * Keep track of the current owners. An owner describes a component
29 * which was responsible to render a specific `vnode`. This exclude
30 * children that are passed via `props.children`, because they belong
31 * to the parent owner.
32 *
33 * ```jsx
34 * const Foo = props => <div>{props.children}</div> // div's owner is Foo
35 * const Bar = props => {
36 * return (
37 * <Foo><span /></Foo> // Foo's owner is Bar, span's owner is Bar
38 * )
39 * }
40 * ```
41 *
42 * Note: A `vnode` may be hoisted to the root scope due to compiler
43 * optimiztions. In these cases the `_owner` will be different.
44 */
45let ownerStack = [];
46
47/**
48 * Get the currently rendered `vnode`
49 * @returns {import('./internal').VNode | null}
50 */
51export function getCurrentVNode() {
52 return renderStack.length > 0 ? renderStack[renderStack.length - 1] : null;
53}
54
55/**
56 * If the user doesn't have `@babel/plugin-transform-react-jsx-source`
57 * somewhere in his tool chain we can't print the filename and source
58 * location of a component. In that case we just omit that, but we'll
59 * print a helpful message to the console, notifying the user of it.
60 */
61let hasBabelPlugin = false;
62
63/**
64 * Check if a `vnode` is a possible owner.
65 * @param {import('./internal').VNode} vnode
66 */
67function isPossibleOwner(vnode) {
68 return typeof vnode.type == 'function' && vnode.type != Fragment;
69}
70
71/**
72 * Return the component stack that was captured up to this point.
73 * @param {import('./internal').VNode} vnode
74 * @returns {string}
75 */
76export function getOwnerStack(vnode) {
77 const stack = [vnode];
78 let next = vnode;
79 while (next._owner != null) {
80 stack.push(next._owner);
81 next = next._owner;
82 }
83
84 return stack.reduce((acc, owner) => {
85 acc += ` in ${getDisplayName(owner)}`;
86
87 const source = owner.__source;
88 if (source) {
89 acc += ` (at ${source.fileName}:${source.lineNumber})`;
90 } else if (!hasBabelPlugin) {
91 hasBabelPlugin = true;
92 console.warn(
93 'Add @babel/plugin-transform-react-jsx-source to get a more detailed component stack. Note that you should not add it to production builds of your App for bundle size reasons.'
94 );
95 }
96
97 return (acc += '\n');
98 }, '');
99}
100
101/**
102 * Setup code to capture the component trace while rendering. Note that
103 * we cannot simply traverse `vnode._parent` upwards, because we have some
104 * debug messages for `this.setState` where the `vnode` is `undefined`.
105 */
106export function setupComponentStack() {
107 let oldDiff = options._diff;
108 let oldDiffed = options.diffed;
109 let oldRoot = options._root;
110 let oldVNode = options.vnode;
111 let oldRender = options._render;
112
113 options.diffed = vnode => {
114 if (isPossibleOwner(vnode)) {
115 ownerStack.pop();
116 }
117 renderStack.pop();
118 if (oldDiffed) oldDiffed(vnode);
119 };
120
121 options._diff = vnode => {
122 if (isPossibleOwner(vnode)) {
123 renderStack.push(vnode);
124 }
125 if (oldDiff) oldDiff(vnode);
126 };
127
128 options._root = (vnode, parent) => {
129 ownerStack = [];
130 if (oldRoot) oldRoot(vnode, parent);
131 };
132
133 options.vnode = vnode => {
134 vnode._owner =
135 ownerStack.length > 0 ? ownerStack[ownerStack.length - 1] : null;
136 if (oldVNode) oldVNode(vnode);
137 };
138
139 options._render = vnode => {
140 if (isPossibleOwner(vnode)) {
141 ownerStack.push(vnode);
142 }
143
144 if (oldRender) oldRender(vnode);
145 };
146}