Skip to content

Commit eb8b681

Browse files
authored
feat(exports): Set up ESM first (#27)
* feat(exports): Set up ESM first * Switch to VItest * Update ESLint * Move register files to a single TS * Enhance replace(..), fix bad RendererProxy mock, and create a RN Vitest environment * Revert README.md changes * Polishing
1 parent febd077 commit eb8b681

22 files changed

+1885
-622
lines changed

eslint.config.mjs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
// @ts-check
2+
import path from "path";
3+
import { fileURLToPath } from "url";
4+
25
import { fixupPluginRules } from "@eslint/compat";
36
import { FlatCompat } from "@eslint/eslintrc";
47
import eslintJs from "@eslint/js";
@@ -11,9 +14,6 @@ import sonarjs from "eslint-plugin-sonarjs";
1114
import globals from "globals";
1215
import eslintTs from "typescript-eslint";
1316

14-
import path from "path";
15-
import { fileURLToPath } from "url";
16-
1717
const project = "./tsconfig.json";
1818
const filename = fileURLToPath(import.meta.url);
1919
const dirname = path.dirname(filename);
@@ -214,7 +214,14 @@ export default eslintTs.config(
214214
order: "asc",
215215
orderImportKind: "asc",
216216
},
217-
groups: ["external", "parent", "sibling", "type"],
217+
groups: [
218+
"builtin",
219+
["external", "internal"],
220+
"parent",
221+
"sibling",
222+
"index",
223+
"type",
224+
],
218225
"newlines-between": "always",
219226
}],
220227
"jsdoc/check-alignment": "error",

