build(metro): add web shims for native React Native API compatibility
Some checks failed
Frontend CI / ota-android (push) Failing after 11s
Frontend CI / build-and-push-web (push) Failing after 1m14s
Frontend CI / build-android-apk (push) Has been cancelled

Add Metro configuration to resolve native-only React Native APIs with web-compatible shims. This enables packages like react-native-pager-view to work on the web platform by providing shim implementations for codegenNativeComponent, codegenNativeCommands, and CodegenTypes that are unavailable in react-native-web.

Also simplify Android signing configuration in CI by using standard Gradle property names (MYAPP_UPLOAD_*) and remove the build.gradle patching step.
This commit is contained in:
lafay
2026-04-22 20:17:38 +08:00
parent 437dab3b6e
commit dcae47218f
7 changed files with 84 additions and 28 deletions

View File

@@ -154,29 +154,6 @@ jobs:
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > android/app/withyou-release-key.keystore
- name: Patch app/build.gradle with signing config
run: |
cd android/app
python3 -c "
import re
with open('build.gradle', 'r') as f:
content = f.read()
release_signing = '''
signingConfigs {
release {
storeFile file(WITHYOU_UPLOAD_STORE_FILE)
storePassword WITHYOU_UPLOAD_STORE_PASSWORD
keyAlias WITHYOU_UPLOAD_KEY_ALIAS
keyPassword WITHYOU_UPLOAD_KEY_PASSWORD
}
}
'''
content = content.replace('signingConfigs {}', release_signing)
content = content.replace('signingConfig signingConfigs.debug', 'signingConfig signingConfigs.release')
with open('build.gradle', 'w') as f:
f.write(content)
"
- name: Configure Gradle with Aliyun Maven mirror
run: |
cd android
@@ -291,10 +268,10 @@ jobs:
run: |
cd android
cat >> gradle.properties << SIGNING_PROPS
WITHYOU_UPLOAD_STORE_FILE=withyou-release-key.keystore
WITHYOU_UPLOAD_STORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
WITHYOU_UPLOAD_KEY_ALIAS=withyou-key
WITHYOU_UPLOAD_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}
MYAPP_UPLOAD_STORE_FILE=withyou-release-key.keystore
MYAPP_UPLOAD_STORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
MYAPP_UPLOAD_KEY_ALIAS=withyou-key
MYAPP_UPLOAD_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}
SIGNING_PROPS
- name: Build Android release APK (arm64 only)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 KiB

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 KiB

After

Width:  |  Height:  |  Size: 522 KiB

View File

@@ -19,6 +19,35 @@ config.resolver.alias = {
// Ensure platform-specific files are resolved correctly
config.resolver.platforms = ['ios', 'android', 'native', 'web'];
// Web shims for native-only React Native APIs that don't exist in react-native-web.
// Packages like react-native-pager-view import codegenNativeComponent from
// react-native/Libraries/Utilities/codegenNativeComponent, which calls
// requireNativeComponent internally. On web (react-native-web), these APIs
// don't exist, causing "(0 , _reactNativeWebDistIndex.requireNativeComponent)
// is not a function".
const webShimPaths = [
'react-native/Libraries/Utilities/codegenNativeComponent',
'react-native/Libraries/Utilities/codegenNativeCommands',
'react-native/Libraries/Types/CodegenTypes',
];
const originalResolveRequest = config.resolver.resolveRequest;
const shimDir = path.resolve(__dirname, 'web-shims');
config.resolver.resolveRequest = (context, moduleName, platform) => {
if (platform === 'web' && webShimPaths.some((p) => moduleName.endsWith(p))) {
const shimName = moduleName.replace('react-native/Libraries/', '');
return {
type: 'sourceFile',
filePath: path.join(shimDir, 'react-native', 'Libraries', shimName + '.js'),
};
}
if (originalResolveRequest) {
return originalResolveRequest(context, moduleName, platform);
}
return context.resolveRequest(context, moduleName, platform);
};
// NOTE: enhanceMiddleware is marked as deprecated in Metro's types,
// but there's currently no official alternative for custom dev server headers.
// For production, configure COEP/COOP headers on your hosting platform.
@@ -31,4 +60,4 @@ config.server.enhanceMiddleware = (middleware) => {
};
};
module.exports = config;
module.exports = config;

View File

@@ -0,0 +1,17 @@
const Double = Number;
const Int32 = Number;
const Float = Number;
const WithDefault = (type, defaultValue) => type;
module.exports = {
Double,
Float,
Int32,
WithDefault,
DirectEventHandler: () => null,
BubblingEventHandler: () => null,
Stringish: String,
ColorValue: null,
HandledState: String,
WithSpringAnimation: (type) => type,
};

View File

@@ -0,0 +1,15 @@
function codegenNativeCommands(options) {
const commands = {};
if (options && options.supportedCommands) {
options.supportedCommands.forEach((commandName) => {
commands[commandName] = (ref, ...args) => {
if (ref && ref.current && typeof ref.current[commandName] === 'function') {
ref.current[commandName](...args);
}
};
});
}
return commands;
}
module.exports = codegenNativeCommands;

View File

@@ -0,0 +1,18 @@
const React = require('react');
function codegenNativeComponent(componentName, options) {
const componentNameClean = componentName
.replace(/^RNC/, '')
.replace(/^RNS/, '')
.replace(/^RN/, '');
const NativeComponent = React.forwardRef((props, ref) => {
const { children, ...rest } = props;
return React.createElement('div', { ...rest, ref, 'data-native-component': componentNameClean }, children);
});
NativeComponent.displayName = componentName;
return NativeComponent;
}
module.exports = codegenNativeComponent;