With the increasing performance of mobile devices, the performance experience of web pages gradually becomes acceptable, and because of the many benefits of web development mode (cross-platform, dynamic update, volume reduction, unlimited expansion), there are more and more embedded web pages in APP clients (in order to match the current popular saying, all web pages are called H5 pages, although it may not matter with H5), and many APPs change some functional modules to be implemented with H5.

Although it is said that the performance of H5 pages has become better, but if there is no targeted optimization, the experience is still very bad, the main two parts of the experience.

  Page start white screen time: open an H5 page needs to do a series of processing, there will be a period of white screen time, the experience is bad.

  Response smoothness: due to the rendering mechanism of webkit, single-threaded, historical baggage, etc., the performance experience of page refresh/interaction is not as good as native.

  In this article, we will not discuss the second point first, but only the first point, how to reduce the white screen time. For some functional modules in APP that use H5, how to speed up their startup speed and make their startup experience close to native.

Process

  Why there is a long white screen time when opening an H5 page? Because it does a lot of things, probably

  initialize webview -> request page -> download data -> parse HTML -> request js/css resources -> dom rendering -> parse JS execution -> JS request data -> parse rendering -> download rendered images

  Some simple pages may not have this step of JS requesting data, but most functional modules should have it.

  Generally, the page can display the prototype after dom rendering, before that the user sees a white screen, and the whole page is displayed in full only after downloading the rendered images, the first screen is optimized to reduce the time consuming process.

Front-end optimization

  The above process of opening a page has many optimization points, both front-end and client-side. Regular front-end and back-end performance optimizations have been best practices in the desktop era, the main ones being

  Reduce request volume: merge resources, reduce the number of HTTP requests, minify / gzip compression, webP, lazyLoad.

  Faster requests: pre-resolution of DNS, reduction of the number of domains, parallel loading, CDN distribution.

  Caching: HTTP protocol cache requests, offline cache manifest, offline data cache localStorage.

  Rendering: JS/CSS optimization, loading order, server-side rendering, pipeline.

  The biggest impact on the first screen launch speed is the network request, so the focus of optimization is caching, here focus on the front-end caching strategy for the request. Let's break it down into HTML cache, JS/CSS/image resource cache, and json data cache.

  HTML and JS/CSS/image resources are static files. HTTP itself provides caching protocols, and browsers implement these protocols to cache static files, which can be found here.

  Ask if there is an update: Ask the back-end request for an update according to protocols such as If-Modified-Since / ETag, and return 304 if there is no update, and the browser uses the local cache.

  

Direct local caching: The Cache-Control / Expires field in the protocol determines how long it is possible to go without asking for updates and use local caching directly.

  The maximum caching policy the front-end can do is to ask the server for updates every time for HTML files, and not to request updates for JS/CSS/Image resource files, and to use local caching directly. So how are JS/CSS resource files updated? It is common practice to give each resource file a version number or hash value during the build process. If the resource file is updated, the version number and hash value change, the URL of the resource request changes, and the corresponding HTML page is updated to request a new resource URL, and the resource is updated.

  Caching of json data can be done using localStorage to cache the requested data, and local data can be used for the first display before requesting an update, which is controlled by the front-end JS.

  These caching policies enable full caching of JS/CSS and other resource files as well as user data caching, so that the local cached data can be used directly every time without waiting for network requests. However, the caching of HTML files cannot be done. For HTML files, if the Expires / max-age time is set long, only the local cache will be used for a long time, and the update will not be timely, and if it is set short, every time the page is opened, a network request will be sent to ask if there is an update, and then determine whether to use local resources. The white screen time felt by the user will still be very long. So there is a contradiction between "caching" HTML files and "updating".

  

