diff --git a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable-complex/input.mjs b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable-complex/input.mjs new file mode 100644 index 000000000000..d55eaf7cd0ec --- /dev/null +++ b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable-complex/input.mjs @@ -0,0 +1,10 @@ +let foo = 'hello'; + +const mutate = () => { + foo = 'goodbye'; +} + +export const Component = () => { + if (Math.random() > 0.5) mutate(); + return {foo}; +}; diff --git a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable-complex/output.mjs b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable-complex/output.mjs new file mode 100644 index 000000000000..83c8f677226e --- /dev/null +++ b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable-complex/output.mjs @@ -0,0 +1,10 @@ +let foo = 'hello'; + +const mutate = () => { + foo = 'goodbye'; +}; + +export const Component = () => { + if (Math.random() > 0.5) mutate(); + return {foo}; +}; diff --git a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable/input.mjs b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable/input.mjs new file mode 100644 index 000000000000..1225d11e87eb --- /dev/null +++ b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable/input.mjs @@ -0,0 +1,6 @@ +let foo = 'hello'; + +export const Component = () => { + foo = 'goodbye'; + return {foo}; +}; diff --git a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable/output.mjs b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable/output.mjs new file mode 100644 index 000000000000..dd266a198d47 --- /dev/null +++ b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/deopt-mutable/output.mjs @@ -0,0 +1,5 @@ +let foo = 'hello'; +export const Component = () => { + foo = 'goodbye'; + return {foo}; +}; diff --git a/packages/babel-traverse/src/path/lib/hoister.js b/packages/babel-traverse/src/path/lib/hoister.js index 2fd8339eaa7d..7e7f85034628 100644 --- a/packages/babel-traverse/src/path/lib/hoister.js +++ b/packages/babel-traverse/src/path/lib/hoister.js @@ -32,6 +32,15 @@ const referenceVisitor = { const binding = path.scope.getBinding(path.node.name); if (!binding) return; + // we can handle reassignments only if they happen in the same scope as the declaration + for (const violation of binding.constantViolations) { + if (violation.scope !== binding.path.scope) { + state.mutableBinding = true; + path.stop(); + return; + } + } + // this binding isn't accessible from the parent scope so we can safely ignore it // eg. it's in a closure etc if (binding !== state.scope.getBinding(path.node.name)) return; @@ -46,6 +55,9 @@ export default class PathHoister { this.breakOnScopePaths = []; // Storage for bindings that may affect what path we can hoist to. this.bindings = {}; + // "true" if the current path contains a reference to a binding whose + // value can change and thus can't be safely hoisted. + this.mutableBinding = false; // Storage for eligible scopes. this.scopes = []; // Our original scope and path. @@ -195,6 +207,8 @@ export default class PathHoister { run() { this.path.traverse(referenceVisitor, this); + if (this.mutableBinding) return; + this.getCompatibleScopes(); const attachTo = this.getAttachmentPath();