1. The background

React Native (RN) is currently widely used by Shopee front-end teams. RN has many advantages, but its development and debugging process is less friendly than Mobile Web, especially at run time.

In development mode, RN provides an official debugging tool, but it is weak compared to the pure front-end browser Devtool. However, in non-development mode, such as Test and UAT environment, RN code is packaged into a Bundle, and official debugging tools are not used at this time, which not only hinders the problem recurrence of Test students, but also makes it more difficult to locate the problem of development students.

At present, there are tools for RN debugging in the industry, but there are more or less defects (as shown in the figure below). Moreover, these tools are aimed at debugging in development mode, and debugging in packaged production environment often needs to be done by human beings, which is inefficient.

This is why a tool to help locate problems in non-development environments is so important. Luna was born, and this article introduces the design and implementation of key technologies in RN.

2. Function Description

Take a look at Luna in the following images:

As can be seen from the picture, Luna is an RN in-app debugging tool, which is more inclined to solve the pain points of debugging in production environment.

Luna consists of an orange trigger button and a body that takes up half the screen. Ontology includes four sections, namely Log, Network, Redux and Shopee, respectively carrying the functions of Log recording, Network request viewing, Redux tree viewing and Shopee related information viewing.

Log and Network exist as core modules, while Shopee and Redux are introduced as public plug-ins provided by Luna. This core-plugin mode is how Luna now operates: by default, it provides Log, Network, and other functions, as well as the ability to write custom modules to import into Luna.

The functions of the four sections are as follows:

1) The Log section

The Log section takes over console. Log, collects all logs and uncaught errors to Luna, and displays them in reverse order. It supports filtering by Log type as well as fuzzy lookup of logs. As shown below:

2) Network

The Network section collects request information issued by the page, including request status, request duration, request header, request body, response header and response body, etc. Users can easily view API requests.

3) Shopee section

Shopee section provides some Shopee app-related functions, such as convenient translation copy switching, Cookies viewing, DataStore storage viewing and deleting, as well as user ID/name, device system information and version number related information viewing. These features make it easier for developers to debug applications, and also make it easier for QA to reproduce and locate bugs faster.

4) Redux

The Redux section shows the Store (Shared data Storage Repository) tree for viewing the status of the entire Store.

3. Scheme design

3.1 Overall Design

Luna is a monorepo multi-package single warehouse architecture project, including Core, Shopee Plugin and Redux Plugin three package modules.

The Core module consists of three parts: Log Log module, Network Network module, and Plugins plug-in access module. The design of each module is described below.

3.2 the Core

The Core module is Luna’s Core module and exists as a separate NPM package, providing the most basic functions and plug-in access capabilities. The Core module is nested as a Provider at the root of the component tree, taking business code and inserting Luna into it. Core uses MOBx as storage, maintains the collection and display of Log and Network records, and controls custom plug-ins.

3.2.1 Access Scheme

Luna is inspired by the Web side of the open source debugging tools vConsole and Eruda, but in the Selection of Luna access solution, we encountered a problem that has never been encountered in Mobile Web: In modern Web development, whether it is Vue or React, any single page application will have a root node for mounting. The whole component tree will be built from this root node. Therefore, the debugging tool only needs to be hung under a certain node to perceive the state of the entire application:

However, in React Native, each page (View) has its own root node (as shown in the figure below). There is no common ancestor node between different pages. To ensure that every page can access Luna, each page has to be injected separately. And data retention is a big problem.

So how to ensure that Luna can be accessed from page to page, retain data from different pages, and not affect Luna in the event of errors, while reducing the cost of page access, has become a challenge. So what did Luna do?

First, Luna decouples initialization from page registration, preloading luna.init to application initialization. This separates data collection from page registration and ensures that switching pages does not result in data loss.

import Luna from "@shopee/luna";
Luna.init();
Copy the code

Luna then used the Shopee Plugin to rewrite the method used to register the Shopee RN Page, wrapping the incoming Page component with a new component, including Luna, and returning the component to the outer layer as HOC. Every page registered using this registration method will automatically include Luna in the page, without the need to manually import Luna on every page, and every page will also have access to Luna.

Finally, Luna wraps a layer of ErrorBoundary around the incoming Component to catch runtime errors on the page, making it accessible to Luna in case of a page error and seeing the error message on Luna.

3.2.2 the Log

Log collection

As the name implies, the Log module is used to display the logs printed by the system and users.

Luna hijacks the global variable global.console to collect logs of various types; Luna also hijacks console.tron.log, collecting relevant logs printed using Reactotron during development; Luna also hijacked ErrorUtils, collecting uncaught errors into the log Store as well. These three types of logs are the data sources for the Log section.

Luna hijacked the global console in a middleware way, adding it to the Log Store, and then executing its original execution function. The main code is as follows:

export const overrideConsole = (consoleStore) = > {
  const mixinType = [
    LOG_TYPE.LOG,
    LOG_TYPE.ERROR,
    LOG_TYPE.WARN,
    LOG_TYPE.DEBUG,
    LOG_TYPE.INFO,
  ];
  mixinType.forEach((type) = > {
    // @ts-ignore
    const originConsoleFun = global.console[type];
    // @ts-ignore
    global.console[type] = (. params) = >{ consoleStore.addLog(params, type); originConsoleFun(... params); }; }); };Copy the code

Logs show

Log logs contain type filters, search boxes, and Log lists. Luna logs have many types, complex content, and are always in a dynamically updated state, which can easily cause performance problems. Therefore, in the log list display part, we made a lot of performance optimization, mainly including two parts, as shown in the figure below:

1) Optimization of nested type display

