Files
Rubén Norte 6760383470 Create source maps for Fantom lazily (#52786)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52786

Changelog: [internal]

It takes around 300ms to generate each source map file, and we really only need them if tests throw errors. Requesting source maps also increases contention to access the Metro server from each test worker.

This refactors the code so we only generate them in that case, which could save up to 20s in test execution time.

Reviewed By: rshest

Differential Revision: D78807672

fbshipit-source-id: af9f0f0377ddcf05014b5aca0b28db938dfb4ce2
2025-08-04 05:56:51 -07:00

134 lines
3.2 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {RunBuildOptions} from 'metro';
import fs from 'fs';
import path from 'path';
type BundleOptions = {
...RunBuildOptions,
out: $NonMaybeType<RunBuildOptions['out']>,
testPath: string,
};
const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
export async function createBundle(options: BundleOptions): Promise<void> {
let bundleResult;
let bundleError;
// Retry in case Metro hasn't seen the changes in the filesystem yet.
// TODO(T231910841): Remove this when Metro fixes consistency issues when resolving HTTP requests.
let attemps = 0;
do {
if (attemps > 0) {
await sleep(500);
}
try {
bundleResult = await fetch(getBundleURL(options));
} catch (e) {
bundleError = e;
}
attemps++;
} while (attemps < 3 && (bundleError || bundleResult?.status === 404));
if (bundleError || bundleResult?.ok !== true) {
throw new Error(
`Failed to request bundle from Metro: ${bundleError?.message ?? (await bundleResult?.text()) ?? ''}`,
);
}
await fs.promises.writeFile(options.out, await bundleResult.text(), 'utf8');
}
export async function createSourceMap(options: BundleOptions): Promise<void> {
const sourceMapResult = await fetch(getSourceMapURL(options));
if (!sourceMapResult.ok) {
throw new Error();
}
await fs.promises.writeFile(
options.out,
await sourceMapResult.text(),
'utf8',
);
}
function getBundleURL(options: BundleOptions): URL {
const baseURL = getBundleBaseURL(options);
baseURL.pathname += '.bundle';
return baseURL;
}
function getSourceMapURL(options: BundleOptions): URL {
const baseURL = getBundleBaseURL(options);
baseURL.pathname += '.map';
return baseURL;
}
function getBundleBaseURL({
entry,
platform,
minify,
dev,
sourceMap,
sourceMapUrl,
}: BundleOptions): URL {
const requestPath = path.relative(PROJECT_ROOT, entry).replace(/\.js$/, '');
const port = getMetroPort();
const baseURL = new URL(`http://localhost:${port}/${requestPath}`);
if (platform != null) {
baseURL.searchParams.append('platform', platform);
}
if (minify != null) {
baseURL.searchParams.append('minify', minify ? 'true' : 'false');
}
if (dev != null) {
baseURL.searchParams.append('dev', dev ? 'true' : 'false');
}
if (sourceMap != null) {
baseURL.searchParams.append('sourceMap', sourceMap ? 'true' : 'false');
}
if (sourceMapUrl != null) {
baseURL.searchParams.append('sourceMapUrl', sourceMapUrl);
}
return baseURL;
}
function getMetroPort(): number {
if (process.env.__FANTOM_METRO_PORT__ == null) {
throw new Error(
'Could not find Metro server port (process.env.__FANTOM_METRO_PORT__ not set by Fantom)',
);
}
const port = Number(process.env.__FANTOM_METRO_PORT__);
if (!Number.isFinite(port) || port <= 0 || port > 65535) {
throw new Error(`Invalid port for Metro server: ${port}`);
}
return port;
}
async function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}