|
@@ -10,15 +10,15 @@ import {
|
|
|
} from 'phosphor/lib/ui/widget';
|
|
|
|
|
|
import {
|
|
|
- maxSatisfying
|
|
|
+ maxSatisfying, satisfies
|
|
|
} from 'semver';
|
|
|
|
|
|
|
|
|
/**
|
|
|
* A module loader using semver for dynamic resolution of requires.
|
|
|
*
|
|
|
- * It is meant to be used in conjunction with the JuptyerLabPlugin
|
|
|
- * for WebPack.
|
|
|
+ * It is meant to be used in conjunction with the JupyterLabPlugin
|
|
|
+ * for WebPack from `@jupyterlab/extension-builder`.
|
|
|
*/
|
|
|
export
|
|
|
class ModuleLoader {
|
|
@@ -29,8 +29,7 @@ class ModuleLoader {
|
|
|
// Provide the `require.ensure` function used for code
|
|
|
// splitting in the WebPack bundles.
|
|
|
// https://webpack.github.io/docs/code-splitting.html
|
|
|
- (this.require as any).ensure = this.ensureBundle.bind(this);
|
|
|
- this._boundRequire = this.require.bind(this);
|
|
|
+ this._boundRequire = this.require.bind(this) as any;
|
|
|
this._boundRequire.ensure = this.ensureBundle.bind(this);
|
|
|
}
|
|
|
|
|
@@ -43,7 +42,7 @@ class ModuleLoader {
|
|
|
* @param callback - The callback function for invoking the module.
|
|
|
*
|
|
|
* #### Notes
|
|
|
- * The callback is called with the module,
|
|
|
+ * This is a no-op if the path is already registered.
|
|
|
*/
|
|
|
define(path: string, callback: ModuleLoader.DefineCallback): void {
|
|
|
if (!(path in this._registered)) {
|
|
@@ -60,18 +59,25 @@ class ModuleLoader {
|
|
|
* @returns The exports of the requested module, if registered. The module
|
|
|
* selected is the registered module that maximally satisfies the semver
|
|
|
* range of the request.
|
|
|
+ *
|
|
|
+ * #### Notes
|
|
|
+ * Will throw an error if the required path cannot be satisfied.
|
|
|
*/
|
|
|
require(path: string): any {
|
|
|
// Check if module is in cache.
|
|
|
let id = this._findMatch(path);
|
|
|
+ if (!id) {
|
|
|
+ throw new Error(`No matching module found for: "${path}"`);
|
|
|
+ }
|
|
|
let installed = this._modules;
|
|
|
if (installed[id]) {
|
|
|
return installed[id].exports;
|
|
|
}
|
|
|
|
|
|
// Create a new module (and put it into the cache).
|
|
|
- let mod: Private.IModule = installed[id] = {
|
|
|
+ let mod: ModuleLoader.IModule = installed[id] = {
|
|
|
exports: {},
|
|
|
+ require: this._boundRequire,
|
|
|
id,
|
|
|
loaded: false
|
|
|
};
|
|
@@ -88,18 +94,20 @@ class ModuleLoader {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Ensure a bundle is loaded on a page.
|
|
|
+ * Ensure a bundle is loaded on the page.
|
|
|
*
|
|
|
* @param path - The public path of the bundle (e.g. "lab/jupyter.bundle.js").
|
|
|
*
|
|
|
* @param callback - The callback invoked when the bundle has loaded.
|
|
|
+ *
|
|
|
+ * @returns A promise that resolves when the bundle is loaded.
|
|
|
*/
|
|
|
ensureBundle(path: string, callback?: ModuleLoader.EnsureCallback): Promise<void> {
|
|
|
let bundle = this._getBundle(path);
|
|
|
|
|
|
if (bundle.loaded) {
|
|
|
if (callback) {
|
|
|
- callback.call(null, this.require);
|
|
|
+ callback.call(null, this._boundRequire);
|
|
|
}
|
|
|
return Promise.resolve(void 0);
|
|
|
}
|
|
@@ -179,7 +187,8 @@ class ModuleLoader {
|
|
|
}
|
|
|
if (sources.length === targets.length && sources.every((source, i) => {
|
|
|
return (source.package === targets[i].package
|
|
|
- && source.module === targets[i].module);
|
|
|
+ && source.module === targets[i].module
|
|
|
+ && satisfies(targets[i].version, source.version));
|
|
|
})) {
|
|
|
matches.push(mod);
|
|
|
versions.push(targets.map(t => t.version));
|
|
@@ -221,7 +230,7 @@ class ModuleLoader {
|
|
|
let promise = new Promise<void>((resolve, reject) => {
|
|
|
script.onload = () => {
|
|
|
while (bundle.callbacks.length) {
|
|
|
- bundle.callbacks.shift().call(null, this.require.bind(this));
|
|
|
+ bundle.callbacks.shift().call(null, this._boundRequire);
|
|
|
}
|
|
|
bundle.loaded = true;
|
|
|
resolve(void 0);
|
|
@@ -267,10 +276,10 @@ class ModuleLoader {
|
|
|
|
|
|
private _registered: { [key: string]: ModuleLoader.DefineCallback } = Object.create(null);
|
|
|
private _parsed: { [key: string]: Private.IPathInfo } = Object.create(null);
|
|
|
- private _modules: { [key: string]: Private.IModule } = Object.create(null);
|
|
|
+ private _modules: { [key: string]: ModuleLoader.IModule } = Object.create(null);
|
|
|
private _bundles: { [key: string]: Private.IBundle } = Object.create(null);
|
|
|
private _matches: { [key: string]: string } = Object.create(null);
|
|
|
- private _boundRequire: any;
|
|
|
+ private _boundRequire: ModuleLoader.IRequire;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -280,36 +289,23 @@ class ModuleLoader {
|
|
|
export
|
|
|
namespace ModuleLoader {
|
|
|
/**
|
|
|
- * The interface for a node require function.
|
|
|
- */
|
|
|
- export
|
|
|
- interface NodeRequireFunction {
|
|
|
- (id: string): any;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * The interface for the node require function.
|
|
|
+ * The interface for the require function.
|
|
|
*/
|
|
|
export
|
|
|
- interface NodeRequire extends NodeRequireFunction {
|
|
|
- resolve(id: string): string;
|
|
|
- cache: any;
|
|
|
- extensions: any;
|
|
|
- main: NodeModule | undefined;
|
|
|
+ interface IRequire {
|
|
|
+ (path: string): any;
|
|
|
+ ensure(path: string, callback?: EnsureCallback): Promise<void>;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * The interface fore a node require module.
|
|
|
+ * The interface for a module.
|
|
|
*/
|
|
|
export
|
|
|
- interface NodeModule {
|
|
|
+ interface IModule {
|
|
|
exports: any;
|
|
|
- require: NodeRequireFunction;
|
|
|
+ require: IRequire;
|
|
|
id: string;
|
|
|
- filename: string;
|
|
|
loaded: boolean;
|
|
|
- parent: NodeModule | null;
|
|
|
- children: NodeModule[];
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -317,13 +313,13 @@ namespace ModuleLoader {
|
|
|
* and a require function.
|
|
|
*/
|
|
|
export
|
|
|
- type DefineCallback = (module: any, exports: any, require: NodeRequire) => void;
|
|
|
+ type DefineCallback = (module: IModule, exports: any, require: IRequire) => void;
|
|
|
|
|
|
/**
|
|
|
* A callback for an ensure function that takes a require function.
|
|
|
*/
|
|
|
export
|
|
|
- type EnsureCallback = (require: NodeRequire) => void;
|
|
|
+ type EnsureCallback = (require: IRequire) => void;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -331,27 +327,6 @@ namespace ModuleLoader {
|
|
|
* A namespace for private module data.
|
|
|
*/
|
|
|
namespace Private {
|
|
|
- /**
|
|
|
- * A module record.
|
|
|
- */
|
|
|
- export
|
|
|
- interface IModule {
|
|
|
- /**
|
|
|
- * The exports of the module.
|
|
|
- */
|
|
|
- exports: any;
|
|
|
-
|
|
|
- /**
|
|
|
- * The id of the module.
|
|
|
- */
|
|
|
- id: string;
|
|
|
-
|
|
|
- /**
|
|
|
- * Whether the module has been loaded.
|
|
|
- */
|
|
|
- loaded: boolean;
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* A bundle record.
|
|
|
*/
|