Secrets of JavaScript Libraries

78
Secrets of JavaScript Libraries (Left to Right) Sam Stephenson (Prototype) Alex Russell (Dojo) Thomas Fuchs (Script.aculo.us) Andrew Dupont (Prototype) John Resig (jQuery)

description

This is the presentation from the "Secrets of JavaScript Libraries" panel at SXSW 2008.

Transcript of Secrets of JavaScript Libraries

Page 1: Secrets of JavaScript Libraries

Secrets ofJavaScript Libraries

(Left to Right)Sam Stephenson (Prototype)

Alex Russell (Dojo)Thomas Fuchs (Script.aculo.us)

Andrew Dupont (Prototype)John Resig (jQuery)

Page 2: Secrets of JavaScript Libraries

What to Cover✦ Topics:

✦ JavaScript Language✦ Cross-Browser Code✦ Events✦ DOM Traversal✦ Style✦ Animations✦ Distribution✦ HTML Insertion

Page 3: Secrets of JavaScript Libraries

Secrets of theJavaScript Language

Page 4: Secrets of JavaScript Libraries

// Set up a class and create an element

var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element("div"); }});

Page 5: Secrets of JavaScript Libraries

// Display the time

var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element("div");

var date = new Date(); this.element.update( date.getHours() + ":" + date.getMinutes().toPaddedString(2) + "." + date.getSeconds().toPaddedString(2) ); }});

$(document.body).insert(new Clock().element);

Page 6: Secrets of JavaScript Libraries

// Add the timer

var Clock = Class.create({ initialize: function() { this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element("div"); }, updateElement: function() { var date = new Date(); this.element.update( date.getHours() + ":" + date.getMinutes().toPaddedString(2) + "." + date.getSeconds().toPaddedString(2) ); }, createTimer: function() { window.setInterval(500, this.updateElement.bind(this)); }});

Page 7: Secrets of JavaScript Libraries

// Add some options

var Clock = Class.create({ color: "black", format: "#{hour}:#{minute}.#{second}", initialize: function(options) { Object.extend(this, options); this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element("div"); this.element.setStyle({ color: this.color }); }, updateElement: function() { this.element.update(this.format.interpolate(this.getTime())); }, getTime: function() { var date = new Date(); return { hour: date.getHours(), minute: date.getMinutes().toPaddedString(2), second: date.getSeconds().toPaddedString(2) } }, ...

$(document.body).insert(new Clock().element);$(document.body).insert(new Clock({ color: "red" }).element);$(document.body).insert(new Clock({ format: "#{hour}:#{minute}" }).element);

Page 8: Secrets of JavaScript Libraries

// Use #toElement

var Clock = Class.create({ ...

toElement: function() { return this.element; }});

$(document.body).insert(new Clock());$(document.body).down("div.clock > div").replace(new Clock());$(document.body).down("div.clock").update(new Clock());

Page 9: Secrets of JavaScript Libraries

// Subclass it

var AmPmClock = Class.create(Clock, { format: "#{hour}:#{minute}:#{second} #{ampm}", getTime: function($super) { var time = $super(); time.ampm = time.hour < 12 ? "am" : "pm"; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; }});

$(document.body).insert(new AmPmClock());

Page 10: Secrets of JavaScript Libraries

// Or monkeypatch it

Object.extend(Clock.prototype, { format: "#{hour}:#{minute}:#{second} #{ampm}", getTime: Clock.prototype.getTime.wrap(function(original) { var time = original(); time.ampm = time.hour < 12 ? "am" : "pm"; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; }});

$(document.body).insert(new Clock());

Page 11: Secrets of JavaScript Libraries

Secrets ofCross-Browser Code

Page 12: Secrets of JavaScript Libraries

Browser Sniffing(wait, hear me out)

Page 13: Secrets of JavaScript Libraries

Conditionally evil

Page 14: Secrets of JavaScript Libraries

In order of desirability:

Page 15: Secrets of JavaScript Libraries

capabilities&

quirks

Page 16: Secrets of JavaScript Libraries

Capabilitiesare easy to sniff

Page 17: Secrets of JavaScript Libraries

Quirksare... more troublesome

Page 18: Secrets of JavaScript Libraries

if (document.evaluate) { // ...}

Object detection(for capabilities)

Page 19: Secrets of JavaScript Libraries

Distill to a booleanvar thinksCommentsAreElements = false;if ( document.createElement('!') ) { thinksCommentsAreElements = true;}

(for stuff in the gray area)

Page 20: Secrets of JavaScript Libraries

...then sniff(for outright bugs/quirks)

if (Prototype.Browser.IE) { element.style.filter = "alpha(opacity=50);";}

Page 21: Secrets of JavaScript Libraries

Try to do the right thing,but don’t stand on principle

Page 22: Secrets of JavaScript Libraries

The social contractGood faith from browser makers ➝

good faith from web authors

Page 23: Secrets of JavaScript Libraries

Secrets of QualityAssurance

Page 24: Secrets of JavaScript Libraries
Page 25: Secrets of JavaScript Libraries
Page 26: Secrets of JavaScript Libraries

0

750

1,500

1.5.01.5.1

1.6.0.2

Page 27: Secrets of JavaScript Libraries
Page 28: Secrets of JavaScript Libraries

Debugging✦ Localize & Reproduce

✦ Find the smallest possible code that generates the problem

✦ Easy test-case generation✦ ./gen.sh 1245 ajax

./gen.sh 1246 dom✦ Simple logging, all browsers:

document.getElementById(“output”) .innerHTML += “<br>” + msg;

Page 29: Secrets of JavaScript Libraries

Secrets ofEvents

Page 30: Secrets of JavaScript Libraries

Event handlersare tricky

Page 31: Secrets of JavaScript Libraries

Don’t store them on the element itselfcircular references = sadness

Page 32: Secrets of JavaScript Libraries

Build a global hashtable(like jQuery does it)

Page 33: Secrets of JavaScript Libraries

Element Data Store✦ Each element gets a unique ID(bound w/ a unique property)elem.jQuery12345 = 1;

✦ Points back to large data structure:data[1] = { ... all element data here ... };

✦ Data (like event handlers) are stored heredata[1] = { handlers: { click: [ function(){...} ], ... }};

Page 34: Secrets of JavaScript Libraries

Then clean up on page unload

So that IE doesn’t keep themin memory in perpetuum

Page 35: Secrets of JavaScript Libraries

Fixing memory leaks

Page 36: Secrets of JavaScript Libraries

Internet Explorer 6red-headed stepchild

Page 37: Secrets of JavaScript Libraries

Don’t “prove” your code has no leaks

(view the results!)

Page 38: Secrets of JavaScript Libraries

Dripis awesome

Page 39: Secrets of JavaScript Libraries

Test page:Create a bunch of elements, assign each an event

handler, then remove each one from the page

Page 40: Secrets of JavaScript Libraries

Demonstrating the LeakNotice the stair-step effect

Page 41: Secrets of JavaScript Libraries

Plugging the leak// In Prototype, will remove all event listeners from an elementEvent.stopObserving(someElement);

Event.purgeObservers = function(element, includeParent) { Element.select(element, "*").each(Event.stopObserving); if ( includeParent ) Event.stopObserving( element );};

Page 42: Secrets of JavaScript Libraries

Redefining functionsadd “before advice” to functionsthat need to remove elements

Page 43: Secrets of JavaScript Libraries

Code sampleElement.Methods.update = Element.Methods.update.wrap( function(proceed, element, contents) { Event.purgeObservers(element); return proceed(element, contents); });

Element.Methods.replace = Element.Methods.replace.wrap( function(proceed, element, contents) { Event.purgeObservers(element, true); return proceed(element, contents); });

Element.Methods.remove = Element.Methods.remove.wrap( function(proceed, element) { Event.purgeObservers(element, true); return proceed(element); });

Element.addMethods();

Page 44: Secrets of JavaScript Libraries

Drop-in fixfor Prototype 1.6

Page 45: Secrets of JavaScript Libraries

Custom Events

• Everything is event based if you squint

• DOM is a good foundation

• Terrible for stitching together non-DOM components and code

• Composition == good, inheritance == bad

• Custom events let us join loosely

• When to use them? Pitfalls?

Page 46: Secrets of JavaScript Libraries

Custom Events (contd.)// in Dojo:dojo.subscribe(“/foo”, function(e, arg){ ... });dojo.publish(“/foo”, [{ data: “thinger”}, “second arg”]);

// in Prototype:document.observe(“event:foo”, function(e){ ... });$(“nodeId”).fire(“event:foo”, { data: “thinger” });

// in jQuery:$(document).bind(“foo”, function(e, data, arg){ ... });$(document).trigger(“foo”, [{ data: “thinger”}, “second”]);

Page 47: Secrets of JavaScript Libraries

Secrets ofDOM Traversal

Page 48: Secrets of JavaScript Libraries

Selector Internals

• Optimized DOM

• Top-down vs. bottom-up

• Caching + winnowing

• XPath

• Native, aka: querySelectorAll()

Page 49: Secrets of JavaScript Libraries

Selector Internals• Tradeoffs: size vs. speed vs. complexity

• Prototype: XPath when possible, else DOM

• Good perf, lower complexity, hackable

• Dojo: all 3 methods, picks best available

• Best perf, biggest, highest complexity

• JQuery: DOM

• Good perf, small size, extensiblity vs. forward-compat tradeoff

Page 50: Secrets of JavaScript Libraries

Secrets ofStyle

Page 51: Secrets of JavaScript Libraries

Computed Style✦ IE way vs. Everyone else

✦ IE returns “actual value”✦ Everyone else returns “pixel value”✦ font-size: 2em;

IE: “2em”Other: 24

✦ Need to convert to common base✦ Performance: Very costly, generally avoided

wherever possible

Page 52: Secrets of JavaScript Libraries

Pixel Values in IE✦ if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { // Remember the original values var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left;

// Put in the new values to get a computed value out elem.runtimeStyle.left = elem.currentStyle.left; elem.style.left = ret || 0; ret = elem.style.pixelLeft + “px”;

// Revert the changed values elem.style.left = style; elem.runtimeStyle.left = runtimeStyle;}

✦ From Dean Edwards:http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

Page 53: Secrets of JavaScript Libraries

Computed Style✦ Safari 2 & 3:

Giant mess for display: none elements✦ Safari 2

✦ getComputedStyle() returns undefined✦ Safari 3

✦ Always return empty strings for value✦ Much harder to detect

var ret = document.defaultView.getComputedStyle( elem, null );return !ret || ret.getPropertyValue(”color”) == “”;

Page 54: Secrets of JavaScript Libraries

Finding dimensions

Page 55: Secrets of JavaScript Libraries

Dimensions of what?content box, border box, margin box...

Page 56: Secrets of JavaScript Libraries

DHTML propertiesclientWidth, offsetWidth

Page 57: Secrets of JavaScript Libraries

The value you want is not captured by any property

Lorem ipsum dolor sit

amet.

width

clientWidth

offsetWidth

Page 58: Secrets of JavaScript Libraries

Computed stylesgetting padding & border value

Page 59: Secrets of JavaScript Libraries

The fool-proof, painful way

Take the offsetWidth,then subtract computed padding & border

Page 60: Secrets of JavaScript Libraries

Code exampleElement.getCSSWidth = function(element) { element = $(element); return element.offsetWidth - parseFloat(element.getStyle("borderLeft")) - parseFloat(element.getStyle("paddingLeft")) - parseFloat(element.getStyle("paddingRight")) - parseFloat(element.getStyle("borderRight")); };

Page 61: Secrets of JavaScript Libraries

Secrets of Animation

Page 62: Secrets of JavaScript Libraries

Old-School“Effects”

Page 63: Secrets of JavaScript Libraries

setInterval

Page 64: Secrets of JavaScript Libraries

Events-based

Page 65: Secrets of JavaScript Libraries

Secrets ofJavaScript Deployment

Page 66: Secrets of JavaScript Libraries

CONTENTEXPIRATION

Page 67: Secrets of JavaScript Libraries
Page 68: Secrets of JavaScript Libraries
Page 69: Secrets of JavaScript Libraries

Concatenation

Page 70: Secrets of JavaScript Libraries
Page 71: Secrets of JavaScript Libraries

GZIP4-5x smaller

Page 72: Secrets of JavaScript Libraries

Packaging

• Dev vs. deployment constraints

• No library a single file, but all ship that way

• # of requests largest constraint

• Sync vs. async

• Static resource servers + CDNs

• Dependency management matters!

• Runtime vs. deployment

Page 73: Secrets of JavaScript Libraries

Packaging// in Dojo:dojo.provide(“foo.bar.Baz”);dojo.require(“dojox.dtl”);

// in GWT:package com.foo.bar;import com.foo.bar.Blah;

// in JSAN:JSAN.use(“foo.bar.Blah”);// exports handled by build tools

Page 74: Secrets of JavaScript Libraries

Packaging

• The build-process divide

• Library support vs. server concat/shrink

• Can we strip “dead” code?

• Social artifacts of packaging systems

Page 75: Secrets of JavaScript Libraries

HTML Insertion✦ $(“div”).append(“<b>foo</b>”);✦ Convert HTML String

✦ innerHTML✦ Range: .createContextualFragment()

✦ Must purify first:✦ Fix <table>s for IE (<tbody> wrap)✦ Handle <option>s (contain in <select>)

Page 76: Secrets of JavaScript Libraries

HTML Insertion✦ // Fix “XHTML”-style tags in all browsers

elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? all : front + “></” + tag + “>”;});

✦ $(“<abbr/>”).html(“hello!”).appendTo(“#foo”);

Page 77: Secrets of JavaScript Libraries

Script Execution✦ Execute Script in Global Scope

✦ var head = document.getElementsByTagName(”head”)[0] || document.documentElement, script = document.createElement(”script”);

script.type = “text/javascript”;if ( jQuery.browser.msie ) script.text = data;else script.appendChild( document.createTextNode( data ) );

head.appendChild( script );head.removeChild( script );

Page 78: Secrets of JavaScript Libraries

Questions✦ Panelists:

✦ John Resig (ejohn.org)✦ Sam Stephenson (conio.net)✦ Alex Russell (alex.dojotoolkit.org)✦ Thomas Fuchs (script.aculo.us/thomas)✦ Andrew Dupont (andrewdupont.net)

✦ Frameworks:✦ Prototype (prototypejs.org)✦ jQuery (jquery.com)✦ Dojo (dojotoolkit.org)✦ Script.aculo.us (script.aculo.us)