- 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.
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
- PUT STYLESHEETS AT THE TOP
- PUT SCRIPTS AT THE BOTTOM
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>");
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
- Ensure scripts execute in order:
-
- necessary when scripts have dependencies
-
- IE:http://stevesouders.com/cuzillion/?ex=10017
-
- FF:http://stevesouders.com/cuzillion/?ex=10018
- Avoid scripts executing in order:
-
- faster first script back is executed immediately
-
- http://stevesouders.com/cuzillion/?ex=10019
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
- don't let scripts block other downloads
- you can still control execution order, busy indicators, and
onload event
- What about inline scripts?
28. 29. synchronous JS example: menu.js
- ['couple-normal.php', 'Normal Script Src'],
- ['couple-xhr-eval.php', 'XHR Eval'],
- ['managed-xhr.php', 'Managed XHR']
- EFWS.Menu.createMenu('examplesbtn', aExamples);
30. asynchronous JS example: menu.js
- var domscript = document.createElement('script');
- domscript.src = "menu.js";
-
document.getElementsByTagName('head')[0].appendChild(domscript);
- ['couple-normal.php', 'Normal Script Src'],
- ['couple-xhr-eval.php', 'XHR Eval'],
- ['managed-xhr.php', 'Managed XHR']
- EFWS.Menu.createMenu('examplesbtn', aExamples);
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
- that depends on the script?
34.
- that depends on the script?
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
37. technique 1: hardcoded callback
- var aExamples = [['couple-normal.php', 'Normal Script Src'],
...];
- EFWS.Menu.createMenu('examplesbtn', aExamples);
- var domscript = document.createElement('script');
- domscript.src = "menu.js";
-
document.getElementsByTagName('head')[0].appendChild(domscript);
- init() is called from within menu.js
- doesn't work for 3 rdparty scripts
38. technique 2: window onload
- var aExamples = [['couple-normal.php', 'Normal Script Src'],
...];
- EFWS.Menu.createMenu('examplesbtn', aExamples);
- if ( window.addEventListener ) {
- window.addEventListener("load", init, false);
- else if ( window.attachEvent ) {
- window.attachEvent("onload", init);
- init() is called at window onload
- must use async technique that blocks onload:
-
- Script in Iframe does this across most browsers
- init() called later than necessary
39. technique 3: timer
- var domscript = document.createElement('script');
- domscript.src = "menu.js";
-
document.getElementsByTagName('head')[0].appendChild(domscript);
- var aExamples = [['couple-normal.php', 'Normal Script Src'],
...];
- EFWS.Menu.createMenu('examplesbtn', aExamples);
- function initTimer(interval) {
- if ( "undefined" === typeof(EFWS) ) {
- setTimeout(initTimer, interval);
- load ifintervaltoo low, delay if too high
- slight increased maintenance EFWS
40. John Resig's degrading script tags
- var aExamples = [['couple-normal.php', 'Normal Script Src'],
...];
- EFWS.Menu.createMenu('examplesbtn', aExamples);
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
- var aExamples = [['couple-normal.php', 'Normal Script
Src'],...];
- EFWS.Menu.createMenu('examplesbtn', aExamples);
- var domscript = document.createElement('script');
- domscript.src = "menu-degrading.js";
- if ( -1 != navigator.userAgent.indexOf("Opera") ) {
- domscript.innerHTML = "init();";
- domscript.text = "init();";
-
document.getElementsByTagName('head')[0].appendChild(domscript);
- elegant, flexible (cool!)
- doesn't work for 3 rdparty scripts (unless...)
42. technique 5: script onload
- var aExamples = [['couple-normal.php', 'Normal Script Src'],
...];
- EFWS.Menu.createMenu('examplesbtn', aExamples);
- var domscript = document.createElement('script');
- domscript.src = "menu.js";
- domscript.onloadDone = false;
- domscript.onload = function() {
- if ( ! domscript.onloadDone ) { init(); }
- domscript.onloadDone = true;
- domscript.onreadystatechange = function() {
- if ( "loaded" === domscript.readyState ) {
- if ( ! domscript.onloadDone ) { init(); }
- domscript.onloadDone = true;
-
document.getElementsByTagName('head')[0].appendChild(domscript);
- pretty nice, medium complexity
43.
- that depend on each other,
- that depends on the scripts?
-
- DOM Element and Doc Write
44. multiple script example: menutier.js
- var aRaceConditions = [['couple-normal.php', 'Normal...];
- var aWorkarounds = [['hardcoded-callback.php',
'Hardcod...];
- var aMultipleScripts = [['managed-xhr.php', 'Managed
XH...];
- var aLoadScripts = [['loadscript.php', 'loadScript'],
...];
- [["Race Conditions", aRaceConditions],
- ["Workarounds", aWorkarounds],
- ["Multiple Scripts", aMultipleScripts],
- ["General Solution", aLoadScripts]];
- EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus);
45. technique 1: managed XHR
- 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],
...];
- EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus);
- EFWS.Script.loadScriptXhrInjection("menu.js", null, true);
- EFWS.Script.loadScriptXhrInjection("menutier.js", init,
true);
- XHR Injection asynchronous technique does not preserve order we
have to add that
before after 46. EFWS.loadScriptXhrInjection
- // Load an external script.
- // Optionally call a callback and preserve order.
- loadScriptXhrInjection: function(url, onload, bOrder) {
- var iQ = EFWS.Script.queuedScripts.length;
- 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 ) {
- EFWS.Script.queuedScripts[iQ].response =
xhrObj.responseText;
- EFWS.Script.injectScripts();
- eval(xhrObj.responseText);
- xhrObj.open('GET', url, true);
process queue (next slide) or... eval now, call callback save
response to queue add to queue (if bOrder) 47.
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.response ) {
- // STOP! need to wait for this response
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 & Opera use Script DOM Element IE
usedocument.writeScript Tag Safari, Chrome no benefit; rely on
Safari 4 and Chrome 2 49. EFWS.loadScripts
- loadScripts: function(aUrls, onload) {
- // first pass: see if any of the scripts are on a different
domain
- var nUrls = aUrls.length;
- for ( var i = 0; i < nUrls; i++ ) {
- if ( EFWS.Script.differentDomain(aUrls[i]) ) {
- // pick the best loading function
- var loadFunc = EFWS.Script.loadScriptXhrInjection;
- if ( -1 != navigator.userAgent.indexOf('Firefox') ||
- -1 != navigator.userAgent.indexOf('Opera') ) {
- loadFunc = EFWS.Script.loadScriptDomElement;
- 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);
50. multiple scripts with dependencies
- 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],
...];
- EFWS.Menu.createTieredMenu('examplesbtn', aSubmenus);
- EFWS.Script.loadScripts(["menu.js", "menutier.js"], init);
-
- order preserved, no blocking
- scripts on different domain:
-
- loads scripts in parallel: all except Saf3, Chr1
-
- load script and image in parallel: FF, Saf4, Chr2
51. asynchronous scripts wrap-up 52. case study: Google
Analytics
- 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"));
- var pageTracker = _gat._getTracker("UA-xxxxxx-x");
- pageTracker._trackPageview();
- document.writeScript Tag approach blocks other resources
1
http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55488
53. case study: dojox.analytics.Urchin 1
- var gaHost = ("https:" == document.location.protocol) ?
- "https://ssl." : "http://www.";
- src: gaHost + "google-analytics.com/ga.js"
- }, dojo.doc.getElementsByTagName("head")[0]);
- setTimeout(dojo.hitch(this, "_checkGA"),
this.loadInterval);
- setTimeout(dojo.hitch(this, !window["_gat"] ? "_checkGA" :
"_gotGA"), this.loadInterval);
- this.tracker = _gat._getTracker(this.acct); ...
- Script DOM Element approach
- "timer" coupling technique(script onload better)
1 http://docs.dojocampus.org/dojox/analytics/Urchin 54.
asynchronous loading & coupling
- async technique: Script DOM Element
-
- doesn't ensure script order
- coupling technique: script onload
-
- fairly easy, cross-browser
-
- ensures execution order for external script and inlined
code
55. bad: stylesheet followed by inline script
- browsers download stylesheets in parallel with other resources
that follow...
- ...unless the stylesheet is followed by an inline script
- http://stevesouders.com/cuzillion/?ex=10021
- best to move inline scripts above stylesheets or below other
resources
56. don't scatter inline scripts eBay MSN MySpace Wikipedia 57.
iframes:most expensive DOM element
- load 100emptyelements of each type
- tested in all major browsers 1
1 IE 6, 7, 8; FF 2, 3.0, 3.1b2; Safari 3.2, 4; Opera 9.63, 10;
Chrome 1.0, 2.0 58. 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
- document.getElementById('iframe1').src=" url ";
59. scripts block iframe
- no surprise scripts in the parent block the iframe from
loading
IE Firefox Safari Chrome Opera script script script 60.
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
61. stylesheets after iframe still block (FF)
- surprise even moving the stylesheetafterthe iframe still causes
the iframe's resources to be blocked in Firefox
IE Firefox Safari Chrome Opera stylesheet stylesheet stylesheet
62. iframes: no free connections
- iframe shares connection pool with parent (here 2 connections
per server in IE 7)
iframe parent 63. flush the document early
-
- 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)
-
- $|orFileHandle
autoflush(Perl),flush(Python),ios.flush(Ruby)
call PHP'sflush() html image image script html image image
script 64. flushing and domain blocking
- you might need to move flushed resources to a domain different
from the HTML doc
case study: Google search blocked by HTML document different
domains html image image script html image image script google
image image script image 204 65. takeaways
- run YSlow:http://developer.yahoo.com/yslow
- this year's focus: JavaScript
66. impact on revenue
1http://home.blarg.net/~glinden/StanfordDataMining.2006-11-29.ppt
2http://www.slideshare.net/stoyan/yslow-20-presentation +500 ms-20%
traffic 1 +400 ms-5-9% full-page traffic 2 +100 ms-1% sales 1 67.
cost savings
- bandwidth reduced response size
http://billwscott.com/share/presentations/2008/stanford/HPWP-RealWorld.pdf
68.
-
- reduced operating expenses
69. Steve Souders [email_address]
http://stevesouders.com/docs/sxsw-20090314.ppt