UNPKG

4.47 kBJavaScriptView Raw
1import assign from 'lodash/object/assign';
2import difference from 'lodash/array/difference';
3
4export default function createPrototypeProxy() {
5 let proxy = {};
6 let current = null;
7 let mountedInstances = [];
8
9 /**
10 * Creates a proxied toString() method pointing to the current version's toString().
11 */
12 function proxyToString(name) {
13 // Wrap to always call the current version
14 return function toString() {
15 if (typeof current[name] === 'function') {
16 return current[name].toString();
17 } else {
18 return '<method was deleted>';
19 }
20 };
21 }
22
23 /**
24 * Creates a proxied method that calls the current version, whenever available.
25 */
26 function proxyMethod(name) {
27 // Wrap to always call the current version
28 const proxiedMethod = function () {
29 if (typeof current[name] === 'function') {
30 return current[name].apply(this, arguments);
31 }
32 };
33
34 // Copy properties of the original function, if any
35 assign(proxiedMethod, current[name]);
36 proxiedMethod.toString = proxyToString(name);
37
38 return proxiedMethod;
39 }
40
41 /**
42 * Augments the original componentDidMount with instance tracking.
43 */
44 function proxiedComponentDidMount() {
45 mountedInstances.push(this);
46 if (typeof current.componentDidMount === 'function') {
47 return current.componentDidMount.apply(this, arguments);
48 }
49 }
50 proxiedComponentDidMount.toString = proxyToString('componentDidMount');
51
52 /**
53 * Augments the original componentWillUnmount with instance tracking.
54 */
55 function proxiedComponentWillUnmount() {
56 const index = mountedInstances.indexOf(this);
57 // Unless we're in a weird environment without componentDidMount
58 if (index !== -1) {
59 mountedInstances.splice(index, 1);
60 }
61 if (typeof current.componentWillUnmount === 'function') {
62 return current.componentWillUnmount.apply(this, arguments);
63 }
64 }
65 proxiedComponentWillUnmount.toString = proxyToString('componentWillUnmount');
66
67 /**
68 * Defines a property on the proxy.
69 */
70 function defineProxyProperty(name, descriptor) {
71 Object.defineProperty(proxy, name, descriptor);
72 }
73
74 /**
75 * Defines a property, attempting to keep the original descriptor configuration.
76 */
77 function defineProxyPropertyWithValue(name, value) {
78 const {
79 enumerable = false,
80 writable = true
81 } = Object.getOwnPropertyDescriptor(current, name) || {};
82
83 defineProxyProperty(name, {
84 configurable: true,
85 enumerable,
86 writable,
87 value
88 });
89 }
90
91 /**
92 * Creates an auto-bind map mimicking the original map, but directed at proxy.
93 */
94 function createAutoBindMap() {
95 if (!current.__reactAutoBindMap) {
96 return;
97 }
98
99 let __reactAutoBindMap = {};
100 for (let name in current.__reactAutoBindMap) {
101 if (typeof proxy[name] === 'function' && current.__reactAutoBindMap.hasOwnProperty(name)) {
102 __reactAutoBindMap[name] = proxy[name];
103 }
104 }
105
106 return __reactAutoBindMap;
107 }
108
109 /**
110 * Applies the updated prototype.
111 */
112 function update(next) {
113 // Save current source of truth
114 current = next;
115
116 // Find changed property names
117 const currentNames = Object.getOwnPropertyNames(current);
118 const previousName = Object.getOwnPropertyNames(proxy);
119 const removedNames = difference(previousName, currentNames);
120
121 // Remove properties and methods that are no longer there
122 removedNames.forEach(name => {
123 delete proxy[name];
124 });
125
126 // Copy every descriptor
127 currentNames.forEach(name => {
128 const descriptor = Object.getOwnPropertyDescriptor(current, name);
129 if (typeof descriptor.value === 'function') {
130 // Functions require additional wrapping so they can be bound later
131 defineProxyPropertyWithValue(name, proxyMethod(name));
132 } else {
133 // Other values can be copied directly
134 defineProxyProperty(name, descriptor);
135 }
136 });
137
138 // Track mounting and unmounting
139 defineProxyPropertyWithValue('componentDidMount', proxiedComponentDidMount);
140 defineProxyPropertyWithValue('componentWillUnmount', proxiedComponentWillUnmount);
141 defineProxyPropertyWithValue('__reactAutoBindMap', createAutoBindMap());
142
143 // Set up the prototype chain
144 proxy.__proto__ = next;
145
146 return mountedInstances;
147 }
148
149 /**
150 * Returns the up-to-date proxy prototype.
151 */
152 function get() {
153 return proxy;
154 }
155
156 return {
157 update,
158 get
159 };
160};
\No newline at end of file