BreezeJS and RequireJS not working as expected
Asked Answered
S

3

7

I am integrating breezeJS into an existing requireJS project which already uses knockoutJS. I ran into a couple of problems.

The first was that breeze is unable to load the Q library unless i include it on my html wrapper as a <script> tag, not as a loaded AMD dependency. In my project i am trying to keep my code down to a single script tag, so this isnt ideal.

The second issue is that breezeJS is unable to load knockout. In my main.js I have defined a path for knockout to be:

knockout: '../libs/knockout/knockout-2.2.0',

(I do this because I like knowing for sure that I am not accessing a global ko)

However when i added breeze to my project, breeze wasn't able to load my knockout library. Looking into the breeze code i can see that it has been hardcoded to load the knockout library as ko.

Not wanting to change all of my code i found that i could add my AMD loaded knockout library to the global window object as window['ko']. But this feels like quite a bodge. Also weirdly adding Q this way and removing the <script> tag didn't work, as i think Q is required too early in the application's lifecycle, even before i get to pollute the global - i did nest my require() calls in main.js but that hid the majority of my application files from the build process so i abandoned that approach.

How can i include Q and knockout and breeze in my project and still use a single line <script> tag, at the moment I am having to include Q as a separate <script> tag and pollute the global to get breeze and knockout to play nicely.

I am using quite a few other libraries in my project and none of them have been this difficult to integrate in.

Any help is much appreciated

Cheers

Gav

EDIT: Here is my full require config:

require.config({
    /**
    * shims are for 3rd party libraries that have not been written in AMD format.
    * shims define AMD modules definitions that get created at runtime.
    */
    shim: {
        'jqueryUI': { deps: ['jquery'] },
        'jqueryAnimateEnhanced': { deps: ['jqueryUI'] },
        'jqueryScrollTo': { deps: ['jquery'] },
        'touchPunch': { deps: ['jquery'] },
        //'Q': { exports: 'Q' },
        //'breeze': { deps: ['Q', 'knockout'], exports: 'breeze' },
        'path': { exports: 'Path' },
        //'signalR': { deps: ['jquery'] },
    },

    paths: {
        //jquery
        jquery: '../libs/jquery/jquery-1.7.2.min',
        'jquery.adapter': '../libs/jquery/jquery.adapter',

        //jquery plugins
        horizontalNav: '../libs/jquery/plugins/horizontalNav/jquery.horizontalNav',
        jqueryUI: '../libs/jquery/plugins/jquery-ui/jquery-ui-1.9.2.custom',
        jqueryAnimateEnhanced: '../libs/jquery/plugins/animate-enhanced/jquery.animate-enhanced',
        touchPunch: '../libs/jquery/plugins/touch-punch/jquery.ui.touch-punch.min',
        //jqueryScrollTo: '../libs/jquery/plugins/jquery-scrollTo/jquery.scrollTo.min',
        //reveal: '../libs/jquery/plugins/reveal/jquery.reveal',
        //opentip: '../libs/jquery/plugins/opentip/opentip-jquery',

        //RequireJS
        domReady: '../libs/require/plugins/domReady',
        text: '../libs/require/plugins/text',
        async: '../libs/require/plugins/async',
        depend: '../libs/require/plugins/depend',
        json: '../libs/require/plugins/json',
        noext: '../libs/require/plugins/noext',

        //coffee-script
        'coffee-script': '../libs/coffee/coffee-script',
        cs: '../libs/require/plugins/cs',

        //Path
        path: '../libs/path/path.min',

        //Knockout
        knockout: '../libs/knockout/knockout-2.2.0',
        knockoutTemplateSource: '../libs/knockout/ko.templateSource',
        knockoutValidation: '../libs/knockout/ko.validation',

        //breeze
        Q: '../libs/breeze/q',
        breeze: '../libs/breeze/breeze.debug',

        //Signals (Observer pattern)
        signals: '../libs/signals/signals',

        //SignalR - Push notifications
        signalR: '../libs/signalR/jquery.signalR-0.5.2.min',

        //utils
        logger: 'common/logging/logger',
        tinycolor: '../libs/tinycolor/tinycolor',
        composing: 'common/composition/composing',

        //app specific
        BaseWidgetViewModel: 'app/view/core/BaseWidgetViewModel',

    }
});
Stomacher answered 18/12, 2012 at 16:41 Comment(1)
Answer coming. I have most of it. Missing one piece. Stay tuned.Hasa
H
3

Sorry for the (holiday) delay in addressing your question.

I understand and appreciate your goal of eliminating every script tag except the one for RequireJS. This goal is not easily obtained in my experience.

You did uncover a Breeze defect. Breeze internally is referencing a different 'require' function than your application's 'require' function. It doesn't know about the application 'require' function or its configuration. Therefore, when you omit the Q script tag, Breeze cannot find Q ... no matter how well you configure the application 'require'.

We'll have to fix that. I'll add a comment here when we do.

Meanwhile you'll must use a script tag for 'Q' and for 'KO' and you must put those tags above the script tag for RequireJs. Please continue to use require for your application scripts.

Unfortunately, you have other problems that are unrelated to the dual-require-function problem.

First, I think you will always have trouble keeping KO out of the global namespace ... and this has nothing to do with Breeze.