Due to the compatibility problem of the tree display inventory of the open source solution, we choose to write our own tree display component to solve the display problem caused by complex data types and large data volume. It has the following characteristics:

  • Supports the expansion and contraction of multi-line text, which only shows part of the content.
  • The lazy loading scheme is adopted for large arrays and objects, and only less than 100 lines of content are displayed after expansion. Every time the user clicks the remaining part (N), the last N*100 data will be displayed. This approach avoids the performance problems associated with big data display;
  • Line wrap control is carried out for a line of super-long text, keeping each Log no more than three lines to ensure that the number of logs per screen is controlled.

2) List sliding performance optimization

Luna’s logs aren’t loaded all at once; they’re generated in real time. This makes it more likely that new data will be generated at the same time as the list is swiped, and users often need to scroll down to find their printed Log. So Luna has also made some specific improvements for sliding performance:

  • Luna adopts FlatList to render Log list, and at the same time, ID is implicitly generated during Log collection, acting on keyExtractor of FlatList to improve rendering efficiency.
  • Since logs are generated dynamically, this has a significant impact on the performance of FlatList. For this purpose, Luna displays the Log list in reverse order, and puts the last data, that is, the data that the user cares most about when clicking Luna, at the top of the FlatList, and prints the time. This reduces the frequency with which users swipe;
  • We also plan to implement more strict Log pagination loading on Luna, separating display and stored Log lists, and obtaining the “next page” of stored Log lists when sliding to the end, completely ensuring list sliding performance during dynamic data generation.

3.2.3 Network

The Network module’s data collection is derived from XMLHttpRequest. Luna hijacked React Native’s XMLHttpRequest and rewrote the open, Send, and setRequestHeader methods to store each request and its associated fields in a Network list. Since XHR is also used at the bottom of RN’s Fetch, hijacking XHR can achieve full coverage. The main code for Network hijacking is as follows:

export const overrideNetwork = (consoleStore) = > {
  originOpen = XMLHttpRequest.prototype.open;
  const originSetHeader = XMLHttpRequest.prototype.setRequestHeader;
  XMLHttpRequest.prototype.open = function (. args) {
    this._xmlItem = { openData: args };
    this.addEventListener("load".() = > {
      const xmlItem = this._xmlItem;
      const requestHeaders = this._requestHeaders;
      const endTime = new Date().getTime();
      const time = endTime - xmlItem.startTime;
      consoleStore.addNetworkLog({
        url: this.responseURL,
        method: xmlItem.openData[0].status: this.status,
        rspHeader: this.getAllResponseHeaders(),
        response: this.response,
        body: xmlItem.sendData,
      });
    });
    originOpen.apply(this, args);
  };
};
Copy the code

In the presentation scheme of Network list, we have added many detailed considerations, such as:

  • Display the end Path of the requested URL first;
  • Set different background colors according to different state codes of Response;
  • Display different time units depending on the length of the request.

These details are incremental improvements over the years, and they really make Luna’s actual user experience even better.

3.3 the Plugins

3.3.1 Plug-in mechanism

Why do you need a plug-in mechanism?

Before introducing what a plug-in mechanism is, you may have a question in your mind: Why is there a plug-in mechanism? The reason is that Luna has some functions that rely on Shopee’s SDK. Other features such as Redux are optional, and the state management framework used by the user may be mBox. To keep Luna’s core modules pure, and to preserve Luna’s extendiability for non-Shopee frameworks, we uncoupled these unnecessary coupling, transforming Shopee modules and Redux modules into plug-in mechanisms that users can reference on demand.

What is the plug-in mechanism?

Luna is in addition to Core modules, and Core also supports custom plug-ins. Luna provides two first-party plugins: the Redux Plugin and the Shopee Plugin. If you need to customize your own App, you can easily write your own Plugin and import it into Luna, as shown below.

3.3.2 Official plug-ins

Luna also uses the Plugin mechanism to provide two official plug-ins: the Redux Plugin and the Shopee Plugin, which are introduced as separate NPM packages for consumers who need them. Among them:

  • The Redux Plugin exists as a Redux middleware and retrieves the state of the Redux via store.getState and displays it on the interface. Users can easily find the stored value of the current Redux.
  • Shopee Plugin is a plug-in based on Shopee React Native SDK, which is specially designed for the development of projects in Shopee App. It provides many functions through the SDK of Shopee. This plug-in is mainly for the development and testing students inside Shopee to facilitate their debugging in Shopee App.

3.3.2 Developing custom plug-ins

In addition to official plug-ins, users can also extend their own plug-ins. How to develop a Luna plugin? Luna’s plug-in mechanism is very similar to Vue’s install-use mechanism, but it omits the install step of the Vue plug-in, as long as the component content is injected into Luna’s use method. So it’s actually a very simple two-step process:

  • Write your component and declare its name;
  • Import components and names into an instance of Luna Core.

Luna recognizes your component and displays it on the main screen, where you can add your own functionality to the plugin.

4. Future outlook

Luna is currently running steadily in some Shopee businesses, and has been well received by developers and testers who use it. In the future, we will work towards two major goals:

1) Automated Luna access

Now Luna access or artificial code access with invasive, the future we are going through the deployment platform, at the time of deployment automatically Luna access into, and only in the development, test environment effect, not only can realize zero code access cost, also does not affect the production environment, also reduced the volume after packaging code.

2) Component tree state viewer

Almost every developer on the Web uses the React Devtool, and one of the favorites is the Components module, which shows the entire tree of Components at development time, along with the Props, State, and Hooks associated with each component. At present, there is no development and debugging tool as useful as React Devtool in React Native, and checking RN status is a big pain point for developers. Therefore, Luna plans to add viewer for component tree and component status in the future. It will not be difficult to view Log, Network, and component states on RN at the same time.

In this paper, the author

Shopee Digital Purchase front end team.