public
Last active

possible jquery plugin boilerplate

  • Download Gist
basic.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Basic approach. Does not try to register in
// a CommonJS environment since jQuery is not likely
// to run in those environments. See next file
// if you want to opt in to CommonJS too.
 
(function(factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else {
// Browser globals
factory(jQuery);
}
}(function($) {
$.fn.myPlugin = function () {};
}));
includecommonjs.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Includes registering in a CommonJS environment,
// but it is unlikely jQuery will run in a CommonJS
// environment. See other file if you do not want
// optional CommonJS registration.
 
(function(factory) {
if (typeof exports === 'object') {
// Node/CommonJS
factory(require('jquery'));
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else {
// Browser globals
factory(jQuery);
}
}(function($) {
$.fn.myPlugin = function () {};
}));

I think you missed a ')' there on line 14?

er... also! thanks a bunch!

Ah, thanks there was a ) missing, at the end of the file. I updated the gist.

What is the point of using root.jQuery in the else as it is not used for the exports and define? Do you gain anything by passing in the this as root if you only use it for the one reference to jQuery?

Would it not be simpler to remove the root argument and not scope the jQuery reference?

@Zoramite: in 'use strict' mode if you just use this.jQuery for that else block, the this in that case would not be the global object, so the global object needs to be passed in to the function.

@jrburke: it makes sense that you are referencing the global by using the root.jQuery. My question is why you don't use just jQuery without the root.? Then you wouldn't need to pass in the root argument.

Or, if you want to keep the root, why not do root.exports and root.define as well as the root.jQuery to make it more consistent since the define and exports are also global objects?

@Zoramite, good question. This template comes from a branch of Backbone where I added optional AMD registration, and as part of the Backbone code, they also allowed using jQuery alternatives like Zepto and Ender. So that else block looks something like the following -- actually a bit more complicated, but for jQuery it is basically this:

factory(root.jQuery || root.Zepto || root.ender)

If just jQuery was used instead of root.jQuery, an error would be thrown if jQuery was not defined in the page, and the other options would not be reached.

However, if you did not want to support those alternatives to jQuery, then you could get by with just jQuery.

Another reason to use just jQuery is if you wanted to support wrapping the above plugin in a closure that also included a private copy of jQuery. In that case, referencing the root.jQuery would be incorrect. That is also the specific reason for not using root.exports and root.define. It is common for those variables to be provided as part of a function wrapper around the code.

Great questions, and good to have the discussion of these variations. When I move this code to the umdjs effort, these variations are definitely worth mentioning, and I would probably start with your suggestion to just uses jQuery then talk about the root option for supporting Zepto and Ender.

@jrburke, thanks for the details! I still feel like a JavaScript noob. I didn't even know Zepto and ender existed. :(

For simplicity, I would go with the most concise version that doesn't pass root as the default and have an alternate example that shows the root argument with the jQuery alternates and/or how you would add a custom version of jQuery, etc.

I'm guessing that 95% of the people that use this as a boilerplate probably wouldn't know/care about the extra things that you could do and just want an easy way to make it compliant with the AMD registrations. And the 5% that do care would look at the alternate example and probably know enough to add in their own definitions as part of the wrapper/additional arguments.

Since stateful plugins require more code complexity, passing the plugin as an argument to the top closure becomes difficult to read. I've created a fork that defines the plugin's constructor, prototype and factory as local vars. The factory function provides the closure to add the plugin to jQuery's prototype. I've also used a leading ! instead of wrapping with parens. I find keeping track of wrapping parens on immediately invoked functions difficult.

https://gist.github.com/1372424

Any thoughts, or issues you think this might have?

@ryanfitzer, That looks similar to my original idea.

I'm still not sure which one I like better. It seems the code having it all in one closure with local variable for the plugin's factory etc is easier to read. But the example from this gist seems a little more concise which I like.

As for the ! that is an interesting way of executing the function, I hadn't actually seen that used in an of the JS code I have seen. :) That does seem like a personal preference. I like the fact that it has a parens around the function since I know what that signifies.

I was trying to try this syntax out today and I ran into a bit of an issue. I'm not sure if it is an issue with how we are using the factory or with the RequireJS library itself.

I changed my gist to reflect the syntax used here and to show the problem I am seeing:

https://gist.github.com/1371054/a8413aca42314b32e2c1949536c41dc430db95f4

In index1.htm I am using it without any dependencies and it runs as expected: In test1. then In index1..

In index2.htm I changed the test.js to have a dependency on jquery and the index is executing before the test 'plugin' and logs: In index2. then In test2..

Is this expected behavior and I am just missing something?

@Zoramite it fails because index2 is asking for the 'scripts/test2' module but that file defines a named module called 'test'. If you use the paths config to map 'test': 'scripts/test2' then ask for require'test' it should work.

I updated the gist to not use root as I agree, it is simpler. I also changed it to register an anonymous module. If you did that in your gist, then you would not need the paths config for 'test'.

@ryanfitzer: that is definitely a way to go. It requires two levels of indent to start your factory logic, where the above is just one level of indent. The feedback I have gotten so far is that people generally prefer to have less indentation for module boilerplate. However, I can definitely see it as a style preference.

I previously did a "put the adapters at the bottom" approach in this file. However, at least when I initially tried that for my backbone branch, the feedback I got was that the tertiary to pass in define seemed unusual, and that version relies on the AMD loader to use Function.prototype.toString() to find the require('') dependencies. And for backbone, since it wanted to get root/the browser global to find a previous Backbone, it ended up needing another level of indentation.

@Zoramite

But the example from this gist seems a little more concise which I like.

Agreed that it is more concise. Mine is different (but related) in that it addresses the needs of more complex plugins. Ones that require larger APIs and can maintain separate states for each collection they're called on in a page.

As for the ! that is an interesting way of executing the function

It's become quite popular, but I've adopted it due to its readability. Like an opening paren, but more so, it signifies that the function will be immediately executed. Ending any function with }() already does a good job of signifying immediate execution and using an extra paren becomes visually redundant.

I hadn't actually seen that used in an of the JS code I have seen. :)

I first saw it used in Qwery.js.

Looking at Qwery again though, I see they have reorganized it into the same pattern as above. Looks a lot more readable than I'd expected. It visually separates the exporting business from the plugin business much better than mine. I've updated my gist to the above pattern. I guess I just needed to see an example with a lot more code to be persuaded :)

@jrburke I have seen the light :)

@jrburke, I tried to change the names of the modules to match the filename and that didn't change anything. I didn't try to add it to the paths. When I made it into an anonymous module it worked as expected. Seems kind of strange that having it as a named module would cause the dependency to fail, even when it matches the name of the file.

I just added a version that does not do optional CommonJS registration, since jQuery is unlikely to run in those environments, but there is also a version with optional CommonJS registration.

@Zoramite, I changed test2.js to be a named module with name 'scripts/test2' and then in index2.htm require 'scripts/test2' and then your previous gist works for me.

@jrburke, I guess that makes sense. It it using the full path as the module name.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.