📜 ⬆️ ⬇️

JavaScript loading and initialization


With the advent of the mobile web, our Internet has become bad again, and the devices are slow. 3G, 4G, Wi-Fi ... - of course, they are somewhere, but when they really need it, the speed usually drops to near-modem and it turns out that our mobile devices of the “Stone Age” fall into the conditions of a modern amount of information. Even in the city center (albeit on the 15th floor), the icon of the mobile Internet can show the magic letter E, hinting that it is better not to waste your nerves and suffer. It is better to use the native version of a web service than to wait each time to download a megabyte to send a short message. Native version of the web service ... Marketing business, application race, of course . However, users choose native web applications that run faster, do not download a lot of resources, although they have to update it periodically.

This article is about how you can optimize the loading and initialization of JavaScript.


Not all code is used.


I did a little research to identify the amount of code that is used when a user visits the first page of various mobile versions of popular sites. For this, I used the Chrome + script-cover plugin.
')
On average, about 40 percent of the code in the hospital is used. There are websites that use 80% of downloadable resources, but there are also those that use only 20%. You will be right if you say that “the rest of the code will be used from the cache on other pages” - we, as developers, know why so much is loaded, and a simple user waits every time that this entire volume is loaded, although he only needs to send a message to my friends.

Cache


Suppose that a user once downloaded all our resources, they got into the cache and the next time he will not download them (Expires: +300 years FTW).

Now the cache size of the desktop web browser is 40-80MB. This amount can be spent for an hour and a half of active web surfing, because almost every site wants to crawl into the cache with its Expires: +300 years on pictures and other dubious resources and force out your useful scripts. It turns out a kind of king of the mountains or an Indian electric train. Even having installed a 400 MB disk cache, you can download the same scripts and styles every day.

Everything is worse with mobile phones - iOS Safari does not have a disk cache (only in memory), for Android it is limited to 20MB.

Optimization application



I created one simple application - it is a chat prototype. We will try to speed up its loading in very harsh conditions - at the speed of the Internet at 7Kb / s. All its versions can be viewed here azproduction.github.com/loader-test

Sequential loading and execution


When you start the application code that loads our scripts, it looks like this:
<script src="js/b-roster.js"></script> <script src="js/b-dialog.js"></script> <script src="js/b-talk.js"></script> <script src="js/index.js"></script> 

Our scripts are loaded and run sequentially (in fact, in modern browsers this is a bit wrong), 4 requests. Of course, this is the worst of all the options. His profile looks like this:

18 seconds ... do not wait so much ...

Parallel loading and execution


We will slightly change our code by adding the async attribute so that our scripts load and start in parallel. For many applications, this method of loading will not work. scripts may have dependencies on previous code.
  <script src="js/b-roster.js" async></script> <script src="js/b-dialog.js" async></script> <script src="js/b-talk.js" async></script> <script src="js/index.js" async></script> 

Let's see what we get

Nothing has changed ... only the DOM ready event is triggered a bit earlier, but this will not help us because we need all the code for the application to work.

Parallel download sequential run


Let us try to apply another “optimization” in parallel, we will load, but run sequentially. To do this, use the LAB.js library:
  <script type="text/javascript" src="vendors/LAB.min.js"></script> <script> $LAB .script("js/b-roster.js") .script("js/b-dialog.js") .script("js/b-talk.js") .wait() .script("js/index.js"); </script> 

But it only got worse:

It would seem to load in parallel - it means that the loading of resources should not be blocking, and everything should be a little, but faster.
In fact, all modern browsers load all the scripts on the page in parallel, but run them sequentially in the order of the declaration in the document. If a script arrives earlier than necessary, then its launch is blocked. In the case of LAB.js, we actually do the work of the browser, even though the LAB.js script blocks the loading of all other scripts, and it also takes up some volume.

We collect and pack


