3

I am fully aware of the dangers of eval and such, so please don't waste your time commenting on how these hacks are hacks.

Say I'm using a third-party JavaScript library, but I'd like all of it's code to be defined within a particular namespace... that I also don't control. For example:

<script src="http://example.com/library.js>
<!-- library.js contains:
     var x = 0;
     function foo() {}
-->

I'd like to be able to define x and foo in the context of, say, namespace Bar, instead of the global context (window).

Assuming Bar is defined, but in another external js file that I don't control, I have tried some hacks, for example:

<script>
var externalCode = $.ajax("http://example.com/library.js");
eval.call(Bar, externalCode);
// and also tried:  
(function(str){ eval(str); }).call(Bar, externalCode);
</script>

But I am not able to access Bar.x nor invoke Bar.foo().

Basically, I want to achieve the results I would get if I pasted the contents of library.js inside my Bar namespace, a la:

var Bar = {
    x: 0,
    foo: function() {}
  }

However, I am trying to do this when I also lack control over the Bar namespace. Hence I can't "copy and paste." Another related example is how we can "punch in" new methods into pre-existing class definitions in Ruby.

Lastly, my specific use case is that I'm trying to "inject" everything from p5.js into the Opal namespace.

2
  • 1
    Ouch! Don't use eval (ever!) and use .bind() instead of call or apply. Commented Mar 11, 2016 at 20:52
  • 3
    @ybakos Not everyone knows about eval and call/apply. In fact, most don't. You may know about them and good for you, but don't chastise us for pointing it out. Commented Mar 11, 2016 at 21:08

2 Answers 2

3

A simple solution that works in browsers today is:

<script src="http://example.com/library.js>
<!-- library.js contains:
 var x = 0;
 function foo() {}
-->
<script>
$.ajax('/library.js', function(jsString) {
    var wrapped  = `function() { 
       ${jsString};
       Bar.x = x;
       Bar.foo = foo;
    }();`
    eval(wrapped);
});
</script>

However, a solution that doesn't require listing modules is EcmaScript 6 modules. If you transpile it with https://babeljs.io/, it can work with today's browsers.

//------ main.js ------
import * as Bar from 'lib';
console.log(Bar.foo()); // no error
console.log(Bar.x); // 0

If you need it to work in all browsers today, and don't want to transpile your code, you'd need the first example, which could be generalized as

function importIntoNs(ns, scriptUrl, symbolNames) {
  $.ajax(scriptUrl, function(jsString) {
    var wrapped = `(function() { 
       ${jsString};`;
    symbolNames.forEach(symbolName => {
        wrapped += 'this.' + symbolName + ' = symbolName;\n'  ;       
    }); 
    wrapped += '})';

    eval(wrapped).apply(ns);
  }
}

importIntoNs(Bar, 'http://example.com/library.js', ['x', 'foo']);

If anyone wants to downvote because of the eval, remember that the OP is already going to execute the script no matter what if they put it in a <script> tag.

Here's a working example without fetching the script.

function importScriptIntoNs(ns, jsString,  symbolNames) {  
  var addSymbols = '';
  symbolNames.forEach(symbolName => {
    addSymbols += 'this.' + symbolName + ' = ' + symbolName + ';\n';
  }); 
  
  var wrapped = `(function() {;
     ${jsString}
     ${addSymbols}
  })`;

  eval(wrapped).apply(ns);
}

var Bar = {};

var jsString = `
   var x = 'It works';
   function foo() {alert(x)}
`;

importScriptIntoNs(Bar, jsString, ['x', 'foo']);

Bar.foo()

Sign up to request clarification or add additional context in comments.

4 Comments

Hmm... with the drawback being that I have to explicitly list all identifiers in the symbolNames array, right?
I would not say that's a drawback, you have know the names to be able to call Bar.foo() anyway. I call it a feature. It should also help you detect/prevent collisions. Another angle, with any module system, you have specify what to export, this is creating a module around what was previously free standing, global polluting code.
Thanks, yes, I get it.
I've updated my answer. You could use EcmaScript 6 modules and transpile it using babeljs.io
1

One way to handle libraries and other dependencies without polluting the global scope is to use some kind of Asynchronous Module Definition. A good one that I like to use and recommend is Require.js.

The whole idea behind it is that you're able to bundle your libraries and dependencies into modules and call on them when you need, without simply loading everything on the global scope.

Here is an example of how you might use it in your situation:

require(['http://example.com/library.js'], function(Bar){
    // library.js is now bound within this scope under the alias Bar
});

10 Comments

Note that the library has to support require, otherwise, good answer
@JuanMendes There are ways with Require.js to accommodate for this, which is one of the reasons I like it :)
I'm not sure what you mean, I know require.js, it's an AMD module loader. A module has to be written specifically so it can be required. You can't require() any script as the OP would like
@JuanMendes Check this out. This is an example of using Require.js with a library like JQuery utilizing shim
That's because jQuery was made to work with require, it had to be modified. Note the first few lines in code.jquery.com/jquery-2.2.1.js
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.