main
js 477 lines 15.4 KB
Raw
1 'use strict';
2
3 const fs = require('node:fs');
4 const {bundleTypes, moduleTypes} = require('./bundles');
5 const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
6
7 const {
8 FB_WWW_DEV,
9 FB_WWW_PROD,
10 FB_WWW_PROFILING,
11 RN_OSS_DEV,
12 RN_OSS_PROD,
13 RN_OSS_PROFILING,
14 RN_FB_DEV,
15 RN_FB_PROD,
16 RN_FB_PROFILING,
17 } = bundleTypes;
18 const {RENDERER, RECONCILER} = moduleTypes;
19
20 const RELEASE_CHANNEL = process.env.RELEASE_CHANNEL;
21
22 // Default to building in experimental mode. If the release channel is set via
23 // an environment variable, then check if it's "experimental".
24 const __EXPERIMENTAL__ =
25 typeof RELEASE_CHANNEL === 'string'
26 ? RELEASE_CHANNEL === 'experimental'
27 : true;
28
29 function findNearestExistingForkFile(path, segmentedIdentifier, suffix) {
30 const segments = segmentedIdentifier.split('-');
31 while (segments.length) {
32 const candidate = segments.join('-');
33 const forkPath = path + candidate + suffix;
34 try {
35 fs.statSync(forkPath);
36 return forkPath;
37 } catch (error) {
38 // Try the next candidate.
39 }
40 segments.pop();
41 }
42 return null;
43 }
44
45 // If you need to replace a file with another file for a specific environment,
46 // add it to this list with the logic for choosing the right replacement.
47
48 // Fork paths are relative to the project root. They must include the full path,
49 // including the extension. We intentionally don't use Node's module resolution
50 // algorithm because 1) require.resolve doesn't work with ESM modules, and 2)
51 // the behavior is easier to predict.
52 const forks = Object.freeze({
53 // Without this fork, importing `shared/ReactSharedInternals` inside
54 // the `react` package itself would not work due to a cyclical dependency.
55 './packages/shared/ReactSharedInternals.js': (
56 bundleType,
57 entry,
58 dependencies,
59 _moduleType,
60 bundle
61 ) => {
62 if (entry === 'react') {
63 return './packages/react/src/ReactSharedInternalsClient.js';
64 }
65 if (entry === 'react/src/ReactServer.js') {
66 return './packages/react/src/ReactSharedInternalsServer.js';
67 }
68 if (entry === 'react-markup/src/ReactMarkupServer.js') {
69 // Inside the ReactMarkupServer render we don't refer to any shared internals
70 // but instead use our own internal copy of the state because you cannot use
71 // any of this state from a component anyway. E.g. you can't use a client hook.
72 return './packages/react/src/ReactSharedInternalsClient.js';
73 }
74 if (bundle.condition === 'react-server') {
75 return './packages/react-server/src/ReactSharedInternalsServer.js';
76 }
77 if (!entry.startsWith('react/') && dependencies.indexOf('react') === -1) {
78 // React internals are unavailable if we can't reference the package.
79 // We return an error because we only want to throw if this module gets used.
80 return new Error(
81 'Cannot use a module that depends on ReactSharedInternals ' +
82 'from "' +
83 entry +
84 '" because it does not declare "react" in the package ' +
85 'dependencies or peerDependencies.'
86 );
87 }
88 return null;
89 },
90
91 // Without this fork, importing `shared/ReactDOMSharedInternals` inside
92 // the `react-dom` package itself would not work due to a cyclical dependency.
93 './packages/shared/ReactDOMSharedInternals.js': (
94 bundleType,
95 entry,
96 dependencies
97 ) => {
98 if (
99 entry === 'react-dom' ||
100 entry === 'react-dom/src/ReactDOMFB.js' ||
101 entry === 'react-dom/src/ReactDOMTestingFB.js' ||
102 entry === 'react-dom/src/ReactDOMServer.js' ||
103 entry === 'react-markup/src/ReactMarkupClient.js' ||
104 entry === 'react-markup/src/ReactMarkupServer.js'
105 ) {
106 if (
107 bundleType === FB_WWW_DEV ||
108 bundleType === FB_WWW_PROD ||
109 bundleType === FB_WWW_PROFILING
110 ) {
111 return './packages/react-dom/src/ReactDOMSharedInternalsFB.js';
112 } else {
113 return './packages/react-dom/src/ReactDOMSharedInternals.js';
114 }
115 }
116 if (
117 !entry.startsWith('react-dom/') &&
118 dependencies.indexOf('react-dom') === -1
119 ) {
120 // React DOM internals are unavailable if we can't reference the package.
121 // We return an error because we only want to throw if this module gets used.
122 return new Error(
123 'Cannot use a module that depends on ReactDOMSharedInternals ' +
124 'from "' +
125 entry +
126 '" because it does not declare "react-dom" in the package ' +
127 'dependencies or peerDependencies.'
128 );
129 }
130 return null;
131 },
132
133 // We have a few forks for different environments.
134 './packages/shared/ReactFeatureFlags.js': (bundleType, entry) => {
135 switch (entry) {
136 case 'react-native-renderer/fabric':
137 switch (bundleType) {
138 case RN_FB_DEV:
139 case RN_FB_PROD:
140 case RN_FB_PROFILING:
141 return './packages/shared/forks/ReactFeatureFlags.native-fb.js';
142 case RN_OSS_DEV:
143 case RN_OSS_PROD:
144 case RN_OSS_PROFILING:
145 return './packages/shared/forks/ReactFeatureFlags.native-oss.js';
146 default:
147 throw Error(
148 `Unexpected entry (${entry}) and bundleType (${bundleType})`
149 );
150 }
151 case 'eslint-plugin-react-hooks/src/index.ts':
152 switch (bundleType) {
153 case FB_WWW_DEV:
154 case FB_WWW_PROD:
155 case FB_WWW_PROFILING:
156 return './packages/shared/forks/ReactFeatureFlags.eslint-plugin.www.js';
157 }
158 return null;
159 case 'react-test-renderer':
160 switch (bundleType) {
161 case RN_FB_DEV:
162 case RN_FB_PROD:
163 case RN_FB_PROFILING:
164 return './packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js';
165 case FB_WWW_DEV:
166 case FB_WWW_PROD:
167 case FB_WWW_PROFILING:
168 return './packages/shared/forks/ReactFeatureFlags.test-renderer.www.js';
169 }
170 return './packages/shared/forks/ReactFeatureFlags.test-renderer.js';
171 default:
172 switch (bundleType) {
173 case FB_WWW_DEV:
174 case FB_WWW_PROD:
175 case FB_WWW_PROFILING:
176 return './packages/shared/forks/ReactFeatureFlags.www.js';
177 case RN_FB_DEV:
178 case RN_FB_PROD:
179 case RN_FB_PROFILING:
180 return './packages/shared/forks/ReactFeatureFlags.native-fb.js';
181 }
182 }
183 return null;
184 },
185
186 './packages/scheduler/src/SchedulerFeatureFlags.js': (
187 bundleType,
188 entry,
189 dependencies
190 ) => {
191 switch (bundleType) {
192 case FB_WWW_DEV:
193 case FB_WWW_PROD:
194 case FB_WWW_PROFILING:
195 return './packages/scheduler/src/forks/SchedulerFeatureFlags.www.js';
196 case RN_FB_DEV:
197 case RN_FB_PROD:
198 case RN_FB_PROFILING:
199 return './packages/scheduler/src/forks/SchedulerFeatureFlags.native-fb.js';
200 default:
201 return './packages/scheduler/src/SchedulerFeatureFlags.js';
202 }
203 },
204
205 './packages/shared/DefaultPrepareStackTrace.js': (
206 bundleType,
207 entry,
208 dependencies,
209 moduleType
210 ) => {
211 if (moduleType !== RENDERER && moduleType !== RECONCILER) {
212 return null;
213 }
214 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
215 for (let rendererInfo of inlinedHostConfigs) {
216 if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
217 if (!rendererInfo.isServerSupported) {
218 return null;
219 }
220 const foundFork = findNearestExistingForkFile(
221 './packages/shared/forks/DefaultPrepareStackTrace.',
222 rendererInfo.shortName,
223 '.js'
224 );
225 if (foundFork) {
226 return foundFork;
227 }
228 // fall through to error
229 break;
230 }
231 }
232 return null;
233 },
234
235 './packages/react-reconciler/src/ReactFiberConfig.js': (
236 bundleType,
237 entry,
238 dependencies,
239 moduleType
240 ) => {
241 if (dependencies.indexOf('react-reconciler') !== -1) {
242 return null;
243 }
244 if (moduleType !== RENDERER && moduleType !== RECONCILER) {
245 return null;
246 }
247 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
248 for (let rendererInfo of inlinedHostConfigs) {
249 if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
250 const foundFork = findNearestExistingForkFile(
251 './packages/react-reconciler/src/forks/ReactFiberConfig.',
252 rendererInfo.shortName,
253 '.js'
254 );
255 if (foundFork) {
256 return foundFork;
257 }
258 // fall through to error
259 break;
260 }
261 }
262 throw new Error(
263 'Expected ReactFiberConfig to always be replaced with a shim, but ' +
264 `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
265 'Did you mean to add it there to associate it with a specific renderer?'
266 );
267 },
268
269 './packages/react-server/src/ReactServerStreamConfig.js': (
270 bundleType,
271 entry,
272 dependencies,
273 moduleType
274 ) => {
275 if (dependencies.indexOf('react-server') !== -1) {
276 return null;
277 }
278 if (moduleType !== RENDERER && moduleType !== RECONCILER) {
279 return null;
280 }
281 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
282 for (let rendererInfo of inlinedHostConfigs) {
283 if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
284 if (!rendererInfo.isServerSupported) {
285 return null;
286 }
287 const foundFork = findNearestExistingForkFile(
288 './packages/react-server/src/forks/ReactServerStreamConfig.',
289 rendererInfo.shortName,
290 '.js'
291 );
292 if (foundFork) {
293 return foundFork;
294 }
295 // fall through to error
296 break;
297 }
298 }
299 throw new Error(
300 'Expected ReactServerStreamConfig to always be replaced with a shim, but ' +
301 `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
302 'Did you mean to add it there to associate it with a specific renderer?'
303 );
304 },
305
306 './packages/react-server/src/ReactFizzConfig.js': (
307 bundleType,
308 entry,
309 dependencies,
310 moduleType
311 ) => {
312 if (dependencies.indexOf('react-server') !== -1) {
313 return null;
314 }
315 if (moduleType !== RENDERER && moduleType !== RECONCILER) {
316 return null;
317 }
318 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
319 for (let rendererInfo of inlinedHostConfigs) {
320 if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
321 if (!rendererInfo.isServerSupported) {
322 return null;
323 }
324 const foundFork = findNearestExistingForkFile(
325 './packages/react-server/src/forks/ReactFizzConfig.',
326 rendererInfo.shortName,
327 '.js'
328 );
329 if (foundFork) {
330 return foundFork;
331 }
332 // fall through to error
333 break;
334 }
335 }
336 throw new Error(
337 'Expected ReactFizzConfig to always be replaced with a shim, but ' +
338 `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
339 'Did you mean to add it there to associate it with a specific renderer?'
340 );
341 },
342
343 './packages/react-server/src/ReactFlightServerConfig.js': (
344 bundleType,
345 entry,
346 dependencies,
347 moduleType
348 ) => {
349 if (dependencies.indexOf('react-server') !== -1) {
350 return null;
351 }
352 if (moduleType !== RENDERER && moduleType !== RECONCILER) {
353 return null;
354 }
355 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
356 for (let rendererInfo of inlinedHostConfigs) {
357 if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
358 if (!rendererInfo.isServerSupported) {
359 return null;
360 }
361 if (rendererInfo.isFlightSupported === false) {
362 return new Error(
363 `Expected not to use ReactFlightServerConfig with "${entry}" entry point ` +
364 'in ./scripts/shared/inlinedHostConfigs.js. Update the renderer config to ' +
365 'activate flight suppport and add a matching fork implementation for ReactFlightServerConfig.'
366 );
367 }
368 const foundFork = findNearestExistingForkFile(
369 './packages/react-server/src/forks/ReactFlightServerConfig.',
370 rendererInfo.shortName,
371 '.js'
372 );
373 if (foundFork) {
374 return foundFork;
375 }
376 // fall through to error
377 break;
378 }
379 }
380 throw new Error(
381 'Expected ReactFlightServerConfig to always be replaced with a shim, but ' +
382 `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
383 'Did you mean to add it there to associate it with a specific renderer?'
384 );
385 },
386
387 './packages/react-client/src/ReactFlightClientConfig.js': (
388 bundleType,
389 entry,
390 dependencies,
391 moduleType
392 ) => {
393 if (dependencies.indexOf('react-client') !== -1) {
394 return null;
395 }
396 if (moduleType !== RENDERER && moduleType !== RECONCILER) {
397 return null;
398 }
399 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
400 for (let rendererInfo of inlinedHostConfigs) {
401 if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
402 if (!rendererInfo.isServerSupported) {
403 return null;
404 }
405 if (rendererInfo.isFlightSupported === false) {
406 return new Error(
407 `Expected not to use ReactFlightClientConfig with "${entry}" entry point ` +
408 'in ./scripts/shared/inlinedHostConfigs.js. Update the renderer config to ' +
409 'activate flight suppport and add a matching fork implementation for ReactFlightClientConfig.'
410 );
411 }
412 const foundFork = findNearestExistingForkFile(
413 './packages/react-client/src/forks/ReactFlightClientConfig.',
414 rendererInfo.shortName,
415 '.js'
416 );
417 if (foundFork) {
418 return foundFork;
419 }
420 // fall through to error
421 break;
422 }
423 }
424 throw new Error(
425 'Expected ReactFlightClientConfig to always be replaced with a shim, but ' +
426 `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
427 'Did you mean to add it there to associate it with a specific renderer?'
428 );
429 },
430
431 // We wrap top-level listeners into guards on www.
432 './packages/react-dom-bindings/src/events/EventListener.js': (
433 bundleType,
434 entry
435 ) => {
436 switch (bundleType) {
437 case FB_WWW_DEV:
438 case FB_WWW_PROD:
439 case FB_WWW_PROFILING:
440 if (__EXPERIMENTAL__) {
441 // In modern builds we don't use the indirection. We just use raw DOM.
442 return null;
443 } else {
444 // Use the www fork which is integrated with TimeSlice profiling.
445 return './packages/react-dom-bindings/src/events/forks/EventListener-www.js';
446 }
447 default:
448 return null;
449 }
450 },
451
452 './packages/use-sync-external-store/src/useSyncExternalStore.js': (
453 bundleType,
454 entry
455 ) => {
456 if (entry.startsWith('use-sync-external-store/shim')) {
457 return './packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js';
458 }
459 if (entry !== 'use-sync-external-store') {
460 // Internal modules that aren't shims should use the native API from the
461 // react package.
462 return './packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js';
463 }
464 return null;
465 },
466
467 './packages/use-sync-external-store/src/isServerEnvironment.js': (
468 bundleType,
469 entry
470 ) => {
471 if (entry.endsWith('.native')) {
472 return './packages/use-sync-external-store/src/forks/isServerEnvironment.native.js';
473 }
474 },
475 });
476
477 module.exports = forks;