package.json

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,71 +13,92 @@
1313
"mocks",
1414
"mock"
1515
],
16-
"main": "./dist/main.js",
16+
"type": "module",
17+
"source": "./src/main.ts",
18+
"main": "./dist/main.cjs",
19+
"module": "./dist/main.js",
1720
"types": "./dist/main.d.ts",
21+
"sideEffects": true,
22+
"exports": {
23+
".": {
24+
"import": "./dist/main.js",
25+
"require": "./dist/main.cjs",
26+
"types": "./dist/main.d.ts",
27+
"default": "./dist/main.js"
28+
},
29+
"./register": {
30+
"import": "./dist/register.js",
31+
"require": "./dist/register.cjs",
32+
"types": "./dist/register.d.ts",
33+
"default": "./dist/register.js"
34+
},
35+
"./package.json": "./package.json"
36+
},
1837
"files": [
19-
"dist/",
20-
"src/",
21-
"register.js"
38+
"./dist",
39+
"./src/",
40+
"./package.json"
2241
],
2342
"engines": {
2443
"node": ">=18"
2544
},
2645
"scripts": {
27-
"build": "tsc -p tsconfig.prod.json",
28-
"check": "yarn compile && yarn lint && yarn test",
46+
"build": "vite build",
47+
"check": "yarn compile && yarn lint && yarn test --run",
2948
"compile": "tsc",
3049
"lint": "eslint .",
3150
"release": "semantic-release",
32-
"test": "NODE_ENV=test mocha"
51+
"test": "vitest"
3352
},
3453
"packageManager": "yarn@4.3.1",
3554
"dependencies": {
3655
"@babel/core": "^7.24.7",
3756
"@babel/register": "^7.24.6",
3857
"babel-plugin-module-resolver": "^5.0.2",
3958
"dot-prop-immutable": "^2.1.1",
59+
"pino": "^9.2.0",
60+
"pino-pretty": "^11.2.1",
4061
"ts-pattern": "^5.2.0"
4162
},
4263
"devDependencies": {
4364
"@assertive-ts/core": "^2.1.0",
4465
"@eslint/compat": "^1.1.0",
4566
"@eslint/eslintrc": "^3.1.0",
46-
"@eslint/js": "^9.5.0",
67+
"@eslint/js": "^9.6.0",
4768
"@react-native/babel-preset": "^0.74.84",
48-
"@stylistic/eslint-plugin": "^2.2.2",
69+
"@stylistic/eslint-plugin": "^2.3.0",
4970
"@testing-library/react-native": "^12.5.1",
5071
"@types/babel__core": "^7.20.5",
5172
"@types/babel__register": "^7.17.3",
5273
"@types/eslint__eslintrc": "^2.1.1",
5374
"@types/eslint__js": "^8.42.3",
54-
"@types/mocha": "^10.0.7",
55-
"@types/node": "^20.14.8",
75+
"@types/node": "^20.14.9",
5676
"@types/react": "^18.3.3",
5777
"@types/react-test-renderer": "^18.3.0",
5878
"@types/sinon": "^17.0.3",
59-
"eslint": "^9.5.0",
79+
"eslint": "^9.6.0",
6080
"eslint-import-resolver-typescript": "^3.6.1",
6181
"eslint-plugin-deprecation": "^3.0.0",
6282
"eslint-plugin-etc": "^2.0.3",
6383
"eslint-plugin-extra-rules": "^0.0.0-development",
6484
"eslint-plugin-import": "^2.29.1",
65-
"eslint-plugin-jsdoc": "^48.4.0",
85+
"eslint-plugin-jsdoc": "^48.5.0",
6686
"eslint-plugin-react": "^7.34.3",
6787
"eslint-plugin-sonarjs": "^1.0.3",
6888
"globals": "^15.6.0",
69-
"mocha": "^10.5.0",
7089
"react": "18.3.1",
7190
"react-native": "^0.74.2",
7291
"react-native-svg": "^15.3.0",
7392
"react-test-renderer": "^18.3.1",
7493
"semantic-release": "^24.0.0",
7594
"semantic-release-yarn": "^3.0.2",
7695
"sinon": "^18.0.0",
77-
"ts-node": "^10.9.2",
7896
"tslib": "^2.6.3",
7997
"typescript": "^5.5.2",
80-
"typescript-eslint": "^7.14.1"
98+
"typescript-eslint": "^7.14.1",
99+
"vite": "^5.3.2",
100+
"vite-plugin-dts": "^3.9.1",
101+
"vitest": "^1.6.0"
81102
},
82103
"peerDependencies": {
83104
"@react-native/babel-preset": ">=0.73.18",

register.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/helpers/commons.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import path from "path";
22

3+
type ExportsLike = object | { default?: unknown; };
4+
35
/**
46
* A simple no-operation function
57
*/
@@ -13,12 +15,15 @@ export function noop(): void {
1315
* @param modulePath the path to the module
1416
* @param exports the exports to replace
1517
*/
16-
export function replace<T>(modulePath: string, exports: T): void {
18+
export function replace<T extends ExportsLike>(modulePath: string, factory: () => T): void {
1719
const id = resolveId(modulePath);
20+
const exports = factory();
1821

1922
require.cache[id] = {
2023
children: [],
21-
exports,
24+
exports: "default" in exports
25+
? { __esModule: true, ...exports }
26+
: exports,
2227
filename: id,
2328
id,
2429
isPreloading: false,
@@ -30,16 +35,6 @@ export function replace<T>(modulePath: string, exports: T): void {
3035
};
3136
}
3237

33-
/**
34-
* Replaces am ESModule with a given `exports` value or another module path.
35-
*
36-
* @param modulePath the path to the ESModule
37-
* @param defaultExport the default export to replace
38-
*/
39-
export function replaceEsm<T>(modulePath: string, defaultExport: T): void {
40-
replace(modulePath, { __esModule: true, default: defaultExport });
41-
}
42-
4338
function resolveId(modulePath: string): string {
4439
try {
4540
return require.resolve(modulePath);

src/lib/Core/RendererProxy.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// eslint-disable-next-line import/no-namespace
2+
import * as RendererImplementation from "react-native/Libraries/ReactNative/RendererImplementation";
3+
4+
export const RendererProxyMock = RendererImplementation;

src/lib/coreMocks.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1-
import { noop, replace, replaceEsm } from "../helpers/commons";
1+
import { noop, replace } from "../helpers/commons";
22
import { mockNativeComponent } from "../helpers/mockNativeComponent";
33

44
import { LinkingMock } from "./Components/Linking";
55
import { NativeComponentRegistryMock } from "./Core/NativeComponentRegistry";
66
import { NativeModulesMock } from "./Core/NativeModules";
7+
import { RendererProxyMock } from "./Core/RendererProxy";
78
import { UIManagerMock } from "./Core/UIManager";
89
import { verifyComponentAttrEqMock } from "./Core/verifyComponentAttributeEquivalence";
910

1011
Object.assign(global, { jest: { fn: () => noop } });
1112

12-
replace("react-native/Libraries/Core/InitializeCore", { });
13-
replace("react-native/Libraries/Core/NativeExceptionsManager", { });
14-
replace("react-native/Libraries/ReactNative/UIManager", UIManagerMock);
15-
replace("react-native/Libraries/Linking/Linking", LinkingMock);
16-
replace("react-native/Libraries/BatchedBridge/NativeModules", NativeModulesMock);
17-
replace("react-native/Libraries/NativeComponent/NativeComponentRegistry", NativeComponentRegistryMock);
18-
replaceEsm("react-native/Libraries/ReactNative/requireNativeComponent", mockNativeComponent);
19-
replace("react-native/Libraries/Utilities/verifyComponentAttributeEquivalence", verifyComponentAttrEqMock);
20-
replace(
21-
"react-native/Libraries/ReactNative/RendererProxy",
22-
"react-native/Libraries/ReactNative/RendererImplementation",
23-
);
13+
replace("react-native/Libraries/Core/InitializeCore", () => ({ }));
14+
replace("react-native/Libraries/Core/NativeExceptionsManager", () => ({ }));
15+
replace("react-native/Libraries/ReactNative/UIManager", () => UIManagerMock);
16+
replace("react-native/Libraries/Linking/Linking", () => LinkingMock);
17+
replace("react-native/Libraries/BatchedBridge/NativeModules", () => NativeModulesMock);
18+
replace("react-native/Libraries/NativeComponent/NativeComponentRegistry", () => NativeComponentRegistryMock);
19+
replace("react-native/Libraries/ReactNative/requireNativeComponent", () => ({ default: mockNativeComponent }));
20+
replace("react-native/Libraries/Utilities/verifyComponentAttributeEquivalence", () => verifyComponentAttrEqMock);
21+
replace("react-native/Libraries/ReactNative/RendererProxy", () => RendererProxyMock);

src/lib/mockNative.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function mockNative(type: NativeKey, methods: Partial<AllNativeMethods |
5656
.with("TextInput", () => mockComponent(Comp, Object.assign({ }, textInputMethodsMock, methods)))
5757
.otherwise(() => mockComponent(Comp, Object.assign({ }, nativeMethodsMock, methods)));
5858

59-
replace(path, type === "ActivityIndicator" ? { default: Mock } : Mock);
59+
replace(path, () => type === "ActivityIndicator" ? { default: Mock } : Mock);
6060
MOCKS.add(type);
6161
}
6262

@@ -74,7 +74,7 @@ export function restoreNativeMocks(): void {
7474
.with("TextInput", () => mockComponent(Comp, textInputMethodsMock))
7575
.otherwise(() => mockComponent(Comp, nativeMethodsMock));
7676

77-
replace(path, type === "ActivityIndicator" ? { default: Mock } : Mock);
77+
replace(path, () => type === "ActivityIndicator" ? { default: Mock } : Mock);
7878
});
7979

8080
MOCKS.clear();

src/lib/polyfills.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
/* eslint-disable @typescript-eslint/no-var-requires */
21
import "@react-native/js-polyfills/error-guard";
32

4-
global.IS_REACT_ACT_ENVIRONMENT = true;
3+
import regeneratorRuntime from "regenerator-runtime/runtime";
4+
55
// Suppress the `react-test-renderer` warnings until New Architecture and legacy
66
// mode are no longer supported by React Native.
7-
// @ts-expect-error type not defined
8-
global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true;
7+
Object.assign(global, {
8+
IS_REACT_ACT_ENVIRONMENT: true,
9+
IS_REACT_NATIVE_TEST_ENVIRONMENT: true,
10+
});
911

1012
Object.defineProperties(global, {
1113
__DEV__: {
@@ -37,7 +39,7 @@ Object.defineProperties(global, {
3739
regeneratorRuntime: {
3840
configurable: true,
3941
enumerable: true,
40-
value: require("regenerator-runtime/runtime") as unknown,
42+
value: regeneratorRuntime,
4143
writable: true,
4244
},
4345
requestAnimationFrame: {

src/load.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import "./lib/babelRegister";
2+
import "./lib/polyfills";
3+
import "./lib/coreMocks";
4+
5+
import { replace } from "./helpers/commons";
6+
import { AnimatedMock } from "./lib/Animated/AnimatedMock";
7+
import { AccessibilityInfoMock } from "./lib/Components/AccessibilityInfo";
8+
import { ActivityIndicatorMock } from "./lib/Components/ActivityIndicator";
9+
import { AppStateMock } from "./lib/Components/AppState";
10+
import { ClipboardMock } from "./lib/Components/Clipboard";
11+
import { ImageMock } from "./lib/Components/Image";
12+
import { ModalMock } from "./lib/Components/Modal";
13+
import { RefreshControlMock } from "./lib/Components/RefreshControl";
14+
import { ScrollViewMock } from "./lib/Components/ScrollView";
15+
import { TextMock } from "./lib/Components/Text";
16+
import { TextInputMock } from "./lib/Components/TextInput";
17+
import { VibrationMock } from "./lib/Components/Vibration";
18+
import { ViewMock } from "./lib/Components/View";
19+
import { ViewNativeComponentMock } from "./lib/Components/ViewNativeComponent";
20+
21+
const libs = "react-native/Libraries";
22+
23+
replace(`${libs}/Image/Image`, () => ImageMock);
24+
replace(`${libs}/Text/Text`, () => TextMock);
25+
replace(`${libs}/Components/TextInput/TextInput`, () => TextInputMock);
26+
replace(`${libs}/Modal/Modal`, () => ModalMock);
27+
replace(`${libs}/Components/AccessibilityInfo/AccessibilityInfo`, () => ({ default: AccessibilityInfoMock }));
28+
replace(`${libs}/Components/Clipboard/Clipboard`, () => ClipboardMock);
29+
replace(`${libs}/Components/RefreshControl/RefreshControl`, () => RefreshControlMock);
30+
replace(`${libs}/Components/ScrollView/ScrollView`, () => ScrollViewMock);
31+
replace(`${libs}/Components/ActivityIndicator/ActivityIndicator`, () => ({ default: ActivityIndicatorMock }));
32+
replace(`${libs}/AppState/AppState`, () => AppStateMock);
33+
replace(`${libs}/Vibration/Vibration`, () => VibrationMock);
34+
replace(`${libs}/Components/View/View`, () => ViewMock);
35+
replace(`${libs}/Components/View/ViewNativeComponent`, () => ViewNativeComponentMock);
36+
replace(`${libs}/Animated/Animated`, () => ({ default: AnimatedMock }));
37+
38+
export { };

src/register.ts

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
1-
import "./lib/babelRegister";
2-
import "./lib/polyfills";
3-
import "./lib/coreMocks";
1+
import { createRequire } from "module";
42

5-
import { replace, replaceEsm } from "./helpers/commons";
6-
import { AnimatedMock } from "./lib/Animated/AnimatedMock";
7-
import { AccessibilityInfoMock } from "./lib/Components/AccessibilityInfo";
8-
import { ActivityIndicatorMock } from "./lib/Components/ActivityIndicator";
9-
import { AppStateMock } from "./lib/Components/AppState";
10-
import { ClipboardMock } from "./lib/Components/Clipboard";
11-
import { ImageMock } from "./lib/Components/Image";
12-
import { ModalMock } from "./lib/Components/Modal";
13-
import { RefreshControlMock } from "./lib/Components/RefreshControl";
14-
import { ScrollViewMock } from "./lib/Components/ScrollView";
15-
import { TextMock } from "./lib/Components/Text";
16-
import { TextInputMock } from "./lib/Components/TextInput";
17-
import { VibrationMock } from "./lib/Components/Vibration";
18-
import { ViewMock } from "./lib/Components/View";
19-
import { ViewNativeComponentMock } from "./lib/Components/ViewNativeComponent";
3+
import pino from "pino";
4+
import pinoPretty from "pino-pretty";
205

21-
replace("react-native/Libraries/Image/Image", ImageMock);
22-
replace("react-native/Libraries/Text/Text", TextMock);
23-
replace("react-native/Libraries/Components/TextInput/TextInput", TextInputMock);
24-
replace("react-native/Libraries/Modal/Modal", ModalMock);
25-
replaceEsm("react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo", AccessibilityInfoMock);
26-
replace("react-native/Libraries/Components/Clipboard/Clipboard", ClipboardMock);
27-
replace("react-native/Libraries/Components/RefreshControl/RefreshControl", RefreshControlMock);
28-
replace("react-native/Libraries/Components/ScrollView/ScrollView", ScrollViewMock);
29-
replaceEsm("react-native/Libraries/Components/ActivityIndicator/ActivityIndicator", ActivityIndicatorMock);
30-
replace("react-native/Libraries/AppState/AppState", AppStateMock);
31-
replace("react-native/Libraries/Vibration/Vibration", VibrationMock);
32-
replace("react-native/Libraries/Components/View/View", ViewMock);
33-
replace("react-native/Libraries/Components/View/ViewNativeComponent", ViewNativeComponentMock);
34-
replaceEsm("react-native/Libraries/Animated/Animated", AnimatedMock);
6+
const start = Date.now();
7+
const logger = pino(pinoPretty({ colorize: true }));
8+
const require = createRequire(import.meta.url);
9+
10+
require("./load.cjs");
11+
12+
const end = Date.now();
13+
const diff = (end - start) / 1000;
14+
15+
logger.info(`React Native testing mocks registered! (${diff}s)`);

0 commit comments

Comments
 (0)