Title: Steve Souders
1Even Faster Web Sites
- Steve Souders
- souders_at_google.com
- http//stevesouders.com/docs/sxsw-20090314.ppt
Disclaimer This content does not necessarily
reflect the opinions of my employer.
2the importance of frontend performance
9
91
17
83
iGoogle, primed cache
iGoogle, empty cache
3time spent on the frontend
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
April 2008
414 Rules
- Make fewer HTTP requests
- Use a CDN
- Add an Expires header
- Gzip components
- Put stylesheets at the top
- Put scripts at the bottom
- Avoid CSS expressions
- Make JS and CSS external
- Reduce DNS lookups
- Minify JS
- Avoid redirects
- Remove duplicate scripts
- Configure ETags
- Make AJAX cacheable
5(No Transcript)
6(No Transcript)
7(No Transcript)
825 discount code "ssouders25"
9Sept 2007
10June 2009
11Even 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)
12why focus on JavaScript?
13scripts block
ltscript src"A.js"gt blocks parallel downloads and
rendering
http//stevesouders.com/cuzillion/?ex10008
14MSN.com parallel scripts
Scripts and other resources downloaded in
parallel! How? Secret sauce?! var p
g.getElementsByTagName("HEAD")0 var
cg.createElement("script") c.type"text/javascri
pt" c.onreadystatechangen c.onerrorc.onloadk
c.srce p.appendChild(c)
15asynchronous script loading
XHR Eval XHR Injection Script in Iframe Script
DOM Element Script Defer document.write Script Tag
16XHR Eval
var xhrObj getXHRObject() xhrObj.onreadystatech
ange function() if (
xhrObj.readyState ! 4 ) return
eval(xhrObj.responseText) xhrObj.open('GET',
'A.js', true) xhrObj.send('')
script must have same domain as main page must
refactor script
http//stevesouders.com/cuzillion/?ex10009
17XHR Injection
var xhrObj getXHRObject() xhrObj.onreadystatech
ange function() if (
xhrObj.readyState ! 4 ) return var
sedocument.createElement('script')
document.getElementsByTagName('head')
0.appendChild(se) se.text
xhrObj.responseText xhrObj.open('GET',
'A.js', true) xhrObj.send('')
script must have same domain as main page
http//stevesouders.com/cuzillion/?ex10015
18Script in Iframe
ltiframe src'A.html' width0 height0
frameborder0 idframe1gtlt/iframegt
iframe must have same domain as main page must
refactor script // access iframe from main
page window.frames0.createNewDiv() // access
main page from iframe parent.document.createElemen
t('div')
http//stevesouders.com/cuzillion/?ex10012
19Script DOM Element
var se document.createElement('script') se.src
'http//anydomain.com/A.js' document.getElement
sByTagName('head')0 .appendChild(se)
script and main page domains can differ no need
to refactor JavaScript
http//stevesouders.com/cuzillion/?ex10010
20Script Defer
ltscript defer src'A.js'gtlt/scriptgt
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/?ex10013
21document.write Script Tag
document.write("ltscr" "ipt
type'text/javascript' src'A.js'gt" "lt/scr"
"iptgt")
parallelization only works in IE parallel
downloads for scripts, nothing else all
document.writes must be in same script block
http//stevesouders.com/cuzillion/?ex10014
22browser busy indicators
23browser busy indicators
status bar progress bar logo cursor block render block onload
normal Script Src FF IE,FF IE,FF FF IE,FF IE,FF
XHR Eval no no no no no no
XHR Injection no no no no no no
Script in Iframe IE,FF FF IE,FF FF no IE,FF
Script DOM Element FF FF FF FF no FF
Script Defer FF FF FF FF FF IE,FF
document.write Script Tag FF IE,FF IE,FF FF IE,FF IE,FF
good to show busy indicators when the user needs
feedback bad when downloading in the background
24ensure/avoid ordered execution
Ensure scripts execute in order necessary when
scripts have dependencies IE http//stevesouders.
com/cuzillion/?ex10017 FF http//stevesouders.co
m/cuzillion/?ex10018 Avoid scripts executing in
order faster first script back is executed
immediately http//stevesouders.com/cuzillion/?ex
10019
25load scripts without blocking
down-loads domains can differ existing scripts browser busy ensures order size (bytes)
normal Script Src no yes yes IE,FF IE,FF 50
XHR Eval IE,FF no no no no 500
XHR Injection IE,FF no yes no no 500
Script in Iframe IE,FF no no IE,FF no 50
Script DOM Element IE,FF yes yes FF FF 200
Script Defer IE yes yes IE,FF IE 50
document.write Script Tag IE yes yes IE,FF IE 100
Only other document.write scripts are downloaded
in parallel (in the same script block).
26and the winner is...
27load 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(No Transcript)
29synchronous JS example menu.js
- ltscript src"menu.js" type"text/javascript"gtlt/scr
iptgt - ltscript type"text/javascript"gt
- 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()
- lt/scriptgt
30asynchronous JS example menu.js
script DOM element approach
- ltscript type"text/javascript"gt
- var domscript document.createElement('script')
- domscript.src "menu.js"
- document.getElementsByTagName('head')0.appendChi
ld(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()
- lt/scriptgt
31before
after
32load scripts without blocking
down-loads domains can differ existing scripts browser busy ensures order size (bytes)
normal Script Src no yes yes IE,FF IE,FF 50
XHR Eval IE,FF no no no no 500
XHR Injection IE,FF no yes no no 500
Script in Iframe IE,FF no no IE,FF no 50
Script DOM Element IE,FF yes yes FF FF 200
Script Defer IE yes yes IE,FF IE 50
document.write Script Tag IE yes yes IE,FF IE 100
!IE
Only other document.write scripts are downloaded
in parallel (in the same script block).
33asynchronous scripts wrap-up
Technique Preserve Order Load Scripts in Parallel Load Script Image in Parallel
single script Script DOM Element na na all
multiple scripts, no dependencies Script DOM Element na all all
- what about
- inlined code
- that depends on the script?
34- what about
- inlined code
- that depends on the script?
35baseline coupling results (not good)
Preserve Execution Order Load Script Image in Parallel
normal Script Src all IE8, Saf4, Chr2
XHR Eval - all
XHR Injection - all
Script in Iframe - all
Script DOM Element FF, Op IE, FF, Saf, Chr
Script Defer FF, Saf, Chr, Op IE, (Saf4, Chr2)
document.write Script Tag all Saf4, Chr2
need a way to load scripts asynchronously AND
preserve order
Scripts download in parallel regardless of the
Defer attribute.
36coupling techniques
- hardcoded callback
- window onload
- timer
- degrading script tags
- script onload
37technique 1 hardcoded callback
- ltscript type"text/javascript"gt
- 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.appendChi
ld(domscript) - lt/scriptgt
- init() is called from within menu.js
- not very flexible
- doesn't work for 3rd party scripts
38technique 2 window onload
- ltiframe src"menu.php" width0 height0
frameborder0gt lt/iframegt - ltscript type"text/javascript"gt
- 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)
-
- lt/scriptgt
- 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
39technique 3 timer
- ltscript type"text/javascript"gt
- var domscript document.createElement('script')
- domscript.src "menu.js"
- document.getElementsByTagName('head')0.appendChi
ld(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)
- lt/scriptgt
- load if interval too low, delay if too high
40John Resig's degrading script tags
- ltscript src"menu-degrading.js"
type"text/javascript"gt - var aExamples 'couple-normal.php', 'Normal
Script Src', ... - function init()
- EFWS.Menu.createMenu('examplesbtn', aExamples)
-
- init()
- lt/scriptgt
at the end of menu-degrading.js var scripts
document.getElementsByTagName("script") var cntr
scripts.length while ( cntr ) var
curScript scriptscntr-1 if
(curScript.src.indexOf("menu-degrading.js")
! -1) eval( curScript.innerHTML )
break cntr--
cleaner clearer safer inlined code not called
if script fails no browser supports it
http//ejohn.org/blog/degrading-script-tags/
41technique 4 degrading script tags
- ltscript type"text/javascript"gt
- 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.appendChi
ld(domscript) - lt/scriptgt
- elegant, flexible (cool!)
- not well known
- doesn't work for 3rd party scripts (unless...)
42technique 5 script onload
- ltscript type"text/javascript"gt
- 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()
- 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.appendChi
ld(domscript)
43- 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
44multiple script example menutier.js
- ltscript src"menu.js" type"text/javascript"gtlt/scr
iptgt - ltscript src"menutier.js" type"text/javascript"gtlt
/scriptgt - ltscript type"text/javascript"gt
- 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) -
- lt/scriptgt
45technique 1 managed XHR
- ltscript type"text/javascript"gt
- 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) - lt/scriptgt
- XHR Injection asynchronous technique does not
preserve order we have to add that
before
after
46EFWS.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.queuedScriptsiQ qScript
-
- var xhrObj EFWS.Script.getXHRObject()
- xhrObj.onreadystatechange function()
- if ( xhrObj.readyState 4 )
- if ( bOrder )
- EFWS.Script.queuedScriptsiQ.response
xhrObj.responseText - EFWS.Script.injectScripts()
-
- else
- eval(xhrObj.responseText)
- if ( onload )
- onload()
47EFWS.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 lt len i )
- var qScript EFWS.Script.queuedScriptsi
- 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
-
-
-
preserves external script order
non-blocking
works in all browsers
couples with inlined code
works with scripts across domains
works with scripts across domains
48technique 2 DOM Element and Doc Write
Preserve Execution Order Load Scripts in Parallel Load Script Image in Parallel
Script DOM Element FF, Op FF, Op, IE, Saf, Chr FF, IE, Saf, Chr
Script Defer IE, Saf, Chr, FF, Op IE IE
document.write Script Tag IE, Saf, Chr, FF, Op IE, Op
Firefox Opera use Script DOM Element IE use
document.write Script Tag Safari, Chrome no
benefit rely on Safari 4 and Chrome 2
49EFWS.loadScripts
- loadScripts 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 lt nUrls i )
- if ( EFWS.Script.differentDomain(aUrlsi) )
- bDifferent true
- break
-
-
- // pick the best loading function
- var loadFunc EFWS.Script.loadScriptXhrInjectio
n - if ( bDifferent )
- if ( -1 ! navigator.userAgent.indexOf('Firefo
x') - -1 ! navigator.userAgent.indexOf('Opera'
) ) - loadFunc EFWS.Script.loadScriptDomElement
-
- else
- loadFunc EFWS.Script.loadScriptDocWrite
50multiple scripts with dependencies
- ltscript type"text/javascript"gt
- 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.loadScripts("menu.js",
"menutier.js", init) - lt/scriptgt
- scripts on same domain
- order preserved, no blocking
- scripts on different domain
- order preserved all
- loads scripts in parallel all except Saf3, Chr1
- load script and image in parallel FF, Saf4, Chr2
51asynchronous scripts wrap-up
Technique Preserve Order Load Scripts in Parallel Load Script Image in Parallel
single script Script DOM Element na na all
multiple scripts, no dependencies Script DOM Element na all all
multiple scripts, dependencies, same domain Managed XHR all all all
multiple scripts, dependencies, same domain Script DOM Element (FF, Op), Doc Write (IE, Saf, Chr) all !Saf3, !Chr1 FF, Saf4, Chr2
52case study Google Analytics
- recommended pattern1
- ltscript type"text/javascript"gt
- var gaJsHost (("https" document.location.pro
tocol) ? "https//ssl." "http//www.") - document.write(unescape("3Cscript src'"
gaJsHost "google-analytics.com/ga.js'
type'text/javascript'3E3C/script3E")) - lt/scriptgt
- ltscript type"text/javascript"gt
- var pageTracker _gat._getTracker("UA-xxxxxx-x")
- pageTracker._trackPageview()
- lt/scriptgt
- document.write Script Tag approach blocks other
resources
1http//www.google.com/support/analytics/bin/answe
r.py?hlenanswer55488
53case study dojox.analytics.Urchin1
- _loadGA function()
- var gaHost ("https" document.location.prot
ocol) ? - "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 approach
- "timer" coupling technique (script onload better)
1http//docs.dojocampus.org/dojox/analytics/Urchin
54asynchronous loading coupling
- async technique Script DOM Element
- easy, cross-browser
- doesn't ensure script order
- coupling technique script onload
- fairly easy, cross-browser
- ensures execution order for external script and
inlined code
55bad 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/?ex10021
- best to move inline scripts above stylesheets or
below other resources - use Link, not _at_import
56don't scatter inline scripts
57iframes 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
58iframes 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 - ltiframe idiframe1 src""gtlt/iframegt
- ltscript type"text/javascript"gt
- document.getElementById('iframe1').src"url"
- lt/scriptgt
59scripts block iframe
IE
script
Firefox
script
script
Safari Chrome Opera
- no surprise scripts in the parent block the
iframe from loading
60stylesheets block iframe (IE, FF)
IE
stylesheet
Firefox
stylesheet
stylesheet
Safari Chrome Opera
- surprise stylesheets in the parent block the
iframe or its resources in IE Firefox
61stylesheets after iframe still block (FF)
IE
stylesheet
Firefox
stylesheet
Safari Chrome Opera
stylesheet
- surprise even moving the stylesheet after the
iframe still causes the iframe's resources to be
blocked in Firefox
62iframes no free connections
parent
iframe
- iframe shares connection pool with parent (here
2 connections per server in IE 7)
63flush the document early
call PHP's flush()
- 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)
- other languages
- or FileHandle autoflush (Perl), flush
(Python), ios.flush (Ruby)
64flushing and domain blocking
- you might need to move flushed resources to a
domain different from the HTML doc
blocked by HTML document
different domains
case study Google search
65takeaways
- focus on the frontend
- run YSlow http//developer.yahoo.com/yslow
- this year's focus JavaScript
- speed matters
66impact on revenue
500 ms ? -20 traffic1 400 ms ? -5-9 full-page
traffic2 100 ms ? -1 sales1
1 http//home.blarg.net/glinden/StanfordDataMinin
g.2006-11-29.ppt 2 http//www.slideshare.net/stoya
n/yslow-20-presentation
67cost savings
- hardware reduced load
- bandwidth reduced response size
http//billwscott.com/share/presentations/2008/sta
nford/HPWP-RealWorld.pdf
68- if you want
- better user experience
- more revenue
- reduced operating expenses
- the strategy is clear
- Even Faster Web Sites
69Steve Souders souders_at_google.com http//stevesoude
rs.com/docs/sxsw-20090314.ppt