For AI agents: the complete documentation index is available at /next/zh/llms.txt, the full documentation bundle is available at /next/zh/llms-full.txt, and this page is available as Markdown at /next/zh/rspeedy/ui-source-map.md.
Rspeedy logo
Rspeedy
  • 简体中文
  • 反解 UI 节点树

    运行时一个 Lynx 页面就是一棵 UI 节点树,也就是引擎真正渲染出来的那些元素。UI source map 让你把树里的每个节点映射回创建它的 JSX(reposourcelinecolumn)。

    它分三步:

    1. 构建:打开 enableUiSourceMap,把 UI source map 写进 debug-metadata.json,并给元素打上 nodeIndex
    2. 导出:让 Lynx 客户端把运行时 UI 树序列化成 JSON。
    3. 反解:把这份 JSON 加上 debug-metadata.json 喂给 remapUiTree,它会给每个节点标注源码位置。

    1. 开启开关

    UI source map 生成默认是关的。在 pluginReactLynx 里打开:

    lynx.config.ts
    import { defineConfig } from '@lynx-js/rspeedy';
    import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin';
    
    export default defineConfig({
      plugins: [
        pluginReactLynx({
          enableUiSourceMap: true,
        }),
      ],
    });

    打开后,主线程的 transform 会给每个创建出来的元素打上 nodeIndex,并记录一张 nodeIndex → (source, line, column) 表。这张表会作为 debug-metadata.jsonuiSourceMap 字段产出:

    interface UiSourceMapData {
      version: 1;
      sources: string[]; // 源文件
      mappings: [number, number, number][]; // [sourceIndex, line, column]
      uiMaps: number[]; // uiMaps[i] 是一个 nodeIndex;mappings[i] 是它的位置
    }

    要让运行时指向这个文件,模板里还得带上它的 debugMetadataUrl:连到 dev server 时会自动写入,生产环境则要你自己注入(见 线上错误反解 → 方案 2)。否则导出的节点 debugMetadataUrl 为空,没法反解。

    2. 从客户端导出 UI 树

    三端用法一致:从 LynxView 异步拿到根 LynxElement,再对它调 toJSONString 把整棵树序列化出来。两个 callback 都会回调到 UI 线程(主线程)。

    iOS (Objective-C)
    Android (Java)
    Harmony (ArkTS)
    #import <Lynx/LynxView.h>
    #import <Lynx/LynxElement.h>
    
    [lynxView getLynxElementRoot:^(LynxElement *_Nullable root) {
      if (root == nil) return;
      [root toJSONString:^(NSString *_Nullable json) {
        if (json == nil) return;
        NSLog(@"LynxElement tree json: %@", json);
      }];
    }];

    序列化出来的每个节点长这样,反解需要的 nodeIndexdebugMetadataUrl 这两个字段嵌在 debugInfo 里:

    {
      "tag": "view",
      "nodeId": 11,
      "position": { "x": 0, "y": 0, "width": 300, "height": 80 },
      "events": [],
      "debugInfo": {
        "nodeIndex": 100,
        "debugMetadataUrl": "https://your-cdn.example.com/dm/<key>/debug-metadata.json",
      },
      "children": [
        { "tag": "text", "debugInfo": { "nodeIndex": 200 }, "children": [] },
      ],
    }

    nodeIndex 是第 1 步里给的编译期标识;debugMetadataUrl 是写进模板的那个 URL。没有 debugInfo.nodeIndex 的节点(比如纯文本)不会被反解。

    3. 反解这棵树

    @lynx-js/debug-metadataremapUiTree 读取的是每个节点顶层的 nodeIndexdebugMetadataUrl,而引擎把它们嵌在 debugInfo 里,所以先把它们提上来再反解。它的第二个参数是一个由你提供的 loader,负责把一个 debugMetadataUrl 取成对应的 DebugMetadataAsset

    import { remapUiTree } from '@lynx-js/debug-metadata';
    
    // 把引擎嵌套的 `debugInfo` 字段提到顶层
    function flatten(node) {
      const { debugInfo, children, ...rest } = node;
      return {
        ...rest,
        nodeIndex: debugInfo?.nodeIndex,
        debugMetadataUrl: debugInfo?.debugMetadataUrl,
        children: children?.map(flatten),
      };
    }
    
    const tree = JSON.parse(jsonFromClient);
    const remapped = await remapUiTree(flatten(tree), async (debugMetadataUrl) => {
      const res = await fetch(debugMetadataUrl);
      return res.json(); // 一个 DebugMetadataAsset
    });

    凡是 nodeIndex 能在对应 debugMetadataUrluiSourceMap 里命中的节点,都会拿到 repo / source / line / column;命中不了的节点,连同节点上原有的其它字段,都原样保留。remapUiTree 对每个不同的 debugMetadataUrl 只调用一次 loader。

    npx @lynx-js/debug-metadata remap --ui <tree.json> CLI 做的是同样的事,适用于节点已经在顶层带有 nodeIndex / debugMetadataUrl 的树。更底层的 buildUiSourceMapLookupnodeIndex → 位置)和 normalizeRepo(git remote → owner/repo)辅助函数也都有导出。

    除非另有说明,本项目采用知识共享署名 4.0 国际许可协议进行许可,代码示例采用 Apache License 2.0 许可协议进行许可。