@expo-harmony/expo-dev-launcher 55.0.36-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 +73 -0
- package/app.plugin.js +3 -0
- package/expo-module.config.json +10 -0
- package/harmony/library/oh-package.json5 +16 -0
- package/harmony/library.har +0 -0
- package/index.d.ts +1 -0
- package/index.js +3 -0
- package/package.json +66 -0
- package/plugin/withDevLauncher.js +89 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @expo-harmony/expo-dev-launcher
|
|
2
|
+
|
|
3
|
+
[**GitHub 仓库**](https://github.com/renbaoshuo/expo-harmony/tree/master/packages/expo-dev-launcher) | [官方文档](https://docs.expo.dev/versions/v55.0.0/sdk/dev-client/)
|
|
4
|
+
|
|
5
|
+
为 HarmonyOS 上的 Expo 开发构建提供原生启动器,支持项目地址、最近项目、mDNS 发现、系统扫码和返回主页。与官方同版本的 `expo-dev-launcher` 配套使用。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @expo-harmony/expo-dev-launcher expo-dev-launcher@55.0.36
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
本包适配 Expo SDK 55 的 `expo-dev-launcher`,提供鸿蒙端的原生启动器,原生模块通过 Expo Harmony 自动链接。启动器只在开发构建中生效,配置插件负责写入开发链接入口和所需权限,见下文。最低支持 HarmonyOS 5.0.1(API 13),宿主的 `compatibleSdkVersion` 也要满足这一要求。
|
|
14
|
+
|
|
15
|
+
使用 `@expo-harmony/expo-dev-client` 时本包由其引入,无需单独安装,但仍需按下文显式注册本包的配置插件。`@expo-harmony/expo-dev-client` 只聚合依赖,不提供插件入口。
|
|
16
|
+
|
|
17
|
+
## Config Plugin
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"expo": {
|
|
22
|
+
"plugins": [
|
|
23
|
+
[
|
|
24
|
+
"@expo-harmony/expo-dev-launcher",
|
|
25
|
+
{
|
|
26
|
+
"launchMode": "launcher",
|
|
27
|
+
"toolsButton": true
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
],
|
|
31
|
+
"harmony": {
|
|
32
|
+
"bundleName": "com.example.app"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`launchMode` 取 `most-recent`(默认)或 `launcher`。前者尝试加载最近项目,失败时留在启动器并显示错误。后者跳过最近项目,始终显示启动器。`toolsButton` 控制开发菜单 FAB 的默认值,用户保存的偏好优先。
|
|
39
|
+
|
|
40
|
+
插件在原生工程中添加 `expo-harmony://open?url=...` 的链接入口、网络权限和启动模式配置。开发链接在普通 Linking 分发前被消费,应用自身的深链接不受影响。扫码使用系统扫码界面,不额外申请应用相机权限。Release 构建保留链接入口,启动器不启用,开发链接也不被消费。
|
|
41
|
+
|
|
42
|
+
## API 对照表
|
|
43
|
+
|
|
44
|
+
### Methods
|
|
45
|
+
|
|
46
|
+
#### `ExpoDevLauncher.loadApp(url)`
|
|
47
|
+
|
|
48
|
+
返回 `Promise<void>`,加载并启动一个开发项目。接受 HTTP(S) 项目地址、`exp://`、`exps://` 地址和开发客户端链接(`expo-harmony://open?url=...`),带凭据、fragment 或编码无效的地址会被拒绝,错误也会显示在启动器界面。一次加载未完成时又收到新地址,以最新的为准。Release 构建中调用直接返回。
|
|
49
|
+
|
|
50
|
+
以 `.bundle` 或 `.js` 结尾的地址按 Metro bundle 加载,其余地址按开发清单处理。清单会与客户端内置的运行时信息逐项比较,runtimeVersion、Expo / RNOH / Hermes 版本、已编入的原生模块和权限要求都要匹配,项目不能要求客户端没有编入的原生能力。已发布的项目需要客户端编入 `@expo-harmony/expo-updates`,由 Updates 下载并提供本地 bundle,没有 Updates 时这类项目拒绝加载。
|
|
51
|
+
|
|
52
|
+
### Native Modules
|
|
53
|
+
|
|
54
|
+
#### `EXDevLauncher.getConstants()`
|
|
55
|
+
|
|
56
|
+
提供 `manifestString` 和 `manifestURL`,分别是当前项目清单的 JSON 字符串和清单地址。未选择项目时两者均为 `null`。只在开发构建中可用。
|
|
57
|
+
|
|
58
|
+
### 启动器
|
|
59
|
+
|
|
60
|
+
启动器界面列出最近项目和局域网内 mDNS 发现的开发服务,也可以手动输入地址或系统扫码。最近项目按地址去重,保存三天。扫码使用系统扫码界面,不申请相机权限,设备不支持时提示错误。
|
|
61
|
+
|
|
62
|
+
开发菜单中的 Home 退出当前项目,回到启动器界面。上一次启动的运行时错误会记录下来,最多 32 KiB,保留三天。下次启动时读取一次并显示在启动器界面,应用停留在启动器,不自动重新进入失败的项目。系统原生崩溃不在此列。
|
|
63
|
+
|
|
64
|
+
> **未实现的内容**
|
|
65
|
+
>
|
|
66
|
+
> - 账户登录、EAS 分支浏览。HarmonyOS 开发客户端没有接入 EAS 账户体系。
|
|
67
|
+
|
|
68
|
+
## Author
|
|
69
|
+
|
|
70
|
+
**expo-harmony** © [Baoshuo](https://github.com/renbaoshuo), Released under the MIT License.<br>
|
|
71
|
+
Authored and maintained by Baoshuo with help from [contributors](https://github.com/renbaoshuo/expo-harmony/contributors).
|
|
72
|
+
|
|
73
|
+
> [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,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"platforms": ["harmony"],
|
|
3
|
+
"harmony": {
|
|
4
|
+
"modules": ["ExpoDevLauncherModule"],
|
|
5
|
+
"runtimeBindings": ["ExpoDevLauncherDevMenuBinding"],
|
|
6
|
+
"reactNativeHostHandlers": ["ExpoDevLauncherReactNativeHostHandler"],
|
|
7
|
+
"reactActivityHandlers": ["ExpoDevLauncherReactActivityHandler"],
|
|
8
|
+
"abilityLifecycleSubscribers": ["ExpoDevLauncherLifecycleSubscriber"]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@expo-harmony/expo-dev-launcher",
|
|
3
|
+
"version": "55.0.36-harmony.1",
|
|
4
|
+
"description": "HarmonyOS port of expo-dev-launcher",
|
|
5
|
+
"main": "Index.ets",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@expo-harmony/expo-dev-menu": "55.0.30-harmony.1",
|
|
9
|
+
"@expo-harmony/expo-dev-menu-interface": "55.0.2-harmony.1",
|
|
10
|
+
"@expo-harmony/expo-modules-core": "55.0.25-harmony.4",
|
|
11
|
+
"@expo-harmony/expo-manifests": "55.0.17-harmony.2",
|
|
12
|
+
"@expo-harmony/expo-updates-interface": "55.1.6-harmony.2",
|
|
13
|
+
"@expo-harmony/expo-eas-client": "55.0.5-harmony.2",
|
|
14
|
+
"@rnoh/react-native-openharmony": "0.84.1"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
Binary file
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@expo-harmony/expo-dev-launcher",
|
|
3
|
+
"version": "55.0.36-harmony.1",
|
|
4
|
+
"description": "HarmonyOS port of expo-dev-launcher",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react-native",
|
|
7
|
+
"expo",
|
|
8
|
+
"expo-harmony",
|
|
9
|
+
"harmonyos"
|
|
10
|
+
],
|
|
11
|
+
"author": "Baoshuo <i@baoshuo.ren>",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/renbaoshuo/expo-harmony.git",
|
|
16
|
+
"directory": "packages/expo-dev-launcher"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/renbaoshuo/expo-harmony/tree/master/packages/expo-dev-launcher#readme",
|
|
19
|
+
"bugs": "https://github.com/renbaoshuo/expo-harmony/issues",
|
|
20
|
+
"main": "index.js",
|
|
21
|
+
"types": "index.d.ts",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"harmony:clean": "expo-harmony-module prepare --clean-only",
|
|
24
|
+
"harmony:build": "expo-harmony-module prepare",
|
|
25
|
+
"prepack": "expo-harmony-module prepack"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@expo-harmony/expo-modules-core": "55.0.25-harmony.4",
|
|
29
|
+
"expo-dev-launcher": "55.0.36"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@expo-harmony/expo-module-scripts": "55.0.0-harmony.4",
|
|
33
|
+
"@expo-harmony/expo-modules-core": "55.0.25-harmony.4",
|
|
34
|
+
"@react-native-oh/react-native-harmony": "0.84.1",
|
|
35
|
+
"expo": "55.0.26",
|
|
36
|
+
"expo-dev-launcher": "55.0.36",
|
|
37
|
+
"expo-modules-core": "55.0.25",
|
|
38
|
+
"react": "19.2.3",
|
|
39
|
+
"react-native": "0.84.1"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@expo-harmony/config-plugins": "55.0.10-harmony.5",
|
|
49
|
+
"@expo-harmony/expo-dev-menu": "55.0.30-harmony.1",
|
|
50
|
+
"@expo-harmony/expo-dev-menu-interface": "55.0.2-harmony.1",
|
|
51
|
+
"@expo-harmony/expo-eas-client": "55.0.5-harmony.2",
|
|
52
|
+
"@expo-harmony/expo-manifests": "55.0.17-harmony.2",
|
|
53
|
+
"@expo-harmony/expo-updates-interface": "55.1.6-harmony.2",
|
|
54
|
+
"@expo/config-plugins": "55.0.10"
|
|
55
|
+
},
|
|
56
|
+
"harmony": {
|
|
57
|
+
"autolinking": {
|
|
58
|
+
"mainHarPath": "harmony",
|
|
59
|
+
"ohPackageName": "@expo-harmony/expo-dev-launcher",
|
|
60
|
+
"etsPackageClassName": "ExpoDevLauncherPackage",
|
|
61
|
+
"etsPackageImport": "named",
|
|
62
|
+
"cppPackageClassName": "ExpoDevLauncherPackage",
|
|
63
|
+
"cmakeLibraryTargetName": "expo_harmony__expo_dev_launcher"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createRunOncePlugin } = require('@expo/config-plugins');
|
|
4
|
+
const {
|
|
5
|
+
HarmonyManifest,
|
|
6
|
+
registerHarmonyConfigPlugin,
|
|
7
|
+
withModuleJson,
|
|
8
|
+
} = require('@expo-harmony/config-plugins');
|
|
9
|
+
|
|
10
|
+
const pkg = require('../package.json');
|
|
11
|
+
|
|
12
|
+
const INTERNET_PERMISSION = 'ohos.permission.INTERNET';
|
|
13
|
+
const NETWORK_INFO_PERMISSION = 'ohos.permission.GET_NETWORK_INFO';
|
|
14
|
+
const LAUNCH_MODE_METADATA = 'expo.devLauncher.launchMode';
|
|
15
|
+
const TOOLS_BUTTON_METADATA = 'EXDevMenuShowFloatingActionButton';
|
|
16
|
+
const DEEP_LINK_SCHEME = 'expo-harmony';
|
|
17
|
+
const DEEP_LINK_HOST = 'open';
|
|
18
|
+
const VIEW_ACTION = 'ohos.want.action.viewData';
|
|
19
|
+
const BROWSABLE_ENTITY = 'entity.system.browsable';
|
|
20
|
+
|
|
21
|
+
function launchMode(options) {
|
|
22
|
+
const value = options.launchMode ?? options.launchModeExperimental ?? 'most-recent';
|
|
23
|
+
if (value !== 'most-recent' && value !== 'launcher') {
|
|
24
|
+
throw new TypeError('expo-dev-launcher launchMode must be \'most-recent\' or \'launcher\'.');
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function selectedAbility(module) {
|
|
30
|
+
const name = module.mainElement;
|
|
31
|
+
return Array.isArray(module.abilities)
|
|
32
|
+
? module.abilities.find((ability, index) => ability?.name === name || (!name && index === 0))
|
|
33
|
+
: undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isLauncherSkill(skill) {
|
|
37
|
+
return Array.isArray(skill?.entities) && skill.entities.length === 1 && skill.entities[0] === BROWSABLE_ENTITY
|
|
38
|
+
&& Array.isArray(skill?.actions) && skill.actions.length === 1 && skill.actions[0] === VIEW_ACTION
|
|
39
|
+
&& Array.isArray(skill?.uris) && skill.uris.length === 1
|
|
40
|
+
&& skill.uris[0]?.scheme === DEEP_LINK_SCHEME && skill.uris[0]?.host === DEEP_LINK_HOST;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function updateManifest(json, options = {}) {
|
|
44
|
+
const module = json.module;
|
|
45
|
+
if (!module || typeof module !== 'object') throw new TypeError('expo-dev-launcher requires a Harmony module manifest.');
|
|
46
|
+
|
|
47
|
+
const ability = selectedAbility(module);
|
|
48
|
+
if (!ability) throw new TypeError('expo-dev-launcher requires an entry UIAbility.');
|
|
49
|
+
|
|
50
|
+
const target = { metadata: module.metadata };
|
|
51
|
+
HarmonyManifest.setMetadata(target, { name: LAUNCH_MODE_METADATA, value: launchMode(options) });
|
|
52
|
+
if (typeof options.toolsButton === 'boolean') {
|
|
53
|
+
HarmonyManifest.setMetadata(target, { name: TOOLS_BUTTON_METADATA, value: String(options.toolsButton) });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const permissions = Array.isArray(module.requestPermissions) ? module.requestPermissions : [];
|
|
57
|
+
const managedPermissions = new Set([INTERNET_PERMISSION, NETWORK_INFO_PERMISSION]);
|
|
58
|
+
const requestPermissions = [
|
|
59
|
+
...permissions.filter(permission => !managedPermissions.has(permission?.name)),
|
|
60
|
+
permissions.find(permission => permission?.name === INTERNET_PERMISSION) ?? { name: INTERNET_PERMISSION },
|
|
61
|
+
permissions.find(permission => permission?.name === NETWORK_INFO_PERMISSION) ?? { name: NETWORK_INFO_PERMISSION },
|
|
62
|
+
];
|
|
63
|
+
const skills = Array.isArray(ability.skills) ? ability.skills : [];
|
|
64
|
+
const launcherSkill = {
|
|
65
|
+
entities: [BROWSABLE_ENTITY],
|
|
66
|
+
actions: [VIEW_ACTION],
|
|
67
|
+
uris: [{ scheme: DEEP_LINK_SCHEME, host: DEEP_LINK_HOST }],
|
|
68
|
+
};
|
|
69
|
+
const abilities = module.abilities.map(item => item === ability
|
|
70
|
+
? { ...item, skills: [...skills.filter(skill => !isLauncherSkill(skill)), launcherSkill] }
|
|
71
|
+
: item);
|
|
72
|
+
|
|
73
|
+
return { ...json, module: { ...module, metadata: target.metadata, requestPermissions, abilities } };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function withHarmonyDevLauncher(config, options = {}) {
|
|
77
|
+
const enabled = config.harmony?.bundleName || config.platforms?.includes('harmony');
|
|
78
|
+
if (!enabled) return config;
|
|
79
|
+
|
|
80
|
+
config = registerHarmonyConfigPlugin(config, pkg.name);
|
|
81
|
+
config = withModuleJson(config, (mod) => {
|
|
82
|
+
mod.modResults = updateManifest(mod.modResults, options);
|
|
83
|
+
return mod;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return config;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = createRunOncePlugin(withHarmonyDevLauncher, pkg.name, pkg.version);
|