1

I'm using RequireJS, and trying to pack up a jQuery widget for easy usage into one file. Inside the widget's JavaScript code are a certain number of non-UI functions that don't call $-anything, that I'd like to export and be able to use on the server side.

(The shared routines that don't depend on jQuery used to be in a separate module called client-server-common.js. But like I said, I'm looking to reduce the number of files...and there's no real reason to be hung up on including the dead code for the widget on the server. So the widget can just subsume the common code.)

I'd like my only dependencies for the widget to be jQuery and underscore, with jQuery optional (and it degrades into what was client-server-common.js in that case). So the interface I'm looking for would be like:

require(['jquery', 'underscore'], function ($, _) {
    var exports = {...};

    if ($) {
        // Do the stuff that only makes sense in the client
        // Totally fine if there is no jquery; in that case
        // all we probably care about are the exports
        ...
    }

    // Do the stuff for both client and server
    ...

    // Return the exported functions
    return exports;        
}

Reading up on what others have asked, I notice this answer to this question from January says "You cannot really set it optional":

requireJS optional dependency

That's not what I want. Is that the last word? :-/

It seems that I can get around this by installing the jquery NPM package, which apparently does stuff with the DOM somehow:

Can I use jQuery with Node.js?

My understanding would be that if I added this dependency and just ignored it, I'd be okay. I might even find that there was some good reason to do DOM manipulation on the server (I haven't thought enough to figure out why or how that would be useful, is it?)

So should I add the dependency for simplicity's sake, and just not use it? Or can I rig it up so that I just give a jQuery of null on the server configuration, through some magic that doesn't involve waiting for timeouts and errors and hacks? Could someone make a "jquery-null" package that somehow just came back and gave you a {} or null for jQuery to smooth over situations like this?

Advice appreciated! :-/

Community
  • 1
  • 1

2 Answers2

1

The answer you mention says "You cannot really set it optional" and then gives a solution to operate even in the absence of modules. You can use an errback that will do nothing if running server-side.

The following code assumes that RequireJS' require call is available as requirejs. When loading RequireJS in a browser, this is the default (that is, after loading RequireJS requirejs === require is true.) Server-side, you would have to make it available with something like:

if (typeof window === "undefined")
    var requirejs = require('requirejs');

(The above code would obviously fail if there is something in Node that sets window globally. I've never run into this problem.)

Once the above is taken care of we can do:

requirejs(['underscore'], function (_) {
    var exports = {...};

    requirejs(['jquery'], function ($) {
        // This will execute only if jquery is present.

        // Do the stuff that only makes sense in the client
        // Totally fine if there is no jquery; in that case
        // all we probably care about are the exports
        ...
    }, function (err) {
        // This will execute if there is an error.

        // If server-side, do nothing. If client-side, scream!
    });

    // Do the stuff for both client and server
    ...

    // Return the exported functions
    return exports;        
});

The answer you mentioned and RequireJS' documentation (which I linked above) mention checking the id of the failed module and undefining it. I'm quite certain that you would not need to undefine it since you won't try to load it again from a different place. Checking the module id would be a way to future-proof your code if someday jQuery depends on some other thing. Right now, loading jQuery just loads jQuery so if there is a failure it cannot be any other module than jQuery.

I would not include the actual code of jQuery server-side unless I'd have an actual substantial reason for it. What I would do if I wanted to get rid of the errback, for whatever reason, would be to have a build of my code for server-side use that includes a fake jQuery module. Something like:

define(function() {
    return "I'm totally fake";
});

And then test it:

requirejs(['jquery'], function ($) {
    if ($ !== "I'm totally fake") {
        // Do the real deal.
    }
});

If you do eventually need jQuery server-side, you'll have to also install something like jsdom. It used to be that installing jQuery with npm install jquery would include jsdom in the installation but I think this has changed recently.

Louis
  • 146,715
  • 28
  • 274
  • 320
  • Thanks Louis, this looks good (or as good as this stuff can look). However, there's a problem... the error handler does not seem to run. On the node side I just get *"Tried loading "jquery" at /home/hostilefork/Projects/blackhighlighter/jquery.js then tried node's require("jquery") and it failed with error: Error: Cannot find module 'jquery'"*. The error function is not called; does the node version of requirejs not call the error handlers? – HostileFork says dont trust SE Mar 19 '14 at 04:16
  • The fake method works, though--although you have to put it in an object, e.g. `return {isFakeJquery: true};` This seems like the cleaner method between the two in any case, as it doesn't break the "expected" pattern. – HostileFork says dont trust SE Mar 19 '14 at 04:23
  • You may have been using Node's `require` by mistake instead of `requirejs`. I had assumed that you knew the drill. I've updated my answer to show how to import RequireJS in Node and use it. I've tested both methods I suggested and they work. – Louis Mar 19 '14 at 23:25
  • Don't assume I know much when it comes to JavaScript! :-) But since this file is, by definition, loaded by both client and server... the `var requirejs = require('requirejs')` won't make sense to the browser. What you might want to update in your answer though is returning a string which doesn't seem to work. But either way, I'm accepting it--thanks for the help!! – HostileFork says dont trust SE Mar 20 '14 at 10:59
  • You're right. I lost track of the initial issue. :) I'm going to update it with a better explanation. – Louis Mar 20 '14 at 11:02
  • why not just make the fake jquery return `undefined` and test like this: `if ($) { /* jquery is available! */ } else { /* no jquery for us :( */ }` or is this not allowed? – Stijn de Witt Oct 13 '15 at 09:30
  • @StijndeWitt You could, but I prefer not to use `undefined` for this. The reason is that `undefined` can happen in JavaScript for all kinds of reasons which are not deliberate. If you try to get the value of an field that has never been set, you get `undefined`, if you forget to return a value from a function, you get `undefined` if you try to access a function's parameter not explicitly set by the caller you get `undefined`, etc. I prefer to set a definite value so that if I find that `$` is `undefined` something definitely went wrong. – Louis Oct 13 '15 at 10:39
  • @Louis Ok I guess that's personal. I myself consider `undefined` to be a perfect semantic match for an optional library not being defined ;) – Stijn de Witt Oct 13 '15 at 11:34
-1

I'm using RequireJS, and trying to pack up a jQuery widget for easy usage into one file. [..] I'd like my only dependencies for the widget to be jQuery and underscore, with jQuery optional [..] Reading up on what others have asked, I notice this answer to this question from January says "You cannot really set it optional" [..] That's not what I want. Is that the last word? :-/

No it's not the last word

This can be done elegantly and simple actually. I've described how to use an AMD module only when it's loaded in my answer to that question, but I'll repeat the gist of it here:

define(['require'], function(require){
  if (require.defined('jquery') {
    var $ = require('jquery');
    $.fn.something = function(){};
  }
});

We don't add the dependency directly, but instead manually require it only when it's already defined so we don't trigger any requests.

This form in which we declare a dependency on require, then use that inside our module is actually recommended by the RequireJS author, but my experimentation indicates that this actually also works:

define(require.defined('jquery') ? ['jquery'] : [], function($){
    if ($) {
        // Yippee, all jQuery awesomeness is available
    } 
});
Community
  • 1
  • 1
Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80
  • There's a gigantic caveat that needs to go with this answer. In the same issue you cite in your answer James Burke also says: "Note: `require.defined()` only returns true **if the module has already been loaded by other code and already defined** -- it would not work for example if they loaded your library first, then triggered a load for jQuery, or had jQuery loading in parallel with your library." (Emphasis added.) This is a significant constraint on how the application operates. Also, the 2nd code snippet will make `r.js` fail to see the dependency on jQuery. – Louis Oct 13 '15 at 10:49
  • I know, but this is essentially the same as first including the script for the OP's lib and then the script for jQuery, or put the jQuery script first but with an `async` attribute if you were to use regular script tags. That also wouldn't work. In other words, this is to be expected I think. I guess it all depends on the level of optionalness of the dependency which approach you choose. – Stijn de Witt Oct 13 '15 at 11:14