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

Remove newly unreferenced VariableDeclarators #146

Merged
merged 5 commits into from
Aug 22, 2018

Conversation

lencioni
Copy link
Collaborator

@lencioni lencioni commented Jul 25, 2018

I've noticed that a common pattern used to assign propTypes--first
define a variable for the propTypes, and then assign it to the component
by reference--ends up deoptimizing this plugin, which leads to larger
bundles in production than desired. The original hypothesis that
minification would clean up these dead references works well for the
simplest of propTypes, but unfortunately it doesn't hold up as
soon as you have a function call in the propTypes variable (such as is
the case for propTypes like arrayOf, objectOf, shape, etc.)
because the minfier does not know that the function call will not have
side-effects:

const propTypes = {
  foo: PropTypes.arrayOf(PropTypes.number),
};

I believe this should be solved by this plugin because we can make the
assumption that function calls inside of propTypes have no side-effects.

There seems to be a weird issue where the transform adds a semicolon
when using in wrapped mode. For now, I decided to simply add the
additional semicolons in the test fixtures, since they will be minified
out anyway.

I don't have much experience working with Babel plugins yet, so it is
likely that I'm doing things in a weird or sub-optimal way here.

Fixes #145

@oliviertassinari
Copy link
Owner

@lencioni Thanks for the failing test case. It comes down to this plugin only removing the reference between the React component and the prop types, deferring potential dead code elimination to another tool.

@oliviertassinari
Copy link
Owner

oliviertassinari commented Jul 25, 2018

I guess #145 and #144 are two flavors of the same core issue. I'm not sure I will prioritize this effort in the near future. I have been pouring most of my free time in OSS to Material-UI.

@lencioni
Copy link
Collaborator Author

Yeah, I think that's a pretty accurate assessment. If I put up a PR that addresses this, would you be open to getting it merged in and published?

@oliviertassinari
Copy link
Owner

@lencioni It might increase the time this plugin takes, but nobody is benchmarking that, so we are good. As for the potential bug it might introduce, I have trust in you, so yes, I'm more than happy to review such work!

@lencioni
Copy link
Collaborator Author

@oliviertassinari Sounds great! Before I begin working on an implementation, do you think the test cases I've added here are adequate? Or are there more cases we want to test against?

@oliviertassinari
Copy link
Owner

Or are there more cases we want to test against?

Maybe one more with an export of the proptypes? I can't think of anything else.

@lencioni lencioni force-pushed the variable-assignment branch 2 times, most recently from db98c38 to 990c22a Compare July 31, 2018 16:36
@lencioni lencioni changed the title Add failing tests for propTypes from variable assignment Remove newly unreferenced VariableDeclarators Jul 31, 2018
@lencioni
Copy link
Collaborator Author

@oliviertassinari I've updated this PR with a solution that passes all tests. I've never really worked with Babel plugins before, so it is likely that I've made some weird decisions. I'd love your feedback on this!

@lencioni lencioni force-pushed the variable-assignment branch from 990c22a to 9001851 Compare July 31, 2018 16:53
@lencioni
Copy link
Collaborator Author

As a sanity check, I ran this on our large internal component library project and it seems to be doing the right thing.


if (
path.parent.type === 'ObjectProperty' &&
(path.parent.key === path.node || path.parent.shorthand)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check if path.parent.computed?

{ [computedKey]: 'foo' }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think we still want identifiers in computed object keys, since by removing the object we may no longer need the identifier used in the computed key and we will want to clean those up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I think we might not actually need the next check for CallExpressions. I'll remove that now.

src/index.js Outdated
return
}

if (path.parent.type === 'CallExpression' && path.parent.callee === path.node) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't actually need this check. I don't actually remember why I added it in the first place.

I've noticed that a common pattern used to assign propTypes--first
define a variable for the propTypes, and then assign it to the component
by reference--ends up deoptimizing this plugin, which leads to larger
bundles in production than desired. The original hypothesis that
minification would clean up these dead references works well for the
simplest of propTypes, but unfortunately it doesn't hold up as
soon as you have a function call in the propTypes variable (such as is
the case for propTypes like `arrayOf`, `objectOf`, `shape`, etc.)
because the minfier does not know that the function call will not have
side-effects:

