Upload
tkramar
View
830
Download
6
Tags:
Embed Size (px)
Citation preview
Optimising Web application frontend
Tomáš Kramár, @tkramar
What happens when I type address into browser?
GET /index.html
GET /index.html
Bro
wse
r
S
erve
r
GET /index.html
index.html
Bro
wse
r
S
erve
r
GET /assets/favicon.ico
favicon.ico
GET /index.html
index.html
Bro
wse
r
S
erve
r
GET /assets/favicon.ico
favicon.ico
GET /assets/application.css
application.css
GET /index.html
index.html
Bro
wse
r
S
erve
r
GET /assets/favicon.ico
favicon.ico
GET /assets/application.css
application.css
GET /assets/bg.png
bg.png
GET /index.html
index.html
Bro
wse
r
S
erve
r
GET /assets/favicon.ico
favicon.ico
GET /assets/application.css
application.css
GET /assets/bg.png
bg.png
Backend
Page Load Time =
Backend Time+
Frontend Time
Optimisation rule #1
Optimise only when it makes sense
Optimisation rule #1
Optimise only when it makes sense
* http://www.stevesouders.com/blog/2012/02/10/the-performance-golden-rule/
Waterfall / Firebug
Demo
Sprechen Sie Firebug?
● Blocking - request is queued and waiting● DNS Lookup - time to resolve hostname● Connection - time to create TCP connection● Sending - sending request headers● Waiting - backend is busy now● Receiving - reading the response ● blue and red vertical lines:
DOMContentLoaded and load events○ http://ie.microsoft.
com/testdrive/HTML5/DOMContentLoaded/Default.html
Optimisation rule #2
Download resources in parallel
Resource downloading rules
Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy.
-- RFC 2616 (HTTP 1.1)
Demo
Solution: Asset subdomains
● assets01.domain.com● assets02.domain.com● ... ● Extra:
○ Cookie-less domain○ HTTP Keep-Alive
● Pitfalls:
○ DNS lookups
Optimisation rule #3
Fastest request is the one that doesn't happen
Avada Kedavra Request!
● Merge● Inline● Sprite● Cache
Merge
● Merge multiple CSS files into a single file● Merge multiple JS files into a single file
○ -> Rails/Sprockets/Asset pipeline$ cat app/assets/javascripts/application.js
//= require jquery_ujs//= require_tree ./vendor//= require document_viewer/dv//= require_tree .
Inline
● Inline JavaScript○ Replace <script src=".."></script> with
<script>//code</script>● Inline CSS
○ <style>body { color: red; }</style>● Usable only for small resources, larger
resources benefit more from caching● Inline images using data URIs
○ <img src='data:image/jpeg;base64,/9j/4AAQSkZJR'/>
○ background-image: url(data:image/jpeg;base64,/9j/4AAQS);
○ Pitfalls: size limit, IE <= 7
Demo
CSS sprites
Merge multiple images into one, use background-position to place.
Caching
First request: cache miss, hit the server, obtain tokenAdditional requests: use the token from first request to make conditional request Conditional requests● If-None-Match● If-Modified-Since
If-None-Match: First request
First request: Request headers:GET / Response headers:Status: 200 OKEtag: "be5c5a3edac0592617693fa..."
If-None-Match: Next requests
Request headers:GET /If-None-Match: "be5c5a3edac0592617693fa..." Response headers:Status: 304 Not ModifiedEtag: "be5c5a3edac0592617693fa..."
If-None-Match
● Server needs to calculate ETag (fingerprint)● How?
○ it depends○ easiest way: generate output, calculate hash
● If ETag matches, send 304 Not Modified, no response body
● You save on the data transfer ("Receiving" in Firebug)
If-Modified-Since: First request
Request headers:GET / Response headers:Status: 200 OKExpires: Thu, 31 Dec 2037 23:55:55 GMTLast-Modified: Mon, 30 Jan 2012 13:36:26 GMT
If-Modified-Since: Local cache
Request headers:none Response headers:none Current time is < resource's Expire header
If-Modified-Since: Forced refresh
Request headers:GET /If-Modified-Since: Mon, 30 Jan 2012 13:36:26 GMT Response headers:Status: 304 Not ModifiedExpires: Thu, 31 Dec 2037 23:55:55 GMTLast-Modified: Mon, 30 Jan 2012 13:36:26 GMT
Refresh
Regular click on a link: uses local cache and no request is made F5: Skips local cache, sends If-Modified-Since request, potentially 304-ing Ctrl+F5: Sends requests without If-None-Match and If-Modified-Since
Demo
Far future expires strategy
Set the Expires header far in the future FAQQ: But what if I need to change the resource?A: Use a different name application-f7fd224c9bc0fd4c2f7.css
fingerprint
Rails gotcha
Asset pipeline sets fingerprints, but not Expire headers. You need to DIY. Nginx: location ~* \.(js|css|jpg|jpeg|gif|png|swf|ico)$ { expires max;}
Optimisation rule #3
If you need to make the request, make the response (and request) small.
Follow these rules
● Minify and gzip CSS and JavaScript.● Do not send/set cookies unless necessary
(asset subdomains)● Do not scale images in HTML
○ do not use <img size=""/>, scale image on server● Optimize images
○ http://www.smushit.com/ysmush.it/
Optimisation rule #4
If possible, defer parsing of JavaScripts
How browsers process JavaScript
When browsers encounter <script> tag, the script is downloaded, parsed and executed. Main parsing thread is busy processing the script. Speculative parsing continues downloading and building the DOM in the background, so main thread can pull this fast, but you still pay the parse and execute penalty.
Do you need to execute the script immediately?
Probably not. Is your code waiting for document.ready? Mouse action? Then definitely not.
Then defer it<script type="text/javascript">function downloadJSAtOnload() { var element = document.createElement("script"); element.src = "application.js"; document.body.appendChild(element);} if (window.addEventListener) window.addEventListener("load", downloadJSAtOnload, false);else if (window.attachEvent) window.attachEvent("onload", downloadJSAtOnload);else window.onload = downloadJSAtOnload;</script>
Demo
Optimisation rule #5
As a last resort: simply cheat.
Perceived speed matters
Move all CSS to the top so DOM nodes can be rendered progressivelyIf you cannot defer, at least move <script> to the bottom of the page.Post-load parts of page that are slow to render.
Optimisation rule #5
Think globally.
Location matters
Downloading image placed on a server in Slovakia is relatively cheap than say downloading the same image from Canada.
Use CDN (content delivery network)
Servers spread through the world. Use CDN on common assets (jQuery etc.). Chance is, you will get a cache hit. https://developers.google.com/speed/libraries/
Tools
Web Page Test, http://www.webpagetest.org/Newrelic RUM, http://newrelic.com
Is this all I can do?
Definitely not. Google Page Speed, http://developers.google.com/speed/pagespeed/YSlow, http://yslow.org/
Future
SPDYHTTP archiveslocal storage
Summary
1. Optimise only when it makes sense2. Download resources in parallel3. Fastest request is the one that doesn't
happen4. If you need to make the request, make the
response (and request) small5. If possible, defer parsing of JavaScripts6. Think globally