-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
feature: Support pure expressions in transform-react-constant-elements #4812
feature: Support pure expressions in transform-react-constant-elements #4812
Conversation
189820b
to
1886a84
Compare
Codecov Report
@@ Coverage Diff @@
## master #4812 +/- ##
==========================================
+ Coverage 89.41% 89.46% +0.05%
==========================================
Files 204 204
Lines 9929 9939 +10
Branches 2678 2683 +5
==========================================
+ Hits 8878 8892 +14
+ Misses 1051 1047 -4
Continue to review full report at Codecov.
|
cc @sebmarkbage @gaearon @spicyj Should note we probably want more deopts given https://github.com/babel/babel/labels/react |
1886a84
to
4a8aa71
Compare
I've updated this to attempt to evaluate the expression passed:
var Foo = React.createClass({
render: function () {
return (
<div data-text={
"Some text, " +
"and some more too."
} />
);
}
});
// becomes
var _ref = <div data-text={"Some text, and some more too."} />;
var Foo = React.createClass({
render: function () {
return _ref;
}
});
function render(offset) {
return function () {
return <div tabIndex={offset + 1} />;
};
}
// becomes
function render(offset) {
var _ref = <div tabIndex={offset + 1} />;
return function () {
return _ref;
};
} Going to tackle some of the open react issues separately. |
4a8aa71
to
415144a
Compare
Rebased in hopes this will make it in with the rest of the constant-elements fixes @hzoo |
It is safe because we freeze props (in DEV) inside |
// be mutated after render. | ||
// https://github.com/facebook/react/issues/3226 | ||
if (t.isImmutable(resultNode)) { | ||
path.replaceWith(resultNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to actually execute this replacement? This visitor at the moment has no side-effects, and it's not obvious that this gets us anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed - this is something for Babili or another uglifier. Have pushed a change.
415144a
to
48a7e49
Compare
Amended to not actually replace the result of a pure expression (this can be done by minifiers). We still need to evaluate it to see if its result is immutable. I'm fairly confident we could safely replace, but it could be a source of bugs and is not really the responsibility of this plugin. |
const expressionResult = path.evaluate(); | ||
if (expressionResult.confident) { | ||
// We know the result; check its mutability. | ||
const resultNode = t.valueToNode(expressionResult.value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized valueToNode
throws if it is given a type that can't be converted to an AST Node, I'm not entirely sure I'm willing to assume that isPure()
will cover all those cases. It likely does, but since they are not related from a code standpoint it makes me hesitant.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically the only values we can avoid deopting on are primitives; could skip this call and just check if number, bool, string, null, undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me.
48a7e49
to
045d23e
Compare
Amended. |
Hey @STRML! It looks like one or more of your builds have failed. I've copied the relevant info below to save you some time. |
045d23e
to
a741ece
Compare
* Add new flow preset (#5288) * Fix PathHoister hoisting JSX member expressions on "this". (#5143) The PathHoister ignored member references on "this", causing it to potentially hoist an expression above its function scope. This patch tells the hoister to watch for "this", and if seen, mark the nearest non-arrow function scope as the upper limit for hoistng. This fixes #4397 and is an alternative to #4787. * Fix PathHoister hoisting before bindings. (#5153) Fixes #5149 and enables a few additional safe hoists. * Fix linting error * feature: Support pure expressions in transform-react-constant-elements (#4812) * Fix loose for-of with label (#5298) * Rewrite Hub as interface #5047 (#5050) * Rewrite Hub as interface #5047 * Update index.js * Avoid adding unnecessary closure for block scoping (#5246) When you write ``` for (const x of l) { setTimeout(() => x); } ``` we need to add a closure because the variable is meant to be block-scoped and recreated each time the block runs. We do this. However, we also add the closure when no loop is present. This isn't necessary, because if no loop is present then each piece of code runs at most once. I changed the transform to only add a closure if a variable is referenced from within a loop. * Add greenkeeperio-bot to mention-bot blacklist (#5301) [skip ci] * Upgrade lerna to current beta. (#5300) * Revert "Upgrade lerna to current beta." (#5303) * Add charset so tests work with convert-source-map@>1.4 (#5302) * Add CHANGELOG for 6.23.0 [skip ci] (#5304) * Update babel-types README from script. * v6.23.0 * Revert change that lerna force-committed. * Revert "Rewrite Hub as interface #5047" (#5306) * v6.23.1 * Revert lerna again
// It is still safe to hoist that, so long as its result is immutable. | ||
// If not, it is not safe to replace as mutable values (like objects) could be mutated after render. | ||
// https://github.com/facebook/react/issues/3226 | ||
if (path.isPure()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pure template literals such as
<a className={`ok`}/>
is missed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Problem
Could describe this as a bugfix or feature, but when a pure expression of any sort is in a JSXExpression, the node is marked as mutable and isn't hoisted. This prevents otherwise eligible elements from being found.
For example, the following types of elements should be hoistable:
The Fix
The fix is very simple: just add a check for
path.isPure()
as an out when determining when to hoist.The expression will remain in the hoisted code, even though it could be statically evaluated. UglifyJS will take care of that in many cases.
Deopt
As noted in facebook/react#3226, it's not safe to reuse elements with mutable props. So the following should not be hoisted:
This is done via an additional check to
path.isObjectExpression()
. Please correct me if this is not the way to find a mutable reference. Unfortunately, this will not catch things like frozen objects.