关于react-native:Chrome-DevTools-Inspector-扩展实践

2次阅读

共计 8179 个字符,预计需要花费 21 分钟才能阅读完成。

截图自:https://developer.chrome.com/…

本文作者:原草

前言

云音乐 app 内有很多应用 react native 开发的利用,例如云贝核心、云音乐商城、会员中心等。为了更好地晋升开发效率,改善调试体验,团队决定开发 react native 调试工具,通过为 react native debugger 减少一些扩大性能,实现业务信息的展现和调试能力,例如:跨端通信信息展现、网络信息展现等。

因为云音乐 react native 利用的网络申请都是通过客户端发送,因而想把客户端网络信息通过 chrome devtools protocol 展现在 network 里。通过尝试后,期间遇到了一些问题和做出的尝试记录如下。

chrome devtools 介绍

chrome devtools 是前端罕用的调试工具,集成在 chrome 里。web 利用通过 chrome devtools protocol 与 devtools frontend(平时关上 f12 调试面板的页面,也是个前端我的项目,上面用 frontend 示意)建设连贯,将被调试利用信息传递到 frontend 上展现。

ChromeDevTools/devtools-frontend 是 chrome devtools frontend 的我的项目代码。也能够作为独自的我的项目应用,用于自定义调试性能。我的项目内应用的 devtools frontend 是 3964 版本,新版的 frontend 对打包形式、代码构造都做了调整。

devtools frontend 模块加载

通过配置近程调试端口 --remote-debugging-port=9222 启动 chrome,http://localhost:9222/ 能够看失去调试链接,例如:http://localhost:9222/devtools/inspector.html?ws=localhost:9222/devtools/page/7B2421304FE8EF659B264D4F476083DA 是一个 inspector 的地址,从 inspector.html 动手看下我的项目如何启动。

inspector.html 加载 Runtime.js 和 inspector.js,inspector.js 只做了一件事

Runtime.startApplication('inspector');

模块加载过程如下

模块通过加载解析过程后,启动利用:

  1. 前端利用通过读取 module.json 取得模块信息;
  2. 实例化 new Runtime,建设 Runtime -> Module -> Extension 依赖关系;
  3. 加载外围模块资源(script 和 css 资源);
  4. 启动外围模块入口(Main.Main)。

到此是模块的加载过程。

inspector 启动形式

查看 inspector.json 内容

{
  "modules" : [{ "name": "screencast", "type": "autostart"}
  ],
  "extends": "devtools_app",
  "has_html": true
}

inspector 利用继承自 devtools_app。只比 devtools_app 多了一个模块 screencast 页面快照,能够用于实时查看页面变动,。

devtools_app 利用就是罕用的 devtools 工具,能够用于 web 利用调试,devtools_app.json 依赖的模块内容如下:

{
  "modules" : [{ "name": "emulation", "type": "autostart"},
    {"name": "inspector_main", "type": "autostart"},
    {"name": "browser_debugger"},
    {"name": "elements"},
    {"name": "network"},
    ...
  ],
  "extends": "shell",
  "has_html": true
}

devtools_app.json 继承自 shell.json,应该就是 devtools 依赖的外围模块。shell.json 依赖的模块内容如下:

{
  "modules" : [{ "name": "bindings", "type": "autostart"},
    {"name": "common", "type": "autostart"},
    {"name": "components", "type": "autostart"},
    {"name": "extensions", "type": "autostart"},
    {"name": "host", "type": "autostart"},
    {"name": "main", "type": "autostart"},
    {"name": "protocol", "type": "autostart"},
    {"name": "ui", "type": "autostart"},
    ...
  ]
}

devtools_app 蕴含的 inspector_main 模块,将通过链接参数中传入的 ws 参数建设 socket 连贯用于获取监听后端 protocol 信息。

SDK._createMainConnection = function() {const wsParam = Runtime.queryParam('ws');
  const wssParam = Runtime.queryParam('wss');
  if (wsParam || wssParam) {const ws = wsParam ? `ws://${wsParam}` : `wss://${wssParam}`;
    SDK._mainConnection = new SDK.WebSocketConnection(ws, SDK._websocketConnectionLost);
  } else if (InspectorFrontendHost.isHostedMode()) {SDK._mainConnection = new SDK.StubConnection();
  } else {SDK._mainConnection = new SDK.MainConnection();
  }
  return SDK._mainConnection;
};

自此建设与被调试我的项目连贯,想理解其余模块的内容,也能够依照下面的思路查看。

react native debugger 调试过程

react native debugger 是一个用于调试 react native 应用程序的 electron 独立程序,基于官网近程调试性能,减少了 react-devtools-core 和 redux-devtools-extension 作为扩大反对。