Client-side optimization

  Next, it's the client's turn to make an appearance. In the desktop era, H5 pages were limited by the browser and could not be optimized more, but now H5 pages are embedded in the client APP and the client has more privileges, so the client can go beyond the browser and do more optimization.

  HTML caching

  The client can intercept all requests for H5 pages and manage the cache by itself. For the above contradiction between "caching" and "updating" of HTML files, we can use this strategy Solution.

  Intercept requests on the client side, cache the data after the first request for the HTML file, and use the cached data directly without sending a request the second time.

  When to request an update? This update request can be freely controlled by the client. You can open the local page with local cache and then launch a request in the background to ask for cache update, which will take effect when you open it next time; or you can launch a request for pre-update in the background when the APP is launched or at a certain time to improve the chance of users accessing the latest code.

  This seems to be perfect, HTML files are cached with client-side policy, and the rest of resources and data are cached in the same way as the front-end, so that the second visit of an H5 page from HTML to JS/CSS/Image resources and then to data can be read directly from local, without waiting for network requests, while keeping the real-time update as much as possible, solving the caching problem. This solves the caching problem and greatly improves the speed of the first screen launch of H5 pages.

  Problems

  The above solution seems to have solved the caching problem in its entirety, but in reality there are many problems.

  No preloading: The experience of opening it for the first time is poor, and all data has to be requested from the network.

  Uncontrollable cache: The cache access is controlled by the system webview, and there is no control over its caching logic, which brings problems including: i. Uncontrollable clearing logic, the cache space is limited, and the important HTML/JS/CSS cache may be cleared after caching a few large images. ii.

  Poor update experience: Full download of HTML/JS/CSS updates in the background, large amount of data, and long download time for weak networks.

  Unable to prevent hijacking: If HTML pages are hijacked by operators or other third parties, the hijacked pages will be cached for a long time.

  These problems can be solved on the client side, but they are a bit tricky, as briefly described below.

  You can configure a preload list to request in advance when the APP is launched or at certain times, this preload list needs to contain the pages and resources of the required H5 module, also need to take into account the case of an H5 module with multiple pages, this list may be very large, also need tools to generate and manage this preload list.

  

The client can take over the caching of all requests, not using the webview default caching logic, and implement its own caching mechanism, which can be prioritized and preloaded.

  Incremental updates can be done for each HTML and resource file, but it's a bit tricky to implement and manage.

  Use httpdns + https on the client side to prevent hijacking.

  The above solution is very cumbersome to implement because there are many HTML and resource files scattered and difficult to manage.

  Offline package

  Since many problems are caused by the difficulty of managing scattered files, and our scenario here is to use H5 to develop functional modules, it is easy to think of packaging all the relevant pages and resources of each functional module and sending them down in a compressed package, which can be called the offline package of the functional module. Using the offline package solution, several of these problems can be solved in a relatively simple way.

  The entire offline package can be pre-downloaded and only needs to be configured by business module, not by file. The offline package contains all the pages related to the business module and can be preloaded at once.

  Offline package core files and page dynamic image resource files cache separation, can be more convenient to manage the cache, offline package can also be loaded into memory in advance as a whole, reduce disk IO consumption time.

  

Offline packages can easily do incremental updates based on the version.

  The offline package is sent as a compressed package and is encrypted and verified so that operators and third parties can't hijack and tamper with it.

  Here, offline package is a good solution for developing functional modules using H5, and briefly recap the offline package solution.

  The backend uses a build tool to package the pages and resources related to the same business module into a single file, and encrypt/sign the file at the same time.

  According to the configuration table, the client goes and pulls down the offline package at a custom time to do the decompression/decryption/checking, etc.

  Based on the configuration table, redirect to the entry page for opening the offline package when opening a particular service.

  Intercept network requests, for files already in offline package, read offline package data directly and return, otherwise go to HTTP protocol cache logic.

  When the offline package is updated, the diff data between the two versions is sent down in the background according to the version number, and the client is merged and updated incrementally.

  

More optimizations

  The offline package solution is pretty much done in terms of caching and can be paired with some more detailed optimizations: the

  Public resource packages

  Each package will use the same JS framework and CSS global style, these resources repeatedly appear in each offline package is too wasteful, you can make a public resource package to provide these global files.

  Preloading webviews

  For both iOS and Android, it takes a lot of time to initialize the local webview, so you can pre-initialize the webview.

  

