17

Suppose we need to embed a widget in third party page. This widget might use jquery for instance so widget carries a jquery library with itself. Suppose third party page also uses jquery but a different version. How to prevent clash between them when embedding widgets? jquery.noConflict is not an option because it's required to call this method for the first jquery library which is loaded in the page and this means that third party website should call it. The idea is that third party site should not amend or do anything aside putting tag with a src to the widget in order to use it.

Also this is not the problem with jquery in particular - google closure library (even compiled) might be taken as an example.

What solutions are exist to isolate different javascript libraries aside from obvious iframe? Maybe loading javascript as string and then eval (by using Function('code to eval'), not the eval('code to eval')) it in anonymous function might do the trick?

sha1dy
  • 980
  • 2
  • 11
  • 27
  • 2
    I can't stress enough how bad of an idea `"loading javascript as string and then eval it"` is, just...don't, there are more reasons coming not to do this. – Nick Craver Nov 12 '10 at 12:59
  • 2
    This is an interesting problem, recurrent in every component platform (DLLs, COM, managed code). It's not a case of _your component is broken and you need to update it to work to the latest libraries_. It's a real problem that I would like to see how it will be solved with dynamic languages. – Humberto Nov 12 '10 at 13:07
  • Nick, you are right, but by eval I meant using Function('code to eval') actually, not eval('code to eval') itself. I will update my question. – sha1dy Nov 12 '10 at 13:11

5 Answers5

9

Actually, I think jQuery.noConflict is precisely what you want to use. If I understand its implementation correctly, your code should look like this:

(function () {
var my$;

// your copy of the minified jQuery source

my$ = jQuery.noConflict(true);
// your widget code, which should use my$ instead of $
}());

The call to noConflict will restore the global jQuery and $ objects to their former values.

lawnsea
  • 6,463
  • 1
  • 24
  • 19
  • The problem is if a third application will load jquery with using noConflict and that version will be non compatible with version which I expect to use. – sha1dy Nov 14 '10 at 21:27
  • Actually, no, that's not a problem given the solution I sketched. Your code references the my$ variable (could be named $, actually) in the scope of the anonymous function. Changes outside that scope by a third application will not have any effect on its value. – lawnsea Nov 15 '10 at 03:53
  • +1 This is certainly the jQuery way of handling this, however it is annoying to refactor all of your own code. It would be better if the third party script used noConflict the noConflict to ensure compatibility with everyone who might use their code. – Muhd Feb 26 '11 at 02:00
  • The `true` in `jQuery.noConflict(true)` handles this by returning the `jQuery` variable back to the "original" version of jQuery, in addition to the usual `$`. See https://github.com/jquery/jquery/blob/2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce/src/exports/global.js – Meekohi Apr 19 '17 at 17:07
  • How does this answer addresses the problem of other libraries clashing? – a--m Aug 25 '17 at 07:46
4

Function(...) makes an eval inside your function, it isn't any better.

Why not use the iframe they provide a default sandboxing for third party content.

And for friendly ones you can share text data, between them and your page, using parent.postMessage for modern browser or the window.name hack for the olders.

Mic
  • 24,812
  • 9
  • 57
  • 70
  • 1
    Yep, I'm going that way - iframe to sandbox my js. – sha1dy Nov 14 '10 at 21:27
  • 1
    Suck frame.. boooooo. I hate iframes. `jQuery.noConflict(true);` is what he needs (for 3rd party injection) iframes are an incredible headache on cross site scripting, and not a resolution for 3rd party plugin authoring. – Piotr Kula Jun 19 '12 at 14:29
  • @ppumkin The question here was not to cross site scripting or plugins, but the contrary, on how to isolate JS code. If you have another solution, feel free to answer. – Mic Jun 19 '12 at 20:52
  • Also, iframes also take care of *CSS* isolation. I very nice bonus indeed. – Stijn de Witt Apr 14 '14 at 13:55
  • @StijndeWitt, Yes. However in our app, we load the CSS in the main page. Prefixing the selectors by the module name. eg: `.moduleName .fieldClass{...}`, so not using the isolation in the end. – Mic Apr 14 '14 at 14:37
2

I built a library to solve this very problem. I am not sure if it will help you of course, because the code still has to be aware of the problem and use the library in the first place, so it will help only if you are able to change your code to use the library.

The library in question is called Packages JS and can be downloaded and used for free as it is Open Source under a Creative Commons license.

It basically works by packaging code inside functions. From those functions you export those objects you want to expose to other packages. In the consumer packages you import these objects into your local namespace. It doesn't matter if someone else or indeed even you yourself use the same name multiple times because you can resolve the ambiguity.

Here is an example:

(file example/greeting.js)

Package("example.greeting", function() {
  // Create a function hello...
  function hello() {
    return "Hello world!";
  };

  // ...then export it for use by other packages
  Export(hello);

  // You need to supply a name for anonymous functions...
  Export("goodbye", function() {
    return "Goodbye cruel world!";
  });
});

(file example/ambiguity.js)

Package("example.ambiguity", function() {
  // functions hello and goodbye are also in example.greeting, making it ambiguous which
  // one is intended when using the unqualified name.
  function hello() {
    return "Hello ambiguity!";
  };

  function goodbye() {
    return "Goodbye ambiguity!";
  };

  // export for use by other packages
  Export(hello);
  Export(goodbye);
});

(file example/ambiguitytest.js)

Package("example.ambiguitytest", ["example.ambiguity", "example.greeting"], function(hello, log) {
  // Which hello did we get? The one from example.ambiguity or from example.greeting?
  log().info(hello());  
  // We will get the first one found, so the one from example.ambiguity in this case.

  // Use fully qualified names to resolve any ambiguities.
  var goodbye1 = Import("example.greeting.goodbye");
  var goodbye2 = Import("example.ambiguity.goodbye");
  log().info(goodbye1());
  log().info(goodbye2());
});

example/ambiguitytest.js uses two libraries that both export a function goodbye, but it can explicitly import the correct ones and assign them to local aliases to disambiguate between them.

To use jQuery in this way would mean 'packaging' jQuery by wrapping it's code in a call to Package and Exporting the objects that it now exposes to the global scope. It means changing the library a bit which may not be what you want but alas there is no way around that that I can see without resorting to iframes.

I am planning on including 'packaged' versions of popular libraries along in the download and jQuery is definitely on the list, but at the moment I only have a packaged version of Sizzle, jQuery's selector engine.

Community
  • 1
  • 1
Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80
  • looks interesting.. but not really brialliant if someobody is a noob and injects a plugin code form allover the place. noconfilt is best practice in most cases for 3rd parties – Piotr Kula Jun 19 '12 at 14:28
  • Would there be a way of transpiling or something to handle the 3rd party libraries? – tom Apr 17 '16 at 13:20
  • Ah you stumbled onto an old post of mine. I no longer use packages.js myself as in the meantime it has been made obsolete by better solutions such as [RequireJS](http://requirejs.org/). Since you mention transpiling: these days I am coding in ES6 (which has native `import` and `export`) and then transpiling my code to ES5 for the browser using [Babel](https://babeljs.io/) and [Webpack](https://webpack.github.io/). – Stijn de Witt Apr 17 '16 at 16:40
0
<script src="myjquery.min.js"></script>
<script>window.myjQuery = window.jQuery.noConflict();</script>
...
<script src='...'></script> //another widget using an old versioned jquery
<script>
(function($){
    //...
    //now you can access your own jquery here, without conflict
})(window.myjQuery);
delete window.myjQuery;
</script>

Most important points:

  1. call jQuery.noConflict() method IMMEDIATELY AFTER your own jquery and related plugins tags

  2. store the result jquery to a global variable, with a name that has little chance to conflict or confuse

  3. load your widget using the old versioned jquery;

  4. followed up is your logic codes. using a closure to obtain a private $ for convience. The private $ will not conflict with other jquerys.

  5. You'd better not forget to delete the global temp var.
Xin Liu
  • 66
  • 5
0

Instead of looking for methods like no conflict, you can very well call full URL of the Google API on jQuery so that it can work in the application.

Andrew Collins
  • 2,541
  • 3
  • 17
  • 16
  • Not always the case - the application may be published in a corporate intranet in which access to the Google APIs URLs are forbidden. – Humberto Nov 12 '10 at 13:40
  • This doesn't address the actual concern, which is that the two versions will both try to bind to window.$ and window.jQuery. – lawnsea Nov 12 '10 at 15:36
  • He asked code for without no conflict so i gave this idea. I dont know what so wrong abt it – Andrew Collins Nov 12 '10 at 16:17
  • Another bad idea because a website could have been made on jquery 1.3.2 and my plugin uses jquery 1.7.x the changes are immense and will break the initial site because most methods are obsolete or do other things completely. Isolation is the only way for injection with your plugin. – Piotr Kula Jun 19 '12 at 14:27