Even Faster Websites

  • Uploaded by: crystal
  • 0
  • 0
  • April 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Even Faster Websites as PDF for free.

More details

  • Words: 2,334
  • Pages: 69
Even Faster Web Sites

Steve Souders [email protected] http://stevesouders.com/docs/sxsw-20090314.ppt Disclaimer: This content does not necessarily reflect the opinions of my employer.

the importance of frontend performance 9%

17%

91%

83%

iGoogle, primed cache

iGoogle, empty cache

time spent on the frontend www.aol.com www.ebay.com www.facebook.com www.google.com/search search.live.com/results www.msn.com www.myspace.com en.wikipedia.org/wiki www.yahoo.com www.youtube.com

Empty Cache 97% 95% 95% 47% 67% 98% 98% 94% 97% 98%

Primed Cache 97% 81% 81% 0% 0% 94% 98% 91% 96% 97% April 2008

14 RULES

1. MAKE FEWER HTTP REQUESTS 2. USE A CDN 3. ADD AN EXPIRES HEADER 4. GZIP COMPONENTS 5. PUT STYLESHEETS AT THE TOP 6. PUT SCRIPTS AT THE BOTTOM 7. AVOID CSS EXPRESSIONS 8. MAKE JS AND CSS EXTERNAL 9. REDUCE DNS LOOKUPS 10.MINIFY JS 11.AVOID REDIRECTS 12.REMOVE DUPLICATE SCRIPTS 13.CONFIGURE ETAGS 14.MAKE AJAX CACHEABLE

25% discount code: "ssouders25"

Sept 2007

June 2009

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)

why focus on JavaScript? Yahoo! Wikipedia eBay AOL MySpace YouTube Facebook

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

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

MSN.com: parallel scripts MSN Scripts 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)

asynchronous script loading XHR Eval XHR Injection Script in Iframe Script DOM Element Script Defer document.write Script Tag

XHR Eval var xhrObj = getXHRObject(); xhrObj.onreadystatechange = 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/?ex=10009

XHR Injection var xhrObj = getXHRObject(); xhrObj.onreadystatechange = function() { if ( xhrObj.readyState != 4 ) return; var se=document.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/?ex=10015

Script in Iframe <iframe src='A.html' width=0 height=0 frameborder=0 id=frame1>

iframe must have same domain as main page must refactor script: // access iframe from main page window.frames[0].createNewDiv(); // access main page from iframe parent.document.createElement('div'); http://stevesouders.com/cuzillion/?ex=10012

Script DOM Element var se = document.createElement('script'); se.src = 'http://anydomain.com/A.js'; document.getElementsByTagName('head')[0] .appendChild(se);

script and main page domains can differ no need to refactor JavaScript

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

Script Defer <script defer src='A.js'>

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

document.write Script Tag document.write("<scr" + "ipt type='text/javascript' src='A.js'>" + "");

parallelization only works in IE parallel downloads for scripts, nothing else all document.writes must be in same script block

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

browser busy indicators

browser busy indicators

good to show busy indicators when the user needs feedback bad when downloading in the background

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

load scripts without blocking

*

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

and the winner is... XHR Eval XHR Injection Script in iframe Script DOM Element Script Defer same domains

different domains

Script DOM Element Script Defer

XHR Eval XHR Injection Script in iframe Script DOM Element (IE)

no order

Script DOM Element

preserve order

no order

preserve order

no busy

Script DOM Element (FF) Script Defer (IE) no busy

XHR Injection XHR Eval Script DOM Element (IE)

Script DOM Element (FF) Script Defer (IE) Managed XHR Eval Managed XHR Injection

Managed XHR Injection Managed XHR Eval show busy

Managed XHR Injection Managed XHR Eval Script DOM Element

show busy

Script DOM Element (FF) Script Defer (IE) Managed XHR Eval Managed XHR Injection

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?

synchronous JS example: menu.js <script src="menu.js" type="text/javascript"> <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();

asynchronous JS example: menu.js script DOM element approach

<script type="text/javascript"> var domscript = document.createElement('script'); domscript.src = "menu.js"; document.getElementsByTagName('head')[0].appendChild(domscri pt); 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();

before after

load scripts without blocking

!IE

*

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

asynchronous scripts wrap-up

what about

inlined code that depends on the script?

what about

inlined code that depends on the script?

baseline coupling results (not good)

need a way to load scripts asynchronously AND preserve order *

Scripts download in parallel regardless of the Defer attribute.

coupling techniques hardcoded callback window onload timer degrading script tags script onload

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(domscri pt);

init() is called from within menu.js not very flexible doesn't work for 3rd party scripts

technique 2: window onload <iframe src="menu.php" width=0 height=0 frameborder=0>

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

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

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

load if interval too low, delay if too high slight increased maintenance – EFWS

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

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

cleaner clearer safer – inlined code not called if script fails no browser supports it http://ejohn.org/blog/degrading-script-tags/

technique 4: 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);

