Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2021 from styled-components/inline-hoist
inline static hoisting
- Loading branch information
Showing
8 changed files
with
337 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// @flow | ||
/** | ||
* This is a modified version of hoist-non-react-statics v3. | ||
* BSD License: https://github.com/mridgway/hoist-non-react-statics/blob/master/LICENSE.md | ||
*/ | ||
import { ForwardRef } from 'react-is' | ||
|
||
const REACT_STATICS = { | ||
childContextTypes: true, | ||
contextTypes: true, | ||
defaultProps: true, | ||
displayName: true, | ||
getDerivedStateFromProps: true, | ||
propTypes: true, | ||
type: true, | ||
} | ||
|
||
const KNOWN_STATICS = { | ||
name: true, | ||
length: true, | ||
prototype: true, | ||
caller: true, | ||
callee: true, | ||
arguments: true, | ||
arity: true, | ||
} | ||
|
||
const TYPE_STATICS = { | ||
[ForwardRef]: { | ||
$$typeof: true, | ||
render: true, | ||
}, | ||
} | ||
|
||
const { | ||
defineProperty, | ||
getOwnPropertyNames, | ||
getOwnPropertySymbols = () => [], | ||
getOwnPropertyDescriptor, | ||
getPrototypeOf, | ||
prototype: objectPrototype, | ||
} = Object | ||
|
||
const { prototype: arrayPrototype } = Array | ||
|
||
export default function hoistNonReactStatics( | ||
targetComponent: any, | ||
sourceComponent: any, | ||
blacklist: ?Object | ||
): any { | ||
if (typeof sourceComponent !== 'string') { | ||
// don't hoist over string (html) components | ||
|
||
const inheritedComponent = getPrototypeOf(sourceComponent) | ||
|
||
if (inheritedComponent && inheritedComponent !== objectPrototype) { | ||
hoistNonReactStatics(targetComponent, inheritedComponent, blacklist) | ||
} | ||
|
||
const keys = arrayPrototype.concat( | ||
getOwnPropertyNames(sourceComponent), | ||
// $FlowFixMe | ||
getOwnPropertySymbols(sourceComponent) | ||
) | ||
|
||
const targetStatics = | ||
TYPE_STATICS[targetComponent.$$typeof] || REACT_STATICS | ||
|
||
const sourceStatics = | ||
TYPE_STATICS[sourceComponent.$$typeof] || REACT_STATICS | ||
|
||
let i = keys.length | ||
let descriptor | ||
let key | ||
|
||
// eslint-disable-next-line no-plusplus | ||
while (i--) { | ||
key = keys[i] | ||
|
||
if ( | ||
// $FlowFixMe | ||
!KNOWN_STATICS[key] && | ||
!(blacklist && blacklist[key]) && | ||
!(sourceStatics && sourceStatics[key]) && | ||
// $FlowFixMe | ||
!(targetStatics && targetStatics[key]) | ||
) { | ||
descriptor = getOwnPropertyDescriptor(sourceComponent, key) | ||
|
||
if (descriptor) { | ||
try { | ||
// Avoid failures from read-only properties | ||
defineProperty(targetComponent, key, descriptor) | ||
} catch (e) { | ||
/* fail silently */ | ||
} | ||
} | ||
} | ||
} | ||
|
||
return targetComponent | ||
} | ||
|
||
return targetComponent | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
/** | ||
* This is a modified version of the hoist-non-react-statics v3 testing suite. | ||
* BSD License: https://github.com/mridgway/hoist-non-react-statics/blob/master/LICENSE.md | ||
*/ | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import hoistNonReactStatics from '../hoist' | ||
|
||
describe('hoist non react statics', () => { | ||
it('should hoist non react statics', () => { | ||
class Component extends React.Component { | ||
static displayName = 'Foo' | ||
static foo = 'bar' | ||
static propTypes = { | ||
on: PropTypes.bool.isRequired, | ||
} | ||
|
||
render() { | ||
return null | ||
} | ||
} | ||
|
||
class Wrapper extends React.Component { | ||
static displayName = 'Bar' | ||
|
||
render() { | ||
return <Component /> | ||
} | ||
} | ||
|
||
hoistNonReactStatics(Wrapper, Component) | ||
|
||
expect(Wrapper.displayName).toEqual('Bar') | ||
expect(Wrapper.foo).toEqual('bar') | ||
}) | ||
|
||
it('should not hoist custom statics', () => { | ||
class Component extends React.Component { | ||
static displayName = 'Foo' | ||
static foo = 'bar' | ||
|
||
render() { | ||
return null | ||
} | ||
} | ||
|
||
class Wrapper extends React.Component { | ||
static displayName = 'Bar' | ||
|
||
render() { | ||
return <Component /> | ||
} | ||
} | ||
|
||
hoistNonReactStatics(Wrapper, Component, { foo: true }) | ||
expect(Wrapper.foo).toBeUndefined() | ||
}) | ||
|
||
it('should not hoist statics from strings', () => { | ||
const Component = 'input' | ||
|
||
class Wrapper extends React.Component { | ||
render() { | ||
return <Component /> | ||
} | ||
} | ||
|
||
hoistNonReactStatics(Wrapper, Component) | ||
expect(Wrapper[0]).toBeUndefined() // if hoisting it would equal 'i' | ||
}) | ||
|
||
it('should hoist symbols', () => { | ||
const foo = Symbol('foo') | ||
|
||
class Component extends React.Component { | ||
render() { | ||
return null | ||
} | ||
} | ||
|
||
// Manually set static property using Symbol | ||
// since createReactClass doesn't handle symbols passed to static | ||
Component[foo] = 'bar' | ||
|
||
class Wrapper extends React.Component { | ||
render() { | ||
return <Component /> | ||
} | ||
} | ||
|
||
hoistNonReactStatics(Wrapper, Component) | ||
|
||
expect(Wrapper[foo]).toEqual('bar') | ||
}) | ||
|
||
it('should hoist class statics', () => { | ||
class Component extends React.Component { | ||
static foo = 'bar' | ||
static test() {} | ||
} | ||
|
||
class Wrapper extends React.Component { | ||
render() { | ||
return <Component /> | ||
} | ||
} | ||
|
||
hoistNonReactStatics(Wrapper, Component) | ||
|
||
expect(Wrapper.foo).toEqual(Component.foo) | ||
expect(Wrapper.test).toEqual(Component.test) | ||
}) | ||
|
||
it('should hoist properties with accessor methods', () => { | ||
class Component extends React.Component { | ||
render() { | ||
return null | ||
} | ||
} | ||
|
||
// Manually set static complex property | ||
// since createReactClass doesn't handle properties passed to static | ||
let counter = 0 | ||
Object.defineProperty(Component, 'foo', { | ||
enumerable: true, | ||
configurable: true, | ||
get: () => { | ||
return counter++ | ||
}, | ||
}) | ||
|
||
class Wrapper extends React.Component { | ||
render() { | ||
return <Component /> | ||
} | ||
} | ||
|
||
hoistNonReactStatics(Wrapper, Component) | ||
|
||
// Each access of Wrapper.foo should increment counter. | ||
expect(Wrapper.foo).toEqual(0) | ||
expect(Wrapper.foo).toEqual(1) | ||
expect(Wrapper.foo).toEqual(2) | ||
}) | ||
|
||
it('should inherit static class properties', () => { | ||
class A extends React.Component { | ||
static test3 = 'A' | ||
static test4 = 'D' | ||
test5 = 'foo' | ||
} | ||
class B extends A { | ||
static test2 = 'B' | ||
static test4 = 'DD' | ||
} | ||
class C { | ||
static test1 = 'C' | ||
} | ||
const D = hoistNonReactStatics(C, B) | ||
|
||
expect(D.test1).toEqual('C') | ||
expect(D.test2).toEqual('B') | ||
expect(D.test3).toEqual('A') | ||
expect(D.test4).toEqual('DD') | ||
expect(D.test5).toEqual(undefined) | ||
}) | ||
|
||
it('should inherit static class methods', () => { | ||
class A extends React.Component { | ||
static test3 = 'A' | ||
static test4 = 'D' | ||
static getMeta() { | ||
return {} | ||
} | ||
test5 = 'foo' | ||
} | ||
class B extends A { | ||
static test2 = 'B' | ||
static test4 = 'DD' | ||
static getMeta2() { | ||
return {} | ||
} | ||
} | ||
class C { | ||
static test1 = 'C' | ||
} | ||
const D = hoistNonReactStatics(C, B) | ||
|
||
expect(D.test1).toEqual('C') | ||
expect(D.test2).toEqual('B') | ||
expect(D.test3).toEqual('A') | ||
expect(D.test4).toEqual('DD') | ||
expect(D.test5).toEqual(undefined) | ||
expect(D.getMeta).toBeInstanceOf(Function) | ||
expect(D.getMeta2).toBeInstanceOf(Function) | ||
expect(D.getMeta()).toEqual({}) | ||
}) | ||
|
||
it('should not inherit ForwardRef render', () => { | ||
class FancyButton extends React.Component {} | ||
function logProps(Component) { | ||
class LogProps extends React.Component { | ||
static foo = 'foo' | ||
static render = 'bar' | ||
render() { | ||
const { forwardedRef, ...rest } = this.props | ||
return <Component ref={forwardedRef} {...rest} foo="foo" bar="bar" /> | ||
} | ||
} | ||
const ForwardedComponent = React.forwardRef((props, ref) => { | ||
return <LogProps {...props} forwardedRef={ref} /> | ||
}) | ||
|
||
hoistNonReactStatics(ForwardedComponent, LogProps) | ||
|
||
return ForwardedComponent | ||
} | ||
|
||
const WrappedFancyButton = logProps(FancyButton) | ||
|
||
expect(WrappedFancyButton.foo).toEqual('foo') | ||
expect(WrappedFancyButton.render).not.toEqual('bar') | ||
}) | ||
}) |
Oops, something went wrong.