kubetail-org / loadjs

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

Not being executed in order I would expect #62

Closed bduff9 closed 6 years ago

bduff9 commented 6 years ago

I see on the docs you can do something like the following:

// define a dependency bundle
loadjs(['/path/to/foo.js', '/path/to/bar.js'], 'foobar');

// OR use more advanced syntax for more options
loadjs.ready('foobar', {
  success: function() { /* foo.js & bar.js loaded */ },
  error: function(depsNotFound) { /* foobar bundle load failed */ },
  before: function(path, scriptEl) { /* execute code before fetch */ },
  async: true,  // load files synchronously or asynchronously (default: true)
  numRetries: 3  // number of times to retry fetch (default: 0)
});

Also, the docs on async indicate that with async: false, the scripts should be loaded in parallel but executed in series. However, I am doing exactly the above, and sometimes the second script is loaded first causing an error to be thrown:

if (!loadjs.isDefined('jquery-validation')) {
  loadjs(['/assets/vendor/jquery-validation/jquery.validate.min.js', '/assets/vendor/jquery-validation/additional-methods.js'], 'jquery-validation');
}

loadjs.ready('jquery-validation', {
  async: false,
  success: function () {
    startFormValidation();
  }
});

...And the error I get is from the additional-methods.js file, since it depends on the jquery.validate.min.js file. This is completely random, so I would imagine its just to do with the order they are executed. Am I doing something wrong or is there a way to ensure the files are loaded in the order I list them in? Thanks!

amorey commented 6 years ago

Sorry, I made some changes to the documentation in a rush yesterday. The before/async/numRetries options must be set at bundle definition time:

loadjs(['/path/to/foo.js', '/path/to/bar.js'], 'foobar', {
  before: function(path, scriptEl) { /* execute code before fetch */ },
  async: true,  // load files synchronously or asynchronously (default: true)
  numRetries: 3  // see caveats about using numRetries with async:false (default: 0)
});

loadjs.ready('foobar', {
  success: function() { /* foo.js & bar.js loaded */ },
  error: function(depsNotFound) { /* foobar bundle load failed */ },
});

I've updated the documentation to reflect this: https://github.com/muicss/loadjs

Please let me know if the new documentation is clearer and if you have any suggestions on how to improve it.

bduff9 commented 6 years ago

I see, thank you for your reply. The docs look better to me, appreciate your fast turn-around. Changing my code to put async: false in the bundle definition does seem to make it work consistently thus far.

One quick question before I close this, is there a way to pre-define a bundle and then load it later? The reason I ask is that we have an application where there are many bundles we need to load, and instead of defining it right when we need to load it, it would be easier to define them all in place and then just reference them by their ID later. It seems from the docs that loadjs([someBundle], 'someBundle') loads the files right when its defined. I do know that I could, of course, just define all the file strings/arrays in one location and then use those when needed, but I like the concept of loadjs bundles so I'd like to perform this all together, if possible.

So, just wondering if there is something like the following available or planned:

loadjs(['file1', 'file2'], 'bundle1', { defer: true }); // Defines bundle but does not load anything
...Other Code...
loadjs.ready('bundle1', function () { /* bundle1 has been loaded */ }); // This line loads the bundle and then executes the success function

Thanks!

amorey commented 6 years ago

Currently, loadjs is designed to load files when you call the loadjs() method but you can implement some defer-like behavior using .ready():

// execute code if/when bundle1 loads
loadjs.ready('bundle1', function() { /* bundle1 has been loaded */ });

// conditionally load bundle1
if (someCondition) {
  loadjs(['file1', 'file2'], 'bundle1');
}

Does that solve your problem?

bduff9 commented 6 years ago

I see what you're saying, and that is useful to know. However, it does not solve the question I was asking. My example I gave was poor, let me try again to make it clearer.

// File Bundles.js
loadjs(['file1', 'file2'], 'bundle1', { defer: true });
loadjs(['file3', 'file4'], 'bundle2', { defer: true });
loadjs('file5', 'bundle3', { defer: true });
loadjs(['file1', 'file6'], 'bundle4', { defer: true });
...etc.

// File Module1.js
if (someCondition) {
  loadjs.ready('bundle1', function () { /* bundle 1 has been loaded */ });
} else if (someOtherCondition) {
  loadjs.ready('bundle4', function () { /* bundle 4 has been loaded */ });
}

// File Module2.js
if (someCondition) {
  loadjs.ready('bundle2', function () { /* bundle 2 has been loaded */ });
}

// File Module3.js
if (someCondition) {
  loadjs.ready('bundle3', function () { /* bundle 3 has been loaded */ });
}

What I am really asking for is a way to centralize all of my dependencies in one location, before they are ever needed as a means to make it easier to keep track of all of them and make maintenance much easier. As it currently stands, I define the bundle where and when its needed, which means I have bundle definitions all over the place (we have very small, focused files so 1 file will have at most 1 or 2 bundles). This makes it hard to know what was loaded as well as where something is defined. It does work, so this is not a problem, per se, but it would make our lives much easier if there was a way to centralize the bundle definitions while deferring their load until actually needed. Does that make sense?

amorey commented 6 years ago

Would something like this work for you?

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

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

require(['bundle1'], function() {
  /* bundle1 loaded */
});

require(['bundle2'], function() {
  /* bundle2 loaded */
});

require(['bundle1', 'bundle2'], function() {
  /* bundle1 and bundle2 loaded */
});

I'm hesitant to add more dependency management features to the library because there are already other options available for fully-featured dependency management (e.g. requirejs) but I see how this would be useful. Let me think about how we can fit it into loadjs while keeping the library small and simple.

bduff9 commented 6 years ago

Sure, I understand what you're saying. The above seems reasonable, so I'll run it by my team. Let me know what you end up deciding. Thanks!

amorey commented 6 years ago

Great, I added this dependency manager example to the documentation in case it's useful for other people. Let me know what your team ends up deciding.