How to isolate different javascript libraries on the same page?
Asked Answered
C

5

16

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?

Chophouse answered 12/11, 2010 at 12:55 Comment(3)
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.Tellurian
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.Capriola
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.Chophouse
S
3

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.

Spectacular answered 12/11, 2010 at 15:17 Comment(5)
Yep, I'm going that way - iframe to sandbox my js.Chophouse
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.Ardene
@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.Spectacular
Also, iframes also take care of CSS isolation. I very nice bonus indeed.Muggins
@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.Spectacular
F
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.

Fabe answered 12/11, 2010 at 15:17 Comment(5)
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.Chophouse
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.Fabe
+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.Berley
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 github.com/jquery/jquery/blob/…Vacillate
How does this answer addresses the problem of other libraries clashing?Sheathbill
S
3

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.

Spectacular answered 12/11, 2010 at 15:17 Comment(5)
Yep, I'm going that way - iframe to sandbox my js.Chophouse
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.Ardene
@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.Spectacular
Also, iframes also take care of CSS isolation. I very nice bonus indeed.Muggins
@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.Spectacular
M
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.

Muggins answered 22/2, 2011 at 14:16 Comment(3)
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 partiesArdene
Would there be a way of transpiling or something to handle the 3rd party libraries?Orton
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. 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 and Webpack.Muggins
A
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.

Anastassia answered 12/11, 2010 at 13:31 Comment(4)
Not always the case - the application may be published in a corporate intranet in which access to the Google APIs URLs are forbidden.Capriola
This doesn't address the actual concern, which is that the two versions will both try to bind to window.$ and window.jQuery.Fabe
He asked code for without no conflict so i gave this idea. I dont know what so wrong abt itAnastassia
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.Ardene
V
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.
Vierra answered 18/3, 2015 at 3:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.