开发者

Include JS file within private scope

开发者 https://www.devze.com 2023-02-10 17:12 出处:网络
I simply want the ability to include a JS file, but not have开发者_运维知识库 it evaluated in the global scope.

I simply want the ability to include a JS file, but not have开发者_运维知识库 it evaluated in the global scope.

I have skimmed over labjs and requirejs, and although they can do 1000 other things it doesn't seem that either is able to solve this problem.

I am aware that I could wrap the code of foo.js such that it expects a certain context, and can act within in, but that is not what I'm looking for (having to alter the source files). Rather, I would like the source JS files to remain as any other JS file that doesn't need any sort of metadata or any resolving of the runtime execution context via code in the included file itself; outside of the included file, that is ok.

Some simple demonstrations for clarity:

/*
 * Example 1 - apply to current context
 */
function x() {  
    include('foo.js'); // provides foo()
    foo(); // ok!
}
foo(); // not ok!


/*
 * Example 2 - apply to namespace context
 */
include.apply(ns, ['foo.js']); // provides foo()
ns.foo(); // ok!
foo(); // not ok!

I know that this can likely be achieved using eval() or by creating a new Function with the string, but I am hoping that there is a better solution.


I do not think it's possible to do so in ways other than what you outlined yourself.

One solution is to wrap the code in a function on the server side and write an include function that loads the js file and responds with the namespace of the function. I think of a solution similar to the require function in node.js.

Wrapped code on serverside

require.response('foo', function(exports) {

    // Content in foo.js
    exports.a = function() { return 'A'; }
    this.b = function() { return 'B'; }
    function c() { return 'c'); }

});

Client side js:

window['require'] = (function() {

    var list = {};
    function require(name, cb) {
        if (list[name]) {
            if (list[name].ns) {
                cb(list[name].ns);
            }
            else {
                list[name].cb.push(cb);
            }
        }
        else {
            list[name] = {cb: [cb]};

            // Code to load js file

        }
    }

    require.response = function(name, func) {
        var ns = {};
        list[name].ns = ns;
        func.call(ns);
        for(var i = 0, l = list[name].cb; i < l; i++) {
            list[name].cb[i](ns);
        }
    }

    return require;

}());

Example usage:

require('foo', function(foo) {
    foo.a(); // ok
    foo.b(); // ok
    foo.c(); // error
};

foo.a(); // error
this.a(); // error
c(); // error


There is no clean way to do this without relying on the included source files to conform to some sort of pattern (I can think of the possibility of injecting each file into its own iframe, so they run with their own window, but I don't think that's ideal).

That doesn't necessarily mean that you cannot achieve decoupling of the included files (without awareness of the runtime context as you say), but the files will still need to be well-behaved.

The CommonJS Module system (which RequireJS supports) shows one way to achieve this. By wrapping code in a function that provides an exports object, and assigning all (previously) global properties to this object, RequireJS can then return this object for you to assign to any object in any scope.

For example:

function x() {  
    var foo = require('foo.js').foo; // provides foo()
    foo(); // ok!
}
foo(); // not ok!

var ns = require('foo.js');
ns.foo(); // ok!
foo(); // not ok!

Yes, this requires editing the source files, but it still provides the benefits you're after.


Going to close out this old question, sadly no acceptable solutions were found. Some good suggestions, but all broke the requirements that I had. I guess it may be impossible with the current featureset of the language.


The clientside-require package utilizes iframes to contain the scope of the loaded javascript.

After loading the script to your page:

<script src = "path/to/clientside-require/src/index.js"></script>

You can conduct the functionality you wanted as follows:

/*
 * Example 1 - apply to current context
 */
require("foo.js")
  .then((foo)=>{
     foo(); // ok
  })
foo(); // not ok


/*
 * Example 2 - apply to namespace context
 */
var promise_foo_attached = require("foo.js")
  .then((foo)=>{
    window.ns.foo = foo; // provides foo to ns
  })

// then, as long as promise_foo_attached has resolved:
ns.foo(); // ok!
foo(); // not ok!

Note, because browsers do not allow synchronous loading of resources (due to poor user experience) any attempt at loading an external script with javascript will necessarily be asynchronous (and thus use promises).

Unless browsers enable synchronous loading, this format will be the closest to what we are looking for.


As an added bonus, the require() functionality that that script loads can be used to load html, css, json, and even node_modules (as long as the modules are capable of working with require() as a promise).

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号