Jump to content

Recommended Posts

A while ago I submitted a SMS build webapp to Google PageSpeed

 

The results are :

 

MOBILE FRIENDLINESS

99/100 - GOOD

 

MOBILE SPEED

62/100 - POOR

 

DESKTOP SPEED

77/100 - FAIR

 

which is not too bad except for the mobile speed ranking.

 

The recommendations from PageSpeed were to basically minify everything (css, javascript, html) and to optimise images

Not too difficult 

 

The following recommendations were deemed 'Must do' changes




 

Browser caching

"The estimate is that nearly half of all downloaded responses can be cached by the browser, which is a huge savings for repeat pageviews and visits."

 

The solution would be to add a meta tag in the html file, something like

<meta http-equiv="Cache-control" content="public">

 

 

Prioritise visible content

"If the amount of data required exceeds a certain threshold (14.6kB compressed), it will require additional round trips between server and browser. For users on networks with high latencies such as mobile networks this can cause significant delays to page loading."

 

The solution would be to split the application in an 'immediate render' part (above the fold) and a 'deferred render' part (below the fold).

This basically means splitting all css and js files in two.

Not sure how this would work using standard SMS

 

 

Eliminate render blocking

"By default JavaScript blocks DOM construction and thus delays the time to first render."

 

One of the options would be to add the "async" parameter to all external js files, something like

<script async src="my.js">

 

 

Has anyone experience with these issues ?

Share this post


Link to post
Share on other sites

I had problems with caching on iOS in the web browser. If you have a lot of pictures (eg. thumbnails), some pictures are not shown, because of caching limitiations on iOS. In my case, I had to put a random number on every image url to get not cached. With that approche all pictures are shown.

 

Unfortunately, when you add your web page to the iOS Desktop, loading images doesn't work anymore. I can only show about 5 - 7 thumbnails :(

 

So far I found out in the net is, that apple wants to prohibit web apps on iOS. You should distribute all apps through the online store (where they get money).

 

Does anybody have experience with converted web apps to real apps with cordoba (phone gap)? Is there caching also an issue?

Share this post


Link to post
Share on other sites

Partial solution to increase PageSpeed ranking : prioritise visible content

 

This 'above the fold' and 'below the fold' concept of Google bugged me a bit.

I can see the point, on high latency networks it is better to show something immediately and load the rest of the app code asap asynchronously

 

A standard sms visual project produces apps completely below the fold and an uncached project of say 2MB on mobile does take quite a while to load.

 

The solution is to split the app in two, or rather make two apps

 

In the most simple form this entails a small loader app (1) with the only function to load the resources of the main app (2)

Code below is inspired by the post in the september 2015 vault

uses
  W3C.HTML5, W3C.DOM, W3C.Console;
 
// style body
var BodyStyle := JHTMLBodyElement(Document.body).style;
BodyStyle.setProperty('border', '0');
BodyStyle.setProperty('margin', '0');
BodyStyle.setProperty('padding', '0');
BodyStyle.setProperty('overflow', 'hidden');
 
// load main script asynchronous
var MainScript := JHTMLScriptElement(Document.createElement('script'));
MainScript.&type := 'text/javascript';
MainScript.async := true;
MainScript.src := 'main-org.js';   //the .js file of the main application (2)
 
Window.addEventListener('load', lambda
    Console.Log('Main script loaded');
    Document.body.appendChild(MainScript);
  end);
 

The effect of the above is that the loader app (1) loads really quickly, after all it is only some 2kB

However for the user it doesn't offer much relief as it results in a black screen until the main js file is loaded

 

To make it a bit more interesting the things which come to mind are to enhance the loader app (1) by

- adding a picture or some text or a splash screen animation

- recreating the look and feel of the first form in the main app which will be replaced by the actual form soon as all resources are loaded

- showing a progress indicator

 

or all of the above

 

The code below produces a progress indicator 

uses
  W3C.HTML5, W3C.Console, W3C.XMLHttpRequest, W3C.ProgressEvents, W3C.Dom;
 
// style body
var BodyStyle := JHTMLBodyElement(Document.body).style;
BodyStyle.setProperty('border', '0');
BodyStyle.setProperty('margin', '0');
BodyStyle.setProperty('padding', '0');
BodyStyle.setProperty('overflow', 'auto');  //hidden
 
//ProgressBar
var ProgressBar := JHTMLProgressElement(Document.createElement('progress'));
ProgressBar.max := 100;
ProgressBar.value := 0;
Document.body.appendChild(ProgressBar);
 
procedure Progress(event: JProgressEvent);
begin
  If event.lengthComputable = true then
    ProgressBar.value := (event.loaded / event.total) * 100;
end;
 
