UNPKG

5.68 kBJavaScriptView Raw
1import WeakMap from 'core-js/library/es6/weak-map';
2import createPrototypeProxy from './createPrototypeProxy';
3import bindAutoBindMethods from './bindAutoBindMethods';
4import deleteUnknownAutoBindMethods from './deleteUnknownAutoBindMethods';
5import supportsProtoAssignment from './supportsProtoAssignment';
6
7const RESERVED_STATICS = [
8 'length',
9 'name',
10 'arguments',
11 'caller',
12 'prototype',
13 'toString'
14];
15
16function isEqualDescriptor(a, b) {
17 if (!a && !b) {
18 return true;
19 }
20 if (!a || !b) {
21 return false;
22 }
23 for (let key in a) {
24 if (a[key] !== b[key]) {
25 return false;
26 }
27 }
28 return true;
29}
30
31let allProxies = new WeakMap();
32
33function proxyClass(InitialComponent) {
34 // Prevent double wrapping.
35 // Given a proxy class, return the existing proxy managing it.
36 if (allProxies.has(InitialComponent)) {
37 return allProxies.get(InitialComponent);
38 }
39
40 let CurrentComponent;
41 let ProxyComponent;
42
43 let staticDescriptors = {};
44 function wasStaticModifiedByUser(key) {
45 // Compare the descriptor with the one we previously set ourselves.
46 const currentDescriptor = Object.getOwnPropertyDescriptor(ProxyComponent, key);
47 return !isEqualDescriptor(staticDescriptors[key], currentDescriptor);
48 }
49
50 function instantiate(factory, context, params) {
51 const component = factory();
52
53 try {
54 return component.apply(context, params);
55 } catch (err) {
56 // Native ES6 class instantiation
57 const instance = new component(...params);
58
59 Object.keys(instance).forEach(key => {
60 if (RESERVED_STATICS.indexOf(key) > -1) {
61 return;
62 }
63 context[key] = instance[key];
64 })
65 }
66 }
67
68 try {
69 // Create a proxy constructor with matching name
70 ProxyComponent = new Function('factory', 'instantiate',
71 `return function ${InitialComponent.name || 'ProxyComponent'}() {
72 return instantiate(factory, this, arguments);
73 }`
74 )(() => CurrentComponent, instantiate);
75 } catch (err) {
76 // Some environments may forbid dynamic evaluation
77 ProxyComponent = function () {
78 return instantiate(() => CurrentComponent, this, arguments);
79 };
80 }
81
82 // Proxy toString() to the current constructor
83 ProxyComponent.toString = function toString() {
84 return CurrentComponent.toString();
85 };
86
87 let prototypeProxy;
88 if (InitialComponent.prototype && InitialComponent.prototype.isReactComponent) {
89 // Point proxy constructor to the proxy prototype
90 prototypeProxy = createPrototypeProxy();
91 ProxyComponent.prototype = prototypeProxy.get();
92 }
93
94 function update(NextComponent) {
95 if (typeof NextComponent !== 'function') {
96 throw new Error('Expected a constructor.');
97 }
98
99 // Prevent proxy cycles
100 if (allProxies.has(NextComponent)) {
101 return update(allProxies.get(NextComponent).__getCurrent());
102 }
103
104 // Save the next constructor so we call it
105 CurrentComponent = NextComponent;
106
107 // Try to infer displayName
108 ProxyComponent.displayName = NextComponent.displayName || NextComponent.name;
109
110 // Set up the same prototype for inherited statics
111 ProxyComponent.__proto__ = NextComponent.__proto__;
112
113 // Copy static methods and properties
114 Object.getOwnPropertyNames(NextComponent).forEach(key => {
115 if (RESERVED_STATICS.indexOf(key) > -1) {
116 return;
117 }
118
119 const staticDescriptor = {
120 ...Object.getOwnPropertyDescriptor(NextComponent, key),
121 configurable: true
122 };
123
124 // Copy static unless user has redefined it at runtime
125 if (!wasStaticModifiedByUser(key)) {
126 Object.defineProperty(ProxyComponent, key, staticDescriptor);
127 staticDescriptors[key] = staticDescriptor;
128 }
129 });
130
131 // Remove old static methods and properties
132 Object.getOwnPropertyNames(ProxyComponent).forEach(key => {
133 if (RESERVED_STATICS.indexOf(key) > -1) {
134 return;
135 }
136
137 // Skip statics that exist on the next class
138 if (NextComponent.hasOwnProperty(key)) {
139 return;
140 }
141
142 // Skip non-configurable statics
143 const descriptor = Object.getOwnPropertyDescriptor(ProxyComponent, key);
144 if (descriptor && !descriptor.configurable) {
145 return;
146 }
147
148 // Delete static unless user has redefined it at runtime
149 if (!wasStaticModifiedByUser(key)) {
150 delete ProxyComponent[key];
151 delete staticDescriptors[key];
152 }
153 });
154
155 if (prototypeProxy) {
156 // Update the prototype proxy with new methods
157 const mountedInstances = prototypeProxy.update(NextComponent.prototype);
158
159 // Set up the constructor property so accessing the statics work
160 ProxyComponent.prototype.constructor = ProxyComponent;
161
162 // We might have added new methods that need to be auto-bound
163 mountedInstances.forEach(bindAutoBindMethods);
164 mountedInstances.forEach(deleteUnknownAutoBindMethods);
165 }
166 };
167
168 function get() {
169 return ProxyComponent;
170 }
171
172 function getCurrent() {
173 return CurrentComponent;
174 }
175
176 update(InitialComponent);
177
178 const proxy = { get, update };
179 allProxies.set(ProxyComponent, proxy);
180
181 Object.defineProperty(proxy, '__getCurrent', {
182 configurable: false,
183 writable: false,
184 enumerable: false,
185 value: getCurrent
186 });
187
188 return proxy;
189}
190
191function createFallback(Component) {
192 let CurrentComponent = Component;
193
194 return {
195 get() {
196 return CurrentComponent;
197 },
198 update(NextComponent) {
199 CurrentComponent = NextComponent;
200 }
201 };
202}
203
204export default function createClassProxy(Component) {
205 return Component.__proto__ && supportsProtoAssignment() ?
206 proxyClass(Component) :
207 createFallback(Component);
208}