Skip to content

Commit

Permalink
perf(props): Remove propTypes from production build
Browse files Browse the repository at this point in the history
perf(props): Remove propTypes from production build
  • Loading branch information
layershifter authored and Alexander Fedyashov committed Oct 25, 2016
1 parent ec2081a commit 2451875
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 63 deletions.
27 changes: 23 additions & 4 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class Dropdown extends Component {
### Define _meta

Every component has a static property called `_meta`. This object defines the component. The values here are used in `propTypes`, generated documentation, generated test cases, and some utilities.
Every component has a static property called `_meta`. This object defines the component. The values here are used for handling props, generated documentation, generated test cases and some utilities.

Here's an example `_meta` object:

Expand All @@ -136,9 +136,7 @@ import { META } from '../../lib'
const _meta = {
name: 'MyComponent',
type: META.TYPES.MODULE,
props: {
pointing: ['bottom left', 'bottom right'],
},
props: ['as', 'children', 'className'],
}
```

Expand All @@ -162,6 +160,27 @@ class MyComponent {
}
```

### Using propTypes

Every component must have fully described `propTypes`, values for them are defined in `props`.

```js
import React, { PropTypes } from 'react'

function MyComponent(props) {
return <div className={props.position}>{props.children}</div>
}

MyComponent.props = {
position: ['left', 'right'],
}

MyComponent.propTypes = {
children: PropTypes.node,
position: PropTypes.oneOf(MyComponent.props),
}
```

### Conformance Test

Review [common tests](#common-tests) below. You should now add the [`isConformant()`](#isconformant-required) common test and get it to pass. This will validate the `_meta` and help you get your component off the ground.
Expand Down
66 changes: 39 additions & 27 deletions src/elements/Rail/Rail.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,43 +47,55 @@ function Rail(props) {
Rail._meta = {
name: 'Rail',
type: META.TYPES.ELEMENT,
props: {
close: ['very'],
position: SUI.FLOATS,
size: _.without(SUI.SIZES, 'medium'),
},
props: [
'as', 'attached', 'children', 'className', 'close', 'dividing', 'internal', 'position', 'size',
],
}

Rail.propTypes = {
/** An element type to render as (string or function). */
as: customPropTypes.as,
if (process.env.NODE_ENV !== 'production') {
Rail.props = {
close: {
values: ['very'],
},
position: {
values: SUI.FLOATS,
},
size: {
values: _.without(SUI.SIZES, 'medium'),
},
}

/** A rail can appear attached to the main viewport. */
attached: PropTypes.bool,
Rail.propTypes = {
/** An element type to render as (string or function). */
as: customPropTypes.as,

/** Primary content. */
children: PropTypes.node,
/** A rail can appear attached to the main viewport. */
attached: PropTypes.bool,

/** Additional classes. */
className: PropTypes.string,
/** Primary content. */
children: PropTypes.node,

/** A rail can appear closer to the main viewport. */
close: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(Rail._meta.props.close),
]),
/** Additional classes. */
className: PropTypes.string,

/** A rail can create a division between itself and a container. */
dividing: PropTypes.bool,
/** A rail can appear closer to the main viewport. */
close: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(Rail.props.close),
]),

/** A rail can attach itself to the inside of a container. */
internal: PropTypes.bool,
/** A rail can create a division between itself and a container. */
dividing: PropTypes.bool,

/** A rail can be presented on the left or right side of a container. */
position: PropTypes.oneOf(Rail._meta.props.position).isRequired,
/** A rail can attach itself to the inside of a container. */
internal: PropTypes.bool,

/** A rail can have different sizes. */
size: PropTypes.oneOf(Rail._meta.props.size),
/** A rail can be presented on the left or right side of a container. */
position: PropTypes.oneOf(Rail.props.position).isRequired,

/** A rail can have different sizes. */
size: PropTypes.oneOf(Rail.props.size),
}
}

export default Rail
7 changes: 2 additions & 5 deletions src/lib/getUnhandledProps.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'lodash'

