feat(update): support safe plugin bundle reload
这个提交包含在:
父节点
c8a6d88ab9
当前提交
e7b31d7fc6
@ -108,6 +108,8 @@ pnpm --dir packages/update test
|
|||||||
- alpha 版本号改为取源码现有序号与 Nexus 远端序号的最大值后加一,禁止版本倒退和重复发布。
|
- alpha 版本号改为取源码现有序号与 Nexus 远端序号的最大值后加一,禁止版本倒退和重复发布。
|
||||||
- `#49` 被 Jenkins CPS 的不可序列化正则 Matcher 阻断;算法改为只保留整数,并从 Nexus group 读标签、向 hosted 写制品。
|
- `#49` 被 Jenkins CPS 的不可序列化正则 Matcher 阻断;算法改为只保留整数,并从 Nexus group 读标签、向 hosted 写制品。
|
||||||
- `#50` 证明旧 Jenkins 工作树仍保留 CRLF;主检出由 `CleanBeforeCheckout` 改为 `WipeWorkspace`,确保 `.gitattributes` 在全新工作树生效。
|
- `#50` 证明旧 Jenkins 工作树仍保留 CRLF;主检出由 `CleanBeforeCheckout` 改为 `WipeWorkspace`,确保 `.gitattributes` 在全新工作树生效。
|
||||||
|
- `XuqmRuntime.activate` 增加显式 `reloadBundle`:buz-only release 可替换旧定义并热加载;含 common 的 release 仍由宿主冷启动。
|
||||||
|
- `#51` 通过换行、格式和类型门禁后,暴露 pack 校验脚本硬编码 Unix `npm`;现改为 `fileURLToPath` 与 Windows `cmd.exe/npm.cmd` 分支。
|
||||||
|
|
||||||
### 2026-07-17 / common 第一轮
|
### 2026-07-17 / common 第一轮
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,10 @@ apply from: file("../../node_modules/@xuqm/rn-update/android/xuqm-bundles.gradle
|
|||||||
|
|
||||||
## 运行时规则
|
## 运行时规则
|
||||||
|
|
||||||
|
安装仅包含 buz 的 release set 后,使用
|
||||||
|
`XuqmRuntime.activate(moduleId, { reloadBundle: true })` 重新求值并激活新 bundle。若 release set
|
||||||
|
包含 common,宿主必须冷启动,不能在同一 JavaScript VM 中混用新旧 common。
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { UpdateSDK, XuqmRuntime } from '@xuqm/rn-update'
|
import { UpdateSDK, XuqmRuntime } from '@xuqm/rn-update'
|
||||||
|
|
||||||
|
|||||||
@ -4,15 +4,26 @@ import { execFileSync } from 'node:child_process'
|
|||||||
import { readFileSync, rmSync } from 'node:fs'
|
import { readFileSync, rmSync } from 'node:fs'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
const cwd = new URL('..', import.meta.url).pathname
|
const cwd = fileURLToPath(new URL('..', import.meta.url))
|
||||||
const result = JSON.parse(
|
const packOutput =
|
||||||
execFileSync('npm', ['pack', '--json', '--ignore-scripts'], {
|
process.platform === 'win32'
|
||||||
|
? execFileSync(
|
||||||
|
process.env.ComSpec ?? 'cmd.exe',
|
||||||
|
['/d', '/s', '/c', 'npm.cmd pack --json --ignore-scripts'],
|
||||||
|
{
|
||||||
cwd,
|
cwd,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
env: { ...process.env, npm_config_cache: path.join(tmpdir(), 'xuqm-rn-npm-cache') },
|
env: { ...process.env, npm_config_cache: path.join(tmpdir(), 'xuqm-rn-npm-cache') },
|
||||||
}),
|
},
|
||||||
)[0]
|
)
|
||||||
|
: execFileSync('npm', ['pack', '--json', '--ignore-scripts'], {
|
||||||
|
cwd,
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: { ...process.env, npm_config_cache: path.join(tmpdir(), 'xuqm-rn-npm-cache') },
|
||||||
|
})
|
||||||
|
const result = JSON.parse(packOutput)[0]
|
||||||
const required = [
|
const required = [
|
||||||
'android/src/main/AndroidManifest.xml',
|
'android/src/main/AndroidManifest.xml',
|
||||||
'android/src/main/java/com/xuqm/update/XuqmBundleModule.java',
|
'android/src/main/java/com/xuqm/update/XuqmBundleModule.java',
|
||||||
|
|||||||
@ -19,6 +19,11 @@ export interface XuqmRuntimeOptions {
|
|||||||
checkUpdatesOnStart?: boolean
|
checkUpdatesOnStart?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface XuqmPluginActivationOptions {
|
||||||
|
/** Re-evaluate the active bundle after a buz-only release was installed. */
|
||||||
|
reloadBundle?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
const definitions = new Map<string, XuqmPluginDefinition>()
|
const definitions = new Map<string, XuqmPluginDefinition>()
|
||||||
|
|
||||||
/** Called by a plugin bundle when it is evaluated. */
|
/** Called by a plugin bundle when it is evaluated. */
|
||||||
@ -55,9 +60,19 @@ export const XuqmRuntime = (() => {
|
|||||||
return { kind: 'none' as const }
|
return { kind: 'none' as const }
|
||||||
},
|
},
|
||||||
|
|
||||||
async activate(moduleId: string): Promise<void> {
|
async activate(
|
||||||
|
moduleId: string,
|
||||||
|
activationOptions: XuqmPluginActivationOptions = {},
|
||||||
|
): Promise<void> {
|
||||||
if (!options) throw new Error('[XuqmRuntime] configure() must be called before activate()')
|
if (!options) throw new Error('[XuqmRuntime] configure() must be called before activate()')
|
||||||
if (active.has(moduleId)) return
|
if (active.has(moduleId) && !activationOptions.reloadBundle) return
|
||||||
|
|
||||||
|
if (activationOptions.reloadBundle) {
|
||||||
|
const previous = definitions.get(moduleId)
|
||||||
|
if (previous?.deactivate) await previous.deactivate()
|
||||||
|
active.delete(moduleId)
|
||||||
|
definitions.delete(moduleId)
|
||||||
|
}
|
||||||
|
|
||||||
let definition = definitions.get(moduleId)
|
let definition = definitions.get(moduleId)
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
|
|||||||
@ -20,4 +20,9 @@ export type {
|
|||||||
ReleaseSetPlan,
|
ReleaseSetPlan,
|
||||||
} from './releaseSet'
|
} from './releaseSet'
|
||||||
export { XuqmRuntime, definePlugin } from './PluginRuntime'
|
export { XuqmRuntime, definePlugin } from './PluginRuntime'
|
||||||
export type { XuqmPluginContext, XuqmPluginDefinition, XuqmRuntimeOptions } from './PluginRuntime'
|
export type {
|
||||||
|
XuqmPluginActivationOptions,
|
||||||
|
XuqmPluginContext,
|
||||||
|
XuqmPluginDefinition,
|
||||||
|
XuqmRuntimeOptions,
|
||||||
|
} from './PluginRuntime'
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户