Skip to content

Commit 271d0a7

Browse files
authored
refactor: inline Metro integration into bundler (#77)
1 parent d50c3e7 commit 271d0a7

37 files changed

+488
-465
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { rnHarnessPlugins } = require('@react-native-harness/babel-preset');
2+
3+
const transform = (args) => {
4+
const { plugins } = args;
5+
const upstreamTransformerPath =
6+
process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH;
7+
8+
if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') {
9+
throw new Error('Upstream transformer path is not a string');
10+
}
11+
12+
const upstreamTransformer = require(upstreamTransformerPath);
13+
const pluginsWithHarness = [
14+
...((plugins ?? [])),
15+
...rnHarnessPlugins,
16+
];
17+
18+
return upstreamTransformer.transform({
19+
...args,
20+
plugins: pluginsWithHarness,
21+
});
22+
};
23+
24+
module.exports = { transform };

packages/bundler-metro/eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default [
99
'error',
1010
{
1111
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
12+
ignoredDependencies: ['@react-native-harness/babel-preset', 'vitest'],
1213
},
1314
],
1415
},

packages/bundler-metro/package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,27 @@
1616
}
1717
},
1818
"dependencies": {
19-
"@react-native-harness/metro": "workspace:*",
19+
"@react-native/metro-config": "*",
20+
"@react-native-harness/babel-preset": "workspace:*",
2021
"@react-native-harness/tools": "workspace:*",
2122
"@react-native-harness/config": "workspace:*",
23+
"@react-native-harness/runtime": "workspace:*",
2224
"connect": "^3.7.0",
2325
"nocache": "^4.0.0",
2426
"tslib": "^2.3.0"
2527
},
2628
"peerDependencies": {
2729
"metro": "*",
28-
"metro-config": "*"
30+
"metro-cache": "*",
31+
"metro-config": "*",
32+
"metro-resolver": "*"
2933
},
3034
"devDependencies": {
3135
"@types/connect": "^3.4.38",
3236
"metro": "*",
33-
"metro-config": "*"
37+
"metro-cache": "*",
38+
"metro-config": "*",
39+
"metro-resolver": "*"
3440
},
3541
"license": "MIT"
3642
}

packages/metro/src/__tests__/paths.test.ts renamed to packages/bundler-metro/src/__tests__/paths.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {
1212
const tempDirs: string[] = [];
1313

1414
const createTempProjectRoot = (): string => {
15-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rn-harness-metro-'));
15+
const tempDir = fs.mkdtempSync(
16+
path.join(os.tmpdir(), 'rn-harness-bundler-metro-')
17+
);
1618
tempDirs.push(tempDir);
1719
return tempDir;
1820
};
@@ -23,7 +25,7 @@ afterEach(() => {
2325
}
2426
});
2527

26-
describe('metro paths', () => {
28+
describe('bundler metro paths', () => {
2729
it('resolves the harness root under the project root', () => {
2830
const projectRoot = createTempProjectRoot();
2931

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { fileURLToPath } from 'node:url';
2+
import { MetroConfig } from '@react-native/metro-config';
3+
4+
export const getHarnessBabelTransformerPath = (
5+
metroConfig: MetroConfig
6+
): string => {
7+
const upstreamTransformerPath = metroConfig.transformer?.babelTransformerPath;
8+
9+
if (!upstreamTransformerPath || typeof upstreamTransformerPath !== 'string') {
10+
throw new Error('Upstream transformer path is not a string');
11+
}
12+
13+
process.env.RN_HARNESS_UPSTREAM_TRANSFORMER_PATH = upstreamTransformerPath;
14+
15+
return fileURLToPath(new URL('../babel-transformer.cjs', import.meta.url));
16+
};

packages/bundler-metro/src/factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { withRnHarness } from '@react-native-harness/metro';
21
import { logger } from '@react-native-harness/tools';
32
import type { Server as HttpServer } from 'node:http';
43
import type { Server as HttpsServer } from 'node:https';
@@ -14,6 +13,7 @@ import {
1413
} from './reporter.js';
1514
import { getExpoMiddleware } from './middlewares/expo-middleware.js';
1615
import { getStatusMiddleware } from './middlewares/status-middleware.js';
16+
import { withRnHarness } from './withRnHarness.js';
1717

1818
const waitForBundler = async (
1919
reporter: Reporter,

packages/metro/src/getHarnessSerializer.ts renamed to packages/bundler-metro/src/getHarnessSerializer.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import { createRequire } from 'node:module';
12
import type { MetroConfig } from 'metro-config';
23

4+
const require = createRequire(import.meta.url);
5+
36
export type Serializer = NonNullable<
47
NonNullable<MetroConfig['serializer']>['customSerializer']
58
>;
69

710
const getBaseSerializer = (): Serializer => {
8-
const baseJSBundle = require('metro/private/DeltaBundler/Serializers/baseJSBundle');
11+
const baseJSBundle = require(
12+
'metro/private/DeltaBundler/Serializers/baseJSBundle'
13+
);
914
const bundleToString = require('metro/private/lib/bundleToString');
1015

1116
return (entryPoint, prepend, graph, bundleOptions) =>
@@ -20,19 +25,16 @@ export const getHarnessSerializer = (): Serializer => {
2025

2126
return async (entryPoint, preModules, graph, options) => {
2227
if (options.modulesOnly) {
23-
// This is most likely a test file
2428
return baseSerializer(entryPoint, preModules, graph, {
2529
...options,
2630
processModuleFilter: (mod) => {
2731
if (
2832
options.processModuleFilter &&
2933
!options.processModuleFilter(mod)
3034
) {
31-
// If the module is not allowed by the processModuleFilter, skip it
3235
return false;
3336
}
3437

35-
// If the module is in the main entry point, skip it
3638
return !mainEntryPointModules.has(mod.path);
3739
},
3840
});

packages/bundler-metro/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export { getMetroInstance } from './factory.js';
22
export type { MetroInstance, MetroFactory, MetroOptions } from './types.js';
33
export { prewarmMetroBundle } from './prewarm.js';
44
export type { Reporter, ReportableEvent } from './reporter.js';
5+
export { isMetroCacheReusable } from './paths.js';

packages/metro/src/jest-globals-mock.ts renamed to packages/bundler-metro/src/jest-globals-mock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Mock module for @jest/globals imports
2-
// This module throws immediately when imported to warn users about using Jest APIs
2+
// This module throws immediately to explain the supported import path.
33

44
throw new Error(
55
"Importing '@jest/globals' is not supported in Harness tests. Import from 'react-native-harness' instead."
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import path from 'node:path';
21
import fs from 'node:fs';
2+
import path from 'node:path';
33
import { Config as HarnessConfig } from '@react-native-harness/config';
4-
import { getHarnessManifestPath } from './paths';
4+
import { getHarnessManifestPath } from './paths.js';
55

66
const getManifestContent = (harnessConfig: HarnessConfig): string => {
77
return `global.RN_HARNESS = {

0 commit comments

Comments
 (0)