Upload
megan-black
View
236
Download
2
Tags:
Embed Size (px)
Citation preview
Steve [email protected]
http://stevesouders.com/docs/google-20090305.ppt
Even Faster Web Sites
Disclaimer: This content does not necessarily reflect the opinions of my employer.
14 Rules1. Make fewer HTTP
requests2. Use a CDN3. Add an Expires header4. Gzip components5. Put stylesheets at the
top6. Put scripts at the
bottom7. Avoid CSS expressions8. Make JS and CSS
external9. Reduce DNS lookups10.Minify JS11.Avoid redirects12.Remove duplicate
scripts13.Configure ETags14.Make AJAX cacheable
Even Faster Web Sites
Split the initial payloadLoad scripts without
blockingCouple asynchronous scripts
Don't scatter inline scriptsSplit the dominant
domainFlush the document early
Use iframes sparingly
Simplify CSS Selectors
O'Reilly, June 2009
Ajax performance (Doug Crockford)
Writing efficient JavaScript (Nicholas Zakas)
Creating responsive web apps (Ben Galbraith, Dion Almaer)
Comet (Dylan Schiemann)Beyond Gzipping (Tony
Gentilcore)Optimize Images (Stoyan
Stefanov, Nicole Sullivan)
AOLeBayFacebookMySpaceWikipediaYahoo!
Why focus on JavaScript?
YouTube
Scripts Block
<script src="A.js"> blocks parallel downloads and rendering
http://stevesouders.com/cuzillion/?ex=10008
MSNScripts and other resources downloaded in parallel! How? Secret sauce?!var p= g.getElementsByTagName("HEAD")[0];var c=g.createElement("script");c.type="text/javascript";c.onreadystatechange=n;c.onerror=c.onload=k;c.src=e;p.appendChild(c)
MSN.com: Parallel Scripts
Advanced Script Loading
XHR Eval
XHR Injection
Script in Iframe
Script DOM Element
Script Defer
document.write Script Tag
Summary of Traits
*Only other document.write scripts are downloaded in parallel (in the same script block).
and the winner is...XHR EvalXHR InjectionScript in iframeScript DOM ElementScript Defer
Script DOM ElementScript Defer
Script DOM Element
Script DOM Element (FF)Script Defer (IE)
XHR EvalXHR InjectionScript in iframeScript DOM Element (IE)
XHR InjectionXHR EvalScript DOM Element (IE)
Managed XHR InjectionManaged XHR EvalScript DOM Element
Managed XHR InjectionManaged XHR Eval
Script DOM Element (FF)Script Defer (IE)Managed XHR EvalManaged XHR Injection
Script DOM Element (FF)Script Defer (IE)Managed XHR EvalManaged XHR Injection
different domains same domains
no order
preserve order
no order
no busyshow busy
show busyno busy
preserve order
what about
inlined code that depends on the script?
synchronous JS example: menu.js
<script src="menu.js" type="text/javascript"></script>
<script type="text/javascript">var aExamples = [ ['couple-normal.php', 'Normal Script Src'], ['couple-xhr-eval.php', 'XHR Eval'], ... ['managed-xhr.php', 'Managed XHR'] ];function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
init();</script>
asynchronous JS example: menu.js
<script type="text/javascript"> var domscript = document.createElement('script');domscript.src = "menu.js"; document.getElementsByTagName('head')
[0].appendChild(domscript);
var aExamples = [ ['couple-normal.php', 'Normal Script Src'], ['couple-xhr-eval.php', 'XHR Eval'], ... ['managed-xhr.php', 'Managed XHR'] ];
function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
init();</script>
before
after
baseline coupling results (not good)
* Scripts download in parallel regardless of the Defer attribute.
need a way to load scripts asynchronously AND preserve order
coupling techniques
hardcoded callback
window onload
timer
script onload
degrading script tags
technique 1: hardcoded callback
<script type="text/javascript">var aExamples = [['couple-normal.php', 'Normal Script Src'],
...];
function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
var domscript = document.createElement('script');domscript.src = "menu.js";document.getElementsByTagName('head')
[0].appendChild(domscript);</script>
init() is called from within menu.jsnot very flexibledoesn't work for 3rd party scripts
technique 2: window onload<iframe src="menu.php" width=0 height=0 frameborder=0> </iframe><script type="text/javascript">var aExamples = [['couple-normal.php', 'Normal Script Src'], ...];
function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
if ( window.addEventListener ) { window.addEventListener("load", init, false);}else if ( window.attachEvent ) { window.attachEvent("onload", init);}</script>
init() is called at window onloadmust use async technique that blocks
onload:Script in Iframe does this across most browsers
init() called later than necessary
technique 3: timer<script type="text/javascript">var domscript = document.createElement('script');domscript.src = "menu.js";document.getElementsByTagName('head')[0].appendChild(domscript);var aExamples = [['couple-normal.php', 'Normal Script Src'], ...];function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
function initTimer(interval) { if ( "undefined" === typeof(EFWS) ) { setTimeout(initTimer, interval); } else { init(); }}initTimer(300);</script>
load if interval too low, delay if too highslight increased maintenance – EFWS
technique 4: script onload<script type="text/javascript">var aExamples = [['couple-normal.php', 'Normal Script Src'], ...];
function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
var domscript = document.createElement('script');domscript.src = "menu.js";domscript.onloadDone = false;domscript.onload = function() { domscript.onloadDone = true; init(); };domscript.onreadystatechange = function() { if ( "loaded" === domscript.readyState && ! domscript.onloadDone ) { domscript.onloadDone = true; init(); }}document.getElementsByTagName('head')[0].appendChild(domscript);</script>
pretty nice, more complicated
John Resig's degrading script tags
<script src="menu-degrading.js" type="text/javascript">var aExamples = [['couple-normal.php', 'Normal Script Src'], ...];
function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
init();</script>
cleanerclearersafer – inlined code not called if script
fails
at the end of menu-degrading.js:var scripts =
document.getElementsByTagName("script");var cntr = scripts.length;while ( cntr ) { var curScript = scripts[cntr-1]; if (curScript.src.indexOf("menu-degrading.js") != -1) { eval( curScript.innerHTML ); break; } cntr--;}
http://ejohn.org/blog/degrading-script-tags/
technique 5: degrading script tags
<script type="text/javascript">var aExamples = [['couple-normal.php', 'Normal Script Src'],...];
function init() { EFWS.Menu.createMenu('examplesbtn', aExamples);}
var domscript = document.createElement('script');domscript.src = "menu-degrading.js";if ( -1 != navigator.userAgent.indexOf("Opera") ) { domscript.innerHTML = "init();";}else { domscript.text = "init();";}document.getElementsByTagName('head')[0].appendChild(domscript);</script>
elegant, flexible (cool!)not well knowndoesn't work for 3rd party scripts
(unless...)
what about
multiple scripts that depend on each other,
and inlined code that depends on the scripts?
two solutions:− Managed XHR− DOM Element and Doc Write
multiple script example: menutier.js
<script src="menu.js" type="text/javascript"></script>
<script src="menutier.js" type="text/javascript"></script>
<script type="text/javascript">var aRaceConditions = [['couple-normal.php', 'Normal...];var aWorkarounds = [['hardcoded-callback.php', 'Hardcod...];var aMultipleScripts = [['managed-xhr.php', 'Managed XH...];var aLoadScripts = [['loadscript.php', 'loadScript'], ...];var aSubmenus = [["Race Conditions", aRaceConditions], ["Workarounds", aWorkarounds], ["Multiple Scripts", aMultipleScripts], ["General Solution", aLoadScripts]];
function init() { EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus);}</script>
technique 1: managed XHR<script type="text/javascript">var aRaceConditions = [['couple-normal.php', 'Normal...];var aWorkarounds = [['hardcoded-callback.php', 'Hardcod...];var aMultipleScripts = [['managed-xhr.php', 'Managed XH...];var aLoadScripts = [['loadscript.php', 'loadScript'], ...];var aSubmenus = [["Race Conditions", aRaceConditions], ...];
function init() { EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus);}
EFWS.Script.loadScriptXhrInjection("menu.js", null, true);EFWS.Script.loadScriptXhrInjection("menutier.js", init, true);</script>
XHR Injection asynchronous technique does not preserve order – we have to add that
before
after
EFWS.loadScriptXhrInjection// Load an external script. // Optionally call a callback and preserve order.loadScriptXhrInjection: function(url, onload, bOrder) { var iQ = EFWS.Script.queuedScripts.length; if ( bOrder ) { var qScript = { response: null, onload: onload, done: false }; EFWS.Script.queuedScripts[iQ] = qScript; } var xhrObj = EFWS.Script.getXHRObject(); xhrObj.onreadystatechange = function() { if ( xhrObj.readyState == 4 ) { if ( bOrder ) { EFWS.Script.queuedScripts[iQ].response = xhrObj.responseText; EFWS.Script.injectScripts(); } else { eval(xhrObj.responseText); if ( onload ) { onload(); } } } }; xhrObj.open('GET', url, true); xhrObj.send('');}
process queue (next slide)
or... eval now, call callback
save response to queue
add to queue (if bOrder)
EFWS.injectScripts// Process queued scripts to see if any are ready to inject.injectScripts: function() { var len = EFWS.Script.queuedScripts.length; for ( var i = 0; i < len; i++ ) { var qScript = EFWS.Script.queuedScripts[i]; if ( ! qScript.done ) { if ( ! qScript.response ) { // STOP! need to wait for this response break; } else { eval(qScript.response); if ( qScript.onload ) { qScript.onload(); } qScript.done = true; } } }}
ready for this script, eval and call callback
bail – need to wait to preserve order
if not yet injected
preserves external script order
non-blockingworks in all browserscouples with inlined
codeworks with scripts across domains
technique 2: DOM Element and Doc Write
Firefox & Opera – use Script DOM Element
IE – use document.write Script Tag
Safari, Chrome – no benefit; rely on Safari 4 and Chrome 2
EFWS.loadScriptsloadScripts: function(aUrls, onload) { // first pass: see if any of the scripts are on a different domain var nUrls = aUrls.length; var bDifferent = false; for ( var i = 0; i < nUrls; i++ ) { if ( EFWS.Script.differentDomain(aUrls[i]) ) { bDifferent = true; break; } }
// pick the best loading function var loadFunc = EFWS.Script.loadScriptXhrInjection; if ( bDifferent ) { if ( -1 != navigator.userAgent.indexOf('Firefox') || -1 != navigator.userAgent.indexOf('Opera') ) { loadFunc = EFWS.Script.loadScriptDomElement; } else { loadFunc = EFWS.Script.loadScriptDocWrite; } }
// second pass: load the scripts for ( var i = 0; i < nUrls; i++ ) { loadFunc(aUrls[i], ( i+1 == nUrls ? onload : null ), true); }}
coupling wrap-up
case study: Google Analytics
recommended pattern:1
<script type="text/javascript">var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");document.write(unescape("%3Cscript src='" + gaJsHost +
"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">var pageTracker = _gat._getTracker("UA-xxxxxx-x");pageTracker._trackPageview();</script>
document.write Script Tag approach blocks other resources
1http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55488
case study: Google Analytics
dojox.analytics.Urchin:1
_loadGA: function(){ var gaHost = ("https:" == document.location.protocol) ? "https://ssl." : "http://www."; dojo.create('script', { src: gaHost + "google-analytics.com/ga.js" }, dojo.doc.getElementsByTagName("head")[0]); setTimeout(dojo.hitch(this, "_checkGA"), this.loadInterval);},
_checkGA: function(){ setTimeout(dojo.hitch(this, !window["_gat"] ? "_checkGA" :
"_gotGA"), this.loadInterval);},
_gotGA: function(){ this.tracker = _gat._getTracker(this.acct); ...}
Script DOM Element approachtimer coupling technique (script onload
better)1http://docs.dojocampus.org/dojox/analytics/Urchin
iframes: most expensive DOM element
load 100 empty elements of each type
tested in all major browsers1
1IE 6, 7, 8; FF 2, 3.0, 3.1b2; Safari 3.2, 4; Opera 9.63, 10; Chrome 1.0, 2.0
iframes block onload
parent's onload doesn't fire until iframe and all its components are downloaded
workaround for Safari and Chrome: set iframe src in JavaScript
<iframe id=iframe1 src=""></iframe><script type="text/javascript">document.getElementById('iframe1').src="url";</script>
scripts block iframe
no surprise – scripts in the parent block the iframe from loading
IE
Firefox
Safari Chrome
Opera
script
script
script
stylesheets block iframe (IE, FF)
surprise – stylesheets in the parent block the iframe or its resources in IE & Firefox
IE
Firefox
Safari Chrome
Opera
stylesheet
stylesheet
stylesheet
stylesheets after iframe still block (FF)
surprise – even moving the stylesheet after the iframe still causes the iframe's resources to be blocked in Firefox
IE
Firefox
Safari Chrome
Opera
stylesheet
stylesheet
stylesheet
iframes: no free connections
iframe shares connection pool with parent (here – 2 connections per server in IE 7)
iframe
parent
flush the document early
gotchas:– PHP output_buffering – ob_flush()– Transfer-Encoding: chunked– gzip – Apache's DeflateBufferSize before
2.2.8– proxies and anti-virus software– browsers – Safari (1K), Chrome (2K)
htmlimageimagescript
htmlimageimagescript
flushing and domain blocking
you might need to move flushed resources to a domain different from the HTML doc
htmlimageimagescript
htmlimageimagescript
googleimageimagescriptimage204
Takeaways
focus on the frontend
run YSlow: http://developer.yahoo.com/yslow
this year's focus: JavaScriptSplit the Initial PayloadLoad Scripts without BlockingDon't Scatter Inline Scripts
speed matters
Impact on Revenue
Google:
Yahoo:
Amazon:
1 http://home.blarg.net/~glinden/StanfordDataMining.2006-11-29.ppt2 http://www.slideshare.net/stoyan/yslow-20-presentation
+500 ms -20% traffic1
+400 ms -5-9% full-page traffic2
+100 ms -1% sales1
Cost Savings
hardware – reduced load
bandwidth – reduced response size
http://billwscott.com/share/presentations/2008/stanford/HPWP-RealWorld.pdf
if you wantbetter user experiencemore revenuereduced operating expenses
the strategy is clear
Even Faster Web Sites
Steve [email protected]
http://stevesouders.com/docs/google-20090305.ppt