Author: Fu Yuan

Image: hackernoon.com/drafts/ro28…

preface

XRN platform is a visual page building platform produced by the cloud music content group. By dragging and dropping components and setting components, XRN can quickly generate pages that can be rendered in React Native and The Web side at the same time. Whether React Native or The Web, we expect to produce high-performance pages that provide a better user experience. This article describes some of our optimization practices to improve page performance. Here are some examples of how to build pages:

Packaging phase optimization

Web side

1. Reduce repeated packaging

XRN’s Web project is mainly divided into two parts. One is the XRN-common package common to React Native, which carries all components and business logic. The other is the XRn-WebView package, which defines a Web container to use components and logic in XRn-common. Because these two packages are interdependent, it is easy to repeat the packaging scenario.

We can usesource-map-explorerTool, through the generated module dependency graph (as shown below), quickly locate and analyze libraries with double packaging problems.

We can adjust accordingly according to the different causes of the problem. For problems caused by version compatibility, we can harmonize the versions of the libraries to make the imported libraries consistent. For the cause of introducing inconsistent paths, we can configure the resolve.alias field in Webpack to specify a uniform reference path to solve the problem of duplicate packaging.

2. Code pruning

Modern packaging tools such as Webpack reduce the size of packed JS files by automatically removing unused snippets of code in a Tree Shaking manner. However, Tree Shaking is triggered by a more stringent requirement that code be imported and exported using ES6’s import/export syntax. More often than not, third-party libraries that need pruning will provide the corresponding Babel plug-in to assist us in pruning by modifying the reference path method.

Note that the react-native Web package is difficult to pry. By using babel-plugin-react-native Web, the import path of components can be modified as follows:

