@expo-harmony/expo__log-box 55.0.12-harmony.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @expo-harmony/expo__log-box
2
+
3
+ [**GitHub 仓库**](https://github.com/renbaoshuo/expo-harmony/tree/master/packages/expo__log-box)
4
+
5
+ 为 HarmonyOS 开发构建提供 Expo LogBox 的日志检查器和原生错误页面,与官方 `@expo/log-box` 配套使用。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ npm install @expo-harmony/expo__log-box @expo/log-box@55.0.12
11
+ ```
12
+
13
+ 本包适配 Expo SDK 55 的 `@expo/log-box`,提供鸿蒙端的原生视图和错误页。原生模块和视图通过 Expo Harmony 自动链接。最低支持 HarmonyOS 5.0.1(API 13),宿主的 `compatibleSdkVersion` 也要满足这一要求。Expo LogBox 是实验功能,安装后还需要注册配置插件并组合 Metro 配置,两者都要求设置 `EXPO_UNSTABLE_LOG_BOX=1`,见下文。
14
+
15
+ ## Config Plugin
16
+
17
+ 在 `app.json` 的 `plugins` 中注册插件。
18
+
19
+ ```json
20
+ {
21
+ "expo": {
22
+ "plugins": ["@expo-harmony/expo__log-box"],
23
+ "harmony": { "bundleName": "com.example.app" }
24
+ }
25
+ }
26
+ ```
27
+
28
+ 再在 `metro.config.js` 中组合配置。
29
+
30
+ ```js
31
+ const { getDefaultConfig } = require('expo/metro-config');
32
+ const { withHarmonyConfig } = require('@expo-harmony/metro-config');
33
+ const { withLogBox } = require('@expo-harmony/expo__log-box/metro');
34
+
35
+ module.exports = withLogBox(withHarmonyConfig(getDefaultConfig(__dirname)));
36
+ ```
37
+
38
+ prebuild、原生构建和 Metro 启动均需设置 `EXPO_UNSTABLE_LOG_BOX=1`(也接受 `true`)。插件把开关写入原生工程配置,原生端只在 debug 构建中启用。变更开关后需重新生成并构建应用,只启用 Metro 时会报告原生配置不匹配。
39
+
40
+ `@expo-harmony/expo__log-box` 的 `withLogBox` 只改写 HarmonyOS 平台上的模块解析,其他平台和其他包不受影响。
41
+
42
+ ## Author
43
+
44
+ **expo-harmony** © [Baoshuo](https://github.com/renbaoshuo), Released under the MIT License.<br>
45
+ Authored and maintained by Baoshuo with help from [contributors](https://github.com/renbaoshuo/expo-harmony/contributors).
46
+
47
+ > [Personal Website](https://baoshuo.ren) · [Blog](https://blog.baoshuo.ren) · GitHub [@renbaoshuo](https://github.com/renbaoshuo) · Twitter [@renbaoshuo](https://twitter.com/renbaoshuo)
package/app.plugin.js ADDED
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const { createRunOncePlugin } = require('@expo/config-plugins');
4
+ const { HarmonyManifest, registerHarmonyConfigPlugin, withModuleJson } = require('@expo-harmony/config-plugins');
5
+ const pkg = require('./package.json');
6
+
7
+ function withLogBox(config) {
8
+ if (!config.harmony?.bundleName && !config.platforms?.includes('harmony')) return config;
9
+ config = registerHarmonyConfigPlugin(config, pkg.name);
10
+
11
+ return withModuleJson(config, (mod) => {
12
+ const enabled = ['1', 'true'].includes(process.env.EXPO_UNSTABLE_LOG_BOX?.toLowerCase());
13
+ HarmonyManifest.setMetadata(mod.modResults.module, {
14
+ name: 'expo.logBox.enabled',
15
+ value: String(enabled),
16
+ });
17
+ return mod;
18
+ });
19
+ }
20
+
21
+ module.exports = createRunOncePlugin(withLogBox, pkg.name, pkg.version);
@@ -0,0 +1,19 @@
1
+ {
2
+ "platforms": [
3
+ "harmony"
4
+ ],
5
+ "harmony": {
6
+ "modules": [
7
+ "ExpoLogBoxModule"
8
+ ],
9
+ "rootViewComponents": [
10
+ "ExpoLogBoxRootView"
11
+ ],
12
+ "reactNativeHostHandlers": [
13
+ "ExpoLogBoxReactNativeHostHandler"
14
+ ],
15
+ "runtimeBindings": [
16
+ "ExpoLogBoxRuntimeBinding"
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@expo-harmony/expo__log-box",
3
+ "version": "55.0.12-harmony.1",
4
+ "description": "HarmonyOS port of @expo/log-box",
5
+ "keywords": [
6
+ "react-native",
7
+ "expo",
8
+ "expo-harmony",
9
+ "harmonyos"
10
+ ],
11
+ "homepage": "https://github.com/renbaoshuo/expo-harmony/tree/master/packages/expo__log-box#readme",
12
+ "repository": "https://github.com/renbaoshuo/expo-harmony",
13
+ "author": {
14
+ "name": "Baoshuo",
15
+ "email": "i@baoshuo.ren",
16
+ "url": "https://github.com/renbaoshuo"
17
+ },
18
+ "main": "Index.ets",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "@expo-harmony/expo-modules-core": "55.0.25-harmony.4",
22
+ "@rnoh/react-native-openharmony": "0.84.1"
23
+ }
24
+ }
Binary file
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = Object.freeze({});
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@expo-harmony/expo__log-box",
3
+ "version": "55.0.12-harmony.1",
4
+ "description": "HarmonyOS port of @expo/log-box",
5
+ "keywords": [
6
+ "react-native",
7
+ "expo",
8
+ "expo-harmony",
9
+ "harmonyos"
10
+ ],
11
+ "homepage": "https://github.com/renbaoshuo/expo-harmony/tree/master/packages/expo__log-box#readme",
12
+ "bugs": "https://github.com/renbaoshuo/expo-harmony/issues",
13
+ "license": "MIT",
14
+ "author": "Baoshuo <i@baoshuo.ren>",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/renbaoshuo/expo-harmony.git",
18
+ "directory": "packages/expo__log-box"
19
+ },
20
+ "main": "index.js",
21
+ "types": "index.d.ts",
22
+ "exports": {
23
+ ".": "./index.js",
24
+ "./metro": "./plugin/withLogBox.js",
25
+ "./app.plugin.js": "./app.plugin.js",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "scripts": {
29
+ "harmony:clean": "expo-harmony-module prepare --clean-only",
30
+ "harmony:build": "node ./scripts/sync-log-box-bundle.mjs && expo-harmony-module prepare",
31
+ "prepack": "node ./scripts/sync-log-box-bundle.mjs && expo-harmony-module prepack"
32
+ },
33
+ "peerDependencies": {
34
+ "@expo-harmony/expo-modules-core": "55.0.25-harmony.4",
35
+ "@expo/log-box": "55.0.12",
36
+ "expo": "55.0.26",
37
+ "expo-modules-core": "55.0.25",
38
+ "react": "^19.2.0",
39
+ "react-native": "^0.83.6 || ^0.84.1"
40
+ },
41
+ "dependencies": {
42
+ "@expo-harmony/config-plugins": "55.0.10-harmony.5",
43
+ "@expo/config-plugins": "55.0.10"
44
+ },
45
+ "devDependencies": {
46
+ "@expo-harmony/expo-module-scripts": "55.0.0-harmony.4",
47
+ "@expo-harmony/expo-modules-core": "55.0.25-harmony.4",
48
+ "@expo/log-box": "55.0.12",
49
+ "@react-native-oh/react-native-harmony": "0.84.1",
50
+ "expo": "55.0.26",
51
+ "expo-modules-core": "55.0.25",
52
+ "react": "19.2.3",
53
+ "react-native": "0.84.1",
54
+ "stacktrace-parser": "0.1.11"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ }
62
+ }
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+
5
+ /** Keep Expo's inspector/parser; adapt only its DOM host on Harmony. */
6
+ function withLogBox(config) {
7
+ const resolve = config.resolver?.resolveRequest;
8
+ const root = path.dirname(require.resolve('@expo/log-box/package.json'));
9
+ const inspector = path.join(root, 'swap-rn-logbox.js');
10
+ const parser = path.join(root, 'swap-rn-logbox-parser.js');
11
+ const nativeInspector = path.join(root, 'src/logbox-rn-polyfill.tsx');
12
+ const dom = path.join(root, 'src/logbox-dom-polyfill.tsx');
13
+ const enabled = ['1', 'true'].includes(process.env.EXPO_UNSTABLE_LOG_BOX?.toLowerCase());
14
+
15
+ return {
16
+ ...config,
17
+ resolver: {
18
+ ...config.resolver,
19
+ resolveRequest: Object.assign(function resolveRequest(context, name, platform) {
20
+ const result = (resolve ?? context.resolveRequest)(context, name, platform);
21
+ if (platform !== 'harmony' || !enabled || result.type !== 'sourceFile') return result;
22
+
23
+ if (result.filePath.endsWith('/Libraries/LogBox/LogBoxInspectorContainer.js')) {
24
+ return { type: 'sourceFile', filePath: inspector };
25
+ }
26
+ if (result.filePath.endsWith('/Libraries/LogBox/Data/parseLogBoxLog.js')) {
27
+ return { type: 'sourceFile', filePath: parser };
28
+ }
29
+ if (context.originModulePath === nativeInspector && result.filePath === dom) {
30
+ return { type: 'sourceFile', filePath: path.join(__dirname, '../src/LogBoxDOMView.tsx') };
31
+ }
32
+ return result;
33
+ }, { harmonyRuntime: resolve?.harmonyRuntime }),
34
+ },
35
+ };
36
+ }
37
+
38
+ module.exports = { withLogBox };
@@ -0,0 +1,66 @@
1
+ import React, { useRef } from 'react';
2
+ import { requireNativeModule, requireNativeViewManager } from 'expo-modules-core';
3
+ import { View, type ViewProps } from 'react-native';
4
+ import type LogBoxPolyfillDOM from '@expo/log-box/src/logbox-dom-polyfill';
5
+ import { getInjectEventScript, NATIVE_ACTION, NATIVE_ACTION_RESULT } from 'expo/src/dom/injection';
6
+
7
+ type Props = React.ComponentProps<typeof LogBoxPolyfillDOM>;
8
+ type NativeRef = { injectJavaScript(script: string): Promise<void> };
9
+ type NativeProps = ViewProps & {
10
+ payload: string;
11
+ onMessage(event: { nativeEvent: { data: string } }): void;
12
+ ref: React.Ref<NativeRef>;
13
+ };
14
+
15
+ const NativeView = requireNativeViewManager<NativeProps>('ExpoLogBox');
16
+
17
+ export default function LogBoxDOMView({ dom, ...props }: Props) {
18
+ const ref = useRef<NativeRef>(null);
19
+ const native = requireNativeModule<{ enabled: boolean; copyTextAsync(text: string): Promise<void> }>('ExpoLogBox');
20
+ if (!native.enabled) {
21
+ throw new Error('Expo LogBox requires EXPO_UNSTABLE_LOG_BOX=1 for both Harmony prebuild and Metro. Rebuild the application after enabling the plugin.');
22
+ }
23
+
24
+ const actions: Record<string, unknown> = {
25
+ ...Object.fromEntries(Object.entries(props).filter(([, value]) => typeof value === 'function')),
26
+ // RNOH 0.84 has neither the public Clipboard export nor its native module.
27
+ onCopyText: (text: string) => native.copyTextAsync(text),
28
+ };
29
+ const payload = JSON.stringify({
30
+ names: Object.keys(actions),
31
+ props: Object.fromEntries(Object.entries(props).filter(([, value]) => typeof value !== 'function')),
32
+ });
33
+
34
+ const onMessage: NativeProps['onMessage'] = async ({ nativeEvent }) => {
35
+ const { type, data } = JSON.parse(nativeEvent.data);
36
+ if (type !== NATIVE_ACTION || typeof data?.uid !== 'string' || typeof data.actionId !== 'string') return;
37
+
38
+ const view = ref.current;
39
+ const { uid, actionId, args } = data;
40
+ let result: unknown;
41
+ let error: { message: string; stack?: string } | undefined;
42
+
43
+ try {
44
+ const action = actions[actionId];
45
+ if (!Object.hasOwn(actions, actionId) || typeof action !== 'function' || !Array.isArray(args)) {
46
+ throw new Error(`Invalid LogBox action: ${actionId}`);
47
+ }
48
+ result = await (action as (...args: unknown[]) => unknown)(...args);
49
+ } catch (cause) {
50
+ error = cause instanceof Error ? { message: cause.message, stack: cause.stack } : { message: String(cause) };
51
+ }
52
+
53
+ // Dismiss/reload can unmount this view while an action is pending.
54
+ if (view && view === ref.current) {
55
+ await view.injectJavaScript(getInjectEventScript({
56
+ type: NATIVE_ACTION_RESULT, data: { uid, actionId, result, error },
57
+ }));
58
+ }
59
+ };
60
+
61
+ return (
62
+ <View style={[{ flex: 1 }, dom?.containerStyle]}>
63
+ <NativeView ref={ref} payload={payload} onMessage={onMessage} style={[{ flex: 1 }, dom?.style]} />
64
+ </View>
65
+ );
66
+ }