@@ -168,7 +168,7 @@ ohpm install @rickytan/ohospatch
168168``` json5
169169{
170170 " dependencies" : {
171- " @rickytan/ohospatch" : " ^1.0.0 "
171+ " @rickytan/ohospatch" : " ^1.0.3 "
172172 }
173173}
174174```
@@ -219,21 +219,32 @@ Patch 脚本可以引用声明文件获得 IDE 补全:
219219完整目标路径格式是:
220220
221221``` text
222- bundleName/moduleName/[packageName/]src/main/ets/File#ExportName
222+ bundleName/moduleName/[packagePath/]src/main/ets/File#ExportName
223+ @package/name/src/main/ets/File#ExportName
224+ /src/main/ets/File#ExportName
223225```
224226
225- Runtime 解析路径时以 ` src/main/ets ` 为锚点:前两段固定是 ` bundleName/moduleName ` ,锚点前剩余的所有段都会作为 OH package name。因此 scoped package 可以直接写完整包名 :
227+ Runtime 解析路径时以 ` src/main/ets ` 为锚点。完整路径里前两段是 ` bundleName/moduleName ` ,锚点前剩余的所有段都会作为 OH package path :
226228
227229``` text
228230com.example.app/entry/@google/somelib/src/main/ets/foo/Bar#Bar
229231```
230232
231233上面会解析为 ` moduleInfo = com.example.app/entry ` ,` modulePath = @google/somelib/src/main/ets/foo/Bar ` 。
232234
233- 例如 Demo 的 ` PatchablePanel ` 导出自 ` entry/src/main/ets/demo/PatchablePanel.ets ` ,所以目标为:
235+ ` #ExportName ` 必须对应 ArkTS 文件中实际 ` export ` 出来的类、函数或自定义 Component。OhosPatch 通过 ` napi_load_module_with_info ` 加载模块后只能从模块导出对象上取值;没有 ` export ` 的内部类型、文件内局部类、未导出的 ` @Entry ` 页面或只在文件内部使用的 helper,运行时无法定位,也就不能作为 ` Fixit.fix() ` 、` Fixit.component() ` 或 ` Fixit.import() ` 的目标。需要被 patch 的业务类或 Component 应保持稳定导出名,并在开启混淆时为这些导出和需要 hook 的方法配置 keep 规则。
236+
237+ 如果目标在当前运行的 APP/module 中,可以省略 ` bundleName/moduleName ` 。` @ ` 开头表示当前 host module 下的 OH package path;` / ` 开头表示当前 host module 自己的源码路径。host 信息来自 ` OhosPatch.init(context) ` :
238+
239+ ``` text
240+ @vendor/business_page/src/main/ets/components/PatchablePanel#PatchablePanel
241+ /src/main/ets/pages/Index#Index
242+ ```
243+
244+ 例如 Demo 的 ` PatchablePanel ` 来自 ` @vendor/business_page ` 包,因此推荐写法为:
234245
235246``` text
236- com.rickytan.ohospatch/entry /src/main/ets/demo /PatchablePanel#PatchablePanel
247+ @vendor/business_page /src/main/ets/components /PatchablePanel#PatchablePanel
237248```
238249
239250下面每个示例都先列出已经发布在 APP 中、无需为 OhosPatch 修改的原始 ArkTS 代码,再列出下发的 JavaScript Patch。
@@ -572,6 +583,64 @@ var originSubmit = panel.node({ // 返回 NodeBuilder。
572583
573584自定义组件是边界。父组件中的 ` <Child /> ` 只是父组件渲染路径上的一个自定义组件创建点,不会把 ` Child ` 内部的 ` Text/Button ` 计入父组件 selector;要修复子组件内部节点,需要对 ` Child ` 自己再写一条 ` Fixit.component(childFullPath) ` 。
574585
586+ 这个边界有一个明确的例外:父组件传给子组件的尾随闭包或 ` @BuilderParam ` 内容仍归父组件所有,可以通过 ` .node(childSelector).slot(nodeSelector) ` 修复。原始组件例如:
587+
588+ ``` ts
589+ @Component
590+ export struct ContentShell {
591+ @BuilderParam contentBuilder : () => void
592+
593+ build () {
594+ Column () {
595+ Text ('Shell title' ) // 子组件自己创建,不属于 slot
596+ this .contentBuilder ()
597+ }
598+ }
599+ }
600+
601+ @Component
602+ export struct ParentPanel {
603+ @State statusText : string = 'Original'
604+
605+ build () {
606+ ContentShell () {
607+ Text (`status=${this.statusText}` ) // slot Text occurrence 0
608+ Button ('Action' ) // slot Button occurrence 0
609+ .height (44)
610+ .onClick (() => {
611+ this .statusText = 'Original click'
612+ })
613+ }
614+ }
615+ }
616+ ```
617+
618+ 对应 Patch:
619+
620+ ``` js
621+ var parent = Fixit .component (
622+ ' @vendor/business/src/main/ets/ParentPanel#ParentPanel'
623+ ); // 返回 ComponentFix,目标是拥有尾随闭包的父组件。
624+
625+ var child = parent .node ({
626+ type: ' @vendor/business/src/main/ets/ContentShell#ContentShell' , // 选择父组件创建的自定义子组件。
627+ occurrence: 0 // 选择第一个 ContentShell 实例。
628+ }); // 返回 ComponentNodeFix。
629+
630+ child .slot ({ type: ' Text' , occurrence: 0 }) // 在该实例收到的 BuilderParam 内容中选择第一个 Text。
631+ .attrs ({ fontColor: ' #C44736' , fontSize: 18 }); // 返回 ComponentSlotNodeFix。
632+
633+ var originClick = child .slot ({ type: ' Button' , occurrence: 0 }) // slot 内各节点类型独立从 0 计数。
634+ .attrs ({ height: 52 , backgroundColor: ' #C44736' }) // 覆盖原 Button 属性;返回 ComponentSlotNodeFix。
635+ .event (' onClick' , function () { // 返回 OriginEvent;this 是 ParentPanel 当前实例。
636+ var result = originClick .apply (this , arguments ); // 调用并取得原 onClick 返回值。
637+ this .statusText = ' Patched click' ; // 原回调执行后修改父组件状态。
638+ return result; // 保持原事件返回语义。
639+ });
640+ ```
641+
642+ ` .slot() ` 只覆盖由父组件提供的 builder 内容,不会选择 ` ContentShell ` 自己创建的 ` Text('Shell title') ` 。slot 内的 ` occurrence ` 每次执行该 BuilderParam 时独立计数,因此上例的 slot Text 是 ` 0 ` ,不受父组件其他 Text 或子组件标题 Text 影响。slot 节点同样支持 ` type + where ` ;未找到节点时保持 no-op 并输出一次 warning。
643+
575644条件渲染时,只统计当前状态下实际执行到的分支。` if ` 分支里第一个 ` Button ` 是该分支执行时的 ` Button occurrence: 0 ` ;切到 ` else ` 分支后,` else ` 分支里实际创建的同类型节点会重新按执行顺序计数。循环和 ` ForEach ` 也是同一规则:每个实际执行的迭代都会按顺序贡献节点,因此列表长度、排序或过滤条件变化会改变后续同类型节点的 ` occurrence ` 。
576645
577646更具体地说,编译后的组件渲染会在执行到每个 ArkUI 内置节点 builder 时累加计数;没有执行到的分支不会占位。
@@ -978,13 +1047,27 @@ $HOME/Library/OpenHarmony/Sdk
9781047
9791048路径不同时,通过环境或仓库变量 ` DEVECO_STUDIO_HOME ` 、` OHOS_BASE_SDK_HOME ` 覆盖。
9801049
1050+ ## 构建产物
1051+
1052+ ` ohospatch ` 的内置 Patch Runtime 源码位于 ` ohospatch/src/main/cpp/runtime/fixit.js ` 。构建 HAR/HAP 时会通过 Gulp 调用 Terser,把它压缩为 ` ohospatch/src/main/resources/rawfile/ohospatch/fixit.min.js ` 后打进 rawfile。
1053+
1054+ 当前压缩不是简单删除注释和空白,而是使用 Terser 的 ` compress ` 和 ` mangle ` 能力:
1055+
1056+ ``` bash
1057+ npm install --prefix tools/fixit-runtime-build
1058+ npm run build:fixit-runtime -- --input ohospatch/src/main/cpp/runtime/fixit.js --output ohospatch/src/main/resources/rawfile/ohospatch/fixit.min.js
1059+ ```
1060+
1061+ CMake 构建也会调用同一个 Gulp task;如果本地没有安装 Node 依赖,会明确提示先在项目根目录执行 ` npm install --prefix tools/fixit-runtime-build ` 。
1062+
9811063## 当前边界
9821064
9831065- prototype hook 不覆盖构造函数、实例字段箭头函数、私有实现或不经过属性查找的调用点。
1066+ - 只有从 ArkTS 模块 ` export ` 出来的类、函数和自定义 Component 能被 patch;未导出的内部类型无法通过运行时模块导出表定位。
9841067- Patch handler 的 ` this ` Proxy 只在当前同步调用或 ` origin ` 调用期间有效,不应保存到 timer、Promise 或全局变量后异步访问。
9851068- ` Fixit.import() ` 返回的持久 Proxy 可保留到 ` OhosPatch.clear() ` 或下一次 patch 替换。
9861069- 普通方法参数和新建 JS 对象仍受 JSON wire 类型限制。
987- - Component DSL 当前支持 API 20 状态管理 V1/V2、导出的自定义组件、首选的 ` type + occurrence ` 节点选择器、` type + where ` 原始属性选择器、JSON 属性参数和同步事件替换。
1070+ - Component DSL 当前支持 API 20 状态管理 V1/V2、导出的自定义组件、父组件尾随闭包/ ` @BuilderParam ` slot、 首选的 ` type + occurrence ` 节点选择器、` type + where ` 原始属性选择器、JSON 属性参数和同步事件替换。
9881071- 非导出的 ` @Entry ` 页面、层级选择器、已挂载组件主动刷新,以及 ` before/after/around ` 事件组合尚未支持。` Resource ` 、Controller 等不可 JSON 序列化对象不能作为 selector 或静态 attr 参数直接下发。
9891072- 单个 runtime 最多同时存在 256 个 timer。
9901073- 单个 patch 最多保留 512 个去重后的动态导入类、实例、方法或嵌套对象句柄。
0 commit comments