UNPKG

orionsoft-react-scripts

Version:

Orionsoft Configuration and scripts for Create React App.

191 lines (162 loc) 5.25 kB
import assign from 'lodash/assign'; import difference from 'lodash/difference'; export default function createPrototypeProxy() { let proxy = {}; let current = null; let mountedInstances = []; /** * Creates a proxied toString() method pointing to the current version's toString(). */ function proxyToString(name) { // Wrap to always call the current version return function toString() { if (typeof current[name] === 'function') { return current[name].toString(); } else { return '<method was deleted>'; } }; } /** * Creates a proxied method that calls the current version, whenever available. */ function proxyMethod(name) { // Wrap to always call the current version const proxiedMethod = function () { if (typeof current[name] === 'function') { return current[name].apply(this, arguments); } }; // Copy properties of the original function, if any assign(proxiedMethod, current[name]); proxiedMethod.toString = proxyToString(name); try { Object.defineProperty(proxiedMethod, 'name', { value: name }); } catch (err) { } return proxiedMethod; } /** * Augments the original componentDidMount with instance tracking. */ function proxiedComponentDidMount() { mountedInstances.push(this); if (typeof current.componentDidMount === 'function') { return current.componentDidMount.apply(this, arguments); } } proxiedComponentDidMount.toString = proxyToString('componentDidMount'); /** * Augments the original componentWillUnmount with instance tracking. */ function proxiedComponentWillUnmount() { const index = mountedInstances.indexOf(this); // Unless we're in a weird environment without componentDidMount if (index !== -1) { mountedInstances.splice(index, 1); } if (typeof current.componentWillUnmount === 'function') { return current.componentWillUnmount.apply(this, arguments); } } proxiedComponentWillUnmount.toString = proxyToString('componentWillUnmount'); /** * Defines a property on the proxy. */ function defineProxyProperty(name, descriptor) { Object.defineProperty(proxy, name, descriptor); } /** * Defines a property, attempting to keep the original descriptor configuration. */ function defineProxyPropertyWithValue(name, value) { const { enumerable = false, writable = true } = Object.getOwnPropertyDescriptor(current, name) || {}; defineProxyProperty(name, { configurable: true, enumerable, writable, value }); } /** * Creates an auto-bind map mimicking the original map, but directed at proxy. */ function createAutoBindMap() { if (!current.__reactAutoBindMap) { return; } let __reactAutoBindMap = {}; for (let name in current.__reactAutoBindMap) { if (typeof proxy[name] === 'function' && current.__reactAutoBindMap.hasOwnProperty(name)) { __reactAutoBindMap[name] = proxy[name]; } } return __reactAutoBindMap; } /** * Creates an auto-bind map mimicking the original map, but directed at proxy. */ function createAutoBindPairs() { let __reactAutoBindPairs = []; for (let i = 0; i < current.__reactAutoBindPairs.length; i += 2) { const name = current.__reactAutoBindPairs[i]; const method = proxy[name]; if (typeof method === 'function') { __reactAutoBindPairs.push(name, method); } } return __reactAutoBindPairs; } /** * Applies the updated prototype. */ function update(next) { // Save current source of truth current = next; // Find changed property names const currentNames = Object.getOwnPropertyNames(current); const previousName = Object.getOwnPropertyNames(proxy); const removedNames = difference(previousName, currentNames); // Remove properties and methods that are no longer there removedNames.forEach(name => { delete proxy[name]; }); // Copy every descriptor currentNames.forEach(name => { const descriptor = Object.getOwnPropertyDescriptor(current, name); if (typeof descriptor.value === 'function') { // Functions require additional wrapping so they can be bound later defineProxyPropertyWithValue(name, proxyMethod(name)); } else { // Other values can be copied directly defineProxyProperty(name, descriptor); } }); // Track mounting and unmounting defineProxyPropertyWithValue('componentDidMount', proxiedComponentDidMount); defineProxyPropertyWithValue('componentWillUnmount', proxiedComponentWillUnmount); if (current.hasOwnProperty('__reactAutoBindMap')) { defineProxyPropertyWithValue('__reactAutoBindMap', createAutoBindMap()); } if (current.hasOwnProperty('__reactAutoBindPairs')) { defineProxyPropertyWithValue('__reactAutoBindPairs', createAutoBindPairs()); } // Set up the prototype chain proxy.__proto__ = next; return mountedInstances; } /** * Returns the up-to-date proxy prototype. */ function get() { return proxy; } return { update, get }; };