Archive for the 'facebook' Category

Here’s to a faster Recommendations plugin

Sunday, May 5th, 2013

So I've been part of the quest of making all Facebook social plugins faster, even if it means rewriting them from scratch. After the Send plugin, Like button (perf optimizations described here), Follow plugin, Facepile and Likebox (perf details here), now you have a faster Recommendations plugin.

The techniques used to make it faster are simple and effective: better resource packaging, reducing number of requests, inlining CSS, reducing the amount of CSS and JS by untangling dependencies, cleaning up, and sometimes simply rewriting. You can read more about these in the previous posts.

Now the results. The before-after comparison is accurate because it uses snapshots taken at the same time. This is possible because we kept an old endpoint serving the old code path. The official URL is /plugins/recommendations.php but we kept the legacy URL /widgets/recommendations.php pointing to the old code for a little while.

Before

After

Some analysis

The total payload change is drastic. Mainly due to better packaging. And better JS modules and dependencies.

The number of requests is reduced by 1/3. Not as drastic, but not too bad either. Most of the requests are images, which is ok. They don't block anything and whenever they arrive, they are welcome. But they are not on the critical path. The reduced requests are all JavaScript. We already had a previous optimization so CSS was already inlined. But thanks to rewrites, now the HTML payload (including inline CSS) is 6.2K gzipped, down from 9.4K. Which means the initial paint can start sooner.

The initial paint (render start) now happens in half the time. And, even better, the initial paint is a complete plugin, except for the images. While before it was just a partial content. This is because CSS is here early and all JS is out of the way (async loaded, also take a peek here)

initial paint screenshots

The fully loaded time is not all that important since the user has a usable list of recommendations already delivered with the initial paint. But it's still 2x faster which makes me happy.

All in all

2x faster plugin overall, 2x faster (and infinitely better) first impression. 7x payload improvement.

Making the web faster and other personal notes

Just want to take a second to mention how good it feels to be working on such high-impact performance optimizations. These social plugins are everywhere on the web. By making them faster I am fortunate to have the opportunity to make the whole web faster. Meaning make millions of sites faster, affecting the live of billions of people, every day.

What can I say, Facebook is a great place to work. The people, the impact. Every line you write matters. It's also up to you to pick what do you want to work on and where your talents and interests will have the greatest impact. And then there are the hackathons and hackamonths which means even more freedom.

I recently finished a hackamonth project, which explains why I've been silent here and on Twitter and everywhere (yet, thanks to O'Reilly folks, even though I missed a few deadlines, we were able to push this baby out the door). Let me tell you - a hack-a-month is better than vacation. Being left alone for a month to explore a completely new (to you) territory - priceless!

