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 upBeloaderEvent#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: [..] });
});