This post was published by the Cloud + community
Author: Siyan Jax
World martial arts, only (wei) fast (fu) not (bu) broken (Po).
With the rapid development of front-end technology in recent years, more and more teams use React, Vue and other SPA frameworks as their main technology stack. In the case of the React app, probably the most important metric from a performance perspective is the time it takes to render the first screen. So today, we are going to share with you a story of optimizing to the extreme.
Our goal is to make the H5 page a Native experience as well, and if you’re looking for a technology that will give your boss a kick in the pants (and save your KPIs), this post may help you.
What is the Penguin Tutorial details page
The course details page is one of the most important pages in Tencent’s Penguin tutoring APP and one of the most heavily trafficked pages, so how fast it opens is also crucial.
This is an H5 page written using React, running on multiple platforms, including: Penguin Tutorial APP, mobile QQ, mobile browser.
Architecture evolution
Pure asynchronous rendering
We know that the current mainstream SPA application default rendering is like this:
In this case, the time from the time the page loads to the time it takes the user to see the page (the first screen rendering) is the time included in the gray border area in the image above.
This is the slowest way. Even if CGI is fast enough, it will take at least 1 to 2 seconds.
Then let’s make a simple optimization:
- Cache static resources so the next time a user opens them, they don’t have to request them from the network.
- Step 4: can the action of pulling CGI be advanced? We can request HTML, first through a JS script to request CGI data, after the fourth step, you can directly get the data, this is CGI preloading.
How do you do that? Our scheme is to uniformly encapsulate the Request tool. When packaging with Webpack, we will inject a pre-loaded CGI JS code into the top of the page and maintain a MAP corresponding to CGI and DATA. When sending requests later, we will first go to the MAP for value. If there are values, they are directly extracted, and if there are none, an HTTP request is made. (Check out our team’s open source Preload tool for details.)
There are other ways to optimize this pattern:
- Loading state or skeleton screen in HTML;
- Remove external CSS;
- Use dynamic polyfill;
- Split common code using SplitChunksPlugin;
- Using Tree Shaking correctly with Webpack 4.0;
- Use dynamic import, split the page code, reduce the first screen JS volume;
- Compile to ES2015+, improve code running efficiency, reduce the volume;
- Use lazyload and placeholder to improve the loading experience.
The effect is shown in the figure below:
Straight out of the homogeneous
In the asynchronous mode, in addition to the above optimization, we also made offline package caching (an optimization scheme independently developed by Tencent hand Q for the mobile end, in short, it is to cache static resources in the mobile APP) in the end (Penguin tutoring APP, mobile QQ). After our data test, The first screen should render in seconds (around 1s).
But to have the performance extreme pursuit for us, certainly is not satisfied.
Continuing with the optimization, the easiest and most popular way is definitely to go straight out (server-side rendering).
There are many, many different solutions out there, and I won’t cover them here, but if you want to learn more about server-side rendering, please refer to this article.
The optimization effect for the first screen time is very obvious, and after our tests, the data is about **25%**.
The effect after straight out is as follows:
You can see that for the first screen, there is no ** ** wait time, visual experience has improved a lot.
PWA straight out
For the above, common straight out application, where can we optimize the point? Let’s take a look at the wave in detail, and that’s what we’re going to focus on today.
First, take a look at the time table that goes straight through each part of the app (local environment 2018 iMac) :
The name of the process | Process cost |
---|---|
CGI pull in Node | 300 ms |
RenderToString | 20 ms |
The network time consuming | 10 ms |
Front-end HTML rendering | 30 ms |
As you can see from the above table, the biggest chunk of straight rendering time is on the CGI pull.
We now ask two questions:
- Can CGI interface data be cached?
- Can HTML be cached?
1. Dynamic and static separation of interface
In the interface data of this page, there are some data that are changing in real time, such as: how many places are left, the price of the course at this moment, whether the user has bought the course, etc.
The nature of the data determines that the data interface cannot be cached. (Assuming you cache it, there is a chance that a user will come in and see that there are 10 slots left, but the course is already sold out)
For this time consuming bulk, we did the separation of action and action of the CGI interface.
Put the data that is not associated with the user mode or the current time (such as the course title, the time of the course, the address of the demo module, etc.) in one interface (static interface), and put the other changing data in another interface (dynamic interface).
It is possible to use a static interface for server-side rendering. The first advantage is that it is faster (less dynamic information, and can also be cached in the background), and the second Node can be cached directly.
2, straight out of Redis cache
This way we can use the static, non-changing data to straight out the HTML and then cache the HTML file into Redis.
The client requests this page. After receiving the request, the Node side will first get the cached HTML from Redis. If the Redis cache does not hit, it will pull the static CGI interface to render the HTML and save it to Redis.
After the client gets the HTML, it will render it immediately, and then use JS to request dynamic data and render it to the corresponding place.
After doing this, we can see that the improvement of the optimization effect is very, very significant:
Straight up from 262ms to 16ms! (local environment), the feeling of flying general, mother no longer need to worry about the time-consuming leadership.
PWA straight out of the cache
For more information on what a PWA is and how to use it, see this article.
After doing the HTML cache directly out of Node, we then optimized and then thought about whether we could cache THE HTML on the client side as well, so as to avoid the network latency.
The answer is to use PWA as an offline cache on the client side, cache our HTML directly in the client side, and each time the user requests the corresponding HTML directly from the PWA offline cache to the user, and then request the Node service to update the local PWA cache. (as shown in the figure below)
Core code:
self.addEventListener("fetch", event => {
// TODO other logic (maybe fetch filter)
// core logic
event.respondWith(
caches.open(cacheName).then(function(cache) {
return cache.match(cacheCourseUrl).then(function(response) {
var fetchPromise = fetch(cacheCourseUrl).then(function(
networkResponse
) {
if (networkResponse.status === 200) {
cache.put(cacheCourseUrl, networkResponse.clone());
}
return networkResponse;
});
returnresponse || fetchPromise; }); })); });Copy the code
No more nonsense, first look at the effect of comparison (left PWA straight out; Right offline package) :
As you can see from the figure above, the first screen rendering with PWA straight out of cache is almost millisecond, so it can be said to be close to Native.
After our data test, using PWA straight out cache, the first screen rendering time can best reach 400ms level:
PWA straight out detail optimization
One, the page beat
Because the interface is separated from the static and static, the static interface is used to directly exit the page, and then the dynamic data is pulled and rendered on the client. This can cause pages to wobble (such as the demo module in the details page, which is rendered on the client side).
As the height changes, there is a visual jitter (see the GIF screenshot of straight out in the section above).
To get rid of page jitter, you must ensure that the height of the container is already present at the point of exit.
For example, the demo module, in fact, the cover image and the demo button can be rendered on the server, while the Video module must be rendered on the client (Tencent Cloud Tcplayer).
So here can be broken down into :(demo cover + button + time) server rendering + underlying Video (client rendering).
What about containers that need to compute heights on the client side (often in ComponentDidMount). If they depend on the client environment (for example, depending on whether the current system is Android or IOS), they definitely can’t be rendered on the server side?
What we do here is we put these calculations in front of the HTML body, use inline scripting to compute the current environment, add a specific class to the body, and then the elements under that particular class can be styled using CSS. For example:
/* * This is because the padding is different in different mobile APP environments. * We need to add the padding */ before the page is rendered
var REGEXP_FUDAO_APP = /EducationApp/;
if (
typeofnavigator ! = ="undefined" &&
REGEXP_FUDAO_APP.test(navigator.userAgent)
) {
if (/Android/i.test(navigator.userAgent)) {
document.body.classList.add("androidFudaoApp");
} else if (/iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)) {
if (window.screen.width === 375 && window.screen.height === 812) {
document.body.classList.add("iphoneXFudaoApp");
} else {
document.body.classList.add("iosFudaoApp");
}
}
}
.androidFudaoApp .tt {
padding-top: 48px;
background-position-y: 84px;
}
.iphoneXFudaoApp .tt {
padding-top: 88px;
background-position-y: 124px;
}
.iosFudaoApp .tt {
padding-top: 64px;
background-position-y: 100px;
}
Copy the code
This code is then inserted through the build before the body of the page.
The anti-shaking optimization effect is as follows (the left is optimized, the right is not optimized) :
Two, cold start preloading
Although we do PWA offline caching, for cold launch, there is no PWA cache in the client, which causes the page to be clicked on the first time, rendering a bit slower.
Here we can use a preloaded script to maximize the number of pages the user is likely to visit at APP launch.
The core code is as follows:
// When the page is preloaded, the PWA precaches the straight out of the course details page
function prefetchCache(fetchUrl) {
fetch("https://you preFetch Cgi")
.then(data= > {
return data.json();
})
.then(res= > {
const { courseInfo = [] } = res.result || {};
courseInfo.forEach(item= > {
if (item.cid) {
caches.open(cacheName).then(function(cache) {
fetch(`${courseURL}? course_id=${item.cid}`).then(function(
networkResponse
) {
if (networkResponse.status === 200) {
cache.put(
`${courseURL}? course_id=${item.cid}`,
networkResponse.clone()
);
}
// return networkResponse;}); }); }}); }) .catch(err= > {
// To monitor err
});
}
Copy the code
The PWA is a legacy problem
First, compatibility issues
With the development of PWA technology, most mobile and PC environments now support PWA. After our test, we found that android is basically supported, and IOS needs 11.3 or above to support.
Service Workers Compatibility diagram
2. IOS rendering issues
As a rule of thumb, the external script tag should be placed after the body because it blocks the DOM rendering of the page.
After testing, we found that the WebView (UIWebView) rendering mechanism in IOS does not render the page as above. Instead, we render the page after the JS is executed. If this is the case, our straight out rendering optimization will not work (because the HTML is not rendered at the beginning). You can use the async and defer properties of the script tag to render asynchronously.
After upgrading WkWebView, the situation improved and rendered normally.
The appendix
The resources
- Exploration of PWA and best practices
- Hundreds of millions of page views under the front end isomorphism straight out of practice
- React 16 Load performance optimization guide
This article has been authorized by the author Tencent Cloud + community in various channels to publish
Penguin Tutorial details page Ms Open secrets – PWA straight out
For more fresh technical dry goods, you can follow usTencent Cloud Technology community – yunjia community official number and zhihu organization number