#desuckify | http://kentbrew.github.io/desuckify | @kentbrew

  1. "Hello, SxSW!": { "title": "De-Suckifying Third-Party JavaScript", "author": "Kent Brewster", "hashtag": "#desuckify", "on the Twitters": "@kentbrew", "source": "http://kentbrew.github.io/desuckify" }
  2. "disclaimerama": [ "about me", "about my employer", "about the talk" ]
  3. "about me": [ "no actual engineering qualifications", "no degree of any sort", "living proof that just about anybody can do this sort of thing" ]
  4. "where I've been": { "Atari": "1977", "WebMD": "1984-2003", "Yahoo": "2004-2009", "Netflix": "2009-2010", "Lexity": "2010-2011", "Pinterest": "current" ]
  5. "a recurring theme in my life": "Rather than building code that runs on our stack, I always seem to wind up building code that has to run on every other freaking page on the Web." }
  6. "about Pinterest": [ "what's it like?", "are you hiring?", "dude, where's my API?" ]
  7. "what's it like to work at Pinterest?": { "answer": "It's a happy, inspiring, creative, friendly place to work on a happy, inspiring, creative, friendly product. I wake up every day excited to go to work.", "author": "Tracy Chou", "via": "Quora" }
  8. "is Pinterest hiring?": { "answer": "Absolutely!", "jobs": [ "front end", "back end", "reliability", "android", "ios", "devops", "security", "data mining", "compensation manager", "hris manager", "product designer", "writer", "community managers", "community ops head", "country managers", "business analyst", "financial analyst", "account manager", "partner ops manager", "awesome people" ], "link": "http://pinterest.com/about/careers" }
  9. "dude, where's my API?": { "official": "Pinterest is thinking about the public API very, very carefully.", "other answers: { "watch this space": "http://developers.pinterest.com", "build widgets": "http://business.pinterest.com/widget-builder/", "some stuff already here": "https://github.com/pinterest/" }, "a personal appeal": "Please don't ever ship anything based on an unsupported API. It will break, and when it does, the people who didn't release the API will look like jerks." }
  10. "the talk": { "about": "Today I will be simultaneously channeling for everyone whose site I have personally broken in the last sixteen years, while explaining why and how I did it.", "Is this HTML5?": "No. This is just barely Web 2.0." }
  11. "how to tell if it's HTML5": [ ]
  12. "how to tell if it's HTML5": [ "Try it with Internet Explorer." ]
  13. "how to tell if it's HTML5": [ "Try it with Internet Explorer.", "Did it work?" ]
  14. "how to tell if it's HTML5": [ "Try it with Internet Explorer.", "Did it work?", "No?" ]
  15. "how to tell if it's HTML5": [ "Try it with Internet Explorer.", "Did it work?", "No?", "That's HTML5." ]
  16. "OMGWTFBBQ": [ "WinXP (and IE6-8 with it) reach end of life on April 14th", "that's thirty days from TODAY", "please visit: noaiee.com" ]
  17. "more dire warnings": { "Murphy's Law applies": "anything that can possibly go wrong with third-party JavaScript will go wrong.", "when will it break?": [ "it will break at 3am", "it will break on IE8", "it will break in an irreproducible fashion", "it will take the surrounding page down with it", "it will cost the site operator hundreds and hundreds of sales" ], "and, of course": "when it breaks, it will be TOTALLY YOUR FAULT" }
  18. "therefore": "Anyone who can possibly be discouraged from shipping third-party JavaScript should be discouraged from shipping third-party JavaScript."
  19. "alternatives to third-party javascript": { "ugh": [ "applets", "flash" ], "yuck": [ "iframe" ] }
  20. "limiting factors": [ "Most free popular platforms (tumblr, wordpress, blogger, etc.) won't allow third-party javascript.", "This is why it's important to have amazing product, marketing, and bizdev people. They can make that happen!" ]
  21. "use cases for third-party javascript": { "cool": [ "instrumentation", "decorated links" ], "maybe": [ "badges", "browser extensions", "toolbar bookmarklets" ], "danger zone": [ "complex interactions", "anything that makes a mark on your database" ] }
  22. "please don't": [ "break the DOM", "create global variables", "hog event listeners", "overwrite my stylesheet" "mess it up on mobile devices", "break my instrumentation", "spy on my users" ] }
  23. "avoid avoid avoid": [ "alert()", "document.write()", "onerror()", "console.log()", "modal dialogues" "hover-based interactions", "drag and drop" ]
  24. "be very careful with": [ "keyboard interactions", "scrolling interactions", "timeouts and intervals", "hover-based interactions" ]
  25. "in the end, there can be only one rule": "Don't block."
  26. "be careful with iframes": [ "very hard for the site operator to instrument", "create their own DOM", "hog the connection pool and cause old browsers to craaaawl", "can block window.onload if called inline", "impossible to style", "spy on my readers" ]
  27. "don't hide the ball": [ "put the source on github", "encourage site operators to host it themselves if they want", "listen and respond to issues", "merge pull requests", "star, follow, and fork them back", "free starting point for your developer page", "excellent source for engineering recruitment" ], "pinterest": "https://github.com/pinterest/widgets"
  28. "no extra libraries": { "rant": "Do NOT throw a bunch of YUI or jQuery down on top of my page. If you can't load all the code you need with a single HTTP request, you are doing it wrong.", "caveat": "data and translations don't count as code; feel free" }
  29. "and especially": { "rant": "If you fail to warn me that you are connecting my site to Facebook or Google Analytics in any manner whatsoever, I will hunt you down and kill you like the dog that you are." }
  30. "some basics": [ "dependencies", "portability", "compatibility" "no surprises" ]
  31. "it can't depend on page load": [ "no matter how you document it, somebody will screw it up", "professionals will want to load asynchronously", "some will want to dynamically generate your widget" ]
  32. "it has to work anywhere": [ "your customers are not technical people", "often they are using CMSs like Shopify or Wordpress", "or bronze-age things like Yahoo! Stores", "with no idea what HEAD, BODY, or HTML mean", "and even if they do, they might not have access" ]
  33. "it must not break in IE": [ "silently fail in older versions", "document what versions are unsupported", "test, with real human users", "you must own Windows XP, Vista, 7, and 8", "you must not even TRY testing on virtual hardware", "be very careful not to let M$ update your test machines" ]
  34. "no surprise updates": { "please": [ "version your widget", "communicate your updates", "support everything, forever" ] }
  35. "implementation": [ "loader", "configuration", "namespacing" ]
  36. "loader": { "my HEAD or BODY might not exist.": [ "make your script tag", "find my first script tag", "insert your tag before mine" ] }
  37. "simple async loader": "(function(d) { var first = d.getElementsByTagName('SCRIPT')[0]; var script = d.createElement('SCRIPT'); script.async = true; script.type = 'text/javascript'; script.charset = 'utf-8'; script.src = '//foo.com/widget.js'; first.parentNode.insertBefore(script, first); }(document));"
  38. "simple inline loader": "<script async type="text/javascript" src="//foo.com/widget.js">", "notes": { "schemeless src": "minimizes security alerts", "async": "helps avoid blocking for many browsers", "type": "not needed per spec but eases validation and may help with funky doctypes and older browsers. Also important if your widget will be used on government or academic sites that must follow accessibility or other hard-line validation procedures." }
  39. "count on site operators to screw this up": [ "they will paste whatever you tell them to paste, right where they expect to see your widget show up.", "they will not listen when you tell them to please call the JavaScript only once", "to the contrary: they will call your JavaScript from inside a repeating template in their Tumblr page." "this will break in many irreproducible ways, browser to browser" ]
  40. "ship a loader and a payload": [ "loader finds and deletes all calls to itself", "carefully accumulating configuration requests", "and then calls the payload script, only once" ], "see example": "https://github.com/pinterest/widgets/blob/master/pinit.js"
  41. "global namespace": [ "your script gets ONE global variable", "HTML IDs count as global variables", "beware namespace collisions" ]"
  42. "how are we going to style this thing?": [ "wrap everything in an anonymous function", "create a random string", "random string is your global namespace root and your CSS className", "create a stylesheet for that className only" ]
  43. "warning: not a magic bullet": [ "The pattern I'm going to show next will deliver a certain degree of separation and safety, so your widget has a fair chance of appearing the way you want it to look, and almost certainly won't get unintentionally hosed by anything else on my page.", "Key word: UNINTENTIONALLY. If there are bad guys loose in my DOM who really want to squash my widget, they can. Of course, now I have two problems...." ]
  44. "simple example": [ "Build a widget that appears on the page wherever the user inserts a single line of JavaScript.", "Accept configuration requests from outside.", "Don't pollute the global namespace.", "Don't break or be broken by the surrounding CSS." ] }
  45. "configuration": { "don't require me to do this": "<script>var config="Hello, SxSW!";</script>", "because": [ "I will screw it up.", "Also, 'config' is a global variable." ] }
  46. "better": { <script src="//foo.com/widget.js" data-config="Hello, SxSW!" asyc type="text/javascript"></script> }
  47. "accessing config": { "theory": [ "Set config as a data- attribute on your SCRIPT tag.", "At runtime, loop through all SCRIPT tags.", "Look for one whose src attribute matches your target.", "Save the value in data-config, if it exists.", "Delete that SCRIPT node (don't worry; it's already running).", "Fire off your behavior, if any." "Quit looking.", ], "bonus": "If the same script tag appears AGAIN in source, it will find itself and do the right thing." }
  48. my favorite JavaScript pattern (function (win, doc, arg) { var $ = win[arg.root] = { 'win': win, 'doc': doc, 'arg': arg, 'func': (function () { return { init: function () { alert('my random ID:' + $.arg.root); } }; }()) }; $.func.init(); }(window, document, { 'root': '_' + new Date().getTime() }));
  49. Pass a pattern to match against script src in $.a.args: }(window, document, { 'root': '_' + new Date().getTime(), 'src': /widget.js$/ })); ... and find the right SCRIPT tag in $.f.init init: function () { var script = $.doc.getElementsByTagName('SCRIPT'), n = script.length, i; for (i = 0; i < n; i = i + 1) { if (script[i].src.match($.arg.src)) { $.func.structure(script[i])); break; } } }
  50. Build the HTML structure: structure: function (script) { $.arg.config = script.getAttribute('data-config'); $.struc = {}; $.struc.body = $.doc.createElement('DIV'); $.struc.body.id = $.arg.root + '_body'; var span = $.doc.createElement('SPAN'); span.innerHTML = $.arg.config; $.struc.body.appendChild(span); $.struc.x = $.doc.createElement('A'); $.struc.x.id = $.arg.root + '_x'; $.struc.x.innerHTML = 'x'; $.struc.body.appendChild($.struc.x); script.parentNode.insertBefore($.struc.body, script); script.parentNode.removeChild(script); $.func.presentation(); }
  51. Pass the CSS as strings through $.arg.rules: }(window, document, { 'k': '_' + new Date().getTime(), 'src': /widget.js$/, 'rules': [ '#_body { padding: 20px 40px; position: absolute;', 'top: 0; right: 0; background: #f00; color: #fff; }', '#_x { position: absolute; top: 2px; right: 10px;', 'color: #ff0; cursor: pointer; }' ] })); CSS shown here as strings; can be built from a SASS-like object for more complex examples.
  52. Create and insert the stylesheet: presentation: function () { var rules = $.arg.rules.join('\n'), css = $.doc.createElement('STYLE'); css.type = 'text/css'; // insert global root as ID for each rule rules = rules.replace(/#_/g, '#' + $.arg.root + '_'); rules = rules.replace(/;/g, '!important;'); if (css.styleSheet) { css.styleSheet.cssText = rules; } else { css.appendChild($.doc.createTextNode(rules)); } // caution: document.head may not exist $.doc.head = $.doc.getElementsByTagName('HEAD')[0]; $.doc.head.appendChild(css); $.func.behavior(); }
  53. All done; start behavior: listen: function (el, ev, fn) { if (typeof $.win.addEventListener !== 'undefined') { el.addEventListener(ev, fn, false); } else if (typeof $.win.attachEvent !== 'undefined') { el.attachEvent('on' + ev, fn); } }, close: function () { $.struc.body.parentNode.removeChild($.struc.body); }, behavior: function () { $.func.listen($.struc.x, 'click', $.func.close); }
  54. "important note about listeners": { "for best results": [ "Don't add an event listener to every link you make.", "Instead you should add a single listener to $.struc.body, find the targets of events, and send them to appropriate handlers." "And remember: trying to implement hover-based interactions will break your heart!" ] }
  55. (function (w, d, a) { var $ = w[a.k] = { 'arg': arg, 'doc': doc, 'win': win, 'struc': {}, 'func': (function () { return { close: function () { $.struc.body.parentNode.removeChild($.struc.body); }, listen: function (el, ev, fn) { if (typeof $.win.addEventListener !== 'undefined') { el.addEventListener(ev, fn, false); } else if (typeof $.win.attachEvent !== 'undefined') { el.attachEvent('on' + ev, fn); } }, behavior: function () { $.func.listen($.struc.x, 'click', $.func.close); }, presentation: function () { var rules = $.arg.rules.join('\n'), css = $.doc.createElement('STYLE'); css.type = 'text/css'; rules = rules.replace(/#_/g, '#' + $.arg.root + '_'); if (css.styleSheet) { css.styleSheet.cssText = rules; } else { css.appendChild($.doc.createTextNode(rules)); } $.doc.head.appendChild(css); $.func.behavior(); }, structure: function (script) { $.arg.config = script.getAttribute('data-config'); $.struc.body = $.doc.createElement('DIV'); $.struc.body.id = $.arg.root + '_body'; var span = $.doc.createElement('SPAN'); span.innerHTML = $.arg.config; $.struc.body.appendChild(span); $.struc.x = $.doc.createElement('A'); $.struc.x.id = $.arg.root + '_x'; $.struc.x.innerHTML = 'x'; $.struc.body.appendChild($.struc.x); script.parentNode.insertBefore($.struc.body, script); script.parentNode.removeChild(script); $.func.presentation(); }, init: function () { $.doc.head = $.doc.getElementsByTagName('HEAD')[0]; var script = $.doc.getElementsByTagName('SCRIPT'), n = script.length, i; for (i = 0; i < n; i = i + 1) { if (script[i].src.match($.arg.src)) { $.func.structure(script[i]); break; } } } }; }()) }; $.func.init(); }(window, document, { 'root': '_' + new Date().getTime(), 'src': /widget.js$/, 'rules': [ '#_body { padding: 20px 40px; position: absolute; top: 0; right: 0;', 'background: #f00; color: #fff; }', '#_x { position: absolute; top: 2px; right: 10px; color: #ff0;', 'cursor: pointer; }' ] } ));
  56. "things to try": [ "View source; note the SCRIPT tag.", "Inspect the widget; note the SCRIPT tag is gone.", "Close the widget; note that it is gone.", ]
  57. "a richer example": [ "calls a json-p endpoint", "builds inline stylesheets from sass-like objects", "does some fairly complicated rendering" ], { "remember when you could roll your own twitter timelines?": "you still can" }
    • "build": "https://twitter.com/settings/widgets/", "hack": "twitter's HTML fragment", "try it right now": "<script src="http://kentbrew.com/desuckify/ twatter.js" data-structure-id="canHazTweets" data-widget-id="418760697137225728" ></script>" "fork": "https://github.com/kentbrew/twatter" }
    • "questions?": { "can haz fork?": "https://github.com/pinterest/widgets/", "https://github.com/kentbrew/twatter" "rant about APIs and developer sites": "Developer Site Preamble" "can haz ping?": { "@kentbrew on": [ "twitter", "github","linkedin", "flickr", "pinterest" ] }, "thank you": [ "@yahoo", "@netflix", "@pinterest", "@atgmeup", "@panic" ] }