//XMLHttpRequest
var req := JXMLHttpRequest.Create;
asm @req.addEventListener('progress', function(event) {@Progress(event); }); end;
req.open('POST','main-org.js',true);           //the .js file of the main application (2)
req.send;
 
//MainScript
var MainScript := JHTMLScriptElement(Document.createElement('script'));
MainScript.&type := 'text/javascript';
MainScript.async := true;
 
JGlobalEventHandlers(req).onLoad := lambda(e:JEvent)
  Console.Log('Main script ' + e.type + 'ed !');
  MainScript.innerHtml := req.responseText;
  Document.body.appendChild(MainScript);
end;
 

The trick here is that normal script loading does not produce progress events, only an 'onload' event at completion

However ajax downloads do generate progress events.

In this piece of code the main app .js file is downloaded through XMLHttpRequest.

The progress events during the ajax download are transferred to the progress bar, which updates itself accordingly.

On completion a new script is created and the ajax response (script from app 2) is copied in there.

Share this post


Link to post
Share on other sites

Thanks for the sample, how large is resulting javascript file? I see there are few more samples of similar approach (without progress bar) here and in sms website:

http://smartmobilestudio.com/2015/09/27/writing-small-splash-screen-pre-loader-code/
http://forums.smartmobilestudio.com/index.php?/topic/4023-big-js-file-size

 

I think it would be best to include in loader first page of large application, which in my case is usually a login page, but in that case it will bring a lot of code from framework so only solutions are to write it all in raw javascript or send a static page in html. Have you done something similar, what is the better approach?

 

Ideally SMS would offer kind of Delphi packages, where common code (framework) would go into one minified js and main app would only include project code.

That way framework code could be cached and put on some global CDN (that's something SMS team could organize) and update with new SMS version.

Share this post


Link to post
Share on other sites

The resulting javascript of the progress bar code is a mere 2K

 

A raw javascript login page shouldn't be too difficult to do, and can be nicely styled as well.

That way the initial load should be less than say 5-8K, which is OK and you can omit the login code from your main app

Just synchronise the actual login with the activation of the downloaded app javascript

 

CDN of framework code is a nice option, however that involves (async) server-client downloads as well

and needs to play nicely with smart linking

Share this post


Link to post
Share on other sites

> The resulting javascript of the progress bar code is a mere 2K

 

 

That's nothing, it will load immediatelly.

 

> A raw javascript login page shouldn't be too difficult to do, and can be nicely styled as well.

> That way the initial load should be less than say 5-8K, which is OK and you can omit the login code from

> your main app Just synchronise the actual login with the activation of the downloaded app javascript

 

Thanks for the idea, I'll probably implement something like that when I have few days to experiment. I'll either do it in raw JS or as a static html, just need to pass entered user/pass to main app code since it handles login to mORMot and sessions.

 

> CDN of framework code is a nice option, however that involves (async) server-client downloads

> as well and needs to play nicely with smart linking

 

I think major JS frameworks does it that way, for example "https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js",there's a good chance browser will already have it it's cache (it could be set never to expire) so there's nothing to download.

I really think SMS should include something like that and then take care of hosting, it would simplify a lot distribution to all developers working with it.

Share this post


Link to post
Share on other sites

So to boost PageSpeed ranking the remaining topics are 'leveraging browser caching' and 'remove renderblocking js'

 

Browser caching

 

Methods to modify caching of resources in the browser :

-1 add a cache-control meta tag in the html-file

-2 add cache control info in a .htaccess file

-3 add cache-control headers in code

 

ad 1) this is not 100% reliable as not always all html is actually processed in the chain between original server and eventual browser. (proxy-caches)

ad 2) this only applies to apache servers

ad 3) So it seems that adding headers in code is the way to go.

 

References indicate that 

- using xmlhttprequests, headers must be produced between the 'open' and 'send' commands

- use 'get' instead of 'post', as post responses apparently aren't kept by most caches

- there is no established way to set infinite caching, but a period of up to 1 year seems to be acceptable

 

So f.i. the previous code (progress indicator) can then be modified to enhance browser caching as :

//XMLHttpRequest
var req := JXMLHttpRequest.Create;
req.open('GET','main-org2.js',true);
req.setRequestHeader("Cache-Control","public, max-age=31536000");   // 1 year
req.send;
 

.

 

Renderblocking

 

To remove renderblocking javascript the options are to 

 

-1) inline javascript

-2) make js asynchronous

-3) defer js loading

 

ad 1) this is meant for small js scripts only

 

ad 2) external js files like jspdf, accounting.js etc. can be safely loaded asynchronously as they usually are not needed 'above the fold'

         ...

         <script type="text/javascript" async src="lib/jspdf.js"></script>

         <script type="text/javascript" async src="lib/accounting.min.js"></script>

 

ad 3) covered previously in this thread

 

 

Conclusion

 