In KO's AMD implementation (at least when I last looked), KO is either in the global namespace or in the require container; never both. Unfortunately, many useful plug-ins (bindingHandlers, debug.helpers) assume it is in the global namespace; you won't be able to use them if you load KO with require.

You can load Knockout in script tags before Require and then shim KO into the Require container during configuration. Your configuration might look like this:

define('knockout', [], function () { return window.ko; });

The jQuery developers realized this would be a problem. Too many good plugins assume jQuery is in the global namespace. Rather than be purists, the jQuery maintainers (correctly in my view) put jQuery in both the Require container and in the global namespace.

Second, many other useful libraries do not support Require. Your code can be written as if these scripts were designed for require. I still think the easiest, clearest way to handle this is specify them in script tags. Then you can add them into the container by defining them with define calls as shown above. With this shimming, your own modules can be consistent in relying on Require for service location and dependency injection of these libraries.

Third, I think you have a bug in your configuration. For reasons that escape me, you decided to refer to the Knockout module as "knockout" in your Require universe.

Fine ... but you shouldn't expect Breeze to anticipate that module name when we fix it to use the application's require container. Breeze will be looking for KnockoutJs under its standard name, "ko". Breeze will not find it under your preferred name, "knockout".

This is not a big deal. If you're wedded to the "knockout" name, you can make the same instance available under both names. I'm pretty sure the following shim line will work when added to your configuration code:

define('ko', ['knockout'], function (ko) { return ko; });

Again ... this will be effective only after we fix our Breeze require bug.

John Papa had to address many similar issues in Code Camper; you might look at what he did there, especially at main.js.

Hasa answered 29/12, 2012 at 6:42 Comment(11)
Thanks for the comprehensive response. I was never aware that require JS path must be specific values, I liked the idea of setting my paths to values other than the globals to ensure I only use the injected modules and not the globals. I inject jquery as "jquery" and not "$" for this reason - so I followed suit with knockout, choosing not to inject it as "ko".Stomacher
I did ask a question about AMD and globals on the RequireJS google group and the answer i got, which is in line with my own thinking is to manually wrap libraries groups.google.com/forum/#!topic/requirejs/8SsxJUcQD3g I've not experienced any other problems with 3rd party libaries using requireJS in this way.Stomacher
i tried a couple of shims for knockout, require.config.shim and manually shimming into a 'ko.shim.js' as you suggested, but neither worked so I have returned to adding it to the global, before breeze i had no problem using a non global knockout in my (large) project. I look forward to the Q fix but for the timebeing i can live with having two script tags in my html wrapper. It is a shame that i have to implement two work arounds to use Breeze, but it will add so much to my project that i am willing to make these compromises.Stomacher
We hear you. We will fix this. It's not a huge priority to be honest but we will get there in the not-distant-future. Happy to hear that Breeze value trumps this headache.Hasa
I suggest you... "Make breeze play nice with AMD loaders like require.js" :) breezejs.uservoice.com/forums/173093-breeze-feature-suggestions/…Reedbuck
We agree. A fix is on our backlog. We'll get to it. It's just not top priority. Any reason why it should be? How is this defect impeding your app development now or any time soon?Hasa
One of the reasons: I would like to use CDN for ko. If I put ko in <script> it's harder to provide fallback in case CDN is blocked/unavailable. With RequireJs it's enough to configure a path like this ko: ['ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1', '/bundles/knockout'] and turn require.config({enforceDefine: true}). I agree - this can be worked around. On the other hand it's a shame that such a beautiful framework misses something so basic.Reedbuck
I want it too. But, as I said, it's not blocking. Getting from a CDN is something you worry about when you're about to deliver to production. It's a late optimization ... one you don't have to think about ahead of time. By the time you "need it", we'll have it.Hasa
@Ward, note that boilerplate yeoman project 'ko', that was presented by Steve Sanderson (vimeo.com/97519516), creates knockout dependency with name 'knockout', not 'ko'. So 'ko' - is not standart name for amd module.Alcyone
@Ward, Knockout plugins such as knockout-projections and knockout-validation, created by Steve Sanderson and Eric Barnard refer to knockout amd module as 'knockout', so it's breezejs that lives in it's own universe with 'ko'. I still hope this referencing issue will be fixed.Alcyone
@blzkovicz - hard to say we're all alone. If you load the Knockout JavaScript file in a browser you will find that it sets the variable window.ko, not window.knockout. Inside the library it is everywhere referred to as ko. The word 'knockout' only ever appears in comments. I still don't believe this is a big deal when a perfectly satisfactory workaround is at hand. I'm scoring this "very low" on our priority scale.Hasa
S
2

This should be fixed as of Breeze v 1.2.4. We no longer use an internal implementation of "require".

Sandlin answered 18/3, 2013 at 22:33 Comment(0)
O
0

Sounds like your require config is not right. Could you post the code of your require config? I am curious:

keep my code down to a single script tag

why that?

Edit: Now i get it. Still sounds like the require config is wrong.

Ong answered 18/12, 2012 at 17:51 Comment(1)
i've added my require config - everything else works fine for me but perhaps i have done something weird? BTW: I want to keep my code down to a single tag <script data-main='...' src=...> and manage all inter-script dependencies with RequireJS - as thats its job.Stomacher

© 2022 - 2024 — McMap. All rights reserved.