Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shim not working? #358

Closed
ianstormtaylor opened this issue Jun 29, 2012 · 21 comments
Closed

shim not working? #358

ianstormtaylor opened this issue Jun 29, 2012 · 21 comments

Comments

@ianstormtaylor
Copy link

i'm not sure if it's not working or i'm not getting the API right. here's what my paths looks like:

paths: {
    // RequireJS plugins
    text   : 'libs/requirejs/plugins/text-2.0.0',
    jadify : 'libs/requirejs/plugins/jadify-0.0.1',

    jquery     : 'libs/jquery/jquery-1.7.1',
    underscore : 'libs/underscore/underscore-1.3.1',
    backbone   : 'libs/backbone/backbone-custom.min',
    d3         : 'libs/d3/d3.v2',
    hephaestus : 'libs/hephaestus/hephaestus.min',
    prometheus : 'libs/prometheus',
    jade       : 'libs/jade/jade'
},

and here's my shim:

shim : {
    underscore : {
        exports : '_'
    },

    backbone : {
        deps : ['underscore', 'jquery'],
        exports : 'Backbone'
    },

    hephaestus : {
        deps : ['backbone'],
        exports : 'Hephaestus'
    },

    d3 : {
        exports : 'd3'
    },

    prometheus : {
        deps : ['d3', 'backbone'],
        exports : 'Prometheus'
    },

    jade : {
        exports : 'jade'
    }
},
  • Hephaestus is a Backbone plugin.
  • Prometheus is a D3/Backbone plugin.

but if i put in breakpoints, hephaestus.min gets run before backbone-custom.min ever does.

@ianstormtaylor
Copy link
Author

and then a further question. if i have a scenario where i wanted to apply a patch to Backbone and have it always applied when I require backbone, how would i do it? or is that not possible?

@ianstormtaylor
Copy link
Author

sorry one more addition, in anonymous define calls requiring backbone and underscore are both returning null instead of the libraries:

define([
    'underscore', 
    'backbone'
], 
function(_, Backbone) { //both of these args are null

@jrburke
Copy link
Member

jrburke commented Jul 2, 2012

The only thing I can think of is to make sure you are using requirejs 2.0.2, at the very least 2.0, since shim is new for 2.0. But at first glance it looks correct. Feel free to pass me a test link though for more thorough debugging. The link can be sent to me off-list.

@chchrist
Copy link

chchrist commented Jul 4, 2012

I have a problem with dependencies too. Shimmed scripts are loaded before their shimmed dependencies. Running 2.0.2

@chchrist
Copy link

chchrist commented Jul 4, 2012

I don't know if it is relevant but the scripts I am trying to load are inside a self executing function

(function(){})()

@jrburke
Copy link
Member

jrburke commented Jul 4, 2012

@chchrist an example that I can try would be helpful. The self executing functions should not be a problem. The point of shim is to be able to use older scripts that do not call define().

I have some unit tests for shim, and I believe it is used in a backbone boilerplate, so the general mechanism works. The issue is likely an edge case I am not aware of.

One caveat, mentioned in the docs (near the end of that section): if these errors are occurring after a build, and because one of the shimmed libraries is excluded in the build, that will fail -- for the shimming to work, all the shimmed libraries need to be included in the built file.

@chchrist
Copy link

chchrist commented Jul 4, 2012

I'll upload an example somewhere to test. I have the problem before the build

@kodemi
Copy link

kodemi commented Jul 7, 2012

I have problems with shim too.
Here my test project (it can be run without web server) : http://www.filedropper.com/requirejs-test
It uses Backbone.js, Underscore.js, jQuery, jQuery Mobile.
My main.js script:

require.config({
paths: {
    'jquery.mobile-config': 'libs/jqm/jquery.mobile-config',
    jquery: 'libs/jquery/jquery',
    'jquery.mobile': 'libs/jqm/jquery.mobile',
    Underscore: 'libs/underscore/underscore',
    Backbone: 'libs/backbone/backbone',
//    jquery: 'http://code.jquery.com/jquery-1.7.2.min',
//    'jquery.mobile': 'http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min',
//    Underscore: 'http://underscorejs.org/underscore-min',
//    Backbone: 'http://backbonejs.org/backbone-min'
},
shim: {
  'Underscore': {
      exports: '_'
  },
  'Backbone': {
      deps: ['Underscore', 'jquery'],
      exports: 'Backbone'
  },
  'jquery.mobile-config': ['jquery'],
  'jquery.mobile': ['jquery', 'jquery.mobile-config']
}
});

require(['app', 'jquery.mobile'], function(App) {
  App.initialize();
});

I need some settings for jQuery Mobile, so I have additional jquery.mobile-config module:

define(['jquery'], function ($) {
    console.log('jquery.mobile-config');
    $(document).on('mobileinit', function () {
        console.log('mobileinit');
        $.mobile.ajaxEnabled = false;
        $.mobile.linkBindingEnabled = false;
        $.mobile.hashListeningEnabled = false;
        $.mobile.pushStateEnabled = false;
        $.mobile.autoInitializePage = false;
        $('div[data-role="page"]').live('pagehide', function (event, ui) {
            $(event.currentTarget).remove();
        });
    });
});

It works just fine without RequireJS optimization step and writes to browser console

jquery.mobile-config
mobileinit

But with optimization jquery.mobile-config module doesn't execute and browser console is empty.
So dependencies for jquery.mobile doesn't work as expected.
Here is my config file for optimization:

({
    appDir: 'src',
    baseUrl: '.',
    dir: 'build',
    mainConfigFile: 'src/main.js',

    name: 'main'
})

I found three ways to fix problem.

  1. Include 'jquery.mobile-config' in main.js require dependencies list:
require(['app', 'jquery.mobile-config', 'jquery.mobile'], function(App) {
  App.initialize();
});
  1. Ugly way. jQuery Mobile invokes define function with one dependence:
define( [ "jquery" ], function ( $ ) {
            factory( $, root, doc );
            return $.mobile;
        });

List of dependencies can be extended by 'jquery.mobile-config' entry.

  1. It works with CDN-hosted version of jQuery Mobile or if you just don't include library in optimized file.
({
    appDir: 'src',
    baseUrl: '.',
    optimize: 'none',
    dir: 'build',
    mainConfigFile: 'src/main.js',
    paths: {
        'jquery.mobile': 'empty:', // <---
    },

    name: 'main'
})

@jrburke
Copy link
Member

jrburke commented Jul 8, 2012

@kodemi: the test zip file's build config is this:

({
    appDir: 'src',
    baseUrl: '.',
    optimize: 'none',
    dir: 'build',
    mainConfigFile: 'src/main.js',
    paths: {
        jquery: 'empty:',
//        'jquery.mobile': 'empty:',
        Underscore: 'empty:',
        Backbone: 'empty:'
    },

    modules: [
        {
            name: 'main',
        }
    ]
})

which will not work -- shim dependencies need to be included in the build, but that build config does not include jquery, backbone or underscore, so that causes one type of error. The need to include shim dependencies in a build is listed in the shim config section of the docs, although it is near the bottom of it.

However, including them does not completely fix the issue, because the shim config is ignored if the module already calls define() to set its dependencies. jquery.mobile calls define, just with a dependency of 'jquery'.

Before a build though, jquery.mobile has not been loaded, so it is not known if it calls define, so the shim config, which mentions jquery.mobile-config is used, an jquery.mobile-config is loaded.

However after a build, jquery.mobile is in the file and its define is seen, so the shim config is ignored. Since no other module in the build file depends on jquery.mobile-config, that module is not executed. That explains the after build behavior.

I would think the way to fix it: remove the shim config for jquery.mobile, and then have your main.js require() call also load jquery.mobile-config. Also, jquery.mobile.config does not need a shim config, since it calls define() itself.

There also seems to be a bug in jquery mobile that will be (is?) fixed in 1.1.1 for jquery mobile, some details here (read the group thread mentioned in the bug):

#342

@jrburke
Copy link
Member

jrburke commented Jul 8, 2012

If anyone has a test case that shows shim failing, please feel free to point me to it. So far, I have not seen something that is a bug in shim, but more about the restrictions in the situations where it is useful. Those scenarios are discussed in the shim config doc section.

I am going to close this ticket for now, but please feel free to continue any discussion, and post any test cases. We can reopen or file specific bugs as needed.

@jrburke jrburke closed this as completed Jul 8, 2012
@aaronjensen
Copy link

FWIW the limitation of shims that they their dependencies cannot be excluded in a build is not incredibly clear from the docs. It's in a CDN loading paragraph at the very end. I wasn't doing CDN loading so I didn't think it applied to me until I got here and read this thread. Something explicit about multi-file builds would be helpful I think.

@jrburke
Copy link
Member

jrburke commented Mar 27, 2013

Thanks for the feedback, I added this info to that section:
2da8e22

@aaronjensen
Copy link

No problem. I can't quite wrap my head around how doing an outer require would work in an optimized build like you mention in that section. The outer require would actually have to happen outside of the file that needs it, so if you had a common module with say, jquery and a page1 module (that excludes common) that needed jquery.foo which depended on jquery, you'd actually need to also have a page1_wrapper that excluded both common and page1 and did the outer common require with inner page require. That's pretty gross. Or am I missing something?

Is there a reason you can't wrap shimmed scripts in a require automatically? Yes it would be without assignment, but at least those things would be ensured to be requried. I know the optimizer puts them in the appropriate order so this wouldn't be necessary if they were all in the same file... but in this case they aren't.

@aaronjensen
Copy link

What would be even better (and i should probably open another issue for this or start a mailing list discussion, let me know if you'd like me to) is if the optimizer was smart enough to do an outer require around everything for anything that is excluded and actually needed. As it is now in the multipage example you need to do require(['common'], function() { require(['page1_stuff'], function() {...}) }) which means you need to put the page1_stuff in an includes directive and you run into these issues w/ shim...

@jrburke
Copy link
Member

jrburke commented Mar 28, 2013

Shim config tries to preserve the scope of the scripts being shimmed, because some make global names by just doing var something = .... If a require() function wrapper is placed around them, then that breaks the library, hides the global it was trying to make. Shim config is just that, a shim, and will always be inferior to proper define()'d modules.

If you want to avoid an extra script to have the nested require() calls, put them inline in the HTML as shown in this example-multipage-shim example:
https://github.com/requirejs/example-multipage-shim/blob/master/www/page1.html

@aaronjensen
Copy link

That makes sense about the globals, thanks.

And thanks for the example, that's helpful and a good idea.

@tobymackenzie
Copy link

Could you do something like this to wrap and capture them for an r.js build?

define('{{defineName}}', {{dependencies || []}}, function({{dependencyNames}})){
    var {{exportName}} = null; //--ensure {{exportName}} is local
    {{scriptContents}}
    if(window.{{exportName}} !== {{exportName}}){
        if({{exportName}} !== null){ //--handle module with locally scoped export
            {% if must export global %}
            window.{{exportName}} = {{exportName}};
            {% endif %}
        }else{ //--handle module that attaches to window explicitely
            {{exportName}} = window.{{exportName}};
        }
    }
    return {{exportName}};
});

{{defineName}} being the name the module is defined as and {{exportName}} being the name it is configured to export in the shim configuration. The {{dependencyNames}} perhaps might be hard, either:

  • get global names from other shimmed dependencies
  • require a shim option to be specified
  • leave empty and rely on globals

For items without an {{exportName}}, the callback would consist only of {{scriptContents}}.

@jrburke
Copy link
Member

jrburke commented Feb 17, 2014

Ticket #623 introduces a wrapShim option that tries to wrap. One the use cases to solve is to set this in the shimmed script to the global as some scripts use that to refer to the global space.

@tobymackenzie
Copy link

Is that the right ticket number? I don't see anything wrapShim related there. I guess this would be a problem with strict mode but fine otherwise. Maybe apply, call, or bind could be used. Like:

define('{{defineName}}', {{dependencies || []}}, function({{dependencyNames}})){
    var fn = function(){
        var {{exportName}} = null; //--ensure {{exportName}} is local
        {{scriptContents}}
        if(this.{{exportName}} !== {{exportName}}){
            if({{exportName}} !== null){ //--handle module with locally scoped export
                {% if must export global %}
                this.{{exportName}} = {{exportName}};
                {% endif %}
            }else{ //--handle module that attaches to window explicitely
                {{exportName}} = this.{{exportName}};
            }
        }
        return {{exportName}};
    };
    return fn.apply(window, arguments);
});

@jrburke
Copy link
Member

jrburke commented Feb 19, 2014

Sorry, wrong repo, thought I was in r.js. Here is the wrapShim:
requirejs/r.js#623

@tobymackenzie
Copy link

Cool. Glad that's going in. I will give it a try.

machristie added a commit to machristie/fmr-mobile that referenced this issue Sep 6, 2014
Shim dependency doesn't work with the optimized build.
See requirejs/requirejs#358
for some more info.  This seems like the best way and what
jburke recommends
requirejs/requirejs#358 (comment)
machristie added a commit to machristie/backbone-jquerymobile that referenced this issue Sep 7, 2014
Shim dependency doesn't work with the optimized build.
See requirejs/requirejs#358
for some more info.  This seems like the best way and what
jburke recommends
requirejs/requirejs#358 (comment)

Conflicts:
	js/main.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants