reanimated-pause-state 0.1.0

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 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "reanimated-pause-state",
3
+ "version": "0.1.0",
4
+ "description": "Pause Reanimated animations and inspect their state at any frame",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./babel": {
13
+ "types": "./dist/babel.d.ts",
14
+ "default": "./dist/babel.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "react-native",
28
+ "reanimated",
29
+ "animation",
30
+ "debug",
31
+ "pause",
32
+ "state",
33
+ "snapshot",
34
+ "babel-plugin"
35
+ ],
36
+ "author": "",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/skylarbarrera/reanimated-pause-state.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/skylarbarrera/reanimated-pause-state/issues"
44
+ },
45
+ "homepage": "https://github.com/skylarbarrera/reanimated-pause-state#readme",
46
+ "peerDependencies": {
47
+ "react-native-reanimated": ">=3.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@babel/core": "^7.24.0",
51
+ "@types/babel__core": "^7.20.5",
52
+ "typescript": "^5.4.0"
53
+ }
54
+ }
package/src/babel.ts ADDED
@@ -0,0 +1,486 @@
1
+ /**
2
+ * reanimated-pause-plugin/babel
3
+ *
4
+ * Babel plugin that instruments Reanimated animation factories
5
+ * to track callsite information and animation metadata for snapshots.
6
+ *
7
+ * IMPORTANT: This plugin must run BEFORE react-native-reanimated/plugin
8
+ * or react-native-worklets/plugin in your babel config.
9
+ */
10
+
11
+ import type { PluginObj, NodePath, types as t } from '@babel/core';
12
+
13
+ const PLUGIN_VERSION = '0.1.0';
14
+
15
+ // Animation factories to instrument
16
+ const INSTRUMENTED_FACTORIES = new Map([
17
+ ['withTiming', 'timing'],
18
+ ['withSpring', 'spring'],
19
+ ['withDecay', 'decay'],
20
+ ['withRepeat', 'repeat'],
21
+ ['withSequence', 'sequence'],
22
+ ['withDelay', 'delay'],
23
+ ]);
24
+
25
+ /**
26
+ * Extract a literal value from an AST node (numbers, strings, booleans)
27
+ * Returns undefined for non-literal/dynamic values
28
+ */
29
+ function extractLiteralValue(
30
+ node: t.Node | undefined,
31
+ t: typeof import('@babel/core').types
32
+ ): number | string | boolean | undefined {
33
+ if (!node) return undefined;
34
+ if (t.isNumericLiteral(node)) return node.value;
35
+ if (t.isStringLiteral(node)) return node.value;
36
+ if (t.isBooleanLiteral(node)) return node.value;
37
+ if (t.isUnaryExpression(node) && node.operator === '-' && t.isNumericLiteral(node.argument)) {
38
+ return -node.argument.value;
39
+ }
40
+ return undefined;
41
+ }
42
+
43
+ /**
44
+ * Extract config properties from an ObjectExpression
45
+ */
46
+ function extractObjectConfig(
47
+ node: t.Node | undefined,
48
+ t: typeof import('@babel/core').types
49
+ ): Array<t.ObjectProperty> {
50
+ const properties: Array<t.ObjectProperty> = [];
51
+
52
+ if (!node || !t.isObjectExpression(node)) {
53
+ return properties;
54
+ }
55
+
56
+ for (const prop of node.properties) {
57
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
58
+ const value = extractLiteralValue(prop.value, t);
59
+ if (value !== undefined) {
60
+ if (typeof value === 'number') {
61
+ properties.push(t.objectProperty(prop.key, t.numericLiteral(value)));
62
+ } else if (typeof value === 'string') {
63
+ properties.push(t.objectProperty(prop.key, t.stringLiteral(value)));
64
+ } else if (typeof value === 'boolean') {
65
+ properties.push(t.objectProperty(prop.key, t.booleanLiteral(value)));
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ return properties;
72
+ }
73
+
74
+ /**
75
+ * Build config metadata based on animation type and arguments
76
+ */
77
+ function buildConfigMetadata(
78
+ animationType: string,
79
+ args: Array<t.Node>,
80
+ t: typeof import('@babel/core').types
81
+ ): t.ObjectExpression {
82
+ const configProps: Array<t.ObjectProperty> = [];
83
+
84
+ switch (animationType) {
85
+ case 'timing': {
86
+ // withTiming(toValue, config?)
87
+ const toValue = extractLiteralValue(args[0], t);
88
+ if (toValue !== undefined) {
89
+ configProps.push(t.objectProperty(t.identifier('toValue'), t.numericLiteral(toValue as number)));
90
+ }
91
+ // Extract duration, easing from config object
92
+ const configObjProps = extractObjectConfig(args[1], t);
93
+ configProps.push(...configObjProps);
94
+ break;
95
+ }
96
+
97
+ case 'spring': {
98
+ // withSpring(toValue, config?)
99
+ const toValue = extractLiteralValue(args[0], t);
100
+ if (toValue !== undefined) {
101
+ configProps.push(t.objectProperty(t.identifier('toValue'), t.numericLiteral(toValue as number)));
102
+ }
103
+ // Extract spring config (damping, mass, stiffness, etc.)
104
+ const configObjProps = extractObjectConfig(args[1], t);
105
+ configProps.push(...configObjProps);
106
+ break;
107
+ }
108
+
109
+ case 'decay': {
110
+ // withDecay(config)
111
+ const configObjProps = extractObjectConfig(args[0], t);
112
+ configProps.push(...configObjProps);
113
+ break;
114
+ }
115
+
116
+ case 'repeat': {
117
+ // withRepeat(animation, numberOfReps?, reverse?)
118
+ const numberOfReps = extractLiteralValue(args[1], t);
119
+ if (numberOfReps !== undefined) {
120
+ configProps.push(t.objectProperty(t.identifier('numberOfReps'), t.numericLiteral(numberOfReps as number)));
121
+ }
122
+ const reverse = extractLiteralValue(args[2], t);
123
+ if (reverse !== undefined) {
124
+ configProps.push(t.objectProperty(t.identifier('reverse'), t.booleanLiteral(reverse as boolean)));
125
+ }
126
+ break;
127
+ }
128
+
129
+ case 'delay': {
130
+ // withDelay(delayMs, animation)
131
+ const delayMs = extractLiteralValue(args[0], t);
132
+ if (delayMs !== undefined) {
133
+ configProps.push(t.objectProperty(t.identifier('delayMs'), t.numericLiteral(delayMs as number)));
134
+ }
135
+ break;
136
+ }
137
+
138
+ case 'sequence': {
139
+ // withSequence(...animations)
140
+ configProps.push(t.objectProperty(t.identifier('count'), t.numericLiteral(args.length)));
141
+ break;
142
+ }
143
+ }
144
+
145
+ return t.objectExpression(configProps);
146
+ }
147
+
148
+ interface PluginState {
149
+ filename: string;
150
+ markerInjected: boolean;
151
+ registryInjected: boolean;
152
+ factoryBindings: Map<string, string>; // local name -> original name
153
+ }
154
+
155
+ export default function reanimatedPausePlugin({
156
+ types: t,
157
+ }: {
158
+ types: typeof import('@babel/core').types;
159
+ }): PluginObj<PluginState> {
160
+ return {
161
+ name: 'reanimated-pause-plugin',
162
+
163
+ pre(file) {
164
+ this.filename = file.opts.filename || 'unknown';
165
+ this.markerInjected = false;
166
+ this.registryInjected = false;
167
+ this.factoryBindings = new Map();
168
+ },
169
+
170
+ visitor: {
171
+ Program: {
172
+ enter(path, state) {
173
+ // Inject marker at the top of the program
174
+ if (!state.markerInjected) {
175
+ const marker = t.expressionStatement(
176
+ t.assignmentExpression(
177
+ '=',
178
+ t.memberExpression(
179
+ t.identifier('globalThis'),
180
+ t.identifier('__RPP_INSTALLED__')
181
+ ),
182
+ t.objectExpression([
183
+ t.objectProperty(
184
+ t.identifier('installed'),
185
+ t.booleanLiteral(true)
186
+ ),
187
+ t.objectProperty(
188
+ t.identifier('version'),
189
+ t.stringLiteral(PLUGIN_VERSION)
190
+ ),
191
+ ])
192
+ )
193
+ );
194
+
195
+ // Check if marker already exists
196
+ const hasMarker = path.node.body.some(
197
+ (node) =>
198
+ t.isExpressionStatement(node) &&
199
+ t.isAssignmentExpression(node.expression) &&
200
+ t.isMemberExpression(node.expression.left) &&
201
+ t.isIdentifier(node.expression.left.property, {
202
+ name: '__RPP_INSTALLED__',
203
+ })
204
+ );
205
+
206
+ if (!hasMarker) {
207
+ path.unshiftContainer('body', marker);
208
+ }
209
+ state.markerInjected = true;
210
+ }
211
+ },
212
+ },
213
+
214
+ // Track imports from react-native-reanimated
215
+ ImportDeclaration(path, state) {
216
+ const source = path.node.source.value;
217
+ if (source !== 'react-native-reanimated') {
218
+ return;
219
+ }
220
+
221
+ for (const specifier of path.node.specifiers) {
222
+ if (t.isImportSpecifier(specifier)) {
223
+ const imported = t.isIdentifier(specifier.imported)
224
+ ? specifier.imported.name
225
+ : specifier.imported.value;
226
+ const local = specifier.local.name;
227
+
228
+ if (INSTRUMENTED_FACTORIES.has(imported)) {
229
+ state.factoryBindings.set(local, imported);
230
+ }
231
+ }
232
+ }
233
+ },
234
+
235
+ // Transform animation factory calls to track them
236
+ // Only track TOP-LEVEL animations (assigned to .value) to avoid noise
237
+ // Nested animations (withRepeat(withTiming(...))) share the same SharedValue
238
+ CallExpression: {
239
+ exit(path, state) {
240
+ const callee = path.node.callee;
241
+
242
+ if (!t.isIdentifier(callee)) {
243
+ return;
244
+ }
245
+
246
+ const localName = callee.name;
247
+ const originalName = state.factoryBindings.get(localName);
248
+
249
+ if (!originalName || !INSTRUMENTED_FACTORIES.has(originalName)) {
250
+ return;
251
+ }
252
+
253
+ // Check if this is a top-level animation being assigned to a SharedValue
254
+ // Pattern: sharedValue.value = withAnimation(...)
255
+ // Only track these - nested animations are just configuration
256
+ let sharedValueRef: t.Expression | null = null;
257
+ let sharedValueName: string | null = null;
258
+ const parentAssignment = path.findParent((p) => p.isAssignmentExpression());
259
+
260
+ if (parentAssignment && t.isAssignmentExpression(parentAssignment.node)) {
261
+ const left = parentAssignment.node.left;
262
+ // Check if left side is X.value (MemberExpression with property 'value')
263
+ if (
264
+ t.isMemberExpression(left) &&
265
+ t.isIdentifier(left.property, { name: 'value' })
266
+ ) {
267
+ // Check if this call is the direct right-hand side of the assignment
268
+ // (not a nested animation inside another)
269
+ if (parentAssignment.node.right === path.node) {
270
+ sharedValueRef = left.object as t.Expression;
271
+ // Try to get the variable name for better descriptions
272
+ if (t.isIdentifier(left.object)) {
273
+ sharedValueName = left.object.name;
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ // Only track if this is assigned to a SharedValue (top-level)
280
+ if (!sharedValueRef) {
281
+ return; // Skip nested animations - they don't need separate tracking
282
+ }
283
+
284
+ const animationType = INSTRUMENTED_FACTORIES.get(originalName)!;
285
+ const loc = path.node.loc;
286
+
287
+ // Get relative filename
288
+ const filename = state.filename
289
+ .replace(/^.*\/node_modules\//, 'node_modules/')
290
+ .replace(/^.*\/packages\//, 'packages/');
291
+
292
+ // Extract animation config from arguments (recursively for nested animations)
293
+ const configMeta = buildConfigMetadata(
294
+ animationType,
295
+ path.node.arguments as t.Node[],
296
+ t
297
+ );
298
+
299
+ // Create metadata object
300
+ const metadataProperties: t.ObjectProperty[] = [
301
+ t.objectProperty(
302
+ t.identifier('__rpp_type'),
303
+ t.stringLiteral(animationType)
304
+ ),
305
+ t.objectProperty(
306
+ t.identifier('__rpp_file'),
307
+ t.stringLiteral(filename)
308
+ ),
309
+ t.objectProperty(
310
+ t.identifier('__rpp_line'),
311
+ t.numericLiteral(loc?.start.line || 0)
312
+ ),
313
+ t.objectProperty(
314
+ t.identifier('__rpp_column'),
315
+ t.numericLiteral(loc?.start.column || 0)
316
+ ),
317
+ t.objectProperty(
318
+ t.identifier('__rpp_id'),
319
+ t.callExpression(
320
+ t.memberExpression(t.identifier('Math'), t.identifier('random')),
321
+ []
322
+ )
323
+ ),
324
+ t.objectProperty(
325
+ t.identifier('__rpp_config'),
326
+ configMeta
327
+ ),
328
+ t.objectProperty(
329
+ t.identifier('__rpp_sharedValue'),
330
+ sharedValueRef
331
+ ),
332
+ ];
333
+
334
+ // Add SharedValue variable name if we captured it
335
+ if (sharedValueName) {
336
+ metadataProperties.push(
337
+ t.objectProperty(
338
+ t.identifier('__rpp_sharedValueName'),
339
+ t.stringLiteral(sharedValueName)
340
+ )
341
+ );
342
+ }
343
+
344
+ const metadata = t.objectExpression(metadataProperties);
345
+
346
+ // Wrap the call: __rppTrack(withTiming(...), metadata)
347
+ const wrappedCall = t.callExpression(
348
+ t.identifier('__rppTrack'),
349
+ [
350
+ t.callExpression(callee, path.node.arguments as t.Expression[]),
351
+ metadata,
352
+ ]
353
+ );
354
+
355
+ // Inject the tracker function if not already done
356
+ if (!state.registryInjected) {
357
+ injectTrackerFunction(path, t);
358
+ state.registryInjected = true;
359
+ }
360
+
361
+ path.replaceWith(wrappedCall);
362
+ path.skip(); // Don't revisit this node
363
+ },
364
+ },
365
+ },
366
+ };
367
+ }
368
+
369
+ /**
370
+ * Inject the __rppTrack function that registers animations
371
+ */
372
+ function injectTrackerFunction(
373
+ path: NodePath<t.CallExpression>,
374
+ t: typeof import('@babel/core').types
375
+ ): void {
376
+ const program = path.findParent((p) => p.isProgram()) as NodePath<t.Program>;
377
+ if (!program) return;
378
+
379
+ // Check if already injected
380
+ const hasTracker = program.node.body.some(
381
+ (node) =>
382
+ t.isVariableDeclaration(node) &&
383
+ node.declarations.some(
384
+ (d) => t.isIdentifier(d.id) && d.id.name === '__rppTrack'
385
+ )
386
+ );
387
+
388
+ if (hasTracker) return;
389
+
390
+ // Create the tracker function
391
+ // const __rppTrack = (animation, meta) => {
392
+ // if (animation && typeof animation === 'object') {
393
+ // animation.__rpp_meta = meta;
394
+ // }
395
+ // if (globalThis.__RPP_REGISTRY__?.register) {
396
+ // globalThis.__RPP_REGISTRY__.register(meta);
397
+ // }
398
+ // return animation;
399
+ // };
400
+ const tracker = t.variableDeclaration('const', [
401
+ t.variableDeclarator(
402
+ t.identifier('__rppTrack'),
403
+ t.arrowFunctionExpression(
404
+ [t.identifier('animation'), t.identifier('meta')],
405
+ t.blockStatement([
406
+ // if (animation && typeof animation === 'object') { animation.__rpp_meta = meta; }
407
+ t.ifStatement(
408
+ t.logicalExpression(
409
+ '&&',
410
+ t.identifier('animation'),
411
+ t.binaryExpression(
412
+ '===',
413
+ t.unaryExpression('typeof', t.identifier('animation')),
414
+ t.stringLiteral('object')
415
+ )
416
+ ),
417
+ t.expressionStatement(
418
+ t.assignmentExpression(
419
+ '=',
420
+ t.memberExpression(
421
+ t.identifier('animation'),
422
+ t.identifier('__rpp_meta')
423
+ ),
424
+ t.identifier('meta')
425
+ )
426
+ )
427
+ ),
428
+ // if (globalThis.__RPP_REGISTRY__?.register) { ... }
429
+ t.ifStatement(
430
+ t.optionalMemberExpression(
431
+ t.memberExpression(
432
+ t.identifier('globalThis'),
433
+ t.identifier('__RPP_REGISTRY__')
434
+ ),
435
+ t.identifier('register'),
436
+ false,
437
+ true
438
+ ),
439
+ t.expressionStatement(
440
+ t.callExpression(
441
+ t.memberExpression(
442
+ t.memberExpression(
443
+ t.identifier('globalThis'),
444
+ t.identifier('__RPP_REGISTRY__')
445
+ ),
446
+ t.identifier('register')
447
+ ),
448
+ [t.identifier('meta')]
449
+ )
450
+ )
451
+ ),
452
+ // return animation;
453
+ t.returnStatement(t.identifier('animation')),
454
+ ])
455
+ )
456
+ ),
457
+ ]);
458
+
459
+ // Find insertion point (after imports)
460
+ let insertIndex = 0;
461
+ for (let i = 0; i < program.node.body.length; i++) {
462
+ const node = program.node.body[i];
463
+ if (!t.isImportDeclaration(node)) {
464
+ // Skip the marker if present
465
+ if (
466
+ t.isExpressionStatement(node) &&
467
+ t.isAssignmentExpression(node.expression) &&
468
+ t.isMemberExpression(node.expression.left) &&
469
+ t.isIdentifier(node.expression.left.property, {
470
+ name: '__RPP_INSTALLED__',
471
+ })
472
+ ) {
473
+ insertIndex = i + 1;
474
+ continue;
475
+ }
476
+ insertIndex = i;
477
+ break;
478
+ }
479
+ insertIndex = i + 1;
480
+ }
481
+
482
+ program.node.body.splice(insertIndex, 0, tracker);
483
+ }
484
+
485
+ module.exports = reanimatedPausePlugin;
486
+ module.exports.default = reanimatedPausePlugin;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * React Native global development flag
3
+ */
4
+ declare const __DEV__: boolean;
5
+
6
+ /**
7
+ * Extend globalThis for worklet context (UI thread has requestAnimationFrame)
8
+ */
9
+ declare namespace globalThis {
10
+ var requestAnimationFrame: ((callback: (timestamp: number) => void) => number) | undefined;
11
+ }