(Oh, if that sounds something you'd like to do, hit me up on ssttoo at ymail with your resume. FB now has engineering offices in NYC, Seattle and London, so if moving was a problem, now there are more options)

 

C3PO: Common 3rd-party objects

Monday, February 18th, 2013

Problem: too much JavaScript in your page to handle 3rd party widgets (e.g. Like buttons)
Possible solution: a common piece of JavaScript to handle all third parties' needs

3t1jsi

What JavaScript?

If you've read the previous post, you see that the most features in a third party widget are possible only if you inject JavaScript from the third party provider into your page. Having "a secret agent" on your page, the provider can take care of problems such as appropriately resizing the widget.

Why is this a problem?

Third party scripts can be a SPOF (an outage), unless you load them asynchronously. They can block onload, unless the provider lets you load it in an iframe (and most don't). There can be security implications because you're hosting the script in your page with all permissions associated with that. And in any case, it's just too much JavaScript for the browser to parse and execute (think of mobile devices)

If you include the most common Like, Tweet and +1 buttons and throw in Disqus comments, you're looking at well over 100K (minified, gzipped) worth of JavaScript (wpt for this jsbin)

This is more than the whole of jQuery, which previous experiments show can take the noticeable 200ms just to parse and evaluate (assuming it's cached) on an iPhone or Android.

What does all of this JS do?

The JavaScript used by third parties is not always all about social widgets. The JS also provides API call utilities, other dialogs and so on. But the tasks related to social widgets are:

  1. Find html tags that say "there be widget here!" and insert an iframe at that location, pointing to a URL hosted by the third party
  2. Listen to requests from the new iframes fulfill these requests. The most common request is "resize me, please"

Now, creating an iframe and resizing it doesn't sound like much, right? But every provider has to do it over and over again. It's just a wasted code duplication that the browser has to deal with.

Can't we just not duplicate this JavaScript? Can we have a common library that can take care of all widgets there are?

C3PO draft

Here's a demo page of what I have in mind. The page is loading third party widgets: like, tweet, +1 and another one I created just for illustration of the messaging part.

It has a possible solution I drafted as the c3po object. View source, the JS is inline.

What does c3po do?

The idea is that the developer should not have to make any changes to existing sites, other than remove FB, G, Tw, etc JS files and replace with the single c3po library. In other words, only the JS loading part should be changed, not the individual widgets code.

c3po is a small utility which can be packaged together with the rest of your application code, so there will be no additional HTTP requests.

Parsing and inserting iframes

The first task for c3po is to insert iframes. It looks for HTML tags such as

<div class="fb-like" data-href="http://phpied.com"></div>

Similar tags are generated by each provider's "wizard" configuration tools.

In place of this tag, there should be an iframe, so the result (generated html) after c3po's parsing should roughly be like:

<div class="fb-like" data-href="http://phpied.com">
  <iframe 
    src="http://facebook.com/plugins/like.php?href=http://phpied.com">
  </iframe>
</div>

The way to do this across providers is to just have every data- attribute passed as a parameter to the 3rd party URL.

Third parties can be setup using a register() method:

// FB
c3po.register({
  'fb-like': 
    'https://www.facebook.com/plugins/like.php?',
  'fb-send':
    'https://www.facebook.com/plugins/send.php?',
});
 
// Tw
c3po.register({
  'twitter-share-button':
    'https://platform.twitter.com/widgets/tweet_button.html#'
});
 
// ...

The only additional parameter passed to the third party URL is cpo-guid=..., a unique ID so that the iframe can identify itself when requesting services.

The parsing and inserting frames works today, as the demo shows. The only problem is you don't know how big the iframes should be. You can guess, but you'll be wrong, given i18n labels and different layouts for the widgets. It's best if the widget tells you (tells c3po) how big it should be by sending a message to it.

X-domain messaging

What we need here is the iframe hosted on the provider's domain to communicate with the page (and c3po script) hosted on your page. X-domain messaging is hard, it requires different methods for browsers and I'm not even going to pretend I know how it works. But, if the browser supports postMessage, it becomes pretty easy. At the time of writing 94.42% of the browsers support it. Should we let the other 5% drag us down? I'd say No!

c3po is meant to only work in the browsers that support postMessage, which means for IE7 and below, the implementers can resort to the old way of including all providers' JS. Or just have less-than-ideally-resized widgets with reasonable defaults.

When the widget wants something, it should send a message, e.g.

var msg = JSON.stringify({
  type: 'resize',
  guid: '2c23263549d648000',
  width: 200, 
  height: 300
});
parent && parent.postMessage(msg, '*');

See the example widget for some working code.

The c3po code that handles the message will check the GUID and the origin of the message and if all checks out it will do something with the iframe, e.g. resize it.

Again, take a look at the demo code to see how it all clicks together

Next?

As you see in the demo, only the example widget is resized properly. This is because it's the only one that sends messages that make sense to c3po.

Next step will be to have all widget providers agree on the messages and we're good to go! The ultimate benefit: one JS for all your widget-y needs. One JS you can package with your own code and have virtually 0 cost during initial load. And when you're ready: c3po.parse() and voila! - widgets appear.

Of course, this is just a draft for c3po, I'm surely missing a lot of things, but the idea is to have soemthing to start the dialogue and have this developed in the open. Here's the github repo for your forking pleasure.

Make sense? Let's talk.

 

Social button BFFs

Tuesday, September 27th, 2011

TL;DR: Loading JavaScript asynchronously is critical for the performance of your web app. Below is an idea how to do it for the most common social buttons out there so you can make sure these don't interfere with the loading of the rest of your content. After all people need to see your content first, then decide if it's share-worthy.

Japanese translation by Koji Ishimoto is here

Facebook now offers a new asynchronous snippet to load the JavaScript SDK, which lets you load social plugins (e.g. Like button) among doing other more powerful things.

It has always been possible to load the JS SDK asynchronously but since recently it's the default. The code looks pretty damn nice (I know, right!), here's how it looks like (taken from here):

(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) {return;}
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

Some nice steal-me JS patterns here:

  • immediate (self-invoking) function so not to bleed vars into global namespace
  • pass oft-used objects (document) and strings ("script", "facebook-jssdk") to the immediate function. Sort of rudimentary manual minification, while keeping the code readable
  • append script node by using the first available script element. That's 99.99% guaranteed to work unless all your code is in body onload="..." or img onload or something similar (insanity, I know, but let's allow generous 0.01% for it)
  • assign an ID to the node you append so you don't append it twice by mistake (e.g. like button in the header, footer and article)

All buttons' JS files

Other buttons exist, most notably the Twitter and Google+1 buttons. Both of these can be loaded with async JavaScript whether or not this is the default in their respective configurators.

So why not make them all get along and shelter them under the same facebook immediate function? We'll save some bytes and extra script tags in the HTML. For G+/T buttons all we need is a new script node. Google+'s snippet has some additional attribs such as type and async, but these are not really needed. Because type is always text/javascript and async is always true. Plus we kinda take care of the async part anyways.

The end result:

  <div id="fb-root"></div>
  <script>(function(d, s, id) {
    // fb + common
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
    fjs.parentNode.insertBefore(js, fjs);
    // +1
    js = d.createElement(s); 
    js.src = 'https://apis.google.com/js/plusone.js';
    fjs.parentNode.insertBefore(js, fjs);
    // tweet
    js = d.createElement(s); 
    js.src = '//platform.twitter.com/widgets.js';
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));</script>

So this thing loads all three JS files required by the three buttons/plugins.

Additionally we can wrap the node creation/appending part into a function. So all the code is tighter. Here's the final snippet:

<div id="fb-root"></div><!-- fb needs this -->
<script>(function(d, s) {
  var js, fjs = d.getElementsByTagName(s)[0], load = function(url, id) {
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.src = url; js.id = id;
    fjs.parentNode.insertBefore(js, fjs);
  };
  load('//connect.facebook.net/en_US/all.js#xfbml=1', 'fbjssdk');
  load('https://apis.google.com/js/plusone.js', 'gplus1js');
  load('//platform.twitter.com/widgets.js', 'tweetjs');
}(document, 'script'));</script>

All buttons' markup

Next is actually advising the scripts where the widgets should be rendered. Facebook offers XFBML syntax, with tags such as <fb:like>, but it also offers pure HTML(5) with data-* attributes. Luckily, so do all others.

Here's an example:

<!-- facebook like -->
<div class="fb-like" data-send="false" data-width="280"></div>
<!-- twitter -->
<a class="twitter-share-button" data-count="horizontal">Tweet</a>
<!-- g+ -->
<div class="g-plusone" data-size="medium"></div>

G+ requires a div element (with g-plusone class name), Twitter requires an a (with a twitter-share-button class name). Facebook will take any element you like with a fb-like class name (or fb-comments or fb-recommendations or any other social plugin you may need)

Also very important to note that you can (and should) load the JS files once and then render as many different buttons as you need. In Facebook's case these can be any type of plugin, not just like buttons. Economy of scale - on JS file, many plugins.

All together now

So here's the overall strategy for loading all those buttons.

  1. Copy the JS above at the bottom of the page right before /body just to be safe (G+ failed to load when the markup is after the JS). This will also help you make sure there should be only one place to load the JS files, although the snippet takes cares of dedupe-ing.
  2. sprinkle plugins and buttons any way you like anywhere on your pages using the appropriate configurator to help you deal with the data-* attributes (FB, G+, Tw)
  3. Enjoy all the social traffic you deserve!

To see it all in action - go to my abandoned phonydev.com blog. Yep, those buttons play nice in mobile too.

 

Performance job offers

Thursday, January 28th, 2010

I'm sure quite a few of you my fellow readers are crazy about web performance. And if you're seeking new challenges, timing can't be any better. Below are three excellent opportunities in three of the most high-traffic sites on the planet.

  • Yahoo
    Yahoo! Search is hiring a senior performance engineer. Yep, you'll be working with me and a bunch of incredible folks.
  • eBay
    eBay is hiring a performance engineer. I had the pleasure of delivering a tech talk there, it looks like a great place to be, fast-paced, and they do take performance seriously, lot of opportunities to sharpen your perf teeth (I don' have a URL, hit me up ssttoo at gmail if you're interested)
  • Facebook
    Facebook is hiring a performance engineer. Depending on who you trust, FB is #2 or #3 most popular site, so the challenge is definitely there. I've spoken to several awesome people, like David Wei performance engineer and researcher extraordinaire, and let me tell you, things are happening and you'll never be bored, even for a second.

And, not perf-related, but an extraordinary opportunity at YUI was announced today, it almost sounds too good to be true. One of the most important thing about a job is the people you'll be working with. Well, with YUI you can't wish for a higher concentration of front-end brain power. It's scary :)

 

LA Web devs meetup at Yahoo

Monday, July 23rd, 2007

So there is this group of local LA web developers that meet every month or so to meet and discuss what's up. More about/join the group here.

This month Yahoo will be hosting the meetup in the Santa Monica office (my workplace), it's actually tomorrow, so if you're in LA, don't miss the opportunity for beer, pizza and meeting fellow web devs. RSVP here.

On Yahoo's side, Jim Bumgardner, a.k.a. krazydad will be demoing the Facebook app he did that allows you to find music videos and discover artists similar to the ones you like. The app, Jim talking about it, Yahoo Developers Network posting.

Sounds like it would be fun, and also a chance for a local web dev to see what Yahoo's office looks like, meet some of the people that work here, and in a way to try-before-you-apply :D