loadjs icon indicating copy to clipboard operation
loadjs copied to clipboard

Allow to specify the target "document" or "window" object

Open niondir opened this issue 8 years ago • 13 comments

Hi, nice library. But I'm missing a way to define the target document. I would like to use this library to load scripts into iFrames.

niondir avatar Jun 02 '16 17:06 niondir

Thanks! If you load the library within the iframe (and the iframe is from the same domain) then you should be able to make calls to loadjs running in the iframe from the main window. Making the target document optional will increase some complexity to the library so I'm not sure it's a better option.

Andres

amorey avatar Jun 02 '16 18:06 amorey

Technically that's possible but from the application point of view it's way more complicated. My iFrame can not be empty but needs code (at least a script tag) to load this library first - which actually should be the job of this library, right? ;) When I load this library to the iFrame I can use the same method to load all other scripts and then I do not need the library anymore. You see what I mean?

In the end I'm looking for something to simply load dependencies of plugins in a way that I do not get version conflicts. From all the script loaders I liked this one the most, just that it lacks this one feature.

I would treat the target as an optional parameter, default to window.

niondir avatar Jun 03 '16 06:06 niondir

That sounds like a useful use case but it would add some complexity to the library so it's worth thinking about other ways to approach the problem. One option is to add loadjs to the iframe dynamically. This is working for me locally:

<html>
  <head>
    <script id="loadjs">
      loadjs=function(){function n(n,e){n=n.push?n:[n];var t,o,r,f,u=[],a=n.length,l=a;for(t=function(n,t){t.length&&u.push(n),l--,l||e(u)};a--;)o=n[a],r=i[o],r?t(o,r):(f=c[o]=c[o]||[],f.push(t))}function e(n,e){if(n){var t=c[n];if(i[n]=e,t)for(;t.length;)t[0](n,e),t.splice(0,1)}}function t(n,e){var t=document,o=t.createElement("script");o.src=n,o.onload=o.onerror=o.onbeforeload=function(t){e(n,t.type[0],t.defaultPrevented)},t.head.appendChild(o)}function o(n,e){n=n.push?n:[n];var o,r=n.length,f=r,u=[];for(o=function(n,t,o){if("e"==t&&u.push(n),"b"==t){if(!o)return;u.push(n)}f--,f||e(u)};r--;)t(n[r],o)}function r(n,t,r,i){var c,a,l;if(t&&!t.call&&(c=t),a=c?r:t,l=c?i:r,c){if(c in u)throw new Error("LoadJS: Bundle already defined");u[c]=!0}o(n,function(n){n.length?(l||f)(n):(a||f)(),e(c,n)})}var f=function(){},u={},i={},c={};return r.ready=function(e,t,o){return n(e,function(n){n.length?(o||f)(n):(t||f)()}),r},r.done=function(n){e(n,[])},r}();
    </script>
  </head>
  <body>
    <script>
      var loadjsSrc = document.getElementById('loadjs').innerHTML;

      // create iframe
      var iframeEl = document.createElement('iframe');
      iframeEl.src = "javascript:false;";
      document.body.appendChild(iframeEl);

      var iframeWin = iframeEl.contentWindow;

      // add loadjs to iframe
      var scriptEl = iframeWin.document.createElement('script');
      scriptEl.innerHTML = loadjsSrc;
      iframeWin.document.body.appendChild(scriptEl);

      // load jquery 2.2.4 in iframe
      var iframeLoadJS = iframeWin.loadjs;
      iframeLoadJS(['//code.jquery.com/jquery-2.2.4.min.js'], 'jquery');

      // load jquery 2.2.3 in main window
      loadjs(['//code.jquery.com/jquery-2.2.3.min.js'], 'jquery');

      // define callbacks
      iframeLoadJS.ready('jquery', function() {
        console.log('iframe version: ' + iframeWin.$.fn.jquery);
      });

      loadjs.ready('jquery', function() {
        console.log('main window version: ' + $.fn.jquery);
      });
    </script>
  </body>
</html>

amorey avatar Jun 04 '16 18:06 amorey

Okay I see the reason to keep the lib simple, it's also one of the main selling points.

What is hard with the above code is to use npm to manage the loadjs dependency but I can gat that working with webpack or maybe just create another library to archive this.

niondir avatar Jun 06 '16 09:06 niondir

Completely untested (I will test manually and via unittests later) but just to provide a possible solution first: https://github.com/Niondir/loadjs/commit/08ca6cb1d94b18ed1b741831077bdb8809720f6d

Adds only 12 characters to the min.js

Do not support bundles when using target, because an iFrame represents already an isolated bundle. That also makes the use of the target in place of the bundleId very intuitive.

The target is passed to the success callback to be able to retrieve the loaded scripts.

niondir avatar Jun 07 '16 10:06 niondir

Here's a version of LoadJS that supports different targets (branch:target): https://github.com/muicss/loadjs/tree/target

To simplify the API I modified the interface to accept a dictionary argument instead of sequential functions:

loadjs(['file1.js', 'file2.js'], 'mybundle', {
  success: function() {},
  fail: function() {},
  target: document
});

Please try it out and let me know what you think. It'd be useful to get some feedback on the new library before merging into master.

amorey avatar Jun 08 '16 03:06 amorey

I had the same idea with the options object. Its a good idea in general. Just the target and bundle is a dangerous combination. If you specify a bundle, everything related it must be cached in the context of the given target. e.g. 2 iFrames loading the same bundle name should not fail but load it twice (once per iFrame)

Another thought about the options object is, that a fluid API would be nice for all the callbacks.

loadjs(['file1.js', 'file2.js'], 'mybundle', {
  target: document
})
.success(function() {})
.fail(function() {});

It's maybe just an aesthetic aspect. Futures would be perfect here but that would raise the requirements on the Browser version.

niondir avatar Jun 14 '16 15:06 niondir

I had the same thought about Futures. It might not be too difficult to pass back a simple object with success/fail callback methods...

With regards to bundles and iframes, at first I was worried about scoping the bundles to the iframe but I think it's actually convenient to be able to use .ready() in the main window based on activity in the iframe. Also the bundle id is flexible enough that you can scope the string yourself (e.g. "iframe1-mybundle", "iframe2-mybundle").

amorey avatar Jun 14 '16 15:06 amorey

Having .ready() work is nice. Just another thought is to change it to .ready('some-bundle', target), where target is optional and default to window. Not sure how much effort it would be to manage the bundles "per target".

But that can also be adopted in another change. For now any way of being able to set the target would help :)

Btw: some code that allow chaining of the ready function is in that branch: https://github.com/niondir/loadjs/tree/chaining-ready Including Tests.

niondir avatar Jun 15 '16 17:06 niondir

This feature is implemented in the loadjs:iframe-target branch: https://github.com/muicss/loadjs/tree/iframe-target

However, I got a Permission Denied error running the tests in IE. Based on what I've read it seems like IE won't let you manipulate the iframe unless it gets loaded with the X-Frame-Options HTTP header: https://davidwalsh.name/iframe-permission-denied

How does your app get around this issue?

amorey avatar Jun 16 '16 02:06 amorey

I was not digging into this. Currently I just load everything in the global namespace - loading to an iFrame went a little bit down on my todo. Regarding the issue I guess I would just set the header on the server. But it's good to know.

niondir avatar Jun 19 '16 16:06 niondir

If you need to load the iframe from the server anyway then it seems like adding loadjs to the iframe server side might simplify things. Let me know if you end up using iframes in your app.

amorey avatar Jun 19 '16 17:06 amorey

There seem to be some caveats with iFrames that let me go with global imports right now, so it might need some time for the topic to become important enough again, but I will let you know.

By the way, the related project is https://github.com/Niondir/iot-dashboard where plugins can load dependencies from other URL's.

niondir avatar Jun 20 '16 01:06 niondir