/ / modify before
import { View } from 'react-native';
/ / modified
import View from 'react-native-web/dist/exports/View'`
Copy the code

Once we’ve done this, it looks like we’ve pruned the package, but if we introduce a third party library that includes require(‘react-native Web ‘), we’ll introduce the entire React-Native Web package into the project again. In such cases, importing source code directly from a third-party library is often a better choice. This issue was also addressed by Expo Web.

3. The Polyfill optimization

Introducing babel-Polyfill in entry files results in a lot of unused polyfill code being pushed into JS packages. To minimize the size of polyfills, one approach is to install core-JS directly and manually import the required polyfills (e.g. ‘core-js/fn/object/values’).

Another more intelligent approach is to use the @babel/ Preset -env plug-in and configure useBuiltIns: ‘Usage’ option so that polyfills can be packaged on demand. The following figure shows the amount of optimization for JS volume (pre-gzip) for @babel/preset-env:

4. Dynamic import

With the dynamically introduced import syntax, modern packaging tools such as Webpack and Rollup can package code into multiple JS file modules, which will be requested asynchronously only when a page really needs a module.

Using the React.lazy and Suspense apis, we can dynamically introduce our component code to accurately load the required components in each page, thus greatly reducing the size of the REQUESTED JS files during page rendering. In addition to components, we can directly use import syntax to dynamically load third-party libraries or JS files, further reducing the size of js files on the first screen.

It is important to note that dynamic introduction can also cause a performance penalty. Dynamic introduction of a large number of small size JS files will cause unnecessary network overhead, and even bring negative optimization effect. Therefore, we need to weigh the number of asynchronous requests, the volume of asynchronous request files and other factors to determine whether we really need to use dynamic import during optimization.

5. More

You can also use the Coverage Tab provided with Chrome DevTools to find every unused line of jsbundle code. In addition, Web Dev offers more advice on this optimization.

The React Native client

1. The unpacking

Similar to the Web, the size of the Jsbundle in React Native is also an important factor in determining page loading speed. XRN separates the business package from the base package when it is built on the RN side. The service package contains xRn-specific components and business logic, while the base package contains cloud music dependent libraries shared by all RN applications (e.g. React, React -native, react- Navigation). The volume of XRN’s Jsbundle decreases by 36% after unpacking.

2. Preloading

As shown in the figure above, the cloud music APP will actively download the service package of the key RN application to realize pre-loading when it starts, thus saving the time of requesting the service package when the RN page is opened for the first time. Before opening RN application, cloud music APP will also warm up an RN container that has finished loading the basic package, and then load the service package to further improve the opening speed of RN page.

Render stage optimization

Through the React Native Web library, XRN realizes that more than 95% of the code can be reused between the Web and React Native side, but this also brings challenges to performance optimization. How to maximize the reuse of the code during optimization is also a problem we have been thinking about.

1. Lazy loading of images

In a long page, there are often a large number of images outside the viewable area of the first screen. If these images are loaded and rendered during the first screen rendering, performance will be greatly affected.

Lazy loading schemes commonly used on the Web include , IntersectionObserver, FlatList (React-native Web), etc. React Native does not support lazy image loading. Instead, FlatList is used to simulate lazy image loading.

FlatList is originally a scheme compatible with both ends, but as a scrolling container, it is not flexible enough in rendering content, and it is not suitable for complex DOM structure and various positioning methods. Therefore, THE final scheme of XRN is a lazy loading mechanism based on ScrollView. The principle is as follows:

<ScrollView
    onScroll={handleScroll}>
    <Component1>{... }</Component1>
    <Component2>
        <LazyLoadImage
            height={300}
            width={300} />
    </Component2>
</ScrollView>
Copy the code

LazyLoadImage is a custom component that calls the React Native measureLayout method after the component is rendered to determine its position in the ScrollView. By listening for the onScroll event and comparing the scrolling height of the page, the image component can determine if it needs to render. When the image is outside the viewable area and does not need to be rendered, render placeholders of the same width and height to keep the page height consistent and improve the user experience.

2. Lazy list loading

XRN also implements lazy loading optimizations similar to images for long lists (such as the song list in the image above). Different from images, React Native images usually know the height and width data before rendering. This data can be directly used for placeholder, saving a lot of rendering time for images outside the visible area.

In the case of lists, because different lists carry different component types (e.g., single, playlist, artist), it is not possible to get the exact height for space before rendering. The strategy adopted by XRN here is to render the single-row list first and then render the rest of the list after the single-row height is obtained, occupying the rows outside the viewable area without rendering the actual content.

3. First screen rendering

When rendering a long page with a lot of content, there are often performance problems such as long loading time and slow speed of the first screen. In XRN, we further optimized the first screen rendering by prioritizing what is visible on the first screen and delaying rendering what is not. When the user enters the page, the height of the first screen rendering will be limited to one and a half screens.

The premise of solving the problem of “how many components can fill a screen and a half height” is to obtain height data for each component. XRN contains a large number of components of variable height, the height of which can only be determined after rendering.

In order to get there before the page render the component’s height, XRN will be constructed on the page according to the result of the component rendering on Web when estimate the height of each component in the page (pictured above), when the page render so you can according to the height of these data to determine the number of components, the first screen rendering to page height control within a screen and a half or so.

4. Progressive rendering

After the first screen rendering is completed, XRN will load the subsequent content after the user slides the page. If the subsequent content is too much or the page is too long (as shown in the figure above), loading the subsequent content at one time will cause the page to be blank for a long time and lag. For the rest of the page components, XRN will load them in installments using setTimeout. Progressive loading of subsequent content ensures that the page has a certain degree of interactivity, but also makes the content in the visible area can be presented to the user the fastest.

5. Reduce repeated rendering

Repeated rendering is a common but significant problem, and React Native core contributor Mike Grabowski mentioned it in his optimization recommendations. As shown above, the React Profiler included with the React Developer Tools browser plug-in can be used to find and locate the reasons for repeated rendering of each component, the number of renders, and the specific elapsed time. After locating the problem component and identifying the reason for the repeated rendering, we can override shouldComponentUpdate to actively control the component rendering logic, You can also use methods like useMemo, useCallback, and React.memo to prevent reference changes in component props to reduce repeated rendering.

summary

After completing the above performance optimization points, the XRN Web page’s Lighthouse score went from an initial 50.3 (based on Lighthouse 5.5.0/198 online pages), Increase your score to 80.4 (based on Lighthouse 5.5.0/618 online pages). React Native also has a 40% faster page load. I believe it can also bring you some valuable reference points when doing Web or RN performance optimization.

The resources

  • apply-instant-loading-with-prpl
  • Fast load times

This article is published from netease Cloud Music big front end team, the article is prohibited to be reproduced in any form without authorization. Grp.music – Fe (at) Corp.Netease.com We recruit front-end, iOS and Android all year long. If you are ready to change your job and you like cloud music, join us!