@thinksharpe/react-compiler-unmemo 0.5.4 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/helpers/remove-hooks.mjs +92 -28
- package/package.json +1 -1
- package/react-compiler-unmemo.mjs +12 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
|
-
[](./tests)
|
|
7
7
|
|
|
8
8
|
A codemod that removes `useMemo` and `useCallback` from your React codebase so you can adopt [React Compiler](https://react.dev/learn/react-compiler) without the manual cleanup.
|
|
9
9
|
|
package/helpers/remove-hooks.mjs
CHANGED
|
@@ -84,6 +84,22 @@ export function findClosingParen(content, startIdx) {
|
|
|
84
84
|
|
|
85
85
|
if (inString || inTemplate) continue;
|
|
86
86
|
|
|
87
|
+
// Skip line comments
|
|
88
|
+
if (ch === "/" && content[i + 1] === "/") {
|
|
89
|
+
const eol = content.indexOf("\n", i + 2);
|
|
90
|
+
if (eol === -1) return -1;
|
|
91
|
+
i = eol;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Skip block comments
|
|
96
|
+
if (ch === "/" && content[i + 1] === "*") {
|
|
97
|
+
const close = content.indexOf("*/", i + 2);
|
|
98
|
+
if (close === -1) return -1;
|
|
99
|
+
i = close + 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
87
103
|
if (ch === "(") depth++;
|
|
88
104
|
if (ch === ")") {
|
|
89
105
|
depth--;
|
|
@@ -120,6 +136,22 @@ export function stripDepsArray(inner) {
|
|
|
120
136
|
}
|
|
121
137
|
if (inString) continue;
|
|
122
138
|
|
|
139
|
+
// Skip line comments
|
|
140
|
+
if (ch === "/" && inner[i + 1] === "/") {
|
|
141
|
+
const eol = inner.indexOf("\n", i + 2);
|
|
142
|
+
if (eol === -1) break;
|
|
143
|
+
i = eol;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Skip block comments
|
|
148
|
+
if (ch === "/" && inner[i + 1] === "*") {
|
|
149
|
+
const close = inner.indexOf("*/", i + 2);
|
|
150
|
+
if (close === -1) break;
|
|
151
|
+
i = close + 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
123
155
|
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
124
156
|
if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
125
157
|
if (ch === "," && depth === 0) {
|
|
@@ -158,6 +190,22 @@ export function unwrapArrowFn(inner) {
|
|
|
158
190
|
}
|
|
159
191
|
if (inString) continue;
|
|
160
192
|
|
|
193
|
+
// Skip line comments
|
|
194
|
+
if (ch === "/" && inner[i + 1] === "/") {
|
|
195
|
+
const eol = inner.indexOf("\n", i + 2);
|
|
196
|
+
if (eol === -1) break;
|
|
197
|
+
i = eol;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Skip block comments
|
|
202
|
+
if (ch === "/" && inner[i + 1] === "*") {
|
|
203
|
+
const close = inner.indexOf("*/", i + 2);
|
|
204
|
+
if (close === -1) break;
|
|
205
|
+
i = close + 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
161
209
|
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
162
210
|
if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
163
211
|
if (ch === "=" && inner[i + 1] === ">" && depth === 0) {
|
|
@@ -434,37 +482,53 @@ export function processFile(filePath, { dryRun = false } = {}) {
|
|
|
434
482
|
if (!found) break;
|
|
435
483
|
}
|
|
436
484
|
|
|
437
|
-
// Phase 3: Clean up imports
|
|
485
|
+
// Phase 3: Clean up imports (skip matches inside strings/comments)
|
|
438
486
|
if (changed) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
487
|
+
const importPatterns = [
|
|
488
|
+
{
|
|
489
|
+
regex: /import React, \{([^}]*)\} from (["'])react\2[^\S\n]*;?/g,
|
|
490
|
+
replace: (match, imports, quote) => {
|
|
491
|
+
const semi = match.trimEnd().endsWith(";") ? ";" : "";
|
|
492
|
+
const cleaned = imports
|
|
493
|
+
.split(",")
|
|
494
|
+
.map((s) => s.trim())
|
|
495
|
+
.filter((s) => s && s !== "useMemo" && s !== "useCallback")
|
|
496
|
+
.join(", ");
|
|
497
|
+
if (!cleaned) return `import React from ${quote}react${quote}${semi}`;
|
|
498
|
+
return `import React, { ${cleaned} } from ${quote}react${quote}${semi}`;
|
|
499
|
+
},
|
|
451
500
|
},
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (!cleaned) return ""; // Remove empty import entirely
|
|
465
|
-
return `import { ${cleaned} } from ${quote}react${quote}${semi}`;
|
|
501
|
+
{
|
|
502
|
+
regex: /import \{([^}]*)\} from (["'])react\2[^\S\n]*;?/g,
|
|
503
|
+
replace: (match, imports, quote) => {
|
|
504
|
+
const semi = match.trimEnd().endsWith(";") ? ";" : "";
|
|
505
|
+
const cleaned = imports
|
|
506
|
+
.split(",")
|
|
507
|
+
.map((s) => s.trim())
|
|
508
|
+
.filter((s) => s && s !== "useMemo" && s !== "useCallback")
|
|
509
|
+
.join(", ");
|
|
510
|
+
if (!cleaned) return ""; // Remove empty import entirely
|
|
511
|
+
return `import { ${cleaned} } from ${quote}react${quote}${semi}`;
|
|
512
|
+
},
|
|
466
513
|
},
|
|
467
|
-
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
for (const { regex, replace } of importPatterns) {
|
|
517
|
+
let result = "";
|
|
518
|
+
let lastIndex = 0;
|
|
519
|
+
let m;
|
|
520
|
+
regex.lastIndex = 0;
|
|
521
|
+
while ((m = regex.exec(content)) !== null) {
|
|
522
|
+
if (isInsideStringOrComment(content, m.index)) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
result += content.slice(lastIndex, m.index);
|
|
526
|
+
result += replace(m[0], m[1], m[2]);
|
|
527
|
+
lastIndex = m.index + m[0].length;
|
|
528
|
+
}
|
|
529
|
+
result += content.slice(lastIndex);
|
|
530
|
+
content = result;
|
|
531
|
+
}
|
|
468
532
|
|
|
469
533
|
// Clean up any resulting blank lines from removed imports
|
|
470
534
|
content = content.replace(/\n\n\n+/g, "\n\n");
|
package/package.json
CHANGED
|
@@ -23,13 +23,24 @@
|
|
|
23
23
|
* npx react-compiler-unmemo /absolute/path/to/project --files "app/**\/*.tsx"
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import fs from "fs";
|
|
26
27
|
import path from "path";
|
|
28
|
+
import { fileURLToPath } from "url";
|
|
29
|
+
|
|
27
30
|
import { run as fixTypes } from "./helpers/fix-type-annotations.mjs";
|
|
28
31
|
import { run as removeHooks } from "./helpers/remove-hooks.mjs";
|
|
29
32
|
|
|
30
33
|
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
31
34
|
|
|
32
35
|
const args = process.argv.slice(2);
|
|
36
|
+
|
|
37
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
38
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"));
|
|
40
|
+
console.log(pkg.version);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
const write = args.includes("--write");
|
|
34
45
|
const dryRun = !write;
|
|
35
46
|
const verbose = args.includes("--verbose");
|
|
@@ -52,6 +63,7 @@ if (!targetDir) {
|
|
|
52
63
|
console.log(" --verbose Show detailed output per transformation");
|
|
53
64
|
console.log(" --files <glob> File glob pattern (default: src/**/*.{tsx,ts})");
|
|
54
65
|
console.log(" --skip-fix Skip the type annotation fix step");
|
|
66
|
+
console.log(" --version, -v Show version number");
|
|
55
67
|
console.log();
|
|
56
68
|
console.log("Examples:");
|
|
57
69
|
console.log(" npx react-compiler-unmemo ./my-react-app # preview (safe default)");
|