Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Eagerly initialize TurboModule because Proxies are slow #671

Merged
merged 4 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module.exports = {
root: true,
extends: ['@react-native', 'plugin:prettier/recommended'],
ignorePatterns: ['scripts', 'lib'],
rules: {
'prettier/prettier': ['warn'],
'@typescript-eslint/consistent-type-imports': 'warn',
},
};
10 changes: 8 additions & 2 deletions package/example/ios/MmkvExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,10 @@
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
"-DRN_FABRIC_ENABLED",
);
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
Expand Down Expand Up @@ -676,7 +679,10 @@
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
"-DRN_FABRIC_ENABLED",
);
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
Expand Down
98 changes: 0 additions & 98 deletions package/src/LazyTurboModule.ts

This file was deleted.

6 changes: 3 additions & 3 deletions package/src/MMKV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { AppState } from 'react-native';
import { createMMKV } from './createMMKV';
import { createMockMMKV } from './createMMKV.mock';
import { isTest } from './PlatformChecker';
import { Configuration } from './NativeMmkv';
import { Listener, MMKVInterface, NativeMMKV } from './Types';
export { Configuration, Mode } from './NativeMmkv';
import type { Configuration } from './NativeMmkv';
import type { Listener, MMKVInterface, NativeMMKV } from './Types';
export type { Configuration } from './NativeMmkv';

const onValueChangedListeners = new Map<string, ((key: string) => void)[]>();

Expand Down
95 changes: 95 additions & 0 deletions package/src/ModuleNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { NativeModules, Platform } from 'react-native';

declare global {
// A react-native internal from TurboModuleRegistry.js
var __turboModuleProxy: unknown | undefined;
}

const BULLET_POINT = '\n* ';

function messageWithSuggestions(
message: string,
suggestions: string[]
): string {
return message + BULLET_POINT + suggestions.join(BULLET_POINT);
}

function getFrameworkType(): 'react-native' | 'expo' | 'expo-go' {
// check if Expo
const ExpoConstants =
NativeModules.NativeUnimoduleProxy?.modulesConstants?.ExponentConstants;
if (ExpoConstants != null) {
if (ExpoConstants.appOwnership === 'expo') {
// We're running Expo Go
return 'expo-go';
} else {
// We're running Expo bare / standalone
return 'expo';
}
}
return 'react-native';
}

