美文网首页
react-native-cli源码分析之RN调试

react-native-cli源码分析之RN调试

作者: FingerStyle | 来源:发表于2022-11-29 21:29 被阅读0次

    启动metro server

    调试RN的第一步是开启metro server,通过命令行react-natvie start可以启动本地node服务,这里会调用metro的API来启动
    (https://github.com/react-native-community/cli/blob/main/packages/cli-plugin-metro/src/commands/start/runServer.ts)

    async function runServer(_argv: Array<string>, ctx: Config, args: Args) {
     //...省略无关代码
    
     const metroConfig = await loadMetroConfig(ctx, {
       config: args.config,
       maxWorkers: args.maxWorkers,
       port: args.port,
       resetCache: args.resetCache,
       watchFolders: args.watchFolders,
       projectRoot: args.projectRoot,
       sourceExts: args.sourceExts,
       reporter,
     });
    
     if (args.assetPlugins) {
       metroConfig.transformer.assetPlugins = args.assetPlugins.map((plugin) =>
         require.resolve(plugin),
       );
     }
    
     const {
       middleware,
       websocketEndpoints,
       messageSocketEndpoint,
       eventsSocketEndpoint,
     } = createDevServerMiddleware({
       host: args.host,
       port: metroConfig.server.port,
       watchFolders: metroConfig.watchFolders,
     });
     middleware.use(indexPageMiddleware);
    
     const customEnhanceMiddleware = metroConfig.server.enhanceMiddleware;
     metroConfig.server.enhanceMiddleware = (
       metroMiddleware: any,
       server: unknown,
     ) => {
       if (customEnhanceMiddleware) {
         metroMiddleware = customEnhanceMiddleware(metroMiddleware, server);
       }
       return middleware.use(metroMiddleware);
     };
    
     const serverInstance = await Metro.runServer(metroConfig, {
       host: args.host,
       secure: args.https,
       secureCert: args.cert,
       secureKey: args.key,
       hmrEnabled: true,
       websocketEndpoints,
     });
    
     //...省略无关代码
    }
    

    Metro.runServer 会启动一个 http server来跟客户端通信(https://github.com/facebook/metro/blob/main/packages/metro/src/index.flow.js)

    exports.runServer = async (
     config,
     {
       hasReducedPerformance = false,
       host,
       onError,
       onReady,
       secureServerOptions,
       secure,
       //deprecated
       secureCert,
       // deprecated
       secureKey,
       // deprecated
       waitForBundler = false,
       websocketEndpoints = {},
     }
    ) => {
     //省略无关代码...
    
     const connect = require("connect");
    
     const serverApp = connect();
     const { middleware, end, metroServer } = await createConnectMiddleware(//这里metroServer作为中间件使用
       config,
       {
         hasReducedPerformance,
         waitForBundler,
       }
     );
     serverApp.use(middleware);
     let inspectorProxy = null;
    
     if (config.server.runInspectorProxy) {
       inspectorProxy = new InspectorProxy(config.projectRoot);
     }
    
     let httpServer;
    
     if (secure || secureServerOptions != null) {
       let options = secureServerOptions;
    
       if (typeof secureKey === "string" && typeof secureCert === "string") {
         options = {
           key: fs.readFileSync(secureKey),
           cert: fs.readFileSync(secureCert),
           ...secureServerOptions,
         };
       }
    
       httpServer = https.createServer(options, serverApp);
     } else {
       httpServer = http.createServer(serverApp);
     }
    
     return new Promise((resolve, reject) => {
       httpServer.on("error", (error) => {
         if (onError) {
           onError(error);
         }
    
         reject(error);
         end();
       });
       httpServer.listen(config.server.port, host, () => {
         if (onReady) {
           onReady(httpServer);
         }
    
         Object.assign(websocketEndpoints, {
           ...(inspectorProxy
             ? { ...inspectorProxy.createWebSocketListeners(httpServer) }
             : {}),
           "/hot": createWebsocketServer({
             websocketServer: new MetroHmrServer(
               metroServer.getBundler(),
               metroServer.getCreateModuleId(),
               config
             ),
           }),
         });
         httpServer.on("upgrade", (request, socket, head) => {
           const { pathname } = parse(request.url);
    
           if (pathname != null && websocketEndpoints[pathname]) {
             websocketEndpoints[pathname].handleUpgrade(
               request,
               socket,
               head,
               (ws) => {
                 websocketEndpoints[pathname].emit("connection", ws, request);
               }
             );
           } else {
             socket.destroy();
           }
         });
    
         if (inspectorProxy) {
           // TODO(hypuk): Refactor inspectorProxy.processRequest into separate request handlers
           // so that we could provide routes (/json/list and /json/version) here.
           // Currently this causes Metro to give warning about T31407894.
           // $FlowFixMe[method-unbinding] added when improving typing for this parameters
           serverApp.use(inspectorProxy.processRequest.bind(inspectorProxy));
         }
    
         resolve(httpServer);
       }); // Disable any kind of automatic timeout behavior for incoming
       // requests in case it takes the packager more than the default
       // timeout of 120 seconds to respond to a request.
    
       httpServer.timeout = 0;
       httpServer.on("close", () => {
         end();
       });
     });
    };
    

    这里createConnectMiddleware方法将metroServer创建出来并返回给外面,作为MetroHmrServer的参数使用。

    RN包的加载

    在我们启动APP,并调用RN框架提供的函数初始化bridge后,就开始RN包的加载流程了。 本地包的加载比较简单,就是调用本地的JS引擎执行对应的JS代码,这里着重讲一下远程加载。
    在远程模式下,客户端会通过websocket链接到本地npm服务的/debugger-proxy端点下的launch-js-devtools接口,如果此时本地node服务已经开启,则会收到websocket的请求,并创建debugger server,同时会调用devToolsMiddleware的launchDevTools方法,打开浏览器的debugger-ui页面。 debugger-ui页面会创建一个websocket的连接,对端是debuggerproxy

    //devToolsMiddleware.ts
    
    function launchDevTools(
     {host, port, watchFolders}: LaunchDevToolsOptions,
     isDebuggerConnected: () => boolean,
    ) {
     // Explicit config always wins
     const customDebugger = process.env.REACT_DEBUGGER;
     if (customDebugger) {
       startCustomDebugger({watchFolders, customDebugger});
     } else if (!isDebuggerConnected()) {
       // Debugger is not yet open; we need to open a session
       launchDefaultDebugger(host, port);
     }
    }
    
    function launchDefaultDebugger(
     host: string | undefined,
     port: number,
     args = '',
    ) {
     const hostname = host || 'localhost';
     const debuggerURL = `http://${hostname}:${port}/debugger-ui${args}`;
     logger.info('Launching Dev Tools...');
     launchDebugger(debuggerURL);
    }
    
    //index.js
    
    function connectToDebuggerProxy() {
     const ws = new WebSocket(
       'ws://' +
         window.location.host +
         '/debugger-proxy?role=debugger&name=Chrome',
     );
     let worker;
    
     function createJSRuntime() {
       // This worker will run the application JavaScript code,
       // making sure that it's run in an environment without a global
       // document, to make it consistent with the JSC executor environment.
       worker = new Worker('./debuggerWorker.js');
       worker.onmessage = function (message) {
         ws.send(JSON.stringify(message.data));
       };
       window.onbeforeunload = function () {
         return (
           'If you reload this page, it is going to break the debugging session. ' +
           'Press ' +
           refreshShortcut +
           ' on the device to reload.'
         );
       };
       updateVisibility();
     }
     //省略部分代码...
    }
    

    接着,客户端发送prepareJSRuntime请求到node服务,node会在本地通过webworker开启一个debuggerWorker.js的线程(https://github.com/react-native-community/cli/blob/main/packages/cli-debugger-ui/src/ui/index.js),监听客户端发过来的请求,并转发给该webworker处理。

    function createJSRuntime() {
       // This worker will run the application JavaScript code,
       // making sure that it's run in an environment without a global
       // document, to make it consistent with the JSC executor environment.
       worker = new Worker('./debuggerWorker.js');
       worker.onmessage = function (message) {
         ws.send(JSON.stringify(message.data));
       };
       window.onbeforeunload = function () {
         return (
           'If you reload this page, it is going to break the debugging session. ' +
           'Press ' +
           refreshShortcut +
           ' on the device to reload.'
         );
       };
       updateVisibility();
     }
    

    再接下来,客户端会把bundle的内容转成字符串,并调用executeApplicationScript接口将该字符串传递给node服务,该请求会附带bundle在服务端的地址,node收到后通过importScript加载本地的bundle(https://github.com/react-native-community/cli/blob/main/packages/cli-debugger-ui/src/ui/debuggerWorker.js)

    var messageHandlers = {
       executeApplicationScript: function (message, sendReply) {
         for (var key in message.inject) {
           self[key] = JSON.parse(message.inject[key]);
         }
         var error;
         try {
           importScripts(message.url);
         } catch (err) {
           error = err.message;
         }
         sendReply(null /* result */, error);
       },
       setDebuggerVisibility: function (message) {
         visibilityState = message.visibilityState;
       },
     };
    

    chrome debugger消息转发

    在cli-server-api的index.ts文件中,我们看到createDevServerMiddleware方法创建了三个endpoint,分别是 debuggerProxyEndpoint、messageSocketEndpoint和eventsSocketEndpoint。其中debuggerProxyEndpoint是作为代理用来转发debugger消息的,我们看下内部如何实现。
    在createDebuggerProxyEndpoint方法中,我们创建了两个socket,一个是debuggerSocket,用来连接chrome debugger,另一个是clientSocket,用来连接客户端。在websocket的connection的回调事件中,根据url中role的不同,分别走了两段逻辑,一段是将debuggerSocket的onmessage方法定义为把数据发送给clientSocket,另一段是把clientSocket的onmessage方法定义为把数据发送给debuggerSocket,这样就实现了数据的转发。

    wss.on('connection', (socket, request) => {
       const {url} = request;
    
       if (url && url.indexOf('role=debugger') > -1) {
         if (debuggerSocket) {
           socket.close(1011, 'Another debugger is already connected');
           return;
         }
         debuggerSocket = socket;
         if (debuggerSocket) {
           debuggerSocket.onerror = debuggerSocketCloseHandler;
           debuggerSocket.onclose = debuggerSocketCloseHandler;
           debuggerSocket.onmessage = ({data}) => send(clientSocket, data);
         }
       } else if (url && url.indexOf('role=client') > -1) {
         if (clientSocket) {
           clientSocket.onerror = () => {};
           clientSocket.onclose = () => {};
           clientSocket.onmessage = () => {};
           clientSocket.close(1011, 'Another client connected');
         }
         clientSocket = socket;
         clientSocket.onerror = clientSocketCloseHandler;
         clientSocket.onclose = clientSocketCloseHandler;
         clientSocket.onmessage = ({data}) => send(debuggerSocket, data);
       } else {
         socket.close(1011, 'Missing role param');
       }
     });
    

    相关文章

      网友评论

          本文标题:react-native-cli源码分析之RN调试

          本文链接:https://www.haomeiwen.com/subject/qherfdtx.html