Sxsw 20090314

  • Published on
    07-May-2015

  • View
    1.604

  • Download
    1

Embed Size (px)

Transcript

<ul><li>1.Steve Souders [email_address] http://stevesouders.com/docs/sxsw-20090314.ppt Even Faster Web Sites Disclaimer:This content does not necessarily reflect the opinions of my employer.</li></ul> <p>2. the importance of frontend performance 17% 83% iGoogle, primed cache 9% 91% iGoogle, empty cache 3. time spent on the frontend April 2008 Empty Cache Primed Cache www.aol.com 97% 97% www.ebay.com 95% 81% www.facebook.com 95% 81% www.google.com/search 47% 0% search.live.com/results 67% 0% www.msn.com 98% 94% www.myspace.com 98% 98% en.wikipedia.org/wiki 94% 91% www.yahoo.com 97% 96% www.youtube.com 98% 97% 4. 14 RULES </p> <ul><li>MAKE FEWER HTTP REQUESTS </li></ul> <ul><li>USE A CDN </li></ul> <ul><li>ADD AN EXPIRES HEADER </li></ul> <ul><li>GZIP COMPONENTS </li></ul> <ul><li>PUT STYLESHEETS AT THE TOP </li></ul> <ul><li>PUT SCRIPTS AT THE BOTTOM </li></ul> <ul><li>AVOID CSS EXPRESSIONS </li></ul> <ul><li>MAKE JS AND CSS EXTERNAL </li></ul> <ul><li>REDUCE DNS LOOKUPS </li></ul> <ul><li>MINIFY JS </li></ul> <ul><li>AVOID REDIRECTS </li></ul> <ul><li>REMOVE DUPLICATE SCRIPTS </li></ul> <ul><li>CONFIGURE ETAGS </li></ul> <ul><li>MAKE AJAX CACHEABLE </li></ul> <p>5. 6. 7. 8. 25% discount code: "ssouders25" 9. Sept 2007 10. June 2009 11. Even Faster Websites Split the initial payload Load scripts without blocking Couple asynchronous scripts Don't scatter inline scripts Split the dominant domain Flush the document early Use iframes sparingly Simplify CSS Selectors 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) 12. why focus on JavaScript? AOL eBay Facebook MySpace Wikipedia Yahoo! YouTube 13. scripts block only supported in IE (just landed in FF 3.1) script and main page domains can differ no need to refactor JavaScript http://stevesouders.com/cuzillion/?ex=10013 21. document.writeScript Tag document.write ("" + "" + "ipt&gt;"); parallelization only works in IE parallel downloads for scripts, nothing else alldocument.write s must be in same script block http://stevesouders.com/cuzillion/?ex=10014 22. browser busy indicators 23. browser busy indicators goodto show busy indicators when the user needs feedback badwhen downloading in the background 24. ensure/avoid ordered execution </p> <ul><li>Ensure scripts execute in order: </li></ul> <ul><li><ul><li>necessary when scripts have dependencies </li></ul></li></ul> <ul><li><ul><li>IE:http://stevesouders.com/cuzillion/?ex=10017 </li></ul></li></ul> <ul><li><ul><li>FF:http://stevesouders.com/cuzillion/?ex=10018 </li></ul></li></ul> <ul><li>Avoid scripts executing in order: </li></ul> <ul><li><ul><li>faster first script back is executed immediately </li></ul></li></ul> <ul><li><ul><li>http://stevesouders.com/cuzillion/?ex=10019 </li></ul></li></ul> <p>25. load scripts without blocking * Only other document.write scripts are downloaded in parallel (in the same script block). 26. and the winner is... XHR Eval XHR Injection Script in iframe Script DOM Element Script Defer Script DOM Element Script Defer Script DOM Element Script DOM Element (FF) Script Defer (IE) XHR Eval XHR Injection Script in iframe Script DOM Element (IE) XHR Injection XHR Eval Script DOM Element (IE) Managed XHR Injection Managed XHR Eval Script DOM Element Managed XHR Injection Managed XHR Eval Script DOM Element (FF) Script Defer (IE) Managed XHR Eval Managed XHR Injection Script DOM Element (FF) Script Defer (IE) Managed XHR Eval Managed XHR Injection different domains same domains no order preserve order no order no busy show busy show busy no busy preserve order 27. load scripts without blocking </p> <ul><li>don't let scripts block other downloads </li></ul> <ul><li>you can still control execution order, busy indicators, and onload event </li></ul> <ul><li>What about inline scripts? </li></ul> <p>28. 29. synchronous JS example: menu.js </p> <ul><li>var aExamples =</li></ul> <ul><li>[ </li></ul> <ul><li>['couple-normal.php', 'Normal Script Src'], </li></ul> <ul><li>['couple-xhr-eval.php', 'XHR Eval'], </li></ul> <ul><li>... </li></ul> <ul><li>['managed-xhr.php', 'Managed XHR'] </li></ul> <ul><li>]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>init(); </li></ul> <p>30. asynchronous JS example: menu.js </p> <ul><li>var domscript = document.createElement('script'); </li></ul> <ul><li>domscript.src = "menu.js";</li></ul> <ul><li>document.getElementsByTagName('head')[0].appendChild(domscript); </li></ul> <ul><li>var aExamples =</li></ul> <ul><li>[ </li></ul> <ul><li>['couple-normal.php', 'Normal Script Src'], </li></ul> <ul><li>['couple-xhr-eval.php', 'XHR Eval'], </li></ul> <ul><li>... </li></ul> <ul><li>['managed-xhr.php', 'Managed XHR'] </li></ul> <ul><li>]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>init(); </li></ul> <p>script DOM element approach 31. before after 32. load scripts without blocking * Only other document.write scripts are downloaded in parallel (in the same script block). !IE 33. asynchronous scripts wrap-up </p> <ul><li>what about </li></ul> <ul><li>inlined code</li></ul> <ul><li>that depends on the script? </li></ul> <p>34. </p> <ul><li>what about </li></ul> <ul><li>inlined code</li></ul> <ul><li>that depends on the script? </li></ul> <p>35. baseline coupling results (not good) *Scripts download in parallel regardless of the Defer attribute. need a way to load scripts asynchronously AND preserve order 36. coupling techniques </p> <ul><li>hardcoded callback </li></ul> <ul><li>window onload </li></ul> <ul><li>timer </li></ul> <ul><li>degrading script tags </li></ul> <ul><li>script onload </li></ul> <p>37. technique 1: hardcoded callback </p> <ul><li>var aExamples = [['couple-normal.php', 'Normal Script Src'], ...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>var domscript = document.createElement('script'); </li></ul> <ul><li>domscript.src = "menu.js"; </li></ul> <ul><li>document.getElementsByTagName('head')[0].appendChild(domscript); </li></ul> <ul><li>init() is called from within menu.js </li></ul> <ul><li>not very flexible </li></ul> <ul><li>doesn't work for 3 rdparty scripts </li></ul> <p>38. technique 2: window onload </p> <ul><li>var aExamples = [['couple-normal.php', 'Normal Script Src'], ...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>if ( window.addEventListener ) { </li></ul> <ul><li>window.addEventListener("load", init, false); </li></ul> <ul><li>} </li></ul> <ul><li>else if ( window.attachEvent ) { </li></ul> <ul><li>window.attachEvent("onload", init); </li></ul> <ul><li>} </li></ul> <ul><li>init() is called at window onload </li></ul> <ul><li>must use async technique that blocks onload: </li></ul> <ul><li><ul><li>Script in Iframe does this across most browsers </li></ul></li></ul> <ul><li>init() called later than necessary </li></ul> <p>39. technique 3: timer </p> <ul><li>var domscript = document.createElement('script'); </li></ul> <ul><li>domscript.src = "menu.js"; </li></ul> <ul><li>document.getElementsByTagName('head')[0].appendChild(domscript); </li></ul> <ul><li>var aExamples = [['couple-normal.php', 'Normal Script Src'], ...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>function initTimer(interval) { </li></ul> <ul><li>if ( "undefined" === typeof(EFWS) ) { </li></ul> <ul><li>setTimeout(initTimer, interval); </li></ul> <ul><li>} </li></ul> <ul><li>else { </li></ul> <ul><li>init(); </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>initTimer(300); </li></ul> <ul><li>load ifintervaltoo low, delay if too high </li></ul> <ul><li>slight increased maintenance EFWS </li></ul> <p>40. John Resig's degrading script tags </p> <ul><li>var aExamples = [['couple-normal.php', 'Normal Script Src'], ...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>init(); </li></ul> <p>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/ cleaner clearer safer inlined code not called if script fails no browser supports it 41. technique 4: degrading script tags </p> <ul><li>var aExamples = [['couple-normal.php', 'Normal Script Src'],...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>var domscript = document.createElement('script'); </li></ul> <ul><li>domscript.src = "menu-degrading.js"; </li></ul> <ul><li>if ( -1 != navigator.userAgent.indexOf("Opera") ) { </li></ul> <ul><li>domscript.innerHTML = "init();"; </li></ul> <ul><li>} </li></ul> <ul><li>else { </li></ul> <ul><li>domscript.text = "init();"; </li></ul> <ul><li>} </li></ul> <ul><li>document.getElementsByTagName('head')[0].appendChild(domscript); </li></ul> <ul><li>elegant, flexible (cool!) </li></ul> <ul><li>not well known </li></ul> <ul><li>doesn't work for 3 rdparty scripts (unless...) </li></ul> <p>42. technique 5: script onload </p> <ul><li>var aExamples = [['couple-normal.php', 'Normal Script Src'], ...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createMenu('examplesbtn', aExamples); </li></ul> <ul><li>} </li></ul> <ul><li>var domscript = document.createElement('script'); </li></ul> <ul><li>domscript.src = "menu.js"; </li></ul> <ul><li>domscript.onloadDone = false; </li></ul> <ul><li>domscript.onload = function() {</li></ul> <ul><li>if ( ! domscript.onloadDone ) { init(); } </li></ul> <ul><li>domscript.onloadDone = true;</li></ul> <ul><li>}; </li></ul> <ul><li>domscript.onreadystatechange = function() {</li></ul> <ul><li>if ( "loaded" === domscript.readyState ) {</li></ul> <ul><li>if ( ! domscript.onloadDone ) { init(); } </li></ul> <ul><li>domscript.onloadDone = true;</li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>document.getElementsByTagName('head')[0].appendChild(domscript); </li></ul> <ul><li>pretty nice, medium complexity </li></ul> <p>43. </p> <ul><li>what about </li></ul> <ul><li>multiple scripts</li></ul> <ul><li>that depend on each other,</li></ul> <ul><li>and inlined code</li></ul> <ul><li>that depends on the scripts? </li></ul> <ul><li>two solutions: </li></ul> <ul><li><ul><li>Managed XHR </li></ul></li></ul> <ul><li><ul><li>DOM Element and Doc Write </li></ul></li></ul> <p>44. multiple script example: menutier.js </p> <ul><li>var aRaceConditions = [['couple-normal.php', 'Normal...]; </li></ul> <ul><li>var aWorkarounds = [['hardcoded-callback.php', 'Hardcod...]; </li></ul> <ul><li>var aMultipleScripts = [['managed-xhr.php', 'Managed XH...]; </li></ul> <ul><li>var aLoadScripts = [['loadscript.php', 'loadScript'], ...]; </li></ul> <ul><li>var aSubmenus =</li></ul> <ul><li>[["Race Conditions", aRaceConditions],</li></ul> <ul><li>["Workarounds", aWorkarounds], </li></ul> <ul><li>["Multiple Scripts", aMultipleScripts], </li></ul> <ul><li>["General Solution", aLoadScripts]]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus); </li></ul> <ul><li>} </li></ul> <p>45. technique 1: managed XHR </p> <ul><li>var aRaceConditions = [['couple-normal.php', 'Normal...]; </li></ul> <ul><li>var aWorkarounds = [['hardcoded-callback.php', 'Hardcod...]; </li></ul> <ul><li>var aMultipleScripts = [['managed-xhr.php', 'Managed XH...]; </li></ul> <ul><li>var aLoadScripts = [['loadscript.php', 'loadScript'], ...]; </li></ul> <ul><li>var aSubmenus = [["Race Conditions", aRaceConditions], ...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus); </li></ul> <ul><li>} </li></ul> <ul><li>EFWS.Script.loadScriptXhrInjection("menu.js", null, true); </li></ul> <ul><li>EFWS.Script.loadScriptXhrInjection("menutier.js", init, true); </li></ul> <ul><li>XHR Injection asynchronous technique does not preserve order we have to add that</li></ul> <p>before after 46. EFWS.loadScriptXhrInjection </p> <ul><li>// Load an external script.</li></ul> <ul><li>// Optionally call a callback and preserve order. </li></ul> <ul><li>loadScriptXhrInjection: function(url, onload, bOrder) { </li></ul> <ul><li>var iQ = EFWS.Script.queuedScripts.length; </li></ul> <ul><li>if ( bOrder ) { </li></ul> <ul><li>var qScript = { response: null, onload: onload, done: false }; </li></ul> <ul><li>EFWS.Script.queuedScripts[iQ] = qScript; </li></ul> <ul><li>} </li></ul> <ul><li>var xhrObj = EFWS.Script.getXHRObject(); </li></ul> <ul><li>xhrObj.onreadystatechange = function() { </li></ul> <ul><li>if ( xhrObj.readyState == 4 ) { </li></ul> <ul><li>if ( bOrder ) { </li></ul> <ul><li>EFWS.Script.queuedScripts[iQ].response = xhrObj.responseText; </li></ul> <ul><li>EFWS.Script.injectScripts(); </li></ul> <ul><li>} </li></ul> <ul><li>else { </li></ul> <ul><li>eval(xhrObj.responseText); </li></ul> <ul><li>if ( onload ) { </li></ul> <ul><li>onload(); </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>}; </li></ul> <ul><li>xhrObj.open('GET', url, true); </li></ul> <ul><li>xhrObj.send(''); </li></ul> <ul><li>} </li></ul> <p>process queue (next slide) or... eval now, call callback save response to queue add to queue (if bOrder) 47. EFWS.injectScripts </p> <ul><li>// Process queued scripts to see if any are ready to inject. </li></ul> <ul><li>injectScripts: function() { </li></ul> <ul><li>var len = EFWS.Script.queuedScripts.length; </li></ul> <ul><li>for ( var i = 0; i &lt; len; i++ ) { </li></ul> <ul><li>var qScript = EFWS.Script.queuedScripts[i]; </li></ul> <ul><li>if ( ! qScript.done ) { </li></ul> <ul><li>if ( ! qScript.response ) { </li></ul> <ul><li>// STOP! need to wait for this response </li></ul> <ul><li>break; </li></ul> <ul><li>} </li></ul> <ul><li>else { </li></ul> <ul><li>eval(qScript.response); </li></ul> <ul><li>if ( qScript.onload ) { </li></ul> <ul><li>qScript.onload(); </li></ul> <ul><li>} </li></ul> <ul><li>qScript.done = true; </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <p>preserves external script order non-blocking works in all browsers couples with inlined code works with scripts across domains ready for this script, eval and call callback bail need to wait to preserve order if not yet injected 48. technique 2: DOM Element and Doc Write Firefox &amp; Opera use Script DOM Element IE usedocument.writeScript Tag Safari, Chrome no benefit; rely on Safari 4 and Chrome 2 49. EFWS.loadScripts </p> <ul><li>loadScripts: function(aUrls, onload) { </li></ul> <ul><li>// first pass: see if any of the scripts are on a different domain </li></ul> <ul><li>var nUrls = aUrls.length; </li></ul> <ul><li>var bDifferent = false; </li></ul> <ul><li>for ( var i = 0; i &lt; nUrls; i++ ) { </li></ul> <ul><li>if ( EFWS.Script.differentDomain(aUrls[i]) ) { </li></ul> <ul><li>bDifferent = true; </li></ul> <ul><li>break; </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>// pick the best loading function </li></ul> <ul><li>var loadFunc = EFWS.Script.loadScriptXhrInjection; </li></ul> <ul><li>if ( bDifferent ) { </li></ul> <ul><li>if ( -1 != navigator.userAgent.indexOf('Firefox') || </li></ul> <ul><li>-1 != navigator.userAgent.indexOf('Opera') ) { </li></ul> <ul><li>loadFunc = EFWS.Script.loadScriptDomElement; </li></ul> <ul><li>} </li></ul> <ul><li>else { </li></ul> <ul><li>loadFunc = EFWS.Script.loadScriptDocWrite; </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <ul><li>// second pass: load the scripts </li></ul> <ul><li>for ( var i = 0; i &lt; nUrls; i++ ) { </li></ul> <ul><li>loadFunc(aUrls[i], ( i+1 == nUrls ? onload : null ), true); </li></ul> <ul><li>} </li></ul> <ul><li>} </li></ul> <p>50. multiple scripts with dependencies </p> <ul><li>var aRaceConditions = [['couple-normal.php', 'Normal...]; </li></ul> <ul><li>var aWorkarounds = [['hardcoded-callback.php', 'Hardcod...]; </li></ul> <ul><li>var aMultipleScripts = [['managed-xhr.php', 'Managed XH...]; </li></ul> <ul><li>var aLoadScripts = [['loadscript.php', 'loadScript'], ...]; </li></ul> <ul><li>var aSubmenus = [["Race Conditions", aRaceConditions], ...]; </li></ul> <ul><li>function init() { </li></ul> <ul><li>EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus); </li></ul> <ul><li>} </li></ul> <ul><li>EFWS.Script.loadScripts(["menu.js", "menutier.js"], init); </li></ul> <ul><li>scripts on same domain: </li></ul> <ul><li><ul><li>order preserved, no blocking </li></ul></li></ul> <ul><li>scripts on different domain: </li></ul> <ul><li><ul><li>order preserved: all </li></ul></li></ul> <ul><li><ul><li>loads scripts in parallel: all except Saf3, Chr1 </li></ul></li></ul> <ul><li><ul><li>load script and image in parallel: FF, Saf4, Chr2 </li></ul></li></ul> <p>51. asynchronous scripts wrap-up 52. case study: Google Analytics </p> <ul><li>recommended pattern: 1 </li></ul> <ul><li>var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); </li></ul> <ul><li>document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); </li></ul> <ul><li>var pageTracker = _gat._getTracker("UA-xxxxxx-x"); </li></ul> <ul><li>pageTracker._trackPageview(); </li></ul> <ul><li>document.writeScript Tag approach blocks other resources </li></ul> <p>1 http://www.google.com/support/analytics/bin/answer.py?hl=en&amp;answer=55488 53. case study: dojox.analytics.Urchin 1 </p> <ul><li>_loadGA: function(){ </li></ul> <ul><li>var gaHost = ("https:" == document.location.protocol) ?</li></ul> <ul><li>"https://ssl." : "http://www."; </li></ul> <ul><li>dojo.create('script', { </li></ul> <ul><li>src: gaHost + "google-analytics.com/ga.js" </li></ul> <ul><li>}, dojo.doc.getElementsByTagName("head")[0]); </li></ul> <ul><li>setTimeout(dojo.hitch(this, "_checkGA"), this.loadInterval); </li></ul> <ul><li>}, </li></ul> <ul><li>_checkGA: function(){ </li></ul> <ul><li>setTimeout(dojo.hitch(this, !window["_gat"] ? "_checkGA" : "_gotGA"), this.loadInterval); </li></ul> <ul><li>}, </li></ul> <ul><li>_gotGA: function(){ </li></ul> <ul><li>this.tracker = _gat._getTracker(this.acct); ... </li></ul> <ul><li>} </li></ul> <ul><li>Script DOM Element approach </li></ul> <ul><li>"timer" coupling technique(script onload better) </li></ul> <p>1 http://docs.dojocampus.org/dojox/analytics/Urchin 54. asynchronous loading &amp; coupling </p> <ul><li>async technique: Script DOM Element </li></ul> <ul><li><ul><li>easy, cross-browser </li></ul></li></ul> <ul><li><ul><li>doesn't ensure script order </li></ul></li></ul> <ul><li>coupling technique: script onload </li></ul> <ul><li><ul><li>fairly easy, cross-browser </li></ul></li></ul> <ul><li><ul><li>ensures execution order for external script and inlined code </li></ul></li></ul> <p>55. bad: stylesheet followed by inline script </p> <ul><li>browsers download stylesheets in parallel with other resources that follow... </li></ul> <ul><li>...unless the stylesheet is followed by a