DailyJS

Let's Make a Framework: Plugins

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials frameworks plugins lmaf documentation

Let's Make a Framework: Plugins

Posted by Alex R. Young on .
Featured

tutorials frameworks plugins lmaf documentation

Let's Make a Framework: Plugins

Posted by Alex R. Young on .

Welcome to part 50 of Let's Make a Framework, the ongoing series about
building a JavaScript framework.

If you haven't been following along, these articles are tagged with
lmaf. The project we're creating is called Turing.

Plugins

Why directly support plugins? Why not just distribute libraries that are
compatible with particular frameworks?

It turns out it can be incredibly useful to directly extend frameworks
to provide new features that work with their familiar APIs. I think the
best example of this is jQuery, but other frameworks also have some form
of plugin support. It actually varies -- some have particular components
designed with plugin APIs, and others just have community supported
plugin repositories.

I started thinking about this back when we were adding the DOM selector
engine. There's special handling for the DOM module to allow other parts
of the framework to work with chained calls off selector results. So for
example, the following is possible:

turing('p')
  .fadeIn(2000)
  .animate(1000, { color: '#ff0000' })
  .click(function() { alert('clicked'); });

The first method returns an object that has aliases to the animation and
events modules. These modules are mostly self-contained, and their
methods accept an element as the first parameter. The chaining is
facilitated simply by wrapping the original calls. This is a little bit
like jQuery.

jQuery Plugins

A jQuery plugin is just a function:

jQuery.fn.myPlugin = function(options) {
  var settings = {
    defaultValue: true
  };

  if (options) { 
    jQuery.extend(settings, options);
  }
};

Inside the myPlugin function, this refers to
the jQuery object the function was called from. And depending on the
type of plugin, jQuery objects can be returned so the results can be
chained. Settings can be handled with jQuery.extend.

The jQuery Plugin Authoring
documentation has examples of namespacing events, data, and some best
practice guidelines.

The important question, however, is what is jQuery.fn? Take
a look at this code from core.js:

var jQuery = function( selector, context ) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init( selector, context, rootjQuery );
  },

// ... Snip ...

jQuery.fn = jQuery.prototype = {
  constructor: jQuery,
  init: function( selector, context, rootjQuery ) {
    var match, elem, ret, doc; 

    // Handle $(""), $(null), or $(undefined)
    if ( !selector ) {
      return this;
    }

    // etc.

It's jQuery's prototype object -- the code that gets instantiated with
your selector and the results of a query. That's how plugins can appear
potentially anywhere in the chain: \$('selector').myPlugin.

Middleware

The idea of middleware seems to be catching on in the Node community.
We've seen this on DailyJS in Express' Connect
middleware
. Some of the
library's functions accept a parameter that is a user-supplied function.
This function has two or three parameters -- the last one is a method
(next()) that can be called to pass execution on to the next piece of middleware.

This approach means user-supplied functions can be injected into key
areas of the library. It also works well with asynchronous code.

Dependencies and Namespacing

jQuery doesn't specifically prevent plugins from overriding each other.
I've tried to keep Turing's modules self-contained, so it might be
useful to be able to express and manage dependencies. A simple format
like NPM's
package.json

could be used to express a plugin's requirements:

{
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "A useful plugin",
  "author": "Name ",
  "cdn": [
    "https://ajax.googleapis.com/ajax/libs/..."
  ],
  "repository": {
    "type": "git",
    "url": "http://..."
  },
  "engines": {
    "node": ">= 0.2.4",
    "browsers": [
      // Does this make sense?
    ]
  },
  "bugs": {
    "url": "http://..."
  },
  "licenses": [
    {
      "type": "MIT",
      "url": "http://..."
    }
  ]
}

As you might have seen on DailyJS, I often get annoyed at the amount of
jQuery plugins that come without licensing information, tests, and
documentation. Encouraging plugin authors to include this kind of
metadata from the start is a good idea.

Turing's Implementation

jQuery's simple plugin approach will play nicely with the chaining API
that we've been building for Turing. The idea of middleware interests me
as well -- it might be useful in areas where the framework needs to be
extended internally. As a bonus, I think we'll be able to use the plugin
system to fix some of the chaining code I've written internally in
Turing.