Creating an embedded widget

Using the modern JS toolchain

Presentation by Emil Stenqvist / @svammel

What's the deal?

Why roll your own when you can embed services like:

  1. Google Analytics
  2. Facebook Like Buttons
  3. Disqus
  4. The Shootitlive player


These are all 3rd party JS widgets!

If Alan Turing had meant for this to happen, he would've written a book on browser compatibility before even thinking about the halting problem.

The challenges are many:

  1. Not breaking the consumer site
  2. Cross-origin communication
  3. Surviving the global namespace wild-west
  4. Not stalling the DOM rendering
  5. Keeping the embed code short

What I do day-to-day

$ curl -s 'http://s3-eu-west-1.amazonaws.com/shootitlive/\
           shootitlive.load.v1.aftonbladet.js' | wc -c
   21036
  • Often gets over 20M hits a month
  • Loads an embedded JS media player

Challenges I'll cover today

  1. A short, customizable and asynchronous embed code
  2. No globals – play in our own sandbox
  3. Use RequireJS – embed-style
  4. Safely use jQuery, underscore, Backbone, etc.

Embed code exhibit

They come in all shapes and lengths.

The short one


<script src="//awesome-service.com/init.js"></script>
<script>
fireItUp({
  // Parameters
  project: 13, client: 'greenfield'
});
</script>
            

The long one

<div id="your-unique-identifier"></div>
<script>
(function() {
  var s = document.createElement('script');
  s.src = '//your-cdn.com/widget/master/main.js';
  s.async = true;
  window.widget_options = (window.widget_options || []).concat([ {
    project: 13, client: 'greenfield', element_id: 'your-unique-identifier'
  }];
  document.body.appendChild(s);
}());
</script>
            

The slick one


<script src="//latest-cdn.com/widget.load.v1.js?project=13&client=greenfield" async></script>
            

How does it work?

              
  <script src="//latest-cdn.com/widget.load.v1.js?project=13&client=greenfield" async></script>
              
            

Trick 1: looking for script tags

              var els = document.getElementsByTagName('script'),
    // Match all script tags whose URI contains widget.load.js
    re = /.*widget\.load\.([^/]+\.)?js/;

// Iterate!
            

Trick 2: parsing a query string

              var a = document.createElement('a');
a.href = scriptElement.src;
var qs = a.search.slice(1);
            

Trick 3: insert widget element next to the embedding script tag

Going RequireJS

  • Starting with RequireJS in the loader file
  • Getting everything wrapped up in a namespace
            (function() {
  var globalName = 'WidgetGlobal',
      global = window,
      namespace = global[globalName];

  // RequireJS source
  // Code that looks for embed tags

  forEachEmbedTag(function(tag) {
    namespace.require.config({
      baseUrl: parameters.base_url + '/js'
    });
    namespace.require([ 'app' ], function(App) {
      new App(parameters);
    });
  });

}());
            

Why go to all this trouble?

You often see people resorting to bad coding standards just 'cause they're writing a widget. With a RequireJS setup we can structure it like any other project

grunt.js

  • Integrates with r.js, the RequrieJS optimizer
  • A bit immature and in active development

Demo

Switching between minified and dev-source in a whiz, is AWESOME

Words of wisdom

Use 3rd party libraries! They're the reason JS is growing so fast.

Mind your globals – learn namespacing and why it matters

Fork projects and improve them – with Javascript it's so damn easy

npm lets you specify git repos as dependencies

Epilogue

I got an email from the lead developer at Aftonbladet...

We can't use it! You're code is from the future.

Don't we all love our bastard child Internet Explorer!

The realistic one


<div class="shootitlive-embed" data="project=46"></div>
<script>(function(){var d=document,b=d.body,s=d.createElement('script');s.src='//s3-eu-west-1.amazonaws.com/shootitlive/shootitlive.load.v1.aftonbladet.js';b.insertBefore(s,b.firstChild);}())</script>

            

<script src="//latest-cdn.com/widget.load.v1.js?project=13&client=greenfield" async></script>
            

Thanks for listening

  1. Find this presentation at github.com/emilisto/sthlmjs
  2. A complete skeleton for an asyncronously loaded RequireJS widget can be found at github.com/shootitlive/widgetloader
  3. Blog post that inspired this talk at http://shootitlive.com/blog