elegant, flexible (cool!) not well known doesn't work for 3rd party scripts (unless...)

technique 5: 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() { 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

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 src="menutier.js" type="text/javascript"> <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); }

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); before after

XHR Injection asynchronous technique does not preserve order – we have to add that

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; } add to queue (if bOrder) var xhrObj = EFWS.Script.getXHRObject(); xhrObj.onreadystatechange = function() { if ( xhrObj.readyState == 4 ) { save response to queue if ( bOrder ) { EFWS.Script.queuedScripts[iQ].response = xhrObj.responseText; EFWS.Script.injectScripts(); } else { process queue (next slide) eval(xhrObj.responseText); if ( onload ) { onload(); } or... eval now, call callback } } }; xhrObj.open('GET', url, true); xhrObj.send(''); }

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 not yet injected if ( ! qScript.response ) { // STOP! need to wait for this response break; bail – need to wait to preserve order } else { eval(qScript.response); if ( qScript.onload ) { qScript.onload(); } ready for this script, qScript.done = true; eval and call callback } } } }

preserves external script order non-blocking couples with inlined code works in all browsers works 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.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 < 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); }

multiple scripts with dependencies <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.loadScripts(["menu.js", "menutier.js"], init);

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

asynchronous scripts 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 type="text/javascript"> var pageTracker = _gat._getTracker("UA-xxxxxx-x"); pageTracker._trackPageview();

document.write Script Tag approach blocks other resources 1

http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55488

case study: 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 approach "timer" coupling technique (script onload better) 1

http://docs.dojocampus.org/dojox/analytics/Urchin

asynchronous 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

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 use Link, not @import

don't scatter inline scripts MSN Wikipedia eBay MySpace

iframes: most expensive DOM element load 100 empty elements of each type tested in all major 1 browsers

1

IE 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=""> <script type="text/javascript"> document.getElementById('iframe1').src="url";

scripts block iframe IE

script

Firefox

script

Safari Chrome Opera

script

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

stylesheets block iframe (IE, FF) IE

stylesheet

Firefox

stylesheet

Safari Chrome Opera

stylesheet

surprise – stylesheets in the parent block the iframe or its resources in IE & Firefox

stylesheets after iframe still block (FF) IE

Firefox

Safari Chrome Opera

stylesheet

stylesheet

stylesheet

surprise – even moving the stylesheet after the iframe still causes the iframe's resources to be blocked in Firefox

iframes: no free connections parent

iframe

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

flush the document early html image image script html image image script

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)

flushing and domain blocking you might need to move flushed resources to a domain different from the HTML doc blocked by HTML document

html image image script html image image script

different domains

case study: Google search google image image script image 204

takeaways focus on the frontend run YSlow: http://developer.yahoo.com/yslow this year's focus: JavaScript speed matters

impact on revenue Google: +500 ms → -20% traffic1 Yahoo: +400 ms → -5-9% full-page traffic 1 Amazon: +100 ms → -1% sales

2

1 2

http://home.blarg.net/~glinden/StanfordDataMining.2006-11-29.ppt http://www.slideshare.net/stoyan/yslow-20-presentation

cost savings hardware – reduced load bandwidth – reduced response size

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

if you want better user experience more revenue reduced operating expenses the strategy is clear

Even Faster Web Sites

Steve Souders [email protected] http://stevesouders.com/docs/sxsw-20090314.ppt

Related Documents

Even Faster Websites
April 2020 119
Even Faster Web Sites
November 2019 64
Faster
October 2019 70
Websites
June 2020 49
Websites
April 2020 55

More Documents from ""