Skip to content

Commit 8f73487

Browse files
Report an actionable error when '@react-native/codegen' is not built (#59650)
Summary: When `react-native/codegen` is checked out from source but never built, `babel-plugin-codegen` failed with a bare `Cannot find module .../lib/...` deep inside Babel preset loading. Catch the failed `lib/` fallback and name the fix, keeping the original failure as `cause`. A missing install and a broken `lib/` build report separately. Adds `codegen-load-test.js` covering all three failures. Changelog: [General][Fixed] - Show an actionable error when `react-native/codegen` is not built Reviewed By: cipolleschi Differential Revision: D127711037
1 parent e362239 commit 8f73487

2 files changed

Lines changed: 112 additions & 9 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
class ModuleNotFoundError extends Error {
12+
code: string = 'MODULE_NOT_FOUND';
13+
}
14+
15+
function missingLibError(): Error {
16+
return new ModuleNotFoundError(
17+
"Cannot find module '@react-native/codegen/lib/parsers/flow/parser'",
18+
);
19+
}
20+
21+
// Failure thrown by the virtual `lib/` mock; tests override it per case.
22+
let mockLibLoadError: Error = missingLibError();
23+
24+
// `lib/` is gitignored build output and may not exist on disk, so it is
25+
// mocked virtually for every test. This stays a top-level `jest.mock`
26+
// because only it accepts the options argument under this repo's Flow libdef.
27+
jest.mock(
28+
'@react-native/codegen/lib/parsers/flow/parser',
29+
() => {
30+
throw mockLibLoadError;
31+
},
32+
{virtual: true},
33+
);
34+
35+
describe('codegen loading', () => {
36+
beforeEach(() => {
37+
mockLibLoadError = missingLibError();
38+
});
39+
40+
it('throws an actionable error when @react-native/codegen is not built', () => {
41+
jest.isolateModules(() => {
42+
jest.doMock('@react-native/codegen/src/parsers/flow/parser', () => {
43+
throw new SyntaxError("Unexpected token '{'");
44+
});
45+
expect(() => {
46+
// $FlowFixMe[untyped-import] `../index.js` is `@noflow`.
47+
require('../index.js');
48+
}).toThrow(/never built.*yarn workspace @react-native\/codegen build/s);
49+
});
50+
});
51+
52+
it('throws an actionable error when @react-native/codegen is not installed', () => {
53+
jest.isolateModules(() => {
54+
jest.doMock('@react-native/codegen/src/parsers/flow/parser', () => {
55+
throw new ModuleNotFoundError('Cannot find module');
56+
});
57+
expect(() => {
58+
// $FlowFixMe[untyped-import] `../index.js` is `@noflow`.
59+
require('../index.js');
60+
}).toThrow(/could not be found/);
61+
});
62+
});
63+
64+
it('throws an actionable error when the built lib is broken', () => {
65+
jest.isolateModules(() => {
66+
mockLibLoadError = new SyntaxError("Unexpected token '{'");
67+
jest.doMock('@react-native/codegen/src/parsers/flow/parser', () => {
68+
throw new ModuleNotFoundError('Cannot find module');
69+
});
70+
expect(() => {
71+
// $FlowFixMe[untyped-import] `../index.js` is `@noflow`.
72+
require('../index.js');
73+
}).toThrow(/failed to load.*Rebuild/s);
74+
});
75+
});
76+
});

‎packages/babel-plugin-codegen/index.js‎

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,49 @@ let FlowParser, TypeScriptParser, RNCodegen;
1515
const {cheap: traverseCheap} = require('@babel/traverse').default;
1616
const {basename} = require('node:path');
1717

18+
function getCodegenLoadError(srcError, libError) {
19+
if (libError != null && libError.code !== 'MODULE_NOT_FOUND') {
20+
return (
21+
'Unable to load @react-native/codegen: its built `lib/` output failed ' +
22+
'to load, so NativeComponent specs cannot be transformed. Rebuild it, ' +
23+
'e.g. `yarn workspace @react-native/codegen build`, then retry.'
24+
);
25+
}
26+
if (srcError != null && srcError.code !== 'MODULE_NOT_FOUND') {
27+
return (
28+
'Unable to load @react-native/codegen: the package is checked out ' +
29+
'from source but was never built (its `lib/` output is missing), so ' +
30+
'NativeComponent specs cannot be transformed. Build it first, e.g. ' +
31+
'`yarn workspace @react-native/codegen build`, then retry.'
32+
);
33+
}
34+
return (
35+
'Unable to load @react-native/codegen: the package could not be found, ' +
36+
'so NativeComponent specs cannot be transformed. Install dependencies ' +
37+
'first, then retry.'
38+
);
39+
}
40+
1841
try {
1942
FlowParser =
2043
require('@react-native/codegen/src/parsers/flow/parser').FlowParser;
2144
TypeScriptParser =
2245
require('@react-native/codegen/src/parsers/typescript/parser').TypeScriptParser;
2346
RNCodegen = require('@react-native/codegen/src/generators/RNCodegen');
24-
} catch (e) {
25-
// Fallback to lib when source doesn't exit (e.g. when installed as a dev dependency)
26-
FlowParser =
27-
// $FlowFixMe[cannot-resolve-module]
28-
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
29-
TypeScriptParser =
47+
} catch (srcError) {
48+
// Fallback to lib when source isn't usable (e.g. when installed as a dev dependency)
49+
try {
50+
FlowParser =
51+
// $FlowFixMe[cannot-resolve-module]
52+
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
53+
TypeScriptParser =
54+
// $FlowFixMe[cannot-resolve-module]
55+
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
3056
// $FlowFixMe[cannot-resolve-module]
31-
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
32-
// $FlowFixMe[cannot-resolve-module]
33-
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
57+
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
58+
} catch (libError) {
59+
throw new Error(getCodegenLoadError(srcError, libError), {cause: libError});
60+
}
3461
}
3562

3663
const flowParser = new FlowParser();

0 commit comments

Comments
 (0)