@usels/babel-plugin-legend-memo 0.0.1-beta.3

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.
@@ -0,0 +1,149 @@
1
+ import pluginTester from 'babel-plugin-tester';
2
+ import plugin from '../src';
3
+
4
+ const babelOptions = {
5
+ plugins: ['@babel/plugin-syntax-jsx'],
6
+ configFile: false,
7
+ babelrc: false,
8
+ };
9
+
10
+ pluginTester({
11
+ plugin,
12
+ pluginOptions: { componentName: 'Reactive', importSource: '@usels/core' },
13
+ babelOptions,
14
+ title: 'componentName option',
15
+ tests: {
16
+ 'uses custom componentName "Reactive"': {
17
+ code: `
18
+ function App() {
19
+ return <div>{count$.get()}</div>;
20
+ }
21
+ `,
22
+ output: `
23
+ import { Reactive } from "@usels/core";
24
+ function App() {
25
+ return (
26
+ <div>
27
+ <Reactive>{() => count$.get()}</Reactive>
28
+ </div>
29
+ );
30
+ }
31
+ `,
32
+ },
33
+ },
34
+ });
35
+
36
+ pluginTester({
37
+ plugin,
38
+ pluginOptions: { importSource: '@my/lib' },
39
+ babelOptions,
40
+ title: 'importSource option',
41
+ tests: {
42
+ 'uses custom importSource "@my/lib"': {
43
+ code: `
44
+ function App() {
45
+ return <div>{count$.get()}</div>;
46
+ }
47
+ `,
48
+ output: `
49
+ import { Memo } from "@my/lib";
50
+ function App() {
51
+ return (
52
+ <div>
53
+ <Memo>{() => count$.get()}</Memo>
54
+ </div>
55
+ );
56
+ }
57
+ `,
58
+ },
59
+
60
+ 'does not duplicate import when already imported from @my/lib': {
61
+ code: `
62
+ import { Memo } from "@my/lib";
63
+ function App() {
64
+ return <div>{count$.get()}</div>;
65
+ }
66
+ `,
67
+ output: `
68
+ import { Memo } from "@my/lib";
69
+ function App() {
70
+ return (
71
+ <div>
72
+ <Memo>{() => count$.get()}</Memo>
73
+ </div>
74
+ );
75
+ }
76
+ `,
77
+ },
78
+ },
79
+ });
80
+
81
+ pluginTester({
82
+ plugin,
83
+ pluginOptions: { allGet: true },
84
+ babelOptions,
85
+ title: 'allGet option',
86
+ tests: {
87
+ 'with allGet:true, wraps store.get() without $ suffix': {
88
+ code: `
89
+ function App() {
90
+ return <div>{store.get()}</div>;
91
+ }
92
+ `,
93
+ output: `
94
+ import { Memo } from "@legendapp/state/react";
95
+ function App() {
96
+ return (
97
+ <div>
98
+ <Memo>{() => store.get()}</Memo>
99
+ </div>
100
+ );
101
+ }
102
+ `,
103
+ },
104
+
105
+ 'with allGet:true, still ignores .get() with args': {
106
+ code: `
107
+ function App() {
108
+ return <div>{map.get("key")}</div>;
109
+ }
110
+ `,
111
+ },
112
+
113
+ 'with allGet:true, still ignores .get() inside function boundary': {
114
+ code: `
115
+ function App() {
116
+ return <button onClick={() => store.get()}>click</button>;
117
+ }
118
+ `,
119
+ },
120
+ },
121
+ });
122
+
123
+ pluginTester({
124
+ plugin,
125
+ pluginOptions: { reactiveComponents: ['CustomReactive'] },
126
+ babelOptions,
127
+ title: 'reactiveComponents option',
128
+ tests: {
129
+ 'does NOT wrap inside custom reactive component': {
130
+ code: `
131
+ function App() {
132
+ return <CustomReactive>{() => count$.get()}</CustomReactive>;
133
+ }
134
+ `,
135
+ },
136
+ },
137
+ });
138
+
139
+ pluginTester({
140
+ plugin,
141
+ pluginOptions: { observerNames: ['reactive'] },
142
+ babelOptions,
143
+ title: 'observerNames option',
144
+ tests: {
145
+ 'does NOT wrap inside custom observer HOC "reactive"': {
146
+ code: `const Comp = reactive(() => <div>{obs$.get()}</div>);`,
147
+ },
148
+ },
149
+ });
@@ -0,0 +1,265 @@
1
+ import pluginTester from 'babel-plugin-tester';
2
+ import plugin from '../src';
3
+
4
+ const babelOptions = {
5
+ plugins: ['@babel/plugin-syntax-jsx'],
6
+ configFile: false,
7
+ babelrc: false,
8
+ };
9
+
10
+ pluginTester({
11
+ plugin,
12
+ pluginOptions: {},
13
+ babelOptions,
14
+ title: 'reactive children auto-wrapping (Memo / Show / Computed)',
15
+ tests: {
16
+ // --- Basic wrapping ---
17
+
18
+ 'wraps Memo children in () => arrow function': {
19
+ code: `
20
+ function App() {
21
+ return <Memo>{count$.get()}</Memo>;
22
+ }
23
+ `,
24
+ output: `
25
+ function App() {
26
+ return <Memo>{() => count$.get()}</Memo>;
27
+ }
28
+ `,
29
+ },
30
+
31
+ 'wraps Show children in () => arrow function': {
32
+ code: `
33
+ function App() {
34
+ return <Show if={cond$}>{count$.get()}</Show>;
35
+ }
36
+ `,
37
+ output: `
38
+ function App() {
39
+ return <Show if={cond$}>{() => count$.get()}</Show>;
40
+ }
41
+ `,
42
+ },
43
+
44
+ 'wraps Computed children in () => arrow function': {
45
+ code: `
46
+ function App() {
47
+ return <Computed>{count$.get()}</Computed>;
48
+ }
49
+ `,
50
+ output: `
51
+ function App() {
52
+ return <Computed>{() => count$.get()}</Computed>;
53
+ }
54
+ `,
55
+ },
56
+
57
+ // --- Skip cases (children already function / reference) ---
58
+
59
+ 'does NOT re-wrap already arrow function children': {
60
+ code: `
61
+ function App() {
62
+ return <Memo>{() => count$.get()}</Memo>;
63
+ }
64
+ `,
65
+ },
66
+
67
+ 'does NOT wrap Identifier children (already a reference)': {
68
+ code: `
69
+ function App() {
70
+ return <Memo>{renderFn}</Memo>;
71
+ }
72
+ `,
73
+ },
74
+
75
+ 'does NOT wrap MemberExpression children (already a reference)': {
76
+ code: `
77
+ function App() {
78
+ return <Memo>{obj.render}</Memo>;
79
+ }
80
+ `,
81
+ },
82
+
83
+ 'does NOT wrap FunctionExpression children': {
84
+ code: `
85
+ function App() {
86
+ return <Memo>{function() { return count$.get(); }}</Memo>;
87
+ }
88
+ `,
89
+ output: `
90
+ function App() {
91
+ return (
92
+ <Memo>
93
+ {function () {
94
+ return count$.get();
95
+ }}
96
+ </Memo>
97
+ );
98
+ }
99
+ `,
100
+ },
101
+
102
+ // --- Direct JSX element child ---
103
+
104
+ 'wraps direct JSX element child in arrow function': {
105
+ code: `
106
+ function App() {
107
+ return <Memo><div>hello</div></Memo>;
108
+ }
109
+ `,
110
+ output: `
111
+ function App() {
112
+ return <Memo>{() => <div>hello</div>}</Memo>;
113
+ }
114
+ `,
115
+ },
116
+
117
+ 'wraps direct JSX element with .get() inside': {
118
+ code: `
119
+ function App() {
120
+ return <Memo><span>{count$.get()}</span></Memo>;
121
+ }
122
+ `,
123
+ output: `
124
+ function App() {
125
+ return <Memo>{() => <span>{count$.get()}</span>}</Memo>;
126
+ }
127
+ `,
128
+ },
129
+
130
+ // --- Multiple children → Fragment ---
131
+
132
+ 'wraps multiple children in Fragment arrow function': {
133
+ code: `
134
+ function App() {
135
+ return <Memo><A /><B /></Memo>;
136
+ }
137
+ `,
138
+ output: `
139
+ function App() {
140
+ return (
141
+ <Memo>
142
+ {() => (
143
+ <>
144
+ <A />
145
+ <B />
146
+ </>
147
+ )}
148
+ </Memo>
149
+ );
150
+ }
151
+ `,
152
+ },
153
+
154
+ // --- Combined: children wrapping + attribute .get() → full Memo wrap ---
155
+
156
+ 'combined: Show with .get() attribute wraps children AND whole element in Memo': {
157
+ code: `
158
+ function App() {
159
+ return <Show if={obs$.get()}>{count$.get()}</Show>;
160
+ }
161
+ `,
162
+ output: `
163
+ import { Memo } from "@legendapp/state/react";
164
+ function App() {
165
+ return <Memo>{() => <Show if={obs$.get()}>{() => count$.get()}</Show>}</Memo>;
166
+ }
167
+ `,
168
+ },
169
+
170
+ // --- Memo empty children → no transform ---
171
+
172
+ 'does NOT transform Memo with no children': {
173
+ code: `
174
+ function App() {
175
+ return <Memo></Memo>;
176
+ }
177
+ `,
178
+ },
179
+
180
+ // --- Nested complex expression ---
181
+
182
+ 'wraps Show children that contain complex expression': {
183
+ code: `
184
+ function App() {
185
+ return <Show if={cond$}>{a$.get() + b$.get()}</Show>;
186
+ }
187
+ `,
188
+ output: `
189
+ function App() {
190
+ return <Show if={cond$}>{() => a$.get() + b$.get()}</Show>;
191
+ }
192
+ `,
193
+ },
194
+ },
195
+ });
196
+
197
+ // --- wrapReactiveChildren: false ---
198
+
199
+ pluginTester({
200
+ plugin,
201
+ pluginOptions: { wrapReactiveChildren: false },
202
+ babelOptions,
203
+ title: 'wrapReactiveChildren: false — disables auto-wrapping',
204
+ tests: {
205
+ 'does NOT wrap Memo children when feature is disabled': {
206
+ code: `
207
+ function App() {
208
+ return <Memo>{count$.get()}</Memo>;
209
+ }
210
+ `,
211
+ },
212
+
213
+ 'does NOT wrap Show children when feature is disabled': {
214
+ code: `
215
+ function App() {
216
+ return <Show if={cond$}>{count$.get()}</Show>;
217
+ }
218
+ `,
219
+ },
220
+
221
+ 'does NOT wrap Computed children when feature is disabled': {
222
+ code: `
223
+ function App() {
224
+ return <Computed>{count$.get()}</Computed>;
225
+ }
226
+ `,
227
+ },
228
+ },
229
+ });
230
+
231
+ // --- wrapReactiveChildrenComponents: custom extension ---
232
+
233
+ pluginTester({
234
+ plugin,
235
+ pluginOptions: { wrapReactiveChildrenComponents: ['Custom'] },
236
+ babelOptions,
237
+ title: 'wrapReactiveChildrenComponents — extends default set',
238
+ tests: {
239
+ 'wraps children of custom reactive component': {
240
+ code: `
241
+ function App() {
242
+ return <Custom>{count$.get()}</Custom>;
243
+ }
244
+ `,
245
+ output: `
246
+ function App() {
247
+ return <Custom>{() => count$.get()}</Custom>;
248
+ }
249
+ `,
250
+ },
251
+
252
+ 'still wraps default Memo children alongside custom': {
253
+ code: `
254
+ function App() {
255
+ return <Memo>{count$.get()}</Memo>;
256
+ }
257
+ `,
258
+ output: `
259
+ function App() {
260
+ return <Memo>{() => count$.get()}</Memo>;
261
+ }
262
+ `,
263
+ },
264
+ },
265
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["src", "tests"],
4
+ "compilerOptions": {
5
+ "outDir": "./dist",
6
+ "rootDir": ".",
7
+ "moduleResolution": "node",
8
+ "module": "CommonJS",
9
+ "target": "ES2020"
10
+ }
11
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ external: ['@babel/core', '@babel/types', '@babel/traverse'],
8
+ clean: true,
9
+ sourcemap: true,
10
+ });
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['tests/**/*.test.ts'],
6
+ environment: 'node',
7
+ globals: true,
8
+ },
9
+ });