Skip to content

Commit

Permalink
Merge pull request #56 from gaearon/next
Browse files Browse the repository at this point in the history
Rewrite for React Hot Loader 3
  • Loading branch information
wkwiatek authored Mar 6, 2017
2 parents 1f461c0 + c019762 commit c679ad8
Show file tree
Hide file tree
Showing 15 changed files with 1,866 additions and 1,500 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ deepForceUpdate(rootInstance);
* Replaces static getters and setters
* Replaces unbound static methods
* Replaces static properties unless they were overwritten by code
* Sets up `this.constructor` to match the most recent class

## Known Limitations

* Does not replace ES7 instance properties
* Does not replace bound static methods
* Replacing a method using [`autobind-decorator`](https://github.com/andreypopp/autobind-decorator) causes its identity to change

## Contributing
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-proxy",
"version": "2.0.8",
"version": "3.0.0-alpha.1",
"description": "Proxies React components without unmounting or losing their state.",
"main": "modules/index.js",
"scripts": {
Expand Down
103 changes: 74 additions & 29 deletions src/createClassProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import supportsProtoAssignment from './supportsProtoAssignment';

const RESERVED_STATICS = [
'length',
'displayName',
'name',
'arguments',
'caller',
Expand All @@ -28,6 +29,13 @@ function isEqualDescriptor(a, b) {
return true;
}

function getDisplayName(Component) {
const displayName = Component.displayName || Component.name;
return (displayName && displayName !== 'ReactComponent') ?
displayName :
'Unknown';
}

// This was originally a WeakMap but we had issues with React Native:
// https://github.com/gaearon/react-proxy/issues/50#issuecomment-192928066
let allProxies = [];
Expand All @@ -49,23 +57,15 @@ function proxyClass(InitialComponent) {

let CurrentComponent;
let ProxyComponent;

let staticDescriptors = {};
function wasStaticModifiedByUser(key) {
// Compare the descriptor with the one we previously set ourselves.
const currentDescriptor = Object.getOwnPropertyDescriptor(ProxyComponent, key);
return !isEqualDescriptor(staticDescriptors[key], currentDescriptor);
}
let savedDescriptors = {};

function instantiate(factory, context, params) {
const component = factory();

try {
return component.apply(context, params);
} catch (err) {
// Native ES6 class instantiation
const instance = new component(...params);

Object.keys(instance).forEach(key => {
if (RESERVED_STATICS.indexOf(key) > -1) {
return;
Expand All @@ -75,10 +75,11 @@ function proxyClass(InitialComponent) {
}
}

let displayName = getDisplayName(InitialComponent);
try {
// Create a proxy constructor with matching name
ProxyComponent = new Function('factory', 'instantiate',
`return function ${InitialComponent.name || 'ProxyComponent'}() {
`return function ${displayName}() {
return instantiate(factory, this, arguments);
}`
)(() => CurrentComponent, instantiate);
Expand All @@ -88,6 +89,11 @@ function proxyClass(InitialComponent) {
return instantiate(() => CurrentComponent, this, arguments);
};
}
try {
Object.defineProperty(ProxyComponent, 'name', {
value: displayName
});
} catch (err) { }

// Proxy toString() to the current constructor
ProxyComponent.toString = function toString() {
Expand All @@ -105,6 +111,9 @@ function proxyClass(InitialComponent) {
if (typeof NextComponent !== 'function') {
throw new Error('Expected a constructor.');
}
if (NextComponent === CurrentComponent) {
return;
}

// Prevent proxy cycles
var existingProxy = findProxy(NextComponent);
Expand All @@ -113,62 +122,98 @@ function proxyClass(InitialComponent) {
}

// Save the next constructor so we call it
const PreviousComponent = CurrentComponent;
CurrentComponent = NextComponent;

// Try to infer displayName
ProxyComponent.displayName = NextComponent.displayName || NextComponent.name;
displayName = getDisplayName(NextComponent);
ProxyComponent.displayName = displayName;
try {
Object.defineProperty(ProxyComponent, 'name', {
value: displayName
});
} catch (err) { }

// Set up the same prototype for inherited statics
ProxyComponent.__proto__ = NextComponent.__proto__;

// Copy static methods and properties
// Copy over static methods and properties added at runtime
if (PreviousComponent) {
Object.getOwnPropertyNames(PreviousComponent).forEach(key => {
if (RESERVED_STATICS.indexOf(key) > -1) {
return;
}

const prevDescriptor = Object.getOwnPropertyDescriptor(PreviousComponent, key);
const savedDescriptor = savedDescriptors[key];

if (!isEqualDescriptor(prevDescriptor, savedDescriptor)) {
Object.defineProperty(NextComponent, key, prevDescriptor);
}
});
}

// Copy newly defined static methods and properties
Object.getOwnPropertyNames(NextComponent).forEach(key => {
if (RESERVED_STATICS.indexOf(key) > -1) {
return;
}

const staticDescriptor = {
const prevDescriptor = PreviousComponent && Object.getOwnPropertyDescriptor(PreviousComponent, key);
const savedDescriptor = savedDescriptors[key];

// Skip redefined descriptors
if (prevDescriptor && savedDescriptor && !isEqualDescriptor(savedDescriptor, prevDescriptor)) {
Object.defineProperty(NextComponent, key, prevDescriptor);
Object.defineProperty(ProxyComponent, key, prevDescriptor);
return;
}

if (prevDescriptor && !savedDescriptor) {
Object.defineProperty(ProxyComponent, key, prevDescriptor);
return;
}

const nextDescriptor = {
...Object.getOwnPropertyDescriptor(NextComponent, key),
configurable: true
};

// Copy static unless user has redefined it at runtime
if (!wasStaticModifiedByUser(key)) {
Object.defineProperty(ProxyComponent, key, staticDescriptor);
staticDescriptors[key] = staticDescriptor;
}
savedDescriptors[key] = nextDescriptor;
Object.defineProperty(ProxyComponent, key, nextDescriptor);
});

// Remove old static methods and properties
// Remove static methods and properties that are no longer defined
Object.getOwnPropertyNames(ProxyComponent).forEach(key => {
if (RESERVED_STATICS.indexOf(key) > -1) {
return;
}

// Skip statics that exist on the next class
if (NextComponent.hasOwnProperty(key)) {
return;
}

// Skip non-configurable statics
const descriptor = Object.getOwnPropertyDescriptor(ProxyComponent, key);
if (descriptor && !descriptor.configurable) {
const proxyDescriptor = Object.getOwnPropertyDescriptor(ProxyComponent, key);
if (proxyDescriptor && !proxyDescriptor.configurable) {
return;
}

// Delete static unless user has redefined it at runtime
if (!wasStaticModifiedByUser(key)) {
delete ProxyComponent[key];
delete staticDescriptors[key];
const prevDescriptor = PreviousComponent && Object.getOwnPropertyDescriptor(PreviousComponent, key);
const savedDescriptor = savedDescriptors[key];

// Skip redefined descriptors
if (prevDescriptor && savedDescriptor && !isEqualDescriptor(savedDescriptor, prevDescriptor)) {
return;
}

delete ProxyComponent[key];
});

if (prototypeProxy) {
// Update the prototype proxy with new methods
const mountedInstances = prototypeProxy.update(NextComponent.prototype);

// Set up the constructor property so accessing the statics work
ProxyComponent.prototype.constructor = ProxyComponent;
ProxyComponent.prototype.constructor = NextComponent;

// We might have added new methods that need to be auto-bound
mountedInstances.forEach(bindAutoBindMethods);
Expand Down
5 changes: 5 additions & 0 deletions src/createPrototypeProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export default function createPrototypeProxy() {
// 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;
}
Expand Down
Loading

0 comments on commit c679ad8

Please sign in to comment.