mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(transformer/react-refresh): support module.property.useHook() (#23190)
## Summary Ports facebook/react#35318 to the React Refresh plugin: hook calls through a nested member expression such as `FancyHook.property.useNestedThing()` are now collected into the `$RefreshSig$` custom-hooks list, so components using them no longer lose state on every fast-refresh update. As in the Babel plugin, the binding is resolved through one extra static member level, and a force reset is emitted when the root object has no binding in scope. Deeper nesting (`A.B.C.useHook()`) remains uncollected, same as upstream; note that Babel additionally emits `forceReset = true` in that case while oxc does not — a pre-existing, intentional divergence from #6143 that this PR does not change. closes #16571 ## Example Input: ```jsx import FancyHook from 'fancy'; export default function App() { const foo = FancyHook.property.useNestedThing(); return <h1>{foo}</h1>; } ``` Signature before: ```js _s(App, "useNestedThing{foo}"); ``` Signature after: ```js _s(App, "useNestedThing{foo}", false, function() { return [FancyHook.property.useNestedThing]; }); ``` --- This PR was prepared with AI assistance; I reviewed, tested, and verified the changes.
This commit is contained in:
1 parent
150bce13e4
commit
6c0bdf0486
6 files changed
+66
-10
No files matched your search
@@ -331,15 +331,27 @@ impl<'a> Traverse<'a, TransformState<'a>> for ReactRefresh<'a> {
|
||||
|
||||
if !is_builtin_hook(&hook_name) {
|
||||
// Check if a corresponding binding exists where we emit the signature.
|
||||
let (binding_name, is_member_expression): (Option<Ident>, _) = match &call_expr.callee {
|
||||
Expression::Identifier(ident) => (Some(ident.name), false),
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
if let Expression::Identifier(object) = &member.object {
|
||||
(Some(object.name), true)
|
||||
} else {
|
||||
(None, false)
|
||||
let (binding_name, middle_property, is_member_expression): (
|
||||
Option<Ident>,
|
||||
Option<Ident>,
|
||||
bool,
|
||||
) = match &call_expr.callee {
|
||||
Expression::Identifier(ident) => (Some(ident.name), None, false),
|
||||
Expression::StaticMemberExpression(member) => match &member.object {
|
||||
// `FancyHook.useHook()`
|
||||
Expression::Identifier(object) => (Some(object.name), None, true),
|
||||
// `FancyHook.property.useHook()`.
|
||||
// Like the upstream Babel plugin (facebook/react#35318), only one
|
||||
// extra member level is supported; deeper nesting is not collected.
|
||||
Expression::StaticMemberExpression(inner_member) => {
|
||||
if let Expression::Identifier(object) = &inner_member.object {
|
||||
(Some(object.name), Some(inner_member.property.name), true)
|
||||
} else {
|
||||
(None, None, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (None, None, false),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@@ -359,6 +371,15 @@ impl<'a> Traverse<'a, TransformState<'a>> for ReactRefresh<'a> {
|
||||
);
|
||||
|
||||
if is_member_expression {
|
||||
if let Some(middle_property) = middle_property {
|
||||
// binding_name.middle_property
|
||||
expr = Expression::from(ctx.ast.member_expression_static(
|
||||
SPAN,
|
||||
expr,
|
||||
ctx.ast.identifier_name(SPAN, middle_property),
|
||||
false,
|
||||
));
|
||||
}
|
||||
// binding_name.hook_name
|
||||
expr = Expression::from(ctx.ast.member_expression_static(
|
||||
SPAN,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: 2688fbd1
|
||||
|
||||
Passed: 237/395
|
||||
Passed: 239/397
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
@@ -664,7 +664,7 @@ after transform: [ReferenceId(0), ReferenceId(1), ReferenceId(4), ReferenceId(9)
|
||||
rebuilt : [ReferenceId(5)]
|
||||
|
||||
|
||||
# babel-plugin-transform-react-jsx (49/52)
|
||||
# babel-plugin-transform-react-jsx (51/54)
|
||||
* refresh/import-after-component/input.js
|
||||
Missing ScopeId
|
||||
Missing ReferenceId: "useFoo"
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export function App() {
|
||||
const foo = GlobalHook.property.useNestedThing();
|
||||
return <h1>{foo}</h1>;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
var _s = $RefreshSig$();
|
||||
export function App() {
|
||||
_s();
|
||||
const foo = GlobalHook.property.useNestedThing();
|
||||
return /* @__PURE__ */ _jsx("h1", { children: foo });
|
||||
}
|
||||
_s(App, "useNestedThing{foo}", true);
|
||||
_c = App;
|
||||
var _c;
|
||||
$RefreshReg$(_c, "App");
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import FancyHook from 'fancy';
|
||||
|
||||
export default function App() {
|
||||
const foo = FancyHook.property.useNestedThing();
|
||||
return <h1>{foo}</h1>;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import FancyHook from "fancy";
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
var _s = $RefreshSig$();
|
||||
export default function App() {
|
||||
_s();
|
||||
const foo = FancyHook.property.useNestedThing();
|
||||
return /* @__PURE__ */ _jsx("h1", { children: foo });
|
||||
}
|
||||
_s(App, "useNestedThing{foo}", false, function() {
|
||||
return [FancyHook.property.useNestedThing];
|
||||
});
|
||||
_c = App;
|
||||
var _c;
|
||||
$RefreshReg$(_c, "App");
|
||||
Reference in new issue
Block a user