react native 在 app 上并不是 web 页面,react native dedug 过程 在关上 debug remote 后:

  1. app 发送 /launch-js-devtools 申请给 server 关上调试 tab,并建设 socket 连贯;
  2. tab 加载 debugger-ui 页面,与 server 建设 socket 连贯(/debugger-proxy?role=debugger&name=Chrome);
  3. tab 关上 debugger-ui 同时,启动了 worker 运行 debuggerWorker.js 脚本,用来运行 bundle 代码;
  4. 最初通过 inspect worker 的形式来达到对 react native 利用的调试。

react native 中如果应用 fetch 或是 XMLHttpRequest 发送的网络申请,是能够在 frontend 调试过程中失去的。云音乐 app 内的 react native 利用的网络申请都是通过客户端发送,那么客户端网络信息通过 chrome devtools protocol 展现在 network 里,要怎么做呢?

chrome devtools inspector 扩大

chrome devtools protocol

减少 network 开始想到最间接的形式,就是通过 web 和 frontend 之间的 socket 链路。

chrome devtools protocol(简称 CDP)是 devtools 和 web 利用间传递的调试器协定。基于 websocket 建设 devtools 和浏览器内核的疾速数据通道,chrome devtools protocol 也容许第三方对 web 利用进行调试。

CDP 协定按域 Domain 划分能力,每个域下有 Method、Event 和 Types。

Method 对应 socket 通信的申请 / 响应模式,Events 对应 socket 通信的公布 / 订阅模式,Types 为交互中应用到的实体。

  • Method: 蕴含 request/response,如同异步调用,通过申请信息,获取相应返回后果,通信须要有 message id
request: {"id":1,"method":"Page.canScreencast"}
response: {"id":1,"result":{"result":false}}
  • Event:产生的事件信息,用于发送告诉信息。

    {"method":"Network.loadingFinished","params:{"requestId":"14307.143","timestamp":1424097364.31611,"encodedDataLength":0}}
  • Types:交互实体

    Network.BlockedReason
    Network.ConnectionType
    Network.Cookie
    ...

在 chrome 中能够应用 “Protocol Monitor” 发送 Method。

在 electron 内,能够通过 app.commandLine.appendSwitch 增加 chrome 的启动参数 remote-debugging-port 开启 chrome 调试端口 9222。

const CHROME_REMOTE_DEBUG_PORT = 9222; // devtools frontend 调试端口
app.commandLine.appendSwitch('remote-debugging-port', `${CHROME_REMOTE_DEBUG_PORT}`);

通过 http://localhost:9222/json 接口申请找到以后页面 React Native Debugger 的调试地址 webSocketDebuggerUrl。

[ {"description": "","devtoolsFrontendUrl":"/devtools/inspector.html?ws=localhost:9222/devtools/page/ADC97007929236D82B4613E4E6B36C4B","id":"ADC97007929236D82B4613E4E6B36C4B","title":"React Native Debugger - Waiting for client connection (port 8081)","type":"page","url":"file:///Users/jarry/netease/react-native-debugger/electron/app.html","webSocketDebuggerUrl":"ws://localhost:9222/devtools/page/ADC97007929236D82B4613E4E6B36C4B"
} ]

而后建设一个 socket 通道给 app。

尝试发现 nework 信息并没有展现,app 通过 socket 连贯发送的信息给到浏览器内核,但浏览器内核并没有将信息转发给 frontend,这里天经地义的把 web 利用作为调试信息解决对象。

采纳 proxy 形式

web 利用和 frontend 在同一网段,能够应用下面的调试形式,如果两局部不在一个内网,就须要用到近程调试形式。在 web 利用和 frontend 之间创立 proxy 服务,实现 CDP 音讯转发,以达到跨域调试指标。

受启发于跨域调试形式,proxy 在 web 和 frontend 之间做转发的同时,也能够转发其余的 CDP 音讯。

  1. 启动 ws 用作 proxy 服务

proxy.js 示例代码:

const WS_PROXY_PORT = 9233; // proxy ws 端口
const CHROME_REMOTE_DEBUG_PORT = 9222; // devtools frontend 调试端口

const proxyStart = async () => {
  // 创立 proxy server

  const server = http.createServer((request, response) => {response.writeHead(404);
    response.end();});
  server.listen(WS_PROXY_PORT);

  const wss = new WebSocket.Server({server});

  wss.on('connection', (ws, request, client) => {
    // 解决来自 remote-debug 的申请
    if (request.headers['sec-websocket-protocol'] === 'remote-debug') {axios.get(`http://127.0.0.1:${CHROME_REMOTE_DEBUG_PORT}/json`).then(res => {
        // 查问被调试页 webSocketDebuggerUrl
        const {data} = res;
        if (data && data.length > 0) {for (let i = 0; i < data.length; i++) {const page = data[i];
            if (page.title.indexOf('React Native Debugger') === 0) {debugConnection = new WebSocket(page.webSocketDebuggerUrl);
              });
              // 把被调试页面的数据全副转发给调试器前端
              debugConnection.on('message', (message) => {if (frontendConnection) {if (debugMessageArr.length) {
                    debugMessageArr.forEach(item => {frontendConnection.send(item);
                    });
                    debugMessageArr = [];}
                  frontendConnection.send(message);
                } else {debugMessageArr.push(message);
                  console.log('无奈转发给 frontend, 没有建设连贯 \n');
                }
              });
              break;
            }
          }
        }
      }).catch(error => {console.log(error);
      });
    }

    // 解决来自 frontend 的申请
    if (request.url === '/frontend') {
      frontendConnection = ws;
      frontendConnection.on('message', (message) => {
        // 把调试器前端的申请间接转发给被调试页面
        if (debugConnection) {if (frontendMessageArr.length) {
            frontendMessageArr.forEach(item => {debugConnection.send(item);
            });
            frontendMessageArr = [];}
          debugConnection.send(message);
        } else {frontendMessageArr.push(message);
          console.log('调试器后端未筹备好, 先关上被调试的页面');
        }
      });
    }

    // 解决来自客户端申请
    if (request.url === '/app') {
      appConnection = ws;
      appConnection.on('message', (message) => {
        // 把客户端的申请间接转发给前端调试器
        if (frontendConnection) {if (debugMessageArr.length) {
            debugMessageArr.forEach(item => {frontendConnection.send(item);
            });
            debugMessageArr = [];}
          frontendConnection.send(message);
        } else {debugMessageArr.push(message);
          console.log('无奈转发给 frontend, 没有建设连贯 \n');
        }
      });
    }
  });
};