```js
const propTypes = {
  foo: PropTypes.arrayOf(PropTypes.number),
};
```

I believe this should be solved by this plugin because we can make the
assumption that function calls inside of propTypes have no side-effects.

There seems to be a weird issue where the transform adds a semicolon
when using in wrapped mode. For now, I decided to simply add the
additional semicolons in the test fixtures, since they will be minified
out anyway.

I don't have much experience working with Babel plugins yet, so it is
likely that I'm doing things in a weird or sub-optimal way here.
@lencioni lencioni force-pushed the variable-assignment branch from 9001851 to 07b667c Compare July 31, 2018 21:12
@lencioni
Copy link
Collaborator Author

lencioni commented Aug 4, 2018

@oliviertassinari are there any changes you want me to make to this PR?

@oliviertassinari
Copy link
Owner

@lencioni Thank you for your time! No, I need to find some to review the pull request :)

@oliviertassinari oliviertassinari self-assigned this Aug 13, 2018
@adamrneary
Copy link

Great PR or greatest PR? This is fabulous. +1000

Copy link

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any tests to cover React.createClass/create-react-class?

src/index.js Outdated
@@ -53,9 +53,39 @@ function isReactClass(superClass, scope, globalOptions) {
return answer
}

function areSetsEqual(set1, set2) {
if (set1.size !== set2.size) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even tho it doesn't matter for this use case, i'd probably add an additional bailout; if (set1 === set2) { return true; } :-)

@@ -1,8 +1,8 @@
"use strict";

var propTypes = {
var propTypes = process.env.NODE_ENV !== "production" ? {
foo: PropTypes.string
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some tests for get bar() { return PropTypes.string; } and similar?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it'd be good to ensure that this doesn't crash:

var propTypes = {
  ...({ foo: PropTypes.string }),
};

foo: PropTypes.string
};
} : {};;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are the double semicolons intended?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a weird issue where the transform adds a semicolon
when using in wrapped mode. For now, I decided to simply add the
additional semicolons in the test fixtures, since they will be minified
out anyway.

If you know why they are there, I'd love to fix!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh oops, missed that.

i don't know why; not that big a deal ofc.

foo: PropTypes.string
};
} : {};;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here

bar: PropTypes.string
};

const propTypesWithExtraReference = Object.assign({}, extraReference, {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an object spread example could be good alongside this

Although this probably doesn't matter for our use-case, it is more
complete.
This gives us more assurance that this code is working as expected.
I'm not entirely sure if this is the desired result, but I think it
maybe is, since anything could be using the getter.
@lencioni
Copy link
Collaborator Author

@ljharb Thanks for the review! I've added some tests.

@lencioni
Copy link
Collaborator Author

@oliviertassinari If you are too busy to work on this, I'd be happy to merge this and publish a new version of the plugin for you if you want.

@oliviertassinari
Copy link
Owner

@lencioni I would love that. Let me send the needed invitation. Thanks.

@lencioni
Copy link
Collaborator Author

My npm username is lencioni

@lencioni lencioni merged commit 4575801 into oliviertassinari:master Aug 22, 2018
@lencioni lencioni deleted the variable-assignment branch August 22, 2018 18:59
@lencioni
Copy link
Collaborator Author

lencioni commented Aug 22, 2018

@oliviertassinari thanks for the permissions on this repo! I've merged this change and once you give me publish access on npm, I can get a new release out.

https://www.npmjs.com/package/babel-plugin-transform-react-remove-prop-types

@oliviertassinari
Copy link
Owner

@lencioni You should already have the access.

@ljharb
Copy link

ljharb commented Aug 22, 2018

The website pages are cached and take awhile to update; try npm owner ls babel-plugin-transform-react-remove-prop-types to verify.

@lencioni
Copy link
Collaborator Author

The npm owner command seems to be cached as well. Regardless, I was able to publish!

@oliviertassinari
Copy link
Owner

oliviertassinari commented Aug 22, 2018

I can't wait to try it out 🎉 !

Copy link

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought of this later

export default function(api) {
const { template, types, traverse } = api

const nestedIdentifiers = new Set()
const removedPaths = new Set()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this contains objects, and is never traversed, so it probably should be a WeakSet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: remove unused propTypes intermediate variable
5 participants