kubetail-org / loadjs

A tiny async loader / dependency manager for modern browsers (899 bytes)
MIT License
2.58k stars 150 forks source link

Use bundle ids in dependency lists #60

Closed uptimestar closed 6 years ago

uptimestar commented 6 years ago

This is documented as item 7, but is not shown in examples.html.

It appears to fail for me, as it tries to load the bundle name via http. So I check if my mistake by running examples. The examples run, but they don't show above documented use case.

so this modified example fails but is documented as working.

  // compose more complex dependency lists
  loadjs('assets/file1.js', 'example7a');
  loadjs(['example7a', 'assets/file3.js'], 'example7b');

  // wait for multiple depdendencies
  loadjs.ready(['example7b'],
               function() {
                 log('Load by bundle');
               },
               function(depsNotFound) {
                 throw "Load should succeed";
               });
amorey commented 6 years ago

For error callbacks you need to use the more advanced syntax (define success/error functions in an object argument). Here's a working example that mixes and matches bundles and urls: https://jsfiddle.net/muicss/ts5fv67L/

uptimestar commented 6 years ago

It is not related to error handling. It is related to using bundle ID. Here is the bug reproduced:

    loadjs(['bundle1','//cdn.jsdelivr.net/npm/fullpage.js@2.9.6/dist/jquery.fullpage.css'], 'bundle2', {
                success: foo,
                async: false,
                error: function() {alert('not an error, bundle is defined')}
    })

    setTimeout(function(){
        loadjs(['//code.jquery.com/jquery-3.3.1.min.js'], 'bundle1')
        }, 1000 
    )

    function foo() {
        alert('success')
    }

The way complex scripts get loaded and bundles defined can get complex. The page for me needs a success function and has different team working on it. So the bundle1 gets loaded a bit later. Since loadjs does not see, it assumes it is a lib. So an bug.

One way to fix is to be able to define a bundle, but don't load. This way I can declare the bundle and dependencies, and then just ask for something later.

(also it be nice to be able to load an image)

amorey commented 6 years ago

In your example, bundle1 gets defined after you list it as a dependency so loadjs isn't aware that it is a bundle name and treats it as a url instead. To change that behavior, loadjs would have to make assumptions about urls and bundle names which might cause problems for some developers.

One way to execute code only after a bundle has been defined is to use the .ready() method:

loadjs.ready('bundle1', function() {
  loadjs('//cdn.jsdelivr.net/npm/fullpage.js@2.9.6/dist/jquery.fullpage.css', function() {
    alert('success');
  });
});

setTimeout(function() {
  loadjs(['//code.jquery.com/jquery-3.3.1.min.js'], 'bundle1');
}, 1000);

One drawback of the code above is that it will load the files in series. If you want to load the files in parallel but you don't know if your colleague's code has executed yet then you can use .isDefined() to execute different code paths:

if (loadjs.isDefined('bundle1')) {
  loadjs(['bundle1', '//cdn.jsdelivr.net/npm/fullpage.js@2.9.6/dist/jquery.fullpage.css'], {
    success: function() {alert('success');},
    error: function() {alert('error');},
    async: false
  });
} else {
  loadjs(['//code.jquery.com/jquery-3.3.1.min.js', '//cdn.jsdelivr.net/npm/fullpage.js@2.9.6/dist/jquery.fullpage.css'], {
    success: function() {alert('success');},
    error: function() {alert('error');},
    async: false
  });
}

Please note that in order for async: false to work with the first conditional code block above your colleague must have defined bundle1 with async: false as well.

If loadjs were to support images, do you have any suggestions for the syntax?

uptimestar commented 6 years ago
amorey commented 6 years ago

Can you explain the problem you are trying to solve in more detail? If I can understand your problem better then maybe I can suggest a different solution.

uptimestar commented 6 years ago

I think it is quite a bit of low detail is given. Maybe higher level as a different POV? A large distributed team on complex pug project that tried webpack code splitting Now using loadjs instead, works much much better. Lots of things required jquery and such, and we also use pug includes a lot. We use work around for async loads of bundles. Some things like fullpage.js requires jq and another lib BEFORE it is loaded. But that is not needed on each page. Also, we check load times a lot view browser tools to check time to screen. If we can get rid of workarounds and just be able to declare few basic bundles that others devs can 'extend' or depend on, that would be nice. So in pug we can say loadjs([baseBundle, fpBudnle, myeffect.js] function(//unhide div). This all works, but is not elegant. If you add some more bundle features, code would look nicer.

amorey commented 6 years ago

Ok, thanks for the overview. I think loadjs has the basic primitives you need and it's just a matter of figuring out where to use them.

Based on your description I would define the bundles in the <head> to take advantage of the browser's built-in parallel load/sequential execution mechanism:

<head>
  <script src="/path/to/jquery"></script>
  <script src="/path/to/baseBundle"></script>
  <script src="/path/to/fpBundle"></script>
  <script>
    // manually declare bundles
    loadjs.done('jquery');
    loadjs.done('baseBundle');
    loadjs.done('fpBundle');
  </script>
</head>

And then wrap your code with .ready() to ensure that the code won't execute until the dependencies have been met:

loadjs.ready(['baseBundle', 'fpBundle'], function() {
  loadjs('/path/to/myeffect.js', function() {
    // unhide div
  });
});

In general, .ready() and .done() can be used together for more fine grained control over bundle definition and execution times.

uptimestar commented 6 years ago

Does not help. No worries. It is Open source, when it hurts enough we'll fork source.

On Sat, Mar 10, 2018 at 5:52 PM Andres Morey notifications@github.com wrote:

Ok, thanks for the overview. I think loadjs has the basic primitives you need and it's just a matter of figuring out where to use them.

Based on your description I would define the bundles in the to take advantage of the browser's built-in parallel load/sequential execution mechanism:

And then wrap your code with .ready() to ensure that the code won't execute until the dependencies have been met:

loadjs.ready(['baseBundle', 'fpBundle'], function() { loadjs('/path/to/myeffect.js', function() { // unhide div }); });

In general, .ready() and .done() can be used together for more fine grained control over bundle definition and execution times.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/muicss/loadjs/issues/60#issuecomment-372073708, or mute the thread https://github.com/notifications/unsubscribe-auth/AeT3VFXpu09iS62s7sBX4XGCH-57bC2sks5tdFkwgaJpZM4SjipS .

amorey commented 6 years ago

You should be able to implement complex dependency chains in a simple way by wrapping your code in .ready() and using .done() for execution. For example, here are three bundles and a plugin ("bundle1a.js", "bundle1b.js", "bundle2.js", "effect.js") where one bundle depends on another and all depend on jquery:

// bundle1a.js
loadjs.ready('jquery', function() {
  // bundle1a code goes here...

  loadjs.done('bundle1a');
});
// bundle1b.js
loadjs.ready('bundle1a', function() {
  // bundle1b code goes here...

  loadjs.done('bundle1b');
});
// bundle2.js
loadjs.ready('jquery', function() {
  // bundle2 code goes here...

  loadjs.done('bundle2');
});
// effect.js
loadjs.ready(['bundle1b', 'bundle2'], function() {
  // effect code goes here...
});

Once the files have been implemented in this way you can load them into the DOM in order you want, synchronously/asynchronously, in series/parallel, with/without loadjs and in javascript or in html.

uptimestar commented 6 years ago

Thank you for doing this example. It perfectly illustrates what the problem is. Lets take a look at first part:

    loadjs.ready('jquery', function() {
      // bundle1a code goes here...

      loadjs.done('bundle1a');
    });

What you did is // // bundle1a code goes here... where there is more load js code. Clever. But our developers include graphic designers. HTML people. Art directors/creatives. jr jquery developers and such. The nesting is complex code for them- and this is just 3 levels deep you showed with majority of code you did as //write code here. If you did add code, it would look quite confusing to ... a CSS person. We need code that makes the user feel more confident. I need them focused on building the site and making the site look nice. This example is not for those developers, it takes away from their work focus. What we need is a more syntax sugar that make is look easy to use bundles to control delayed loading. I'd like for the user of loadjs to be the one that feels clever. (at the expense of suffering of loadjs maintainer ).

amorey commented 6 years ago

Would it help to be able to define a bundle inline?

// bundle1a.js
loadjs.ready('jquery', 'bundle1a', function() {
  // bundle1a code goes here...
});
// bundle1b.js
loadjs.ready('bundle1a', 'bundle1b', function() {
  // bundle1b code goes here...
});
// bundle2.js
loadjs.ready('jquery', 'bundle2', function() {
  // bundle2 code goes here...
});
// effect.js
loadjs.ready(['bundle1b', 'bundle2'], function() {
  // effect code goes here...
});
uptimestar commented 6 years ago

I think you are saying your done is 'auto'. It may be more readable if it was different order, done as last

    loadjs.ready(['bundles1', 'bundle2'], function() {
            // more code and loadjs

        }, 'bundles3'
    )

I think an improvement for ready. But still as soon as I define a bundle it loads. If I can define a bundle that is lazy. loadjs.define( ...) that waits till it sees a ready().

uptimestar commented 6 years ago

How would the wrapping ready know when some loadjsit is wrapping in 'code' is done? I don't think that you can implement that.

amorey commented 6 years ago

But still as soon as I define a bundle it loads. If I can define a bundle that is lazy. loadjs.define( ...) that waits till it sees a ready().

Have you tried using RequireJS?

How would the wrapping ready know when some loadjsit is wrapping in 'code' is done? I don't think that you can implement that.

Internally, loadjs will call .done() after the wrapped function is executed.

amorey commented 6 years ago

@uptimestar This issue came up in another thread (https://github.com/muicss/loadjs/issues/62). Here's some custom code that you can use to implement a lazy loader with loadjs:

var bundles = {
  'bundle1': ['file1', 'file2'],
  'bundle2': ['file3', 'file4']
};

function require(bundleIds, callbackFn) {
  bundleIds.forEach(function(bundleId) {
    if (!loadjs.isDefined(bundleId)) loadjs(bundles[bundleId], bundleId);
  });
  loadjs.ready(bundleIds, callbackFn);
}

require(['bundle1'], function() { /* bundle1 loaded */ });
require(['bundle2'], function() { /* bundle2 loaded */ });
require(['bundle1', 'bundle2'], function() { /* bundle1 and bundle2 loaded */ });
cekvenich commented 6 years ago

Thank you for keeping it in consideration. Does the last 3 lines see the bundle names that is in closure in first 4 lines? It be very nice to add some syntax sugar for the 'diverse' .js developers to make it easier to read/teach.

amorey commented 6 years ago

Yes, the require() method has access to the bundles object. Here's a working version: https://jsfiddle.net/muicss/4791kt3w/

You can modify this code to change the syntax. Let us know when you find a syntax you like.

cekvenich commented 6 years ago

Thx again. I think loadjs.require(....) is fine sugar.

I think of this more as a better/manual webpack code splitting w/ CDN than requirejs. Now if you let me load an image asset like I can load css, perfect. I can now 'loadjs.require()' css, image and .js before I $.show() a div or such.

amorey commented 6 years ago

Ok, if you want to add this to your implementation you can attach require to loadjs:

loadjs.require = function(bundleIds, callbackFn) {
  bundleIds.forEach(function(bundleId) {
    if (!loadjs.isDefined(bundleId)) loadjs(bundles[bundleId], bundleId);
  });
  loadjs.ready(bundleIds, callbackFn);
}
amorey commented 6 years ago

@cekvenich I added support for image loading to the loadjs:img branch of the repo: https://github.com/muicss/loadjs/tree/img

Here are links to loadjs payloads with the new code: https://cdn.rawgit.com/muicss/loadjs/img/examples/assets/loadjs/loadjs.js https://cdn.rawgit.com/muicss/loadjs/img/examples/assets/loadjs/loadjs.min.js

It passed these tests in modern browsers and IE9+: https://github.com/muicss/loadjs/blob/img/test/tests.js#L352-L467

Let me know if you notice any issues with the new features.

cekvenich commented 6 years ago

I'll test! Is 'require' sugar included? This way the examples of lazy loading are on your site.

amorey commented 6 years ago

Sorry, the require method is not in the library but you can copy the code if you want to add it to your site.

amorey commented 6 years ago

@cekvenich Have you had a chance to try out the img loading feature?

cekvenich commented 6 years ago

It is on my list - during week I am busy and will be out of town for a few. Don't worry!

amorey commented 6 years ago

Ok, no worries. It's pretty well tested and backwards compatible so I think I'll push to master soon.