wakaru
wakaru copied to clipboard
[smart-rename] improve `handleReactRename`
Currently smart-rename
's implementation has a handleReactRename
function that appears to have renames for:
-
const uContext = o.createContext(u);
-
const uRef = o.useRef(u);
-
const [e, SetE] = o.useState(0);
I figured I would use this as a bit of a meta-issue for capturing improvements that could be made to this smart-rename
for React.
Context
If not otherwise specified, the webpack code I am looking at to derive my examples is the following (Ref), after using the CLI to unpack it to ./496-unpacked
, and then unminify it to ./496-unminified
:
⇒ cd ./unpacked/_next/static/chunks
⇒ npx @wakaru/unpacker 496.js -o ./496-unpacked
# ..snip..
⇒ npx @wakaru/unminify ./496-unpacked/* -o ./496-unminified
# ..snip..
TODO
- [ ]
useState
(but actually it ends up being more about@swc/helpers
) (Ref)- See also: https://github.com/pionxzh/wakaru/issues/50
See Also
- #48
useState
(but actually it ends up being more about @swc/helpers
)
Looking at module-10604.js
, we can see that it's a React component using useState
:
module-10604.js
(full source)
Unpacked:
var r = require(39324),
a = require(22830),
i = require(4337),
o = require(35250),
s = require(19841),
l = require(70079),
u = require(34303),
d = require(38317);
function c() {
var e = (0, i._)(["absolute right-0 top-1/2 -translate-y-1/2"]);
return (
(c = function () {
return e;
}),
e
);
}
exports.Z = l.forwardRef(function (e, t) {
var n = e.name,
i = e.placeholder,
u = e.type,
c = e.displayName,
h = e.onChange,
g = e.onBlur,
m = e.value,
p = e.saveOnBlur,
v = e.icon,
x = e.onInputIconClick,
b = e.className,
y = e.autoComplete,
w = e.autoFocus,
j = e.onPressEnter,
_ = (0, a._)((0, l.useState)(m), 2),
C = _[0],
M = _[1],
k = (0, l.useCallback)(
function (e) {
null == g || g(e), p && M(e.target.value);
},
[g, p]
),
T = (0, l.useCallback)(
function (e) {
null == h || h(e), p && M(e.target.value);
},
[h, p]
),
N = (0, l.useCallback)(
function (e) {
"Enter" === e.key && j && (e.preventDefault(), j());
},
[j]
);
(0, l.useEffect)(
function () {
M(m);
},
[m]
);
var S = (0, r._)({}, p ? {} : { value: m }, p ? { value: C } : {});
return (0,
o.jsxs)("div", { className: (0, s.Z)("rounded-md border border-gray-300 px-3 py-2 shadow-sm focus-within:border-indigo-600 focus-within:ring-1 focus-within:ring-indigo-600 dark:bg-gray-700", b), children: [(0, o.jsx)("label", { htmlFor: n, className: "block text-xs font-medium text-gray-900 dark:text-gray-100", children: c }), (0, o.jsxs)("div", { className: (0, s.Z)(c && "mt-1", "relative"), children: [(0, o.jsx)("input", (0, r._)({ ref: t, type: u, name: n, id: n, className: (0, s.Z)("block w-full border-0 p-0 text-gray-900 placeholder-gray-500 outline-none focus:ring-0 dark:bg-gray-700 dark:text-gray-100 sm:text-sm", v && "pr-6"), placeholder: i, onBlur: k, onChange: T, onKeyDown: N, autoComplete: y, autoFocus: w }, S)), v && (0, o.jsx)(f, { onClick: x, children: (0, o.jsx)(d.ZP, { icon: v }) })] })] });
});
var f = u.Z.button(c());
Unminified:
const { _: _$1 } = require(39324);
const { _: _$0 } = require(22830);
const { _ } = require(4337);
const { jsxs, jsx } = require(35250);
const { Z: Z$0 } = require(19841);
const l = require(70079);
const { useState, useCallback, useEffect } = l;
const u = require(34303);
const d = require(38317);
function c() {
const e = _(["absolute right-0 top-1/2 -translate-y-1/2"]);
c = () => e;
return e;
}
export const Z = l.forwardRef((e, t) => {
const {
name,
placeholder,
type,
displayName,
onChange,
onBlur,
value,
saveOnBlur,
icon,
onInputIconClick,
className,
autoComplete,
autoFocus,
onPressEnter,
} = e;
const [C, M] = _$0(useState(value), 2);
const k = useCallback(
(e) => {
if (onBlur != null) {
onBlur(e);
}
if (saveOnBlur) {
M(e.target.value);
}
},
[onBlur, saveOnBlur]
);
const T = useCallback(
(e) => {
if (onChange != null) {
onChange(e);
}
if (saveOnBlur) {
M(e.target.value);
}
},
[onChange, saveOnBlur]
);
const N = useCallback(
(e) => {
if (e.key === "Enter" && onPressEnter) {
e.preventDefault();
onPressEnter();
}
},
[onPressEnter]
);
useEffect(() => {
M(value);
}, [value]);
const S = _$1(
{},
saveOnBlur ? {} : { value: value },
saveOnBlur ? { value: C } : {}
);
return (
<div
className={Z$0(
"rounded-md border border-gray-300 px-3 py-2 shadow-sm focus-within:border-indigo-600 focus-within:ring-1 focus-within:ring-indigo-600 dark:bg-gray-700",
className
)}
>
<label
htmlFor={name}
className="block text-xs font-medium text-gray-900 dark:text-gray-100"
>
{displayName}
</label>
<div className={Z$0(displayName && "mt-1", "relative")}>
<input
{..._$1(
{
ref: t,
type: type,
name: name,
id: name,
className: Z$0(
"block w-full border-0 p-0 text-gray-900 placeholder-gray-500 outline-none focus:ring-0 dark:bg-gray-700 dark:text-gray-100 sm:text-sm",
icon && "pr-6"
),
placeholder: placeholder,
onBlur: k,
onChange: T,
onKeyDown: N,
autoComplete: autoComplete,
autoFocus: autoFocus,
},
S
)}
/>
{icon && <F onClick={onInputIconClick}>{<d.ZP icon={icon} />}</F>}
</div>
</div>
);
});
var F = u.Z.button(c());
Unpacked:
var r = require(39324),
a = require(22830),
// ..snip
l = require(70079),
// ..snip
exports.Z = l.forwardRef(function (e, t) {
var n = e.name,
// ..snip
m = e.value,
// ..snip
_ = (0, a._)((0, l.useState)(m), 2),
C = _[0],
M = _[1],
// ..snip
});
Unminified:
// ..snip
const { _: _$0 } = require(22830);
// ..snip
export const Z = l.forwardRef((e, t) => {
const {
// ..snip..
value,
// ..snip..
} = e;
const [C, M] = _$0(useState(value), 2);
// ..snip
});
While there is a smart-rename
for useState
already (Ref), it appears it may not be getting applied due to the _$0
function that's wrapping const [C, M] = _$0(useState(value), 2);
Looking through the rest of the webpack bundle code (Ref) for the 22830
module, we find it in main.js
; which after unpacking, becomes module-22830.js
:
⇒ npx @wakaru/unpacker main.js -o ./main-unpacked/
# ..snip..
⇒ npx @wakaru/unminify ./main-unpacked/* -o ./main-unminified
# ..snip..
module-22830.js
(full source)
Unpacked:
"use strict";;
;
var n = require(59378);
function o(e, t) {
return (
(function (e) {
if (Array.isArray(e)) return e;
})(e) ||
(function (e, t) {
var r,
n,
o =
null == e
? null
: ("undefined" != typeof Symbol && e[Symbol.iterator]) ||
e["@@iterator"];
if (null != o) {
var a = [],
i = !0,
u = !1;
try {
for (
o = o.call(e);
!(i = (r = o.next()).done) &&
(a.push(r.value), !t || a.length !== t);
i = !0
);
} catch (e) {
(u = !0), (n = e);
} finally {
try {
i || null == o.return || o.return();
} finally {
if (u) throw n;
}
}
return a;
}
})(e, t) ||
(0, n.N)(e, t) ||
(function () {
throw TypeError(
"Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
);
})()
);
}
module.exports = {
_: o,
_sliced_to_array: o
};
Unminified:
const { N } = require(59378);
function o(e, t) {
return (
((e) => {
if (Array.isArray(e)) {
return e;
}
})(e) ||
((e, t) => {
let r;
let n;
let o =
e == null
? null
: (typeof Symbol != "undefined" && e[Symbol.iterator]) ||
e["@@iterator"];
if (o != null) {
const a = [];
let i = true;
let u = false;
try {
for (
o = o.call(e);
!(i = (r = o.next()).done) &&
(a.push(r.value), !t || a.length !== t);
i = true
) {}
} catch (e) {
u = true;
n = e;
} finally {
try {
if (!i && o.return != null) {
o.return();
}
} finally {
if (u) {
throw n;
}
}
}
return a;
}
})(e, t) ||
N(e, t) ||
(() => {
throw TypeError(
"Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
);
})()
);
}
export default {
_: o,
_sliced_to_array: o,
};
Looking at module-22830.js
, we find the following code:
// ..snip..
N(e, t) ||
(() => {
throw TypeError(
"Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
);
})()
);
}
export default {
_: o,
_sliced_to_array: o,
};
Which after using GitHub code search:
- https://github.com/search?type=code&auto_enroll=true&q=%22Invalid+attempt+to+destructure+non-iterable+instance.%22+_sliced_to_array
We find a relevant looking reference in a test for next/swc
's hook_optimizer
:
- https://github.com/vercel/next.js/blob/32c9ce6805ac66d3d1d91b982fac86c5b1f70134/test/unit/next-swc.test.ts#L11-L72
Which we can see appears to be testing the output of swc
compiling some React useState
code:
// ..snip..
describe('next/swc', () => {
describe('hook_optimizer', () => {
it('should leave alone array destructuring of hooks', async () => {
const output = await swc(
trim`
import { useState } from 'react';
const [count, setCount] = useState(0);
`
)
// ..snip..
And compares it to the compiled output, which includes helper functions like:
-
function _array_like_to_array(arr, len) {
-
function _array_with_holes(arr) {
-
function _iterable_to_array_limit(arr, i) {
-
function _non_iterable_rest() {
-
function _sliced_to_array(arr, i) {
-
function _unsupported_iterable_to_array(o, minLen) {
Within that output code, we see:
// ..snip..
function _non_iterable_rest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
// ..snip..
import { useState } from "react";
var _useState = _sliced_to_array(useState(0), 2),
count = _useState[0],
setCount = _useState[1];
Looking at the latter part of that, we can see how the _sliced_to_array(useState(0), 2)
matches the format of our original unpacked useState
code from module-10604.js
:
Unpacked:
_ = (0, a._)((0, l.useState)(m), 2),
C = _[0],
M = _[1],
Unminified:
const [C, M] = _$0(useState(value), 2);
Which means that, based on the above, the _$0
in my webpacked code is likely the swc
helper function _sliced_to_array
:
function _sliced_to_array(arr, i) {
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
}
We can also generate this output ourselves using the swc
playground:
- https://swc.rs/playground
- Input
-
import { useState } from 'react'; const [count, setCount] = useState(0);
-
- Input
Searching the swc
GitHub repo for _sliced_to_array
, we see that it seems to be included in the @swc/helpers
package:
- https://github.com/search?q=repo%3Aswc-project%2Fswc%20_sliced_to_array&type=code
- https://github.com/swc-project/swc/blob/main/packages/helpers/esm/_sliced_to_array.js
Conclusion
It seems that the issue here is less about smart-rename
's handleReactRename
not handling useState
properly; and more that wakaru
needs to add support for swc
's 'runtime helper' functions like _sliced_to_array
/ etc from @swc/helpers
; probably in a similar way to how babel
's are currently implemented:
- https://github.com/pionxzh/wakaru/tree/main/packages/unminify/src/transformations/runtime-helpers
- https://github.com/pionxzh/wakaru/blob/main/packages/unminify/src/transformations/runtime-helpers/index.ts#L11-L20
- https://github.com/pionxzh/wakaru/tree/main/packages/unminify/src/transformations/runtime-helpers/babel
This may in part be relevant to the following 'module detection' issue as well:
- #41
Edit: I spun the @swc/helpers
part of this out into a more focussed issue here:
- https://github.com/pionxzh/wakaru/issues/50