I expect using a combination of (all of) the above the PageSpeed's "mobile speed ranking" will improve considerably
I'll post the new comparable rankings when available.

Share this post


Link to post
Share on other sites

New PageSpeed results after implementing most of the above :

 
DESKTOP SPEED
93/100 - GOOD  (was : fair - 77/100)
 
MOBILE SPEED
83/100 - FAIR     (was : poor - 62/100)
 
MOBILE FRIENDLINESS
99/100 - GOOD 
 
--------------------------------------------------------------------------------------------------------------------------------------
 
Desktop ranking :
desktop.JPG
 
 
---------------------------------------------------------------------------------------------------------------------------------------------------
 
Mobile ranking
 
Mobile.JPG
 
 
-------------------------------------------------------------------------------------------------------------------------
 
There is some room for improvement but this looks pretty much ok

Share this post


Link to post
Share on other sites

Looking at increasing the mobile PageSpeed results further (see Prioritize Visual Content) it occurred to me that instead of a splash screen or a spinner showing until the main js file is loaded, it would actually help to have a quick pre-render of the first form.

 

The recipe for this could be something like this :

 

0) develop the project as per normal

 

1) get the rendered html code of the main form

    (in Chrome open the developer console and type in 'copy(document.body.innerHTML);'

    This will copy the generated html <body> code onto the clipboard

    A simple form with a button and a label generates this

  <script type="text/javascript"> /* This prevents the window being moved by touches,
 to give the impression of a native app */
document.ontouchmove = function(e) { e.preventDefault(); }
 </script>
 
<script type="text/javascript" src="main.js"></script>
<div id="OBJ1" class="TW3Display" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 0px; width: 1407px; height: 522px;">
<div id="OBJ2" class="TW3DisplayView" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 0px; height: 522px; width: 1407px;">
<div id="OBJ3" class="TW3CustomForm" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 0px; -webkit-transform: none; width: 1407px; height: 522px;">
<button id="OBJ4" class="TW3Button" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 144px; top: 88px; width: 128px; height: 32px;">W3Button</button>
<fieldset id="OBJ5" class="TW3Label" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 160px; top: 160px; height: 32px; width: 128px;">
<div id="OBJ6" class="TW3LabelText" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 7px; text-overflow: ellipsis; white-space: nowrap; width: 65px; height: 18px;">W3Label
</div></fieldset></div></div></div></div>

2) make a copy of the project index.html file, delete the <body> content and replace it with the code above

 

3) add a simple script to load the 'main.js' project file on dom-ready

<script type="text/javascript">
function downloadJSAtOnload() {
var element = document.createElement("script");
element.src = "main.js";
document.body.appendChild(element);
window.location.href="index.html";
}
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>

You'll end up with this 

<!DOCTYPE html>
<html manifest="app.manifest">
<head> 
  <meta charset="UTF-8" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="viewport" content="width=device-width, maximum-scale=1.0, initial-scale=1.0, user-scalable=no"/>
  <link rel="manifest" href="webapp.json">
  
 
<title>SSR-V01</title>
 
<link rel="stylesheet" type="text/css" href="res/app.css"/>
 
  
</head>
 
<body>  <script type="text/javascript"> /* This prevents the window being moved by touches,
 to give the impression of a native app */
document.ontouchmove = function(e) { e.preventDefault(); }
 </script>  
 
<script type="text/javascript" src="main.js"></script>
<div id="OBJ1" class="TW3Display" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 0px; width: 1407px; height: 522px;">
<div id="OBJ2" class="TW3DisplayView" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 0px; height: 522px; width: 1407px;">
<div id="OBJ3" class="TW3CustomForm" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 0px; -webkit-transform: none; width: 1407px; height: 522px;">
<button id="OBJ4" class="TW3Button" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 144px; top: 88px; width: 128px; height: 32px;">W3Button</button>
<fieldset id="OBJ5" class="TW3Label" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 160px; top: 160px; height: 32px; width: 128px;">
<div id="OBJ6" class="TW3LabelText" style="visibility: visible; display: inline-block; position: absolute; overflow: hidden; left: 0px; top: 7px; text-overflow: ellipsis; white-space: nowrap; width: 65px; height: 18px;">W3Label
</div></fieldset></div></div></div></div> 

<script type="text/javascript">
function downloadJSAtOnload() {
var element = document.createElement("script");
element.src = "main.js";
document.body.appendChild(element);
window.location.href="index.html";
}
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>
</body>
 
</html>

The above will

- render an image of the first form straight away and very fast. This image will be styled beautifully

- load the main js file immediately after

- optionally redirect to the original index.html file after the js has been cached

 

In terms of user experience an image of the first form will look better than a spinner. It won't be responsive until the js logic has loaded but it least it will look good

In terms of PageSpeed score (and SEO ranking) this will boost both of them considerably

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×