First preload: The first initialization of a webview within a process is different from the second initialization in that the first time is much slower than the second. The reason is expected to be that after the first initialization of a webview, even though the webview has been released, some global service or resource objects shared by multiple webviews are still not released, so the second initialization does not need to generate these objects again to make it faster. We can pre-initialize a webview at APP startup and then release it, so that it will be faster when the user actually walks to the H5 module to load the webview.

  webview pool: you can reuse two or more webviews instead of creating a new webview every time you open the H5, but this way we have to solve the problem of emptying the previous page when page jumping, and if there is a memory leak of JS on one H5 page, it will affect other pages and cannot be released during the APP running.

  You can refer to this article from Meituan Dianping.

  Preloading data

  Ideally, all HTML/JS/CSS are cached locally when the offline package is opened for the first time, no need to wait for network requests, but the user data on the page still needs to be pulled in real time. The webview initialization takes some time and there are no network requests during this time, so parallel requests at this time can save a lot of time.

  

The client initiates a request at the same time as the webview is initialized, the request is managed by a manager, and the result is cached when the request is completed, then the webview starts requesting the preloaded URL after initialization, the client intercepts the request and forwards it to the request manager mentioned earlier, and if the preload is completed, the content is returned directly, and if not, it waits.

  Fallback

If the user accesses an offline package module, but the offline package has not been downloaded, or the configuration table detects a new version but the local version is old, how to handle the situation? There are several options.

  The simple solution is to synchronize blocking and wait for the latest offline package to be downloaded if the local offline package is not available or not up-to-date. This user experience of opening is even worse, because the offline package is relatively large in size.

  It can also be if there is an old package locally, the user will directly use the old package this time, if not then synchronize blocking wait, this will lead to untimely update, can not ensure that the user use the latest version.

  You can also do an online version of the offline package, the files in the offline package have a one-to-one corresponding access address on the server side, when there is no local offline package, directly access the corresponding online address, the same as the traditional open an online page, this experience is better than waiting to download the whole offline package, but also to ensure that the user access to the latest.

  The third Fallback method also brings the benefit of underwriting, in some unexpected cases when the offline package is wrong, you can directly access the online version, the function is not affected, in addition, such as the public resource package update is not timely, resulting in the version does not correspond to the online version can also be directly accessed, is a good underwriting solution.

  

Several of the above solution strategies can also be mixed, depending on business requirements.

  Using client-side interfaces

  If you use webkit's ajax and localStorage interfaces, there are a lot of limitations that make it difficult to optimize, so you can provide these interfaces to JS on the client side. Optimization.

  Server-side rendering

  In early web pages, JS was only responsible for interaction and all content was directly in HTML, to modern H5 pages, a lot of content has relied on JS logic to decide what to render, such as waiting for JS to request JSON data, then stitching it together into HTML to generate DOM to render to the page, so the page rendering display has to wait for this whole process, here there is a time consuming, reduce the time consuming here This is a time consuming process, and reducing the time consumed here is also within the scope of white screen optimization.

  Optimization can be done by artificially reducing the JS rendering logic, or more radically, by returning to the original, where all content is determined by the HTML returned by the server, without waiting for the JS logic, called server-side rendering. Whether or not to do this optimization depends on the business case, after all, this can have negative effects in terms of development model changes/increased traffic/increased server-side overhead. Some of Hand Q's pages are rendered using server-side rendering, called dynamic direct output, see the article.

  Finally

  From front-end optimization, to client-side caching, to offline packages, to more detailed optimization, to do the above points, H5 pages in the launch almost comparable to the native experience.

  To sum up, the general idea of optimization is: cache/preload/parallel, cache all network requests, try to load all the content before the user opens, can do things in parallel do not serial. Here some optimization means need to do a good job a set of tools and processes to support, need to weigh with the development efficiency, depending on the actual needs of optimization.

  In addition, the above discussion is for the functional modules of the H5 page open in seconds optimization program, the client APP in addition to functional modules, some other H5 pages like marketing activities / external access may not apply to some optimization points, depending on the actual situation and needs. In addition, the WeChat applets belong to the category of functional modules, which is almost the same routine.

  After the above optimization, basically only the webview's own startup/rendering mechanism is left, and this problem belongs to another optimization scope together with the subsequent response smoothness, which is a class RN / Weex program, so we will discuss it again when we have a chance.