Script Loading Toolkit
A browser library for handling asynchronously loading and interacting with third party script dependencies without race conditions or render blocking. Written in TypeScript.
Installation
For EcmaScript and CommonJS module distributions install with the package manager of your choice. (Recommended)
# npm npm install script-loading-toolkit --save# yarn yarn add script-loading-toolkit
or include the UMD browser distribution directly:
IMPORTANT: Script Loading Toolkit requires Promises. If you wish to support older browsers that do not implement promises then you will need to pollyfill this functionality yourself. You can do so with this NPM library or with Babel.
Usage
The script loading toolkit provides three major tools (Script
, BasicScript
and FunctionQueue
) for managing script loading. These can be used directly by newing them up and setting a script src; however their intended use is for extending or mixing into your own classes to create facades or adapters to wrap third party libraries in order decouple your code from a third party interface you do not control and may or may not yet exist in the global scope.
We recommend you use Async/Await when dealing with promises to simplify your code and reduce callback chains, however the below examples will also demonstrate Promise/then callback syntax.
Script
The Script
class can be used to load any script (by setting the src
attribute to a url) and has an asynchronous queueing API so you can start queueing up functions to be run once it has finished loading:
;// or use scriptToolkit.Script if using the unpkg browser distro. { super; thissrc = "http://acme.com/acmeScript.js"; };
Script class can be used directly aswell by passing a valid url string to the constructor, or an object with an src
attribute.
const myScript = "http://acme.com/acmeScript.js";const myScript2 = src: "http://acme.com/acmeScript.js" ; console; // > "http://acme.com/acmeScript.js"console; // > "http://acme.com/acmeScript.js"
Loading
Script Promise<this>
The .load()
method will return a promise that will reject if the script fails to load or resolve with the instance itself once loading is complete.
{ super; thissrc = "http://acme.com/acmeScript.js"; }; const myScript = ; /** Promise/then **/myScript; /** Async/Await **/try await acmeScript; catcherr // Oh no it failed to load!
Calling .load()
multiple times will not cause the script to load more than once. Subsequent calls to .load()
will all return the same promise or resolve immediatly if the script has already loaded.
// These will all return the same promise.myScript;await myScript; // This will resolve immediatlymyScript;
Enabling / Disabling loading
Script this
Script this
You can disable a script from loading, or re enable it with the .disable()
and .enable()
methods.
myScript;await myScript; // Console Warning > Could not load disabled script.console // > false
Queueing
Script Promise<T>
You can queue callbacks to run once your script has loaded. When using .enqueue()
a promise will be returned that will resolve with the return value of the passed function.
{ super; thissrc = "http://acme.com/acmeScript.js"; };const myScript = ; /** Promise/then **/// The enqueued function will not execute until the script has loaded.myScript; myScript; /** Async/Await **/const result = await myScript;console // > Loaded!
If the callback function returns a promise that promise will be resolved with .enqueue
.
myScript;const result = await myScript;console // > Loaded!
Once the script has loaded, enqueued callbacks will be executed immediatly.
await myScript;await myScript;
The .enqueue()
method is most powerful for use when extending Script
to create a facade that hides the need for the rest of your code to know whether the script has loaded to begin calling it's methods.
{ super; thissrc = "http://acme.com/acmeScript.js"; } { return this; } { return this; }; const myScript = ; // Methods of AcmeScript can be called before it is loaded and they will execute once it has loaded.myScript;myScript;
Initialization
After the script has loaded, executing queued callbacks is triggered by the initialize()
method. This method is not intended to be used directly, as it is called automatically after script loading finishes. If the script you have loaded requires initialization/configuration before it can be used; you can override the initialize()
method and add your initialization logic there. Make sure to call super.initialize()
after your initialization, in order to continue the Script lifecyle's completion.
{ super; thissrc = "http://acme.com/acmeScript.js"; } async { windowacmeScript; super; // Make sure to call the super method after your initialization is complete! };
Dependencies
Script this
If other scripts are required for a script to function and you don't want to handle loading them separately you can add them as dependencies. By default dependencies will be loaded simultaneously with the dependant script.
{ super; thissrc = "http://acme.com/someDependency.js"; }; { super; thissrc = "http://acme.com/acmeScript.js"; }; const myDependency = ;const myScript = ; // 'myScript' and 'myDependency' will start loading simultaneously. 'myScript' will not finsih loading until 'myDependency' has also finished.myScript;await myScript;console; // > True!
If a dependency MUST be loaded before it's dependant script (i.e loading it has side effects that must be in place for the dependant to not error), add it with the hasSideEffects
argument set to true
.
// 'myScript' will not begin loading until 'myDependency' has finished loading.myScript;await myScript;
Properties
The following properties are available on Script
instances:
Property | Type | Default | Description |
---|---|---|---|
.src | string | "" |
URL of the script to load, including protocol (//, http://, https://, etc). |
.htmlElement | HTMLScriptElement | new HTMLScriptElement() |
HTML element for the script that will be added to the DOM on loading. |
.isEnabled | boolean | true |
True if loading this script is enabled. |
.isLoading | boolean | false |
True if the script is currently loading. |
.isLoaded | boolean | false |
True if the script has finished loading without error. |
.isErrored | boolean | false |
True if the script has failed to load for some reason. |
.isExecuted | boolean | false |
True if the script's callback queue has been executed. |
.isInitialized | boolean | false |
True if the script has been initialized. |
.hasDependencies | boolean | false |
True if dependencies have been added to load with this script. |
Lifecycle Methods
Method | Description |
---|---|
onEnabled | Called every time after the .enable() method is called. |
onDisabled | Called every time after the .disable() method is called. |
onLoading | Called the first time .load() method is called, if the script is enabled. |
onLoaded | Called the first time after script loading completes. |
onErrored | Called if the script fails to load (only if it was enabled). |
onExecuted | Called the first time after all queued callbacks execute; triggered automatically after loading completes, as part of initialization. |
onInitialized | Called the first time the .initialize() method is called, this happens automatically after loading completes. |
Lifecycle methods are intended for use by overriding them when extending Script.
{ super; thissrc = "http://acme.com/acmeScript.js"; } // Use lifecycle methods like this: { console; }; // It is *not* recommended to override lifecycle methods directly:const myScript = ;myScript console; // Don't do this.
Direct Usage
You can use script directly without extension by creating an instance and overriding the src
property.
;// or use scriptToolkit.Script if using the unpkg browser distro. const acmeScript = ;acmeScriptsrc = "http://acme.com/acmeScript.js"; acmeScript;
BasicScript
BasicScript
is a leaner implementation of Script without the asynchronous queueing API. You can use this when you don't need queueing functionality. This is mainly inteded to give you flexibility when composing your own objects with the provided Mixin or with extension.
; const acmeScript = "http://acme.com/acmeScript.js"; acmeScript;
Properties
Property | Type | Default | Description |
---|---|---|---|
.src | string | "" |
URL of the script to load, including protocol (//, http://, https://, etc). |
.htmlElement | HTMLScriptElement | new HTMLScriptElement() |
HTML element for the script that will be added to the DOM on loading. |
.isEnabled | boolean | true |
True if loading this script is enabled. |
.isLoading | boolean | false |
True if the script is currently loading. |
.isLoaded | boolean | false |
True if the script has finished loading without error. |
.isErrored | boolean | false |
True if the script has failed to load for some reason. |
.hasDependencies | boolean | false |
True if dependencies have been added to load with this script. |
Lifecycle Methods
Method | Description |
---|---|
onEnabled | Called every time after the .enable() method is called. |
onDisabled | Called every time after the .disable() method is called. |
onLoading | Called the first time .load() method is called, if the script is enabled. |
onLoaded | Called the first time after script loading completes. |
onErrored | Called if the script fails to load (only if it was enabled). |
FunctionQueue
FunctionQueue
is only the queueing functionality from Script
without the script loading functionality. This can be useful for objects that might rely on a third party library being loaded, but you do not want to couple them with the logic to determine when that script should load.
; const myQueue = ; const myVideo = id: 123 { return myQueue; } { return myQueue; } // It is safe to call this method even if window.acmeVideo doesn't exist yet.// It wont run until we execute the queue.myVideo; const acmeScript = ;acmeVideosrc = "http://acme.com/acme-video-library.js"; acmeScript;
Properties
Property | Type | Default | Description |
---|---|---|---|
.isExecuted | boolean | false |
True if the script's callback queue has been executed. |
Lifecycle Methods
Method | Description |
---|---|
onEnabled | Called every time after the .enable() method is called. |
onExecuted | Called the first time after all queued callbacks execute; triggered automatically after loading completes, as part of initialization. |
Mixins
The Script Loading Toolkit includes 'Mixin' implementations of each of the above classes to allow you greater flexibility over classic inheritence. The mixin function will add all the functionality of one of the toolkit classes to the given constructor:
; { console; } AcmeSuperClass { thissrc = "http://acme.com/acme-video-library.js"; }const acmeScriptInstance = ;acmeScriptInstance; // > hi!acmeScriptInstance;
Function | Description |
---|---|
ScriptMixin | Adds Script functionality. |
FunctionQueueMixin | Adds FunctionQueue functionality. |
BasicScriptMixin | Adds BasicScript functionality. |
ScriptInitializerMixin | This mixin is to be used specifically on constructors/classes that implement both the FunctionQueue and BasicScript interfaces and adds the initializing functionality from Script . |