export class ModuleNotFoundError extends Error {
constructor(cause?: unknown) {
// TurboModule not found, something went wrong!
if (global.__turboModuleProxy == null) {
// TurboModules are not available/new arch is not enabled.
// react-native-mmkv 3.x.x requires new arch (react-native >0.74)
// react-native-mmkv 2.x.x works on old arch (react-native <0.74)
const message =
'Failed to create a new MMKV instance: react-native-mmkv 3.x.x requires TurboModules, but the new architecture is not enabled!';
const suggestions: string[] = [];
suggestions.push(
'Downgrade to react-native-mmkv 2.x.x if you want to stay on the old architecture.'
);
suggestions.push(
'Enable the new architecture in your app to use react-native-mmkv 3.x.x. (See https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md)'
);
const error = messageWithSuggestions(message, suggestions);
super(error, { cause: cause });
return;
}

const framework = getFrameworkType();
if (framework === 'expo-go') {
super(
'react-native-mmkv is not supported in Expo Go! Use EAS (`expo prebuild`) or eject to a bare workflow instead.'
);
return;
}

const message =
'Failed to create a new MMKV instance: The native MMKV Module could not be found.';
const suggestions: string[] = [];
suggestions.push(
'Make sure react-native-mmkv is correctly autolinked (run `npx react-native config` to verify)'
);
suggestions.push(
'Make sure you enabled the new architecture (TurboModules) and CodeGen properly generated the react-native-mmkv specs. See https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md'
);
suggestions.push(
'Make sure you are using react-native 0.74.0 or higher, because react-native-mmkv is a C++ TurboModule.'
);
suggestions.push('Make sure you rebuilt the app.');
if (framework === 'expo') {
suggestions.push('Make sure you ran `expo prebuild`.');
}
switch (Platform.OS) {
case 'ios':
case 'macos':
suggestions.push(
'Make sure you ran `pod install` in the ios/ directory.'
);
break;
case 'android':
suggestions.push('Make sure gradle is synced.');
break;
default:
throw new Error(`MMKV is not supported on ${Platform.OS}!`);
}

const error = messageWithSuggestions(message, suggestions);
super(error, { cause: cause });
}
}
38 changes: 21 additions & 17 deletions package/src/NativeMmkv.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { TurboModule } from 'react-native';
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
import { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
import { getLazyTurboModule } from './LazyTurboModule';
import { PlatformContext } from './NativeMmkvPlatformContext';
import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
import { ModuleNotFoundError } from './ModuleNotFoundError';
import { getMMKVPlatformContextTurboModule } from './NativeMmkvPlatformContext';

/**
* Configures the mode of the MMKV instance.
Expand Down Expand Up @@ -85,21 +85,25 @@ export interface Spec extends TurboModule {
createMMKV(configuration: Configuration): UnsafeObject;
}

let basePath: string | null = null;
let module: Spec | null;

function getNativeModule(): Spec | null {
if (basePath == null) {
// use default base path from the Platform (iOS/Android)
basePath = PlatformContext.getBaseDirectory();
}
export function getMMKVTurboModule(): Spec {
try {
if (module == null) {
// 1. Load MMKV TurboModule
module = TurboModuleRegistry.getEnforcing<Spec>('MmkvCxx');

const module = TurboModuleRegistry.get<Spec>('MmkvCxx');
// 2. Get the PlatformContext TurboModule as well
const platformContext = getMMKVPlatformContextTurboModule();

if (module != null) {
// initialize MMKV
module.initialize(basePath);
}
// 3. Initialize it with the documents directory from platform-specific context
const basePath = platformContext.getBaseDirectory();
module.initialize(basePath);
}

return module;
return module;
} catch (cause) {
// TurboModule could not be found!
throw new ModuleNotFoundError(cause);
}
}
export const MMKVTurboModule = getLazyTurboModule(getNativeModule);
21 changes: 16 additions & 5 deletions package/src/NativeMmkvPlatformContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TurboModule, TurboModuleRegistry } from 'react-native';
import { getLazyTurboModule } from './LazyTurboModule';
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
import { ModuleNotFoundError } from './ModuleNotFoundError';

export interface Spec extends TurboModule {
/**
Expand All @@ -8,7 +9,17 @@ export interface Spec extends TurboModule {
getBaseDirectory(): string;
}

function getModule(): Spec | null {
return TurboModuleRegistry.get<Spec>('MmkvPlatformContext');
let module: Spec | null;

export function getMMKVPlatformContextTurboModule(): Spec {
try {
if (module == null) {
// 1. Get the TurboModule
module = TurboModuleRegistry.getEnforcing<Spec>('MmkvPlatformContext');
}
return module;
} catch (e) {
// TurboModule could not be found!
throw new ModuleNotFoundError(e);
}
}
export const PlatformContext = getLazyTurboModule(getModule);
6 changes: 4 additions & 2 deletions package/src/createMMKV.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { Configuration } from './MMKV';
import { MMKVTurboModule } from './NativeMmkv';
import { getMMKVTurboModule } from './NativeMmkv';
import type { NativeMMKV } from './Types';

export const createMMKV = (config: Configuration): NativeMMKV => {
const instance = MMKVTurboModule.createMMKV(config);
const module = getMMKVTurboModule();

const instance = module.createMMKV(config);
if (__DEV__) {
if (typeof instance !== 'object' || instance == null) {
throw new Error(
Expand Down
3 changes: 2 additions & 1 deletion package/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useRef, useState, useMemo, useCallback, useEffect } from 'react';
import { MMKV, Configuration } from './MMKV';
import type { Configuration } from './MMKV';
import { MMKV } from './MMKV';

function isConfigurationEqual(
left?: Configuration,
Expand Down
2 changes: 1 addition & 1 deletion package/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './MMKV';
export * from './hooks';

export { Mode, Configuration } from './NativeMmkv';
export type { Mode, Configuration } from './NativeMmkv';
19 changes: 14 additions & 5 deletions package/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,36 @@
"compilerOptions": {
"rootDir": ".",
"paths": {
"react-native-mmkv": ["./src/index"]
"react-native-mmkv": [
"./src/index"
]
},
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"lib": ["esnext", "DOM"],
"lib": [
"esnext",
"DOM"
],
"module": "esnext",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noImplicitUseStrict": false,
"noStrictGenericChecks": false,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
},
"include": ["src"]
"include": [
"src",
".eslintrc.js",
"babel.config.js",
"react-native.config.js",
]
}
Loading