main
ts 180 lines 5.21 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import type * as BabelCore from '@babel/core';
9 import type * as t from '@babel/types';
10
11 export interface ResolvedOptions {
12 // Pre-resolved by JS
13 shouldCompile: boolean;
14 enableReanimated: boolean;
15 isDev: boolean;
16 filename: string | null;
17
18 // Pass-through
19 compilationMode: string;
20 panicThreshold: string;
21 target: unknown;
22 gating: unknown;
23 dynamicGating: unknown;
24 noEmit: boolean;
25 outputMode: string | null;
26 eslintSuppressionRules: string[] | null;
27 flowSuppressions: boolean;
28 ignoreUseNoForget: boolean;
29 customOptOutDirectives: string[] | null;
30 environment: Record<string, unknown>;
31 }
32
33 export interface Logger {
34 logEvent(filename: string | null, event: unknown): void;
35 debugLogIRs?(value: unknown): void;
36 }
37
38 export type PluginOptions = Partial<ResolvedOptions> & {
39 sources?: ((filename: string) => boolean) | string[];
40 enableReanimatedCheck?: boolean;
41 logger?: Logger | null;
42 } & Record<string, unknown>;
43
44 /**
45 * Check if the Babel pipeline uses the Reanimated plugin.
46 */
47 function pipelineUsesReanimatedPlugin(
48 plugins: Array<BabelCore.PluginItem> | null | undefined,
49 ): boolean {
50 if (Array.isArray(plugins)) {
51 for (const plugin of plugins) {
52 if (plugin != null && typeof plugin === 'object' && 'key' in plugin) {
53 const key = (plugin as any).key;
54 if (
55 typeof key === 'string' &&
56 key.indexOf('react-native-reanimated') !== -1
57 ) {
58 return true;
59 }
60 }
61 }
62 }
63 // Check if reanimated module is available
64 if (typeof require !== 'undefined') {
65 try {
66 return !!require.resolve('react-native-reanimated');
67 } catch {
68 return false;
69 }
70 }
71 return false;
72 }
73
74 /**
75 * Prepare the environment config for JSON serialization to Rust.
76 * Converts Map instances to plain objects, pre-resolves moduleTypeProvider,
77 * and strips non-serializable fields.
78 */
79 function serializeEnvironment(
80 rawEnv: Record<string, unknown>,
81 ast: t.File,
82 ): Record<string, unknown> {
83 const environment: Record<string, unknown> = {...rawEnv};
84
85 // Convert customHooks Map to plain object for JSON serialization
86 if (rawEnv.customHooks instanceof Map) {
87 const hooks: Record<string, unknown> = {};
88 for (const [key, value] of rawEnv.customHooks) {
89 hooks[key] = value;
90 }
91 environment.customHooks = hooks;
92 }
93
94 // Pre-resolve moduleTypeProvider: collect all import sources from AST,
95 // call the provider for each, and serialize results as a map
96 const moduleTypeProvider = rawEnv.moduleTypeProvider as
97 | ((name: string) => unknown)
98 | null
99 | undefined;
100 delete environment.moduleTypeProvider;
101
102 if (typeof moduleTypeProvider === 'function') {
103 const moduleTypes: Record<string, unknown> = {};
104 for (const node of ast.program.body) {
105 if (
106 node.type === 'ImportDeclaration' &&
107 typeof node.source.value === 'string'
108 ) {
109 const moduleName = node.source.value;
110 if (!(moduleName in moduleTypes)) {
111 const result = moduleTypeProvider(moduleName);
112 if (result != null) {
113 moduleTypes[moduleName] = result;
114 }
115 }
116 }
117 }
118 if (Object.keys(moduleTypes).length > 0) {
119 environment.moduleTypeProvider = moduleTypes;
120 }
121 }
122
123 delete environment.flowTypeProvider;
124
125 return environment;
126 }
127
128 export function resolveOptions(
129 rawOpts: PluginOptions,
130 file: BabelCore.BabelFile,
131 filename: string | null,
132 ast: t.File,
133 ): ResolvedOptions {
134 // Resolve sources filter (may be a function)
135 let shouldCompile = true;
136 if (rawOpts.sources != null && filename != null) {
137 if (typeof rawOpts.sources === 'function') {
138 shouldCompile = rawOpts.sources(filename);
139 } else if (Array.isArray(rawOpts.sources)) {
140 shouldCompile = rawOpts.sources.some(
141 (prefix: string) => filename.indexOf(prefix) !== -1,
142 );
143 }
144 } else if (rawOpts.sources != null && filename == null) {
145 shouldCompile = false; // sources specified but no filename
146 }
147
148 // Resolve reanimated check
149 const enableReanimated =
150 rawOpts.enableReanimatedCheck !== false &&
151 pipelineUsesReanimatedPlugin(file.opts.plugins);
152
153 // Resolve isDev
154 const isDev =
155 (typeof globalThis !== 'undefined' &&
156 (globalThis as any).__DEV__ === true) ||
157 process.env['NODE_ENV'] === 'development';
158
159 return {
160 shouldCompile,
161 enableReanimated,
162 isDev,
163 filename,
164 compilationMode: (rawOpts.compilationMode as string) ?? 'infer',
165 panicThreshold: (rawOpts.panicThreshold as string) ?? 'none',
166 target: rawOpts.target ?? '19',
167 gating: rawOpts.gating ?? null,
168 dynamicGating: rawOpts.dynamicGating ?? null,
169 noEmit: rawOpts.noEmit ?? false,
170 outputMode: (rawOpts.outputMode as string) ?? null,
171 eslintSuppressionRules: rawOpts.eslintSuppressionRules ?? null,
172 flowSuppressions: rawOpts.flowSuppressions ?? true,
173 ignoreUseNoForget: rawOpts.ignoreUseNoForget ?? false,
174 customOptOutDirectives: rawOpts.customOptOutDirectives ?? null,
175 environment: serializeEnvironment(
176 (rawOpts.environment as Record<string, unknown>) ?? {},
177 ast,
178 ),
179 };
180 }