这里解决了三方面的数据

  • remote-debug:被调试页面数据。通过查找 http://127.0.0.1:${CHROME_REMOTE_DEBUG_PORT}/json 获取 webSocketDebuggerUrl,建设 debugConnection 连贯,被调试信息从此来,发送给 frontend;
  • frontend:调试器数据。转发此数据给 debugConnection。
  • app:客户端数据,转发此数据给 debugConnection。
  1. remote-debug 建连

remote-debug 跑在 web,是为了告诉 proxy 建设连贯的,因为我的 proxy 跑在 electron,和 remote debug 同域,所以从 proxy 间接获取 webSocketDebuggerUrl。也能够在 remote-debug 内获取 webSocketDebuggerUrl 用告诉的形式通知 proxy。

const ws = new WebSocket('ws://localhost:9233', 'remote-debug');
  1. frontend 建连

frontend 因为要更换 ws 地址,因而这里没有采纳 chrome 自带的 devtools,而是本地启动的 frontend 服务,更换 ws 连贯为 proxy frontend 连贯。无关 frontend web 服务会在下一节介绍。

http://localhost:8090/front_end/devtools_app.html?ws=localhost:9233/frontend
  1. app 侧

app 端会在 okhttp 框架里增加拦截器,拦挡所有申请数据而后通过 ws 发送给 proxy。

自此就实现了 inspector 扩大 network 信息的性能。

frontend 本地服务

因为要自定义开发,devtools frontend 版本是追随 chrome 版本公布的,最好采纳和 chrome 版本统一的 frontend,比方我这里通过 process.versions.chrome 拿到 electron 内 chrome 版本号是 78.0.3904.130,对应的 devtools 版本号就是 3904,但能取得到最早的版本就是 3964 了,应用 3964 临时也没遇到其余问题。

devtools-fronend->scripts->server.js

启动本服务就能够失去一个运行在 8090 的 frontend 服务。

总结

本文从 chrome devtools inspector 扩大为出发点,介绍了 devtools frontend 调试原理及模块加载形式,react native debugger 调试原理,跨域调试计划,最终实现 devtools inspector 的调试扩大。文内波及各调试工具常识较多,大多做了概括,技术细节也都留了文档链接能够自行获取,心愿对做 chrome 调试工具的同学有所启发和帮忙。欢送对文中相干问题批评指正。

参考链接

  • https://github.com/jhen0409/r…
  • https://segmentfault.com/a/11…
  • https://github.com/ChromeDevT…
  • https://memoryza.gitbook.io/c…
  • https://zhaomenghuan.js.org/b…

本文公布自网易云音乐技术团队,文章未经受权禁止任何模式的转载。咱们长年招收各类技术岗位,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe(at)corp.netease.com!

正文完
 0