/**
* Returns an object consisting of props beyond the scope of the Component.
* Useful for getting and spreading unknown props from the user.
Expand All @@ -7,11 +8,7 @@ import _ from 'lodash'
* @returns {{}} A shallow copy of the prop object
*/
const getUnhandledProps = (Component, props) => {
const handledProps = _.union(
Component.autoControlledProps,
_.keys(Component.defaultProps),
_.keys(Component.propTypes),
)
const handledProps = Component._meta ? Component._meta.props || [] : []

return _.omit(props, handledProps)
}
Expand Down
31 changes: 26 additions & 5 deletions test/specs/commonTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,27 @@ export const isConformant = (Component, options = {}) => {
})
})

describe('handles props', () => {
it('defines handled props in Component._meta.props', () => {
Component.should.have.any.keys('_meta')
Component._meta.should.have.any.keys('props')
Component._meta.props.should.be.an('array')
})

it('Component._meta.props includes all handled props', () => {
const computedProps = _.union(
Component.autoControlledProps,
_.keys(Component.defaultProps),
_.keys(Component.propTypes),
)

Component._meta.props.should.to.deep.equal(computedProps,
'It seems that not all props were defined in Component._meta.props, you need to check that they equal to ' +
'union of Component.autoControlledProps and keys of Component.defaultProps and Component.propTypes'
)
})
})

// ----------------------------------------
// Events
// ----------------------------------------
Expand Down Expand Up @@ -466,11 +487,11 @@ export const rendersChildren = (Component, options = {}) => {
// className from prop
// ----------------------------------------
const _definesPropOptions = (Component, propKey) => {
it(`defines ${propKey} options in Component._meta.props`, () => {
Component.should.have.any.keys('_meta')
Component._meta.should.have.any.keys('props')
Component._meta.props.should.have.any.keys(propKey)
Component._meta.props[propKey].should.be.an('array')
it(`defines ${propKey} options in Component.props`, () => {
Component.should.have.any.keys('props')
Component.props.should.have.any.keys(propKey)
Component.props[propKey].should.have.any.keys('values')
Component.props[propKey].values.should.be.an('array')
})
}

Expand Down
31 changes: 9 additions & 22 deletions test/specs/lib/getUnhandledProps-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { PropTypes } from 'react'

import React from 'react'
import { getUnhandledProps } from 'src/lib'

// We spread the unhandled props onto the rendered result.
Expand All @@ -10,33 +9,21 @@ function TestComponent(props) {
}

beforeEach(() => {
delete TestComponent.propTypes
delete TestComponent.defaultProps
delete TestComponent.autoControlledProps
delete TestComponent._meta
})

describe('getUnhandledProps', () => {
it('removes props defined in propTypes', () => {
TestComponent.propTypes = { 'data-remove-me': PropTypes.string }
it('removes props defined in _meta.props', () => {
TestComponent._meta = { props: ['data-remove-me'] }
shallow(<TestComponent />)
.should.not.have.prop('data-remove-me', 'thanks')
})
it('removes props defined in defaultProps', () => {
TestComponent.defaultProps = { 'data-remove-me': 'thanks' }
shallow(<TestComponent />)
.should.not.have.prop('data-remove-me', 'thanks')
})
it('removes props defined in autoControlledProps', () => {
TestComponent.autoControlledProps = ['data-remove-me']
shallow(<TestComponent />)
.should.not.have.prop('data-remove-me')
})
it('removes default versions of autoControlledProps', () => {
TestComponent.autoControlledProps = ['data-remove-me']
shallow(<TestComponent />)
.should.not.have.prop('defaultRemoveMe')
it('leaves props that are not defined _meta.props', () => {
TestComponent._meta = {}
shallow(<TestComponent data-leave-this='it is unhandled' />)
.should.have.prop('data-leave-this')
})
it('leaves props that are not defined in propTypes', () => {
it('leaves props that are not defined _meta.props', () => {
shallow(<TestComponent data-leave-this='it is unhandled' />)
.should.have.prop('data-leave-this')
})
Expand Down

0 comments on commit 2451875

Please sign in to comment.