progressive downloads and rendering
DESCRIPTION
Progressive downloads and rendering. Stoyan Stefanov, Yahoo! HighLoad ++, Moscow, 2010 http://slideshare.net/stoyan/. About me. YSlow 2.0. Why progressive?. Importance of performance. Psychology, physiology Effects of waiting “Time is money” Make people happy. Perception. - PowerPoint PPT PresentationTRANSCRIPT
Progressive downloads
and rendering
Stoyan Stefanov, Yahoo! HighLoad++, Moscow, 2010http://slideshare.net/stoyan/
About meYSlow 2.0
Why progressive?
Importance of performance• Psychology, physiology • Effects of waiting• “Time is money”• Make people happy
Perception
Perception
Perception
Perception
Time is relative• Sometimes crawls• Sometimes flies• “it depends”
Durations
actual
expectedperceived
rem’d
time
It feels slower when…• Unpleasant• Unknown• Boring• Too much to keep track
First time experience• Unfamiliar = slow• Optimize empty cache or
there will be no full cache
So… go progressive!
But before we begin…
The basics• Reducing the # HTTP• Gzip• Minification• Image smushing• Expires• CDN
The basics• Yahoo!’s best practices +
YSlowhttp://developer.yahoo.com/performance/
• Google’s too + Page Speed
http://code.google.com/speed/
Progressive
Progressive enhancement
Progressive downloads
Progressive rendering
Agenda1. Prevent download blocks:
scripts, styles, CC, favicon2. Ways to render sooner:
flush, data URIs, lazy loading, lazy evaluation, preloading, animations
Blocking JavaScript
JavaScript blocks
htmljs
pngpng
JavaScript blocks• A no-no!
<script src="jquery.js"></script><script src="jquery.twitter.js"></script><script src="jquery.cookie.js"></script><script src="myapp.js"></script>
This waterfall looks ridiculous
htmljs
pngpng
jsjs
js
JavaScript at the bottom
html
js
pngpng
Non-blocking JavaScript• defer and async• Defer: IE innovation, ok to
delay, but keep order• Async: HTML5, whatever<script async src="my.js"
onload="doIt()"></script><script defer src="my.js"
onload="doIt()"></script>
defer and async timeline
DOMContentLoaded load
async
defer
Non-blocking JavaScript• Asynchronous loading
var h, js = document.createElement('script');js.src = 'myscript.js';h = document.getElementsByTagName('head')[0];h.appendChild(js);
htmljs
pngpng
Non-blocking JavaScript• Others:
- iframe- XHR- <object>- …
CSS and rendering
Worst enemy?
CSS
CSS blocks rendering• The worst component type• Place way at the top• @media print, etc in the same external CSS
http://www.phpied.com/delay-loading-your-print-css/http://www.phpied.com/rendering-styles/
CSS
CSS
CSS blockdownloads?
But they do block:• When followed
by an inline script• When in conditional
comments
Inline CSS• Google search• Bing.com: inline + postload
Same domain• If you split across domains• and if you don’t use CDN• Saves a DNS lookup
• e.g. Google and Bing’s CDN
CC block
Normal page
With conditionally commented CSS file
http://www.phpied.com/conditional-comments-block-downloads/
What…?! Case #1 <link type="text/css" rel="stylesheet" href="1.css"> <!--[if IE 6]> <link type="text/css" rel="stylesheet" href="ie.css"> <![endif]-->
What…?! Case #2<!--[if IE 6]> <body class="ie6"> <![endif]--><!--[if !IE]><!--> <body><!--<![endif]-->
Solution for case #1<!DOCTYPE html><!--[if IE 6]><![endif]-->
<html> ...
Solution for case #2<!--[if IE 6]> <html class="ie6"> <![endif]--><!--[if !IE]><!--> <html><!--<![endif]-->
Blocking favicon
Flush
flush() earlyhtml
pngjs
css
html
pngjs
css
✔
flush()<html><head> <script src="my.js"
type="text/javascript"></script> <link href="my.css"
type="text/css" rel="stylesheet" /></head>
<body> ....
<?php flush() ?>
Chunked encodingHTTP/1.1 200 OKContent-Type: text/plainTransfer-Encoding: chunked
25This is the data in the first chunk
1Cand this is the second one
0
Chunked encoding
• Progressive rendering- Semantic app chunksvs.- Server-level chunks
Progressive renderingChunk #1
Chunk #2
Chunk #3
<!doctype html><html><head><title>My App</title></head><body> <div id="header"> <img src="logo.png" /> ... </div> <!-- end of chunk #1 --> ... The full body of the page ... <!-- end of chunk #2 -->
<script src="all_20100925.js"></script></body></html> <!-- end of chunk #3 -->
Progressive + source order
1
23
4
HTTP chunking: not only HTML
HTTP chunking: not only HTML• Google Instant• /*""*/ - delimited JSON
pieces• Chunk #1 suggestions• Chunk #2 results
http://tinyurl.com/chunkview
Data URIs
Fewer HTTP requests• Inline images:
in CSS spriteswith data: URI scheme
http://csssprites.com http://spriteme.org
Fewer HTTP requests• data: URI scheme
$ php -r "echo base64_encode(file_get_contents('my.png'));”iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC
Fewer HTTP requests• data: URI scheme
background-image: url("data:image/png;base64,iVBORw0KG...");
Fewer HTTP requests• data: URI scheme
<img src="data:image/png;base64,iVBOR..." />
Both• flushes• data: URIs
Fewer HTTP requests• data: URI scheme• works in IE!...
Fewer HTTP requests• data: URI scheme• works in IE8!
Fewer HTTP requests• data: URI scheme• MHTML for IE < 8
MHTML • MIME HTML• Works in IE 6,7• Indeed it actually absolutely does work in IE7/Vista too
http://phpied.com/the-proper-mhtml-syntax/
MHTML - one partContent-Location: myimageContent-Transfer-Encoding: base64
iVBORw0KGgoAAAANSU....U5ErkJggg==
MHTML - multi partsContent-Type: multipart/related; boundary="MYSEPARATOR"
--MYSEPARATOR
[here comes part one]
--MYSEPARATOR
[here's part two]
--MYSEPARATOR--
The double-dash of doom
MHTML.css – all together/*Content-Type: multipart/related; boundary="MYSEPARATOR" --MYSEPARATORContent-Location: myimageContent-Transfer-Encoding: base64 iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAD....U5ErkJggg==--MYSEPARATORContent-Location: anotherContent-Transfer-Encoding: base64 iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAA....U5ErkJggg==--MYSEPARATOR--*/.myclass { background-image:url(mhtml:http://example.org/styles.css!myimage);}.myotherclass { background-image:url(mhtml:http://example.org/styles.css!another);}
MHTML: inline too<!doctype html><html> <head> <title>Look Ma' No HTTP requests</title> <style type="text/css">/*Content-Type: multipart/related; boundary="_"
--_Content-Location:locolocoContent-Transfer-Encoding:base64
iVBOR...CC--_Content-Location:pollolocoContent-Transfer-Encoding:base64
iVBOR....gg==--_--*/.image1 { background-image: url("data:image/png;base64,iVBOR...CC"); /* normal */ *background-image: url(mhtml:http://...html!locoloco); /* IE < 8 */}.image2 { background-image: url("data:image/png;base64,iVBOR...gg=="); /* normal */ *background-image: url(mhtml:http://...html!polloloco); /* IE < 8 */}body { font: bold 24px Arial;} </style> </head> <body> <h1>MHTML + Data:URIs inline in <code>style</code></h1> <p class="image1">hello<br>hello</p> <p class="image2">bonjour<br>bonjour</p> </body></html>
http://phpied.com/inline-mhtml-data-uris/
<!doctype html><html> <head> <title>Look Ma' No HTTP requests</title>
<style type="text/css"> ...
/*Content-Type: multipart/related; boundary="_"
--_Content-Location:locolocoContent-Transfer-Encoding:base64
iVBOR...CC--_Content-Location:pollolocoContent-Transfer-Encoding:base64
iVBOR....gg==--_--*/
.image1 { background-image: url("data:image/png;base64,iVBOR...CC"); /* normal */ *background-image: url(mhtml:http://...html!locoloco); /* IE < 8 */}.image2 { background-image: url("data:image/png;base64,iVBOR...gg=="); /* normal */ *background-image: url(mhtml:http://...html!polloloco); /* IE < 8 */}body { font: bold 24px Arial;}
... </style>
</head> <body> <h1>MHTML + Data:URIs inline in <code>style</code></h1> <p class="image1">hello<br>hello</p> <p class="image2">bonjour<br>bonjour</p> </body></html>
MHTML + data URI• X-browser single request web apps
Single request• WT☠?• Separation of concerns• Content-presentation-behavior• yes, it’s a tradeoff
MHTML + data URI• drawback: repeats the same encoded image• solutions: - browser-specific CSS - keep close = better gzip - or… an ingenious hack
Single stream MHTML/data URI• image header + css + data/9j/4AA0;background-image:url(data:image/jpeg;base64;00,/9j/4AA0
Reality:
IE:
Others:
http://habrahabr.ru/blogs/webdev/90761/
/*Content-Type: multipart/related; boundary="granitza"
--granitzaContent-Type: text/css;
*/#myid {/*--granitzaContent-Location: myimageContent-Transfer-Encoding: base64Content-Type: image/jpeg;*/
/9j/4AA0;background-image:url(data:image/jpeg;base64;00,/9j/4AAQSkZJRgA...);/*--granitzaContent-Type: text/css;
*/background-image: url(mhtml:http://localhost/my.css!myimage) !ie;}/*--granitza--*/
/*Content-Type: multipart/related; boundary="granitza"
--granitzaContent-Type: text/css;
*/#myid {/*--granitzaContent-Location: myimageContent-Transfer-Encoding: base64Content-Type: image/jpeg;*/
/9j/4AA0;background-image:url(data:image/jpeg;base64;00,/9j/4AAQSkZJRgA...);/*--granitzaContent-Type: text/css;
*/background-image: url(mhtml:http://localhost/my.css!myimage) !ie;}/*--granitza--*/
???
/*Content-Type: multipart/related; boundary="granitza"
--granitzaContent-Type: text/css;
*/#myid {/*--granitzaContent-Location: myimageContent-Transfer-Encoding: base64Content-Type: image/jpeg;*/
/9j/4AA0;background-image:url(data:image/jpeg;base64;00,/9j/4AAQSkZJRgA...);/*--granitzaContent-Type: text/css;
*/background-image: url(mhtml:http://localhost/my.css!myimage) !ie;}/*--granitza--*/
/*Content-Type: multipart/related; boundary="granitza"
--granitzaContent-Type: text/css;
*/#myid {/*--granitzaContent-Location: myimageContent-Transfer-Encoding: base64Content-Type: image/jpeg;*/
/9j/4AA0;background-image:url(data:image/jpeg;base64;00,/9j/4AAQSkZJRgA...);/*--granitzaContent-Type: text/css;
*/background-image: url(mhtml:http://localhost/my.css!myimage) !ie;}/*--granitza--*/
???1
2
3
Single stream MHTML/data URI• And for <img> too!
Single stream <img><h2>Hello Kitty</h2><!--granitzaContent-Location: myimageContent-Transfer-Encoding: base64Content-Type: image/gif2 invalid MHTML headers lines followed by data--><![if gt IE 7]><img width="16" height="16" src="data:image/gif;base64,
R0lGODl ... FxMKVvDijNQaodOl+N5Pk+pmIX7brGweCg4SCEhEAOw=="><![endif]>
<!--[if lt IE 8]><img src="mhtml:http://.../bolknote.html!myimage" width="16" height="16"><![endif]-->
<p> more page... </p>
Single stream <img><h2>Hello Kitty</h2><!--granitzaContent-Location: myimageContent-Transfer-Encoding: base64Content-Type: image/gif2 invalid MHTML headers lines followed by data--><![if gt IE 7]><img width="16" height="16" src="data:image/gif;base64,
R0lGODl ... FxMKVvDijNQaodOl+N5Pk+pmIX7brGweCg4SCEhEAOw=="><![endif]>
<!--[if lt IE 8]><img src="mhtml:http://.../bolknote.html!myimage" width="16" height="16"><![endif]-->
<p> more page... </p>
Lazy loading
Lazy loading aka post-loading• After-onload• Some images• Below the fold (on scroll)• Hidden content e.g. tabs
Amazon’s lazy bestsellers • Page’s purpose is ranking• Details can come later• via onload XHR• JS off = no details• but that’s fine (see bullet #1)
Lazy evaluation
GMail mobile’s lazy JS <!doctype html><html><body>...<script id="lazy">/*console.log("I can wait");*/</script>...<script>console.log("I'm needed");window.onload = function () { var comment = document.getElementById('lazy') .innerHTML, code = comment.substring(3, comment.length - 3); eval(code);};</script></body></html> http://googlecode.blogspot.com/2009/09/gmail
-for-mobile-html5-series-reducing.html
Lazy HTML<!doctype html><html><body>...<div id="lazy"><!--<p>lots of html goes here...</p>--></div>...<script>window.onload = function () { var el = document.getElementById('lazy'), inner = el.innerHTML, code = inner.substring(4, inner.length - 3); el.innerHTML = code;};</script></body></html>
http://phpied.com... (coming-soon)
Lazy HTML test• 500K (200K gzipped) HTML doc• “Sherlock Holmes”• comment out 95%• still one whole chapter left
http://www.phpied.com/files/lazyhtml/start.html
Lazy HTML test results
Lazy HTML - misc• Who loads a book?• Use case: blog comments• SEO? Content is hidden• What about display: none?• The test page was simple-to-render, no complex layout
Preloads
Preloads• Anticipate next page• Problems: - does next page anticipate you? - parsing and execution time• <link prefetch="http://..">
Preload sans executevar preload; if (/*@cc_on!@*/false) { // IE preload = function (file) { new Image().src = file; };} else { preload = function (file) { var obj = document.createElement('object'), body = document.body; obj.width = 0; obj.height = 0; obj.data = file; body.appendChild(obj); };}
Preload, then executevar loader = function (file, callback) { var obj = document.createElement('object'), body = document.body; obj.width = 0; obj.height = 0; obj.data = file; obj.onload = function () { var h, js = document.createElement('script'); js.src = file; js.onload = callback; h = document.getElementsByTagName('head')[0]; h.appendChild(js); }; body.appendChild(obj);};
Browser search preload
Chrome search• In-browser search box• Gives suggestions as you type• Visual suggestions in IE8+
IE8 Visual Search Suggestions
...<Item> <Text>Currently: Partly Cloudy, 67F</Text> <Description>High: 71F Low: 63F</Description> <Url>http://weather.yahoo.com/forecast/USCA1024_f.html</Url> <Image source="http://l.yimg.com/a/i/us/we/31/30.gif" alt="Partly Cloudy" width="31" height="31"/></Item>...
IE8 Visual Search Preload
...<Item> <Text>any search suggestion</Text> <Image source="http://path/to/sprite.png" width="0" height="0"/></Item>...
IE8 Visual Search Preload
IE8 Visual Search Preload
• didn’t work for CSS and JS
IE8 Visual Search
• preload images, e.g. sprite• DNS lookups via beacons
Parting words
Do care about• Progressive, non-blocking, asynchronous downloads• Progressive rendering
Thank you!
Stoyan Stefanov@stoyanstefanovhttp://www.phpied.comSlides: http://slideshare.net/stoyan/