Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing propTypes in production build? #209

Closed
timche opened this issue Jul 26, 2016 · 39 comments
Closed

Removing propTypes in production build? #209

timche opened this issue Jul 26, 2016 · 39 comments

Comments

@timche
Copy link

timche commented Jul 26, 2016

What do you think about removing the propTypes in the production build to reduce the bundle size using babel-plugin-transform-react-remove-prop-types?

I've already prepared a fork, so if you think it is a good idea, I can submit a pull request.

@timche timche changed the title Removing propTypes in production build Removing propTypes in production build? Jul 26, 2016
@gaearon
Copy link
Contributor

gaearon commented Jul 26, 2016

@spicyj Do you think this is reasonable for most apps?

@ForbesLindesay
Copy link
Contributor

Seems like a good idea to me.

@insin
Copy link
Contributor

insin commented Jul 26, 2016

Related: it would be cool if wrapping propType declarations in an if (process.env.NODE_ENV !== 'production') block became a common thing to make this automatic for your own code and particularly for your dependencies, for which you're probably using the transpiled ES5 they published to npm - is there a plugin for that?

Edit: Created an issue here: oliviertassinari/babel-plugin-transform-react-remove-prop-types#35 - would be amazing to land this change and promote its use throughout the React ecosystem!

@vjeux
Copy link
Contributor

vjeux commented Jul 26, 2016

We screwed up and didn't remove propTypes in dev when we started and now people rely on it existing in their app, so doing it now would be a big breaking change.

But for new apps that would be a good default imo. Now, it may be confusing that it is removed in this starter kit and not anywhere else

@gaearon
Copy link
Contributor

gaearon commented Jul 26, 2016

Now, it may be confusing that it is removed in this starter kit and not anywhere else

I think it’s not a big deal because React will warn very soon when you attempt to call PropTypes validators manually: facebook/react#7132. So people will know they’re not meant to be used in production, whether or not they use this kit.

@sophiebits
Copy link
Contributor

This would probably be fine. I can imagine some people using it to mask the props object but hopefully not many. Can we add a linter against .propTypes?

@gaearon
Copy link
Contributor

gaearon commented Jul 26, 2016

A linter against reading it?

@gaearon
Copy link
Contributor

gaearon commented Jul 26, 2016

I think there are some valid use cases for accessing it, like

DangerButton.propTypes = {
  ...Button.propTypes,
  somethingElse: ...
}

@taion
Copy link

taion commented Jul 26, 2016

React-Bootstrap uses propTypes to mask props objects extensively, and a number of the components there would break outright if you stripped out propTypes in node_modules.

It's a fine optimization in most cases but it'd cause difficult-to-diagnose errors if you were to run it against node_modules.

@gaearon
Copy link
Contributor

gaearon commented Jul 26, 2016

We wouldn’t touch node_modules, just the user code.

On the other hand there’s no reason why somebody shouldn’t be able to copy and paste a component from React Bootstrap and tweak it.

So I guess as long as people rely on this, we can’t do it. (Unless we specifically lint against it.)

@taion
Copy link

taion commented Jul 26, 2016

FWIW, this specific use case in React-Bootstrap is something like this: https://github.com/react-bootstrap/react-bootstrap/blob/v0.30.0/src/DropdownButton.js#L26-L27

We have parent components that render two child components, and we use propTypes to split out which prop goes where.

Looks hacky but it's fairly DRY and has worked well in practice.

@timche
Copy link
Author

timche commented Jul 26, 2016

Thanks for the info! Looks indeed hacky, but if libraries rely on component propTypes, user code can rely on them as well and removing them can lead to unexpected behaviour in production. Seems like this can be closed then, right?

@taion
Copy link

taion commented Jul 26, 2016

I guess? It's a shame, though – that's a really cool transform, and I'm going to look at using it for production app builds now that I know about it.

@gaearon
Copy link
Contributor

gaearon commented Jul 26, 2016

OK, I don't think the size reduction is worth the surprising behavior here.

@benwiley4000
Copy link

benwiley4000 commented Jun 7, 2017

Quite glad you went with this decision - the performance benefit is likely relatively nonexistent and the size benefit seems questionable to me (if you really need to save space that badly, composing propTypes sounds like a bette idea to me). Knowledge of propTypes can be really useful if you're passing a very large number of props, some updating often, to a large number of components, each of which uses a small subset of those props. You can have your child component's shouldComponentUpdate screen for wanted props using propTypes and ignore the rest.

For my library I'm exposing an extendable class I'm calling PurePropTypesComponent (might need a better name):

class PurePropTypesComponent extends React.Component {
  shouldComponentUpdate (nextProps, nextState) {
    if (this.props) {
      // for props, only shallow compare keys found in propTypes
      for (const propName of Object.keys(this.constructor.propTypes || {})) {
        if (this.props[propName] !== nextProps[propName]) {
          return true;
        }
      }
    }
    if (this.state) {
      // for state, do normal shallow compare
      for (const key of Object.keys(this.state)) {
        if (!(key in nextState)) {
          return true;
        }
      }
      for (const key of Object.keys(nextState)) {
        if(!(this.state[key] === nextState[key])) {
          return true;
        }
      }
    }
    return false;
  }
}

If a user stripped propTypes out of a bundle the components would fail to update at all. The alternative - always updating when propTypes are missing - totally misses the performance benefit I was aiming for.

ETA: however reading this discussion convinces me a higher-order component might be better/more opt-outable implementation option for me.

@gaearon
Copy link
Contributor

gaearon commented Jun 7, 2017

To be honest I feel like it might have been better to actually enforce stripping propTypes in user code. We might make a breaking change like this in the future.

