Question

I am going round and round in circles trying to understand RequireJS and how to use it to load dependencies that DON'T show up as Undefined when I try and use them in Modules.

I started with John Papa's Pluralsight "SPA with HTML5" course. What I never understood was why there was a need to specify every library in a script tag that went into a bundle when I thought the whole point of AMD in requireJS was that you handed over responsibility to it and its data-main attribute to asynchronously load what was required. What was doubly confusing about the solution presented in that course was it didn't follow the whole data-main set up documented in the RequireJS documentation.

Move on a few months and we now have the "Hot Towel" template with Durandal, a sample "service" module, a set-up that follows the RequireJS documentation and a fairly easy to understand starter app. The only thing that sounds alarm bells is the idea that most of the libraries are in the usual "scripts" folder while Durandal is in a completely separate 'app' folder along with the application logic. If I've learnt one thing from the pluralsight SPA course and playing with requireJS it's that moving stuff out of a single folder things get very messy very quickly.

Anyway, the new template works fine. Module dependencies are working great, Views and ViewModels are binding and there's a sample logger module with a module dependency on a Durandal module (ie in the app folder) that works great. It should be simple to add another simple module that uses jQuery and mockJson based on the code that's in the Hot Towel template, right? Err, no, not really.

Here's a very simple declaration for the start of my module 'dataservice.js' inside the 'app/services' folder

