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)

Upload: jeresig

Post on 05-Dec-2014

44.705 views

Category:

Technology


1 download

DESCRIPTION

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

TRANSCRIPT

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)