We definitely don’t recommend relying on propTypes for any runtime behavior. It’s not at all obvious, and can lead to confusing times for anyone working on the project in the future.

@gaearon gaearon added this to the 2.0.0 milestone Jun 7, 2017
@gaearon gaearon reopened this Jun 7, 2017
@benwiley4000
Copy link

Aha - sorry I brought it up :P

Good to know though - not sure what's so bad about relying on propTypes, would love some elaboration. Also, not sure if there would now be a solution for my problem (wanting a reasonably automatic mechanism which could be leveraged by a developer to prevent too-frequent updates to components receiving many but consuming few props).

@gaearon
Copy link
Contributor

gaearon commented Jun 7, 2017

not sure what's so bad about relying on propTypes, would love some elaboration.

It’s just absolutely not obvious that something that’s used for one purpose (DEV-only type validation) is also being used for some other purpose. People with experience of working on React projects would be confused if they bumped into a project where it mattered.

Also, not sure if there would now be a solution for my problem (wanting a reasonably automatic mechanism which could be leveraged by a developer to prevent too-frequent updates to components receiving many but consuming few props).

Why are they receiving props they aren’t consuming? If you want shouldComponentUpdate to bail out in these cases, why pass these props in the first place? Since any components below won’t be properly updated anyway.

@benwiley4000
Copy link

Im not building react router, but consider React Router, whose Route component passes a few props to every component it receives regardless of whether those props get used, because it's up to the user what the route component implementation looks like. In my case it's an audio player component which provides presentational control children with a large set of callbacks as well as some data props which update frequently (e.g. currentTime). There may be a better approach for this - this release is pre alpha - but I'm not sure what it would be if I want to provide users the flexibility to define their own UI.

@gaearon
Copy link
Contributor

gaearon commented Jun 7, 2017

Im not building react router, but consider React Router, whose Route component passes a few props to every component it receives regardless of whether those props get used, because it's up to the user what the route component implementation looks like.

If that component doesn't care about those props and wants to short-circuit rendering even if some of them changes, it can render another component which is just a PureComponent.

function MyRoute(props) {
  return <MyPureRoute propICareAbout={props.propICareAbout} />
}

class MyPureRoute extends React.PureComponent {
  // ...
}

export default MyRoute

@benwiley4000
Copy link

benwiley4000 commented Jun 7, 2017

Nice - this is a solution I hadn't considered in this instance.

However my fear is this kind of strips the ease of use I had associated with my original solution. It would be possible to implement a higher order component whose creator function takes a props array or hash - e.g. pureWithProps(props)(MyComponent) - but at this point I would be duplicating information already in propTypes unless I did the sensible thing, which would be passing in the component's propTypes. Yet then propTypes could be magically deleted at compile time!

I guess if propTypes need to be dev-only, they should be defined with something like Flow, not a first class object. IMO it's unreasonable to delete code written by a user just because you're assuming it only existed for consumption by React.

@iamandrewluca
Copy link
Contributor

Would this solve the problem?

const devProps = (component, props) =>
  process.env.NODE_ENV === 'development' && (component.propTypes = props)

devProps(MyComp, {
  someProp: PropTypes.string
})

@gaearon
Copy link
Contributor

gaearon commented Jan 16, 2018

No because most dead-code elimination tools (including Uglify that we use) aren't smart enough to process this. Maybe Google Closure Compiler could do this but it’s too hard to integrate for CRA users.

So, does anyone still want to send a PR? I think we’d take it for 2.0.

@iansu
Copy link
Contributor

iansu commented Jan 16, 2018

Is this just a matter of applying babel-plugin-transform-react-remove-prop-types in the production webpack config?

@gaearon
Copy link
Contributor

gaearon commented Jan 16, 2018

I think so, hopefully.

@gaearon
Copy link
Contributor

gaearon commented Jan 16, 2018

(or rather in our Babel preset)

@iansu
Copy link
Contributor

iansu commented Jan 16, 2018

Ah, okay. It looks like that plugin also has a couple of options to consider. For example, do we want to strip PropTypes from packages in node_modules? I'm thinking no.

I'll put together a PR.

@gaearon
Copy link
Contributor

gaearon commented Jan 16, 2018

Not from node_modules. (But also we don't run our preset on node_modules, only /dependencies version.)

@oliviertassinari
Copy link

oliviertassinari commented Jan 16, 2018

Not from node_modules

Some library authors are using the "wrap" mode of babel-plugin-transform-react-remove-prop-types to get the propTypes removed from the bundles in production. This was causing confusion at the beginning. I haven't heard any complaints about it for months now.

@taion
Copy link

taion commented Jan 16, 2018

Yeah – discretion there should be left to library authors for now.

@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

The wrap mode would break for libs that enumerate propTypes to do filtering etc. I feel comfortable with asking people not to do this in their own apps, but I'm wary of breaking third-party code like this where there might not be an easy fix.

@quantizor
Copy link

Aren’t propTypes required for anything using context?

@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

No, contextTypes / childContextTypes are. Those aren't removed as far as I can tell.

gaearon pushed a commit that referenced this issue Jan 17, 2018
* Remove PropTypes from production build (#209)

* Added react/forbid-foreign-prop-types rule to eslint config

* Removed react/forbid-foreign-prop-types rule from eslint config
akstuhl pushed a commit to akstuhl/create-react-app that referenced this issue Mar 15, 2018
* Remove PropTypes from production build (facebook#209)

* Added react/forbid-foreign-prop-types rule to eslint config

* Removed react/forbid-foreign-prop-types rule from eslint config
@lock lock bot locked and limited conversation to collaborators Jan 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests