Freeze Module Id #458

Open
jeffrose opened this Issue Sep 12, 2012 · 12 comments

Comments

Projects
None yet
5 participants

Is it possible to freeze a module id? That is, once an id has been set either by define() or a path entry, prevent a future define() from overwriting it.

For example, I have jquery setup within my development stack but often have to deal with 3rd-party code that may also contain an AMD-aware version of jQuery. Once their code is included on the page, jquery gets redefined.

Owner

jrburke commented Sep 12, 2012

If you grab the module ID reference via a define/require call, it should be the first module that claims that module ID. However, the jQuery global will change, it will be the last loaded version. So this should give you the first jQuery 1.7+ that is loaded on the page:

define(['jquery'], function ($) {
    //Local $ here is first jquery loaded.
});

If you are not seeing that a test case or more info would be helpful. Also note that it is whatever jQuery finishes loading first that gets the slot, which may not be the one that is first added to the page via a script tag -- depends on network load/execution order.

I threw together a quick test to illustrate the problem...

<script type="text/javascript" src="require.js"></script>
<script type="text/javascript">
    require.config( {
        "paths": {
            "jquery": "my_jquery"
        }
    } );
</script>
<script type="text/javascript" src="jquery-1.7.2.js"></script>
<script type="text/javascript">
    require( [ 'jquery' ], function( $ ){
        $( function(){
            console.log( $.fn.mine );
        } );
    } );
</script>

... where my_jquery.js contains:

define( [ 'namespace', 'jquery-1.8.1' ], function( namespace ){
    namespace.jQuery = namespace.$ = jQuery.noConflict( true );
    namespace.jQuery.fn.mine = true;

    return namespace.jQuery;
} );

If you remove jquery-1.7.2.js from the page, you get true, otherwise you get undefined.

Contributor

neonstalwart commented Sep 12, 2012

as expected... the first one won.

Obviously the first define() won, but moreover a module basically overrode the configuration.

Admittedly this is not a common, happy path scenario, but in large enterprises things are often neither "common" nor "happy."

It's easy to say you shouldn't do what my example illustrates, but I also think there should be a way to enforce the configuration.

Contributor

neonstalwart commented Sep 13, 2012

unfortunately, there's actually no way for the loader to know for certain which jquery is calling define so it can't tell that the "wrong" one called it first.

maybe you can suggest a way that what you want could be achieved?

Granted that there is no way for the loader to know who called define().

Maybe the problem is less about freezing a module id and more about enforcing how a module is defined. Provide an async-only option on a path/module entry that would cause the loader to ignore define() for that id unless it was retrieved as a dependency in require() or define().

Owner

jrburke commented Sep 13, 2012

Even if there was an option to say "only listen to things that were required" it would still be possible to a scenario where the one you may not want gets in there, depending on how that other thing is loaded. One way to solve this particular situation would be to move the jquery.js script tag before the require.js tag. In that way, define() will not be defined yet, so the first jquery define() will not happen.

I agree the implementation above is not by any means ideal but it does illustrate how little authority the configuration carries. Basically one stray script tag broke it. I hoping there was a way to protect and enforce it in order to guard against the stupid mistakes that can easily happen.

Owner

jrburke commented Sep 17, 2012

@jeffrose agreed, we'll need something like the ECMAScript Harmony Module Loaders API though to properly segment code into isolated load environments. The other option is to somehow expand the config and loader to only allow specific versions of a library, but it gets unwieldy to try to work out how to know the loaded version for each file, makes the loader bigger too. Particularly since it would just be a stop-gap measure, only prevents some types of errors, where a Module Loader support in the language would be better. At least that is my initial read of it.

You could allow a kind of interceptor functionality that takes the exported value and returns a boolean. Leave working out the loaded version to your library users, e.g.

require.config({
  intercept : {
    "jquery" : function( $ ) {
      // Only accept version 1.7.2
      return "1.7.2" === $.fn.jquery;
    }
 });

This would only actually register and define jQuery if the version was a match.

(Come to think of it: in the case of jQuery an author could expand on this and use it as a general extensibility point to also include things such as calling the noConflict method.)

I am working on a loader to solve this particular problem. Would love some feedback:
https://github.com/leachryanb/getJQuery-require

Owner

jrburke commented Nov 8, 2012

@rjgotten there may be something to that intercept approach. I'll put this for consideration for a 2.2, but it may not make it. I'm a bit concerned about modules that have dependencies for example -- those dependencies would be loaded first and executed, and the module that may be intercepted has to have its factory function already called.

But maybe this more of a limited feature for something that does not have dependencies, like jquery. Still not sure if it implies more of contract and support for prohibiting just the execution of the module though. Needs a bit more thought.

@leachryanb a loader plugin is definitely a good way to go about this using the toolset of today.

jrburke added the 2.x? label Mar 15, 2016

@jrburke jrburke modified the milestone: 2.2.0 Mar 15, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment