Manual Reference Source Test

Beloader manual

Installation

Beloader is built on UMD architecture and can be embedded as a module or standalone as browser script.

As a module

npm install beloader

or

yarn add beloader

Then simply require/import it :

import Beloader from 'beloader';
const {Beloader} = require('beloader');
const Beloader = require('beloader').default;
const Beloader = require('beloader').Beloader;

Beloader have been built on a ECMA6 class pattern and transpiled.

In browser

Beloader is available as CDN external library or can easily be installed locally. Beloader is using dynamic imports with modules. You must require the full path, otherwise Beloader won't be able to resolve modules URL.

Bundle

<script type="text/javascript" src="https://bundle.run/beloader@latest"></script>

Bundle generate a beloader object that hoist Beloader constructor. So, you must call it like this :

var loader = new beloader.Beloader();

JsDelivr

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/beloader@latest"></script>

Unpkg

<script type="text/javascript" src="https://unpkg.com/beloader@latest"></script>

Local install

For browser install, you can simply fetch folder dist in this repo (or clone it) and load the script :

<script type="text/javascript" src="myJsFolder/beloader/dist/beloader.min.js"></script>

Using Beloader in browser

There is two ways to embed Beloader in browser to be sure it will be available at runtime :

Calling it synchronously (in <HEAD> or <BODY> but before subsequent calls)

<script type="text/javascript" src="beloader.min.js"></script>

Add a callback when loading asynchronously

<script type="text/javascript" src="beloader.min.js" onload="start()" async></script>
<!-- or -->
<script type="text/javascript" src="beloader.min.js" onload="start()" defer></script>

<script>
  function start() { //stuff here }
</script>

Browser compatibility

Beloader should work with no tweaks in all modern browsers and IE >= 10 (even 8 if not using webfontloader).

Basic usage

Assets loading

In most case a simple instance with no options will be enough and you can then fetch assets :

var loader = new Beloader();

loader.fetch('script', 'https://code.jquery.com/jquery-3.3.1.js');
loader.fetch('font', {
  webfont: {
    google: {
      families: ['Droid Sans', 'Droid Serif']
    }
  }
});

With this, we're not doing far better than directly embedding a script and a link in HTML code, except loading is asynchronous.

Using promises to perform actions when assets are loaded

If you want to perform things when assets are loaded, you can use the exposed promise property of an item :

var loader = new Beloader();

loader.fetch('script', 'https://cdn.jsdelivr.net/npm/elementify@latest')
  .promise.then(function() {
    console.log('Elementify loaded !');
  });

loader.fetch('font', {
  webfont: {
    google: {
      families: ['Droid Sans', 'Droid Serif']
    }
  }
}).then(function() {
  var div = document.createElement('div');

  div.innerHTML = '<span style="font-family:\'Droid Sans\'">Hello, world</span>';
  document.body.appendChild(div);
});

You may notice that it will be way better to use Elementify to perform the body insertion when font has loaded. But, we have to make sure that Elementify has loaded before font.

The first idea may be to nest fetching and it will work but at the cost of losing parallels ajax loadings. Anyway, there is better solutions.

Defering assets loading

Beloader can resolve loadings in the same order that they have been added to the queue.

var loader = new Beloader({
  defer: true // Change is here
});

loader.fetch('script', 'https://cdn.jsdelivr.net/npm/elementify@latest')
  .promise.then(function() {
    Elementify.load();
  });

loader.fetch('font', webfont: {
  google: {
    families: ['Droid Sans', 'Droid Serif']
  }
}).then(function() {
  Q('body').append('<div style="font-family:\'Droid Sans\'">Hello, world</div>');
});

In that case, Beloader will take care of waiting for Elementify request to resolve before resolving font. It is possible to use the defer behaviour only on a subset of fetched items by providing defer as an option to the fetch instead of as a global trigger.

Awaiting dependencies

In some cases, multiple dependencies can't be simply resolved with defer. Beloader offers an awaiting option to deal with complex patterns.

var loader = new Beloader();

loader.fetch('script', {
  id: 'elementify', // Change is here
  url: 'https://cdn.jsdelivr.net/npm/elementify@latest'
})
  .promise.then(function() {
    Elementify.load();
  });

loader.fetch('script', {
   id: 'lodash',
  url: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js'
})
  .promise.then(function() {
    Elementify.load();
  });

loader.fetch('font', {
  awaiting: ['elementify','lodash'] // Change is here
  webfont: {
    google: {
      families: ['Droid Sans', 'Droid Serif']
    }
  }
}).then(function() {
  Q('body').append('<div style="font-family:\'Droid Sans\'">Hello, world</div>');
});

Bulk assets loading

You can provide multiple assets request in a single call. Just set an id to each asset and swap type as an option value.

var loader = new Beloader();
var items = {
  elementify: {
    type: 'script',
    url: 'https://cdn.jsdelivr.net/npm/elementify@latest'
  },
  lodash: {
    type: 'script',
    url: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js'
  },
  font: {
    type: 'font',
    webfont: {
      google: {
        families: ['Droid Sans', 'Droid Serif']
      }
    }
  }
};

loader.fetchAll(items).promise.then(items => {
  Elementify.load();
  Q('body').append('<div style="font-family:\'Droid Sans\'">Hello, world</div>');
});

Each item is available in an array indexed in the same order that they have been declared. The fetchAll promise only resolves when all assets are loaded. If one asset is in error, the promise will be rejected.

Delaying queue process

As a default, Beloader will automatically process all fetch requests. In some cases, you may want to delay the process.

var loader = new Beloader({
  autoprocess: false // Add this options
});

loader.fetch('script', {
  id: 'elementify',
  url: 'https://cdn.jsdelivr.net/npm/elementify@latest'
})
  .promise.then(function() {
    Elementify.load();
  });

loader.fetch('script', {
  id: 'lodash',
  url: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js'
})
  .promise.then(function() {
    Elementify.load();
  });

loader.fetch('font', {
  awaiting: ['elementify','lodash']
  webfont: {
    google: {
      families: ['Droid Sans', 'Droid Serif']
    }
  }
}).then(function() {
  Q('body').append('<div style="font-family:\'Droid Sans\'">Hello, world</div>');
});

// Do things that take time and must be done before processing

loader.process(); // Order Beloader to process the loading queue

Loaders

Built-in loaders

Using built-in loaders

Loaders are automatically selected given the type parameter value provided to fetch or option provided to fetchAll.

See API for the list of loaders.

Each QueueItem instance exposes a loader property where the given loader instance is accessible.

Script loader

This loader is designed to load scripts sync or async (default). It is triggered when requested type value is set to js, script, javascript or ecmascript.

When loaded async, you can provide inline options :

  • If false (by default), the script is loaded through a <script src="url" async></script> tag
  • If true, the script is fetched through XHR and inserted in a <script></script> tag

Stylesheet loader

This loader is designed to load stylesheets sync or async (default). It is triggered when requested type value is set to css, style, styles or stylesheet.

Font loader

This loader is designed to load fonts sync (default). It is triggered when requested type value is set to font or webfont.

The font loader relies on webfontloader api to manager font loading. It takes two steps :

  • Loading the stylesheet which describes the font
  • Loading the font file itself

A font loader instance will only resolve at the very end of the process to guarantee that font is ready to be used without FOUT.

For more informations about webfontloader configuration, please visit their website.

Image loader

This loader is designed to load images sync or async (default). It is triggered when requested type value is set to img or image.

The image node will be exposed as image property of the requested item. It has to be managed programmatically or with a plugin.

var loader = new Beloader({
  cache: false
});

loader.fetch('img', {
  url: 'https://upload.wikimedia.org/wikipedia/commons/d/d9/Test.png'
}).promise.then(item => {
  document.body.appendChild(item.image);
}).catch(item => {
  console.log(item.error);
});

JSON loader

This loader is designed to load JSON data async (only). It is triggered when requested type value is set to json.

JSON data is exposed as response property of the requested item.

None loader

This is a special loader only designed to produce side-effect. It's especially useful when you want to perform action at a given step of the loading queue while using the awaiting mode.

var loader = new Beloader();

loader.fetch('none', {
  awaiting: ['asset1', 'asset2', 'asset5', 'asset6']
}).then(item => {
  // Do things when asset1, asset2, asset5 and asset6 are loaded
  // Plugins will be available
});

loader.fetchAll({
  'asset1' => {
    type: 'script',
    url: 'http://server.com/asset1'
  },
  'asset2' => {
    type: 'script',
    url: 'http://server.com/asset2'
  },
  'asset3' => {
    type: 'css',
    url: 'http://server.com/asset3'
  },
  'asset4' => {
    type: 'css',
    url: 'http://server.com/asset4'
  },
  'asset5' => {
    type: 'script',
    url: 'http://server.com/asset5'
  },
  'asset6' => {
    type: 'json',
    url: 'http://server.com/asset6'
  },
}).then(items => {
  // Only resolved when all assets are resolved  
});

Plugin loader

Another special loader designed to load Beloader plugins asynchronously from url or official repo.

See plugins section of the manual for more informations.

Custom loaders

Tweaking XMLHttpRequest instance

In async mode, Beloader relies on a very simple XHR instance with nearly zero-configuration.

You can provide xhr.method to options for changing the request method option and xhr.data to provide some data to send in the request body. In the latter case, data pre-processing is up to you as Beloader will output raw xhr.data content to the request body.

If you need more fine-grained control over the XHR instance, you can do it with loadstart event callback (see Events section).

var loader = new Beloader();

loader.fetch({
  'type': 'image',
  'url': 'http://server.com/script.js',
  'on': {
    'loadstart': function(event) {
      var xhr = event.target.xhr; // Target is Loader
      // or
      var xhr = this.loader.xhr: // Context is QueueItem

      xhr.setRequestHeader('Accept', 'weird/mimetype');
    }
  }
});

Replacing sync and async loading engines for built-in loaders

If needed, Beloader let you replace loading engines for built-in loader.

It can be useful if you wish to use a third party ajax query engine for instance like jQuery or Request. In that case, you must take care of firing intermediate events as needed.

Simply provide a new callback that returns a promise and resolve or reject it at will.

The engines can be overriden for a whole Beloader instance and/or per item added.

var sync = function() {
  return new Promise((resolve, reject) => {
    reject();
  });
}

var async = function() {
  return new Promise((resolve, reject) => {
    resolve();
  });
}

var loader = new Beloader({
  loader: {
    sync: sync // Will override all sync loader
  }
});

loader.fetch('script', {
  url: 'https://cdn.jsdelivr.net/npm/elementify@latest',
  loader: {
    async: async // Will override only async loader of this item
  }
});

The custom loader will not override data post-process for given type.

Creating custom type and loader

At last, Beloader let's you create your own loader for unsupported types. The callback must return and resolve or reject a promise. The callback is called within the Loader context.

var myLoader = function() {
  return new Promise((resolve, reject) => {
    // You can access QueueItem instance with this.parent
    // You can access Beloader instance with this.parent.parent
    // You can access plugins with this.pluginName
    // Do things
    if(good) resolve();
    else reject();
  });
}

var loader = new Beloader();

loader.fetch('custom', {
  url: 'https://server.com',
  loader: myLoader
}).then(item => console.log('Hurrah!'));

Events

Beloader provides a full internal event system.

For list of the available built-in events, please check API.

Registering callbacks to be run after of before built-in callback

Beloader let you define if your callback must be run before or after the matching built-in event.

If you provide a function named pre, the callback will be run before. If you provide any other named function or a closure, it will be run after.

var cbBefore = function pre(event) {
  // will be run before the built-in hook
}

var cbAfter = function (event) {
  // will be run after the built-in hook
}

Registering callbacks in options

You can register callbacks when providing options to a Beloader instance or creating a QueueItem instance with fetch.

var cbBefore = function pre(event) {
  // will be run before the built-in hook
}

var cbAfter = function (event) {
  // will be run after the built-in hook
}

var loader = new Beloader({
  on: {
    // Will be fired at each item loadstart event
    loadstarted: event => { console.log('Loading started for ' + event.target.parent.id) },
    afterprocess: cbBefore // Will run before the built-in hook
  }
});

fetch('script', {
  id: 'elementify',
  url: 'https://cdn.jsdelivr.net/npm/elementify@latest',
  on: {
    loadstarted: cbAfter // Will run after the built-in hook only for this item
  }
});

fetch('script', {
  id: 'lodash',
  url: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js'
});

Registering callbacks externally

You can also register callbacks with the on method exposed by Beloader, QueueItem and each Loader and Plugin.

var loader = new Loader();

loader.on('afterprocess', event => console.log('Over'));

You can use this way with autoprocess option set to false to add event to QueueItem instances.

Custom events

Beloader, QueueItem and each Loader and Plugin instance exposes a fire method to trigger events firing. You can use it to override internal events firing or add your custom ones.

Events are only bubbling up like this :

  • Loader -> QueueItem -> Beloader
  • Plugin -> Beloader

An event fired at the Beloader level will only affect the Beloader instance.

Controlling events

The Beloader event object mimics the native Event object and exposes 3 methods to control events :

  • BeloaderEvent#preventdefault() : If called inside a callback that will run before the built-in one, it will prevent default behaviour. It will not stop event propagation.
  • BeloaderEvent#stopPropagation() : Prevent the event from bubbling up
  • BeloaderEvent#stopImmediatePropagation() : Prevent any further callbacks to be called, even buit-in ones

Plugins

Plugins are very easy to implements in order to extends Beloader functionnalities and/or behaviours.

Load plugins

Name, URL and alias

You can provide up to three options when requiring a plugin :

  • name : name of the plugin that must match the name of the variable exposed in global scope for this given plugin
  • url : URL to load plugin from. If not provided, Beloader will assume that it must fetch plugin in official repo and will try to fetch the beloader-pluginName script ('beloader-' is automatically prependend to plugin name) from jsDelivr CDN : https://cdn.jsdelivr.net/gh/beloader/beloader-pluginName@latest
  • alias: Name of the variable that will be exposed in Beloader, QueueItem, Loader and Plugin instances to access plugin properties and methods. If not provided, the name will be used.

Only the plugin name is mandatory. Plugin architecture is designed to avoid using eval. Therefore, plugin are always loaded and evaluated in global namespace that may cause some conflicts for short plugins name.

Fetch plugins

You can directly use the plugin loader to fetch plugins. As soon as they will be loaded, they will be available in Beloader, QueueItem, Loader and other Plugin instances.

In case of long name, you can provide an alias that will be used as var name when using plugin

var loader = new Beloader({
  defer: true // will load plugins in the same order
});

loader.fetch('plugin', {
  name: 'plugin', // will look for beloader-plugin in official repo
  alias: 'p'
});

loader.fetch('plugin', {
  name: 'myplugin',
  url: 'myURLforThisplugin'
});

// We can use them safely thanks to defer
loader.fetch('none').promise.then(item => {
  p.doSomething(); // use alias
  myplugin.doSomething();
});

You can use awaiting mode for more exotic loading patterns or fetchAll to concatenate calls to plugins.

Fetch plugins at Beloader instance creation

Beloader lets you define an array of plugins requires when creating an instance.

In that case, you must use the ready promise property to ensure plugins are resolved firts or set global defer option to true.

var loader = new Beloader({
  plugins: [
    'plugin', // only the name
    {myplugin: 'myURLforThisplugin'}, // shortcut for name + url
    {
      name: 'myLongPluginName', // Full import format
      url: 'http://url',
      alias: 'mlp'
    }
  ]
});

loader.ready.then(() => {
  fetch('none').promise.then(item => {
    plugin.doSomething();
    myplugin.doSomething();
    mlp.doSomething();
  });
});

Custom instant plugins

You can easily add your own plugins to a Beloader instance with the pluginize method. Beloader will wrap your code inside an AbstractPlugin instance so all other plugins and events methods will be available.

Beloader will also always calls the init function of a plugin when loaded/pluginized. If the custom plugin is a function, Beloader will create an instance rather than adding raw prototype. It lets you use the init function as a constructor. The third argument of pluginize will be passed as argument to init function (and also available under this.options anyway).

init is also a good place to register callbacks for events if needed.

var myPlugin1 = {
  mystuff: function() {
    console.log('Yuups');
  }
};

var myPluginClass = function() {
  this.init = function(options) {
    this.index = options.index;
  };

  this.mystuff = function() {
    console.log('Yuups' + this.index);
  };
};

var loader = new Beloader();

loader.pluginize('p1', myPlugin1);
loader.pluginize('p2', myPluginClass, {index: 2});
loader.pluginize('p3', myPluginClass, {index: 3});

loader.ready.then(() => {
  fetch('none').promise.then(item => {
    p1.mystuff(); // echo 'Yuups'
    p2.mystuff(); // echo 'Yuups2'
    p3.mystuff(); // echo 'Yuups3'
  });
});

Async Plugins

A plugin can expose a promise property to be treated asynchronously. PluginLoader will wait for this promise to be resolved for considering that plugin is ready. It can be useful if plugin have to require some dependencies to be loaded before being usable.

Use Beloader as a module

Beloader is based on dynamic imports for its own loaders. Therefore, the BeLoader modules won't be automatically resolved in bundle when using webpack as modules for your own bundle.

For using Beloader as a module, you need to explicitly import then in your bundle.

Static import

import Beloader from 'beloader';
import 'beloader/dist/modules/FontLoader.min.js'; //will bundle the font loader into the main bundle

const loader = new Beloader();

loader.fetch('font', { webfont: [..] });

Lazy loading

import Beloader from 'beloader';

const loader = new Beloader();

import('beloader/dist/modules/FontLoader.min.js').then(() => {
  loader.fetch('font', { webfont: [..] });
});