Let's apply another rather obvious optimization - we will collect all the scripts in 1 file and compress this code with some minifiers.
 $ cat **/*.js > main.js $ java -jar yuicompressor.jar main.js -o main.min.js 

I think, for many of these lines are familiar, I use the YUI Compressor , but I would advise using the UglifyJs or Closure Compiler

The result of this optimization is very obvious. In principle, it is possible to further not optimize :) But! 9c ... - so many users will not wait every time.

AppCache - offline storage


Unlike the shared cache, this one is personal to each application, and other applications will not be able to replace its resources. The only thing that can happen is that the total quota for AppCache will end or the user will clear it. AppCache is supported by many browsers . Although it is called offline storage - but we can use its resources for online work.

Connecting it is very simple:

It is enough to register the manifest attribute with a link to the appcache file
 <html manifest="example.appcache"> </html> 

Create this file listing all the resources that should be cached (this is the simplest version of the file)
 CACHE MANIFEST # v1 - 2011-08-13 http://example.com/index.html http://example.com/main.js 

And in the settings of your web server, register several lines so that the file is given with the correct MIME type and is not cached
 AddType text/cache-manifest .appcache ExpiresByType text/cache-manifest "access plus 0 seconds" 

Using AppCache, you can promptly (without extra crutches) inform the user that his application is out of date and ask to reload the page, or ask for a resolution to reload. The browser takes over all network activity and cache checking on itself — all you have to do is subscribe to the updateready event and check out the cache stutus.
 if (window.applicationCache) { applicationCache.addEventListener('updateready', function() { if (confirm('An update is available. Reload now?')) { window.location.reload(); } }); //       if(window.applicationCache.status === window.applicationCache.UPDATEREADY) { if (confirm('An update is available. Reload now?')) { window.location.reload(); } } } 

Pros of AppCache
1. Reliable caching
2. Work offline
3. Simple version control
4. Timely update

Cons Appache
1. Disk quota may end
2. The user may not allow your site to use the cache (in the case of Firefox)
3. Online resources will not be available on the cached page unless NETWORK:\n*
4. If you suddenly put Expires +300 years on your .appcache file - your user will forever get stuck on the old version
5. Redirects to other domains are perceived as Failure.
A few more minuses AppCache www.alistapart.com/articles/application-cache-is-a-douchebag

Let me insert a few comments from VitaZheltyakov , thanks to him:
- Update files in Application Cache when configured standard caching in FF. - files will not be updated.
- Add specific links (not patterns) to NETWORK and FALLBACK sections - all unspecified files will not be loaded at all.
- In the frame, open the page with Application Cache caching the first - the application will hang, cycling the pages.
- Try to work with an offline copy of the main page where the manifest is declared - it will load from the cache as a working one.


I think it is obvious what kind of results we will get when reloading the application from the cache - 0 requests, 0 bytes. (1 request can go to download the .appcache file)

Learn more about AppCache
Article on AppCache on MDN tinyurl.com/mdn-appcache
AppCache FAQ appcachefacts.info
AppCache for newbies www.html5rocks.com/en/tutorials/appcache/beginner

Selective download


Although we now have good caching, but loading our application is far from optimal. We are still loading extra resources that the user may not need right now. Let's apply optimization with lazy loading of scripts. We can use AMD ’s “pattern” - Asynchronous Module Definition and the RequireJS library that implements this API.

1. Ship the main parts
2. The rest is necessary
3. Auto-dependencies
four.…
5. PROFIT

We can divide our application into 2 parts - this is a roster with a list of contacts and a dialogue. The roster should always be shown, and the dialogue opens less frequently, so we will load it by necessity.

Our html became such
 <script data-main="js/amd/index" src="vendors/require.js"></script> 

Each module we wrapped in the necessary wrapper for require.js. In the case of index.js, it will be like this:
 require(["b-roster"], function(Roster) { new Roster($('body')); }); 

And we will wrap each loadable module in a different wrapper:
 define(function () { //  -   return ModuleName; }); 

At startup, we load only index.js and roster.js, and by clicking on the roster element we load the rest of the files:
 querySelector('.b-roster').addEventListener('click', function (e) { require(["b-dialog"], function(Dialog) { new Dialog(element); }); }, false); 

The idea and implementation is simple, let's see what we got in the end:

Compared to the previous result, we began to do 2 more requests, but the initial volume of scripts decreased by 16.5Kb, time by 2.1s

This approach, of course, has one significant drawback - the user has to wait 4 seconds to click on the roster element (for the rest of the scripts to load), which, of course, is not very good.

Lazy loading and initialization


7.4 seconds at the start is, of course, not 18, but not 3-5 seconds, which the user can suffer.

With an increase in the volume of scripts, the start time of the application grows - Startup Latency. The fact is that when starting our browser has to interpret and initialize all the functions, objects, constructors that were listed in this file, even if we are absolutely not needed at the moment. And the initialization time of 1 MB of javascript can be up to 3 seconds depending on the browser and the load on the device resources. For desktop browsers, this figure is, of course, much lower (100-300ms).

When building our application, we can transform our scripts into strings, and then, if necessary, replay them and get the code. Yes, eval is slower than the usual launch, but this initialization does not occur during the start, but during the application, which allows our application to run faster.

LMD


Based on this idea, I created the principle LMD - Lazy Module Declaration and made another “loader” with the same name. In addition to lazy initialization, LMD has a number of other advantages compared to others:

2. Node.js-like modules

AMD requires us to write define () each time, although we can do without define and write code for the browser 1 in 1 as for Node.js without any wrappers and magic with exports.

This is what the index.js code looks like under LMD:
 var $ = require().$, // require("undefined") Roster = require("b-roster"); new Roster($('body')); 

When assembling an LMD package, LMD already wraps the given code in the necessary wrapper for it.

3. Built-in collector and packer

The script collector and packer are already built into LMD. You just need to declare all your scripts that should be included in the package:
 { "path": "./modules/", "modules": { "main": "index.js", "b-roster": "b-roster.js", "undefined": "utils.js", //     * "*": "*.js" } } 

LMD will compress and pack them, but compression and packaging are optional - you can manage them for the entire file or for each module separately.

4. Flexible Library Size

Unlike Require.js-AMD, the entire LMD code is included in your script package already at the time of assembly, without any unnecessary gestures. You can flexibly turn on or off all sorts of optimizations and "plugins." For example, if your code will work only in modern mobile devices, then why do you need all these hacks for IE6 ?! In the LMD, this “optimization” can be easily disabled in the config. Or you don't want to load CSS dynamically - why do you need to drag extra code ?! - disable the option and the code becomes smaller. The minimum amount of LMD.js is only 288 bytes.

5. Hot build project

There are such web applications that need to be rebuilt each time to check what happened. LMD also suffers from this, but it does not force you to do make every time or set up a tricky build on the server. You just need to run LMD in watch mode and each time you change any file included in the assembly, LMD rebuilds the whole project.

 $ lmd watch config.lmd.json output.js 

6. Smart collector

I try to make an intelligent collector from LMD.js that can point out possible errors during the build - ParseError, missing / extra flag in the config, direct use of globals in lazy-modules and other optimizations.

So many advantages, but in fact:

Compared to AMD, we have reduced the volume by another 13.5 Kb of the start time by 2.1 s, and the number of requests by 2.

If we compare with the very first case, then we get amazing results:


Conclusion


1. Use AppCache, but with care
AppCache is a fairly simple optimization that allows you to add a good cache to your web application without changing the project code. AppCache carries a number of problems that you can read about here. Application Cache is a Douchebag . If you are too lazy to compile a list of your resources or write some scripts, you can use the Confess tinyurl.com/confessjs tool that does all this for you and will profile your CSS selectors in addition.

2. Build scripts
This is the most cost-effective optimization, if you are not compressing the scripts yet - stop reading and finally write the make file :)

3. Start using LMD or AMD
Existing applications are quite difficult to translate to LMD or AMD, but if you start writing, then it is easy and profitable for both your users and developers. In addition to lazy loading, you also get completely isolated modules, which is very beneficial in teamwork.



Any links


The application we optimized azproduction.github.com/loader-test

Tulsa and scripts
LMD github.com/azproduction/lmd
Confess tinyurl.com/confessjs
Require.js requirejs.org
YUI compressor tinyurl.com/yui-compressor
Can I Use caniuse.com
script-cover code.google.com/p/script-cover
UglifyJS github.com/mishoo/UglifyJS
half ompiler code.google.com/p/closure-compiler

By appcache
Article on AppCache on MDN tinyurl.com/mdn-appcache
AppCache FAQ appcachefacts.info
AppCache for newbies www.html5rocks.com/en/tutorials/appcache/beginner
Article about AppCache issues www.alistapart.com/articles/application-cache-is-a-douchebag

PS This is an updated version of my report at DUMP 2012

UPD Added AppCache problems, thanks yeremeiev and VitaZheltyakov for useful links and tips.

Source: https://habr.com/ru/post/145269/


All Articles