50
Steve Souders [email protected] http://stevesouders.com/docs/google-20090305.ppt Even Faster Web Sites Disclaimer: This content does not necessarily reflect the opinions of my

Steve Souders [email protected] Even Faster Web Sites Disclaimer: This content does not necessarily reflect

Embed Size (px)

Citation preview

Page 1: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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.

Page 2: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 3: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect
Page 4: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect
Page 5: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect
Page 6: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect
Page 7: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect
Page 8: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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)

Page 9: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

AOLeBayFacebookMySpaceWikipediaYahoo!

Why focus on JavaScript?

YouTube

Page 10: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

Scripts Block

<script src="A.js"> blocks parallel downloads and rendering

http://stevesouders.com/cuzillion/?ex=10008

Page 11: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 12: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

Advanced Script Loading

XHR Eval

XHR Injection

Script in Iframe

Script DOM Element

Script Defer

document.write Script Tag

Page 13: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

Summary of Traits

*Only other document.write scripts are downloaded in parallel (in the same script block).

Page 14: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 15: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

what about

inlined code that depends on the script?

Page 16: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect
Page 17: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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>

Page 18: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 19: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect
Page 20: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

baseline coupling results (not good)

* Scripts download in parallel regardless of the Defer attribute.

need a way to load scripts asynchronously AND preserve order

Page 21: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

coupling techniques

hardcoded callback

window onload

timer

script onload

degrading script tags

Page 22: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 23: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 24: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 25: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 26: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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/

Page 27: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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...)

Page 28: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 29: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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>

Page 30: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 31: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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)

Page 32: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 33: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 34: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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); }}

Page 35: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

coupling wrap-up

Page 36: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 37: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 38: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 39: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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>

Page 40: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

scripts block iframe

no surprise – scripts in the parent block the iframe from loading

IE

Firefox

Safari Chrome

Opera

script

script

script

Page 41: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 42: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 43: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

iframes: no free connections

iframe shares connection pool with parent (here – 2 connections per server in IE 7)

iframe

parent

Page 44: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 45: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

flushing and domain blocking

you might need to move flushed resources to a domain different from the HTML doc

htmlimageimagescript

htmlimageimagescript

googleimageimagescriptimage204

Page 46: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 47: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

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

Page 48: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

Cost Savings

hardware – reduced load

bandwidth – reduced response size

http://billwscott.com/share/presentations/2008/stanford/HPWP-RealWorld.pdf

Page 49: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

if you wantbetter user experiencemore revenuereduced operating expenses

the strategy is clear

Even Faster Web Sites

Page 50: Steve Souders souders@google.com  Even Faster Web Sites Disclaimer: This content does not necessarily reflect

Steve [email protected]

http://stevesouders.com/docs/google-20090305.ppt