define(['jquery'],
function ($) {
    var init - function....

I do of course get 'undefined' for $ when I try and access it within my code block. So after reading far too much confusing literature (much of which ends up with a comment to say 'This changed in jquery xxxx so isn't relevant any more') I have the following questions:

  1. When I monitor the network traffic I can see that the jQuery library is being downloaded correctly, as is mockJSON if I add it to the 'vendor' bundle defined in BundleConfig.cs So jQuery is there and waiting in my browser, even if I haven't given it a module definition myself. To get rid of the 'undefined' reference in my module documentation seemed to imply I need to add something like the following:

    require.config({
        paths: {
            'text': 'durandal/amd/text',
            jquery: '../Scripts/jquery-1.9.1'
        }
    });
    

    (the 'text' declaration was already there. I just added the jquery alias) This causes my module to have a function rather than 'undefined' which solves part of the problem except that it causes a second copy of the script I've already seen downloaded because of the Hot Towell templates 'vendor' bundle. I don't want to be downloading two copies of jQuery for obvious reasons so how do I fix this?

  2. I've added the mockJSON library to my 'vendor' bundle and this is normally referred to using $.mockJSON I can't do anything to make this valid. Even if I hack in a reference to the script using the same process I did for jquery with a 'require.config({paths:' declaration I'm getting undefined, not to mention the 'the library is downloaded twice' problem already mentioned. How can I get the library dependency defined so that my use of that library in a module will work?

  3. I'm guessing all this pain is something to do with the fact that requireJS is about downloading asynchronous modules but jQuery and mockjson are synchronous so I have to download them by other means (the bundling hard-coded reference) but that still means my requireJS modules need a way of stating them as dependencies and nothing I've tried is working. Is my assumption about this being a syncrhonoys/asynchronous issue correct? I had hoped to find at least one sample app in Durandal that used jQuery and ideally a jquery plug-in but all I can see is module code that uses Durandal modules rather than anything else. The fact that all these libraries are outside the main root for requireJS is probably exacerbating the problem as I'm having to resort to paths with '../' strings in them to get references. Does anybody know why this structure of 'all libraries except Durandal are in one folder, but Durandal is in a completeley separate one' exists?

  4. I understand that requireJS uses 'convention over configuration' on the define command so that if I miss out a module id then it assumes an id based on the modules source file name and path from the defined root folder specified using data-main. However Durandal refers to a module called 'entrance' in its code. This module is not in the root folder but in a folder called 'Durandal/transitions/entrance.js' so I am confused as to why any reference to it is 'entrance' rather than 'Durandal/transitions/entrance'. Where is it being aliased?

  5. Finally (hoorah) I haven't grasped the subtleties of the difference between specifying dependencies as the first argument (a string array) to a define statement vs omitting those dependencies but then in the module factory that becomes the first argument specifying something like var system = require('../system') - why would I use one form in preference to another. I see the two types being mixed across the sample application and don't understand why.

P.S.: When I edit this entry I see five questions numbered 1 to 5. When I view it I see it gets rendered as 1 and then 1 to 4. Some weird HTML editor vs rendering bug that no matter what formatting I try I haven't been able to fix, so please post any comments as if there are five questions numbered 1 to 5 rather than 2 numbered 1 and three numbered 2 to 4!)

Was it helpful?

Solution

Wow that's a lot of questions!

Check out this post on the durandal google group: https://groups.google.com/forum/#!searchin/durandaljs/papa/durandaljs/Ku1gwuvqPQQ/gupgNqGIK1MJ

The interesting point from the thread is that John Papa, the author of the Hot Towel template, recommends loading all your third party libraries up front using script tags or bundles and then just using the require/AMD approach just for your own modules. Your own modules should then reference the global properties of jquery ($) and knockout (ko).

I think that if you load third party modules up front and then via require/AMD then you can get unpredictable bugs with some code using the global and some using the AMD version. (This is especially common with knockout, it seems).

The approach is not quite as architecturally clean I'll admit, but having been heads down on durandal for 5 days myself I am starting to prefer loading known third party libraries up front.

OTHER TIPS

Oh for Pete's sake! I couldn't wade through all of this.

If you have your heart set on injecting jQuery and knockout and such, put this in your main.js, around line #6, before the define that gets things going.

var root = this; // the global namespace, window

// The following are already loaded via bundles 
// which put them in the root object. Add them as services.
define('jquery', [], function () { return root.jQuery; });
define('ko', [], function () { return root.ko; });
define('breeze', [], function () { return root.breeze; });
define('Q', [], function () { return root.Q; });
define('moment', [], function () { return root.moment; });
define('sammy', [], function () { return root.Sammy; });
define('toastr', [], function () { return root.toastr; });

Satisfied? :)

Quick code answer is below. Something somewhere (presumably in Durandal) is making jQuery available without any modules that use it having to declare it as a dependency (I guess there's a define statement somewhere in Durandal code but without a specific sample it's hard to realise this. I guess very few people out there are using jQuery or plug-ins (sarcasm)). Hopefully John Papa's upcoming "Hot Towel" course will deal with some of these questions/issues. And not just jQuery itself but plug-ins too.

**The trick to solving the jQuery plugin problem, which is the real problem behind all the questions my team were asking, is the following command at the start of the main.js file pointed to by the script reference to requireJS:

require.config({
paths: {
    'text': 'durandal/amd/text',
    'mockjson': '../Scripts/jquery.mockjson.js'
},
shim: {
    'mockjson': {
        deps: ['jquery'],
        exports: ['mockJSON']
    }
}

});

Notice that mockJSON library will need to be added to a bundle (I used 'vendor' but you could add a new bundle) and that they key point here is that as it's a jQuery plugin you should not declare it as a dependency in any of your modules that require it (or at least that's what works for me).

Sorry for the length of the original questions but the reality is I have a team who want answers to these questions, can't find them on the web and at the time of writing only have the Code Camper app from John Papa to go on - an app which has a great course and is a great starting point if you want to write a lot of plumbing code to wire various libraries together (We don't. We need to be productive, although the code is a great way to UNDERSTAND what newer frameworks like Durandal are coding up for you through a much simpler 'convention over coding' approach). The problem with that course for our team is that when you dig into the code, you end up having more questions than you find answers to (eg "Why when I move libraries into their own sub-folders instead of everything in one big folder does requireJS throw out an error that says "You're loading libraries behind my back. Let me do it and don't do it behind my back", "Why does the code seem to get away with using some libraries that aren't in bundles and which a search for only shows as being in a 'more.info.txt' file whose usage/intention is never explained?"; "Why have module naming conventions that differ between a dot delimiter and a slash delimiter for no apparent reason (or at least one that's never explained?" "Why are there references in bundles to script files that aren't there in the solution (is it just unused debris not removed during evolution of the app)?" "Why has the author moved the NetEye IsBusy indicator library outside of its own library and renamed it as a script in his own 'KoLite' library?" etc etc

I'm sorry that Ward apparently doesn't think trying to UNDERSTAND how these things fit together and are working is important (which is why I asked the five questions I did - questions my whole team are asking and aren't explained in any of the 'cut and paste' answers around jquery and require we found on the web). As a trainer I'd have expected him to understand that 'give me some cut and past code from a Pluralsight course that isn't really relevant. I don't need to understand it' wasn't what I was trying to do here and sometimes if the available material doesn't answer a whole heap of questions a whole group of people are struggling with then it makes sense to try and explain what it is you don't understand. My bad!

I guess there's an opportunity here for someone to write a decent requireJS course ;-)

For what's worth for latecomers. Durandal has improved the documentation around this issue. The section "Configure Dependencies" in the page below explains the difference between registering modules using define(alias, globalVariable) and using the paths and shim strategy.

Both work but if you use path and shim, all dependencies will have to be registered in a similar way. This is specially true for plugins making use of " jQuery.fn.extend...".

http://durandaljs.com/documentation/Conversion-Guide/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top