Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

es-module-loader

Package Overview
Dependencies
Maintainers
2
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

es-module-loader - npm Package Compare versions

Comparing version 1.2.1 to 1.3.0

docs/system-register-dynamic.md

80

bench/1-register-loading.js

@@ -675,20 +675,36 @@ import RegisterLoader from '../core/register-loader.js';

suite.add('Importing mulitple trees at the same time with RegisterLoader', async function() {
var loader = declaredRegisterLoader();
await Promise.all(allModules.map(m => loader.import(m)));
suite.add('Importing multiple trees at the same time with RegisterLoader', {
defer: true,
fn: async function(deferred) {
var loader = declaredRegisterLoader();
await Promise.all(allModules.map(m => loader.import(m)));
deferred.resolve();
}
});
suite.add('Importing a deep tree of modules with RegisterLoader', async function() {
var loader = declaredRegisterLoader();
await loader.import('_a.js');
suite.add('Importing a deep tree of modules with RegisterLoader', {
defer: true,
fn: async function(deferred) {
var loader = declaredRegisterLoader();
await loader.import('_a.js');
deferred.resolve();
}
});
suite.add('Importing a module with deps with RegisterLoader', async function() {
var loader = declaredRegisterLoader();
await loader.import('es6-withdep.js');
suite.add('Importing a module with deps with RegisterLoader', {
defer: true,
fn: async function(deferred) {
var loader = declaredRegisterLoader();
await loader.import('es6-withdep.js');
deferred.resolve();
}
});
suite.add('Importing a single registered module with RegisterLoader', async function() {
var loader = declaredRegisterLoader();
await loader.import('no-imports.js');
suite.add('Importing a single registered module with RegisterLoader', {
defer: true,
fn: async function(deferred) {
var loader = declaredRegisterLoader();
await loader.import('no-imports.js');
deferred.resolve();
}
});

@@ -723,20 +739,36 @@

suite.add('Importing mulitple trees at the same time with SystemJS', async function() {
var loader = declaredSystemJSLoader();
await Promise.all(allModules.map(m => loader.import(m)));
suite.add('Importing multiple trees at the same time with SystemJS', {
defer: true,
fn: async function(deferred) {
var loader = declaredSystemJSLoader();
await Promise.all(allModules.map(m => loader.import(m)));
deferred.resolve();
}
});
suite.add('Importing a single registered module with SystemJS', async function() {
var loader = declaredSystemJSLoader();
await loader.import('no-imports.js');
suite.add('Importing a deep tree of modules with SystemJS', {
defer: true,
fn: async function(deferred) {
var loader = declaredSystemJSLoader();
await loader.import('_a.js');
deferred.resolve();
}
});
suite.add('Importing a module with deps with SystemJS', async function() {
var loader = declaredSystemJSLoader();
await loader.import('es6-withdep.js');
suite.add('Importing a module with deps with SystemJS', {
defer: true,
fn: async function(deferred) {
var loader = declaredSystemJSLoader();
await loader.import('es6-withdep.js');
deferred.resolve();
}
});
suite.add('Importing a deep tree of modules with SystemJS', async function() {
var loader = declaredSystemJSLoader();
await loader.import('_a.js');
suite.add('Importing a single registered module with SystemJS', {
defer: true,
fn: async function(deferred) {
var loader = declaredSystemJSLoader();
await loader.import('no-imports.js');
deferred.resolve();
}
});

@@ -9,2 +9,8 @@ import fs from 'fs';

process.on('unhandledRejection', function (e) {
setTimeout(function () {
throw e;
});
});
function runNextBenchmark() {

@@ -34,3 +40,3 @@ var nextBenchmark = benchmarks.shift();

suite.run({ async: true, maxTime: 1, minSamples: 1 });
suite.run({ defer: true });
});

@@ -43,2 +49,2 @@ })

runNextBenchmark();
runNextBenchmark();

@@ -11,7 +11,15 @@ /*

export function pathToFileUrl(filePath) {
/*
* Simple Symbol() shim
*/
var hasSymbol = typeof Symbol !== 'undefined';
export function createSymbol (name) {
return hasSymbol ? Symbol() : '@@' + name;
}
export function pathToFileUrl (filePath) {
return 'file://' + (isWindows ? '/' : '') + filePath;
}
export function fileUrlToPath(fileUrl) {
export function fileUrlToPath (fileUrl) {
if (fileUrl.substr(0, 7) !== 'file://')

@@ -65,3 +73,3 @@ throw new RangeError(fileUrl + ' is not a valid file url');

var errArgs = new Error(0, '_').fileName == '_';
function LoaderError__Check_error_message_above_for_loader_stack(childErr, newMessage) {
function LoaderError__Check_error_message_for_loader_stack (childErr, newMessage) {
// Convert file:/// URLs to paths in Node

@@ -92,2 +100,2 @@ if (!isBrowser)

}
export { LoaderError__Check_error_message_above_for_loader_stack as addToError }
export { LoaderError__Check_error_message_for_loader_stack as addToError }

@@ -1,11 +0,6 @@

import { baseURI, addToError } from './common.js';
export { Loader, Module, ModuleNamespace as InternalModuleNamespace }
import { baseURI, addToError, createSymbol } from './common.js';
/*
* Simple Symbol() shim
*/
var hasSymbol = typeof Symbol !== 'undefined';
function createSymbol(name) {
return hasSymbol ? Symbol() : '@@' + name;
}
// multiple exports of Module for backwards compat
// only ModuleNamespace will remain in next break!
export { Loader, Module, Module as InternalModuleNamespace, Module as ModuleNamespace }

@@ -15,3 +10,3 @@ /*

*/
function arrayValues(arr) {
function arrayValues (arr) {
if (arr.values)

@@ -24,7 +19,7 @@ return arr.values();

var iterable = {};
iterable[Symbol.iterator] = function() {
iterable[Symbol.iterator] = function () {
var keys = Object.keys(arr);
var keyIndex = 0;
return {
next: function() {
next: function () {
if (keyIndex < keys.length)

@@ -52,3 +47,3 @@ return {

// 3.1.1
function Loader(baseKey) {
function Loader (baseKey) {
this.key = baseKey || baseURI;

@@ -60,6 +55,11 @@ this.registry = new Registry();

// 3.3.2
Loader.prototype.import = function(key, parent) {
Loader.prototype.import = function (key, parent) {
if (typeof key !== 'string')
throw new TypeError('Loader import method must be passed a module key string');
return this.load(key, parent);
// custom resolveInstantiate combined hook for better perf
return Promise.resolve(this[RESOLVE_INSTANTIATE](key, parent || this.key))
.then(Module.evaluate)
.catch(function (err) {
throw addToError(err, 'Loading ' + key + (parent ? ' from ' + parent : ''));
});
};

@@ -69,9 +69,16 @@ // 3.3.3

// instantiate sets the namespace into the registry
// it is up to implementations to ensure instantiate is debounced properly
var INSTANTIATE = Loader.instantiate = createSymbol('instantiate');
/*
* Combined resolve / instantiate hook
*
* Not in current reduced spec, but necessary to separate RESOLVE from RESOLVE + INSTANTIATE as described
* in the spec notes of this repo to ensure that loader.resolve doesn't instantiate when not wanted.
*
* We implement RESOLVE_INSTANTIATE as a single hook instead of a separate INSTANTIATE in order to avoid
* the need for double registry lookups as a performance optimization.
*/
var RESOLVE_INSTANTIATE = Loader.resolveInstantiate = createSymbol('resolveInstantiate');
Loader.prototype.resolve = function(key, parent) {
Loader.prototype.resolve = function (key, parent) {
return this[RESOLVE](key, parent)
.catch(function(err) {
.catch(function (err) {
throw addToError(err, 'Resolving ' + key + (parent ? ' to ' + parent : ''));

@@ -81,32 +88,9 @@ });

// 3.3.4
Loader.prototype.load = function(key, parent) {
var loader = this;
var registry = loader.registry._registry;
var resolvedKey;
// there is the potential for an internal perf optimization to allow resolve to return { resolved, namespace }
// but this needs to be done based on performance measurement
return Promise.resolve(this[RESOLVE](key, parent || this.key))
.then(function(resolved) {
var existingNamespace = registry[resolved];
if (existingNamespace)
return Promise.resolve(existingNamespace);
return loader[INSTANTIATE](resolved)
.then(function(namespace) {
// returning the namespace from instantiate can be considered a sort of perf optimization
if (!namespace)
namespace = loader.registry.get(resolvedKey);
else if (!(namespace instanceof ModuleNamespace))
throw new TypeError('Instantiate did not resolve a Module Namespace');
return namespace;
});
})
.catch(function(err) {
throw addToError(err, 'Loading ' + key + (resolvedKey ? ' as ' + resolvedKey : '') + (parent ? ' from ' + parent : ''));
// 3.3.4 (import without evaluate)
// this is not documented because the use of deferred evaluation as in Module.evaluate is not
// documented, as it is not considered a stable feature to be encouraged
Loader.prototype.load = function (key, parent) {
return Promise.resolve(this[RESOLVE_INSTANTIATE](key, parent || this.key))
.catch(function (err) {
throw addToError(err, 'Loading ' + key + (parent ? ' from ' + parent : ''));
});

@@ -129,3 +113,3 @@ };

// 4.4.1
Registry.prototype.constructor = function() {
Registry.prototype.constructor = function () {
throw new TypeError('Custom registries cannot be created.');

@@ -136,3 +120,3 @@ };

// 4.4.2
Registry.prototype[Symbol.iterator] = function() {
Registry.prototype[Symbol.iterator] = function () {
return this.entries()[Symbol.iterator]();

@@ -142,5 +126,5 @@ };

// 4.4.3
Registry.prototype.entries = function() {
Registry.prototype.entries = function () {
var registry = this._registry;
return arrayValues(Object.keys(registry).map(function(key) {
return arrayValues(Object.keys(registry).map(function (key) {
return [key, registry[key]];

@@ -152,9 +136,9 @@ }));

// 4.4.4
Registry.prototype.keys = function() {
Registry.prototype.keys = function () {
return arrayValues(Object.keys(this._registry));
};
// 4.4.5
Registry.prototype.values = function() {
Registry.prototype.values = function () {
var registry = this._registry;
return arrayValues(Object.keys(registry).map(function(key) {
return arrayValues(Object.keys(registry).map(function (key) {
return registry[key];

@@ -164,8 +148,8 @@ }));

// 4.4.6
Registry.prototype.get = function(key) {
Registry.prototype.get = function (key) {
return this._registry[key];
};
// 4.4.7
Registry.prototype.set = function(key, namespace) {
if (!(namespace instanceof ModuleNamespace))
Registry.prototype.set = function (key, namespace) {
if (!(namespace instanceof Module))
throw new Error('Registry must be set with an instance of Module Namespace');

@@ -176,7 +160,7 @@ this._registry[key] = namespace;

// 4.4.8
Registry.prototype.has = function(key) {
Registry.prototype.has = function (key) {
return !!this._registry[key];
};
// 4.4.9
Registry.prototype.delete = function(key) {
Registry.prototype.delete = function (key) {
if (this._registry[key]) {

@@ -195,28 +179,44 @@ //delete this._registry[key];

*/
function ModuleNamespace(baseObject, evaluate) {
var ns = this;
Object.keys(baseObject).forEach(function(key) {
Object.defineProperty(ns, key, {
configurable: false,
enumerable: true,
get: function () {
return baseObject[key];
},
set: function() {
throw new TypeError('Module exports cannot be changed externally.');
}
});
var EVALUATE = createSymbol('evaluate');
var EVALUATION_CONTEXT = createSymbol('evaluationContext');
var BASE_OBJECT = createSymbol('baseObject');
var EVALUATE_ERROR = createSymbol()
// 8.3.1 Reflect.Module
/*
* Best-effort simplified non-spec implementation based on
* a baseObject referenced via getters.
*
* Allows:
*
* loader.registry.set('x', new Module({ default: 'x' }));
*
* Optional evaluation function provides experimental Module.evaluate
* support for non-executed modules in registry.
*/
function Module (baseObject, evaluate) {
Object.defineProperty(this, BASE_OBJECT, {
value: baseObject
});
if (evaluate)
Object.defineProperty(ns, '$__evaluate', {
// evaluate defers namespace population
if (evaluate) {
Object.defineProperty(this, EVALUATE, {
value: evaluate,
configurable: true,
writable: true
});
}
}
else {
Object.keys(baseObject).forEach(extendNamespace, this);
}
};
// 8.4.2
Module.prototype = Object.create(null);
if (typeof Symbol !== 'undefined' && Symbol.toStringTag)
ModuleNamespace.prototype[Symbol.toStringTag] = 'Module';
Module.prototype[Symbol.toStringTag] = 'Module';
else
Object.defineProperty(ModuleNamespace.prototype, 'toString', {
value: function() {
Object.defineProperty(Module.prototype, 'toString', {
value: function () {
return '[object Module]';

@@ -226,36 +226,40 @@ }

// 8.3.1 Reflect.Module
function Module(descriptors, executor, evaluate) {
if (typeof descriptors !== 'object')
throw new TypeError('Expected descriptors object');
// instead of providing a mutator, just provide the base object
var baseObject = {};
// 8.2.1 ParseExportsDescriptors
Object.keys(descriptors).forEach(function(key) {
var descriptor = descriptors[key];
if (!('value' in descriptor))
throw new TypeError('Error reading descriptor for "' + key + '" - module polyfill only supports value descriptors currently');
baseObject[key] = descriptor.value;
function extendNamespace (key) {
Object.defineProperty(this, key, {
enumerable: true,
get: function () {
return this[BASE_OBJECT][key];
},
set: function () {
throw new TypeError('Module exports cannot be changed externally.');
}
});
}
var ns = new ModuleNamespace(baseObject, evaluate);
function doEvaluate (evaluate, context) {
try {
evaluate.call(context);
}
catch (e) {
return e;
}
}
if (executor)
executor(baseObject, ns);
// 8.4.1 Module.evaluate... not documented or used because this is potentially unstable
Module.evaluate = function (ns) {
var evaluate = ns[EVALUATE];
if (evaluate) {
ns[EVALUATE] = undefined;
var err = doEvaluate(evaluate);
if (err) {
// cache the error
ns[EVALUATE] = function () {
throw err;
};
throw err;
}
Object.keys(ns[BASE_OBJECT]).forEach(extendNamespace, ns);
}
// make chainable
return ns;
};
// 8.4.2
Module.prototype = null;
// 8.4.1 Module.evaluate
Module.evaluate = function(ns) {
if (ns.$__evaluate) {
ns.$__evaluate();
ns.$__evaluate = undefined;
}
};

@@ -1,8 +0,8 @@

import { Loader, Module, InternalModuleNamespace as ModuleNamespace } from './loader-polyfill.js';
import { Loader, Module, } from './loader-polyfill.js';
import { resolveUrlToParentIfNotPlain } from './resolve.js';
import { addToError, global } from './common.js';
import { addToError, global, createSymbol } from './common.js';
export default RegisterLoader;
export var emptyModule = new ModuleNamespace({});
export var emptyModule = new Module({});

@@ -19,10 +19,14 @@ /*

*/
function RegisterLoader(baseKey) {
var REGISTER_REGISTRY = createSymbol('registerRegistry');
var REGISTERED_LAST_ANON = createSymbol('registeredLastAnon');
function RegisterLoader (baseKey) {
Loader.apply(this, arguments);
// last anonymous System.register call
this._registeredLastAnon = undefined;
this[REGISTERED_LAST_ANON] = undefined;
// in-flight es module load records
this._registerRegistry = {};
this[REGISTER_REGISTRY] = {};

@@ -38,306 +42,358 @@ // tracing

// these are implementation specific
// this allows a v2 migration path into symbols so normalize and instantiate
// aren't exposed to end-users
// NB replace with createSymbol('normalize'), ... for next major
RegisterLoader.normalize = 'normalize';
RegisterLoader.instantiate = 'instantiate';
RegisterLoader.createMetadata = 'createMetadata';
RegisterLoader.processRegisterContext = 'processRegisterContext';
// default normalize is the WhatWG style normalizer
RegisterLoader.prototype.normalize = function(key, parentKey, metadata) {
RegisterLoader.prototype.normalize = function (key, parentKey, metadata) {
return resolveUrlToParentIfNotPlain(key, parentKey);
};
RegisterLoader.prototype.instantiate = function(key, metadata) {};
RegisterLoader.prototype.instantiate = function (key, metadata) {};
// this function is an optimization to allow loader extensions to
// this function is an optimization to allow loader extensions to
// implement it to set the metadata object shape upfront to ensure
// it can run as a single hidden class throughout the normalize
// and instantiate pipeline hooks in the js engine
RegisterLoader.prototype.createMetadata = function() {
RegisterLoader.prototype.createMetadata = function () {
return {};
};
function ensureResolution (resolvedKey) {
if (resolvedKey === undefined)
throw new RangeError('No resolution found.');
return resolvedKey;
}
function resolve (loader, key, parentKey, metadata) {
return Promise.resolve()
.then(function () {
return loader.normalize(key, parentKey, metadata);
})
.then(ensureResolution);
}
var RESOLVE = Loader.resolve;
RegisterLoader.prototype[RESOLVE] = function(key, parentKey) {
var loader = this;
var registry = loader.registry._registry;
// normalization shortpath if already in the registry or loading
if (loader._registerRegistry[key] || registry[key])
RegisterLoader.prototype[RESOLVE] = function (key, parentKey) {
if (loader[REGISTER_REGISTRY][key] || loader.registry._registry[key])
return Promise.resolve(key);
var metadata = this.createMetadata();
return Promise.resolve(loader.normalize(key, parentKey, metadata))
.then(function(resolvedKey) {
if (resolvedKey === undefined)
throw new RangeError('No resolution normalizing "' + key + '" to ' + parentKey);
// we create the in-progress load record already here to store the normalization metadata
if (!registry[resolvedKey])
(loader._registerRegistry[resolvedKey] || createLoadRecord(loader, resolvedKey)).metadata = metadata;
return resolvedKey;
});
return resolve(this, key, parentKey, this.createMetadata());
};
// provides instantiate promise cache
// we need to first wait on instantiate which will tell us if it is ES or not
// this record represents that waiting period, and when set, we then populate
// the esLinkRecord record into this load record.
// instantiate is a promise for a module namespace or undefined
function createLoadRecord(loader, key) {
return loader._registerRegistry[key] = {
// once evaluated, the linkRecord is set to undefined leaving just the other load record properties
// this allows tracking new binding listeners for es modules through importerSetters
// for dynamic modules, the load record is removed entirely.
function createLoadRecord (key, registration) {
return this[REGISTER_REGISTRY][key] = {
key: key,
metadata: undefined,
// define cache
defined: undefined,
// in-flight
instantiatePromise: undefined,
// loaded
// defined System.register cache
registration: registration,
// module namespace object
module: undefined,
// es-specific
esLinkRecord: undefined,
importerSetters: undefined
// es-only
// this sticks around so new module loads can listen to binding changes
// for already-loaded modules by adding themselves to their importerSetters
importerSetters: undefined,
// in-flight linking record
linkRecord: {
metadata: undefined,
// promise for instantiated
instantiatePromise: undefined,
dependencies: undefined,
execute: undefined,
// underlying module object bindings
moduleObj: undefined,
// es only, also indicates if es or not
setters: undefined,
// promise for instantiated dependencies (dependencyInstantiations populated)
depsInstantiatePromise: undefined,
// will be the array of dependency load record or a module namespace
dependencyInstantiations: [],
// indicates if the load and all its dependencies are instantiated and linked
// but not yet executed
// mostly just a performance shortpath to avoid rechecking the promises above
linked: false,
error: undefined
// NB optimization and way of ensuring module objects in setters
// indicates setters which should run pre-execution of that dependency
// hoisted: undefined
}
};
}
RegisterLoader.prototype[Loader.instantiate] = function(key) {
RegisterLoader.prototype[Loader.resolveInstantiate] = function (key, parentKey) {
var loader = this;
return instantiate(this, key)
.then(function(instantiated) {
if (instantiated instanceof ModuleNamespace)
return Promise.resolve(instantiated);
var registry = loader.registry._registry;
var registerRegistry = loader[REGISTER_REGISTRY];
return instantiateAllDeps(loader, instantiated, [])
.then(function() {
var err = ensureEvaluated(loader, instantiated, []);
if (err)
return Promise.reject(err);
return resolveInstantiate(loader, key, parentKey, registry, registerRegistry)
.then(function (instantiated) {
if (instantiated instanceof Module)
return instantiated;
if (loader.trace)
traceLoadRecord(loader, instantiated, []);
return instantiated.module || emptyModule;
// resolveInstantiate always returns a load record with a link record and no module value
if (instantiated.linkRecord.linked)
return ensureEvaluate(loader, instantiated, instantiated.linkRecord, registry, registerRegistry);
return instantiateDeps(loader, instantiated, instantiated.linkRecord, registry, registerRegistry, [instantiated])
.then(function () {
return ensureEvaluate(loader, instantiated, instantiated.linkRecord, registry, registerRegistry);
})
.catch(function(err) {
.catch(function (err) {
clearLoadErrors(loader, instantiated);
throw err;
});
})
});
};
// instantiates the given module name
// returns the load record for es or the namespace object for dynamic
// setting the dynamic namespace into the registry
function instantiate(loader, key) {
var load = loader._registerRegistry[key];
function resolveInstantiate (loader, key, parentKey, registry, registerRegistry) {
// normalization shortpaths for already-normalized key
// could add a plain name filter, but doesn't yet seem necessary for perf
var module = registry[key];
if (module)
return Promise.resolve(module);
// this is impossible assuming resolve always runs before instantiate
if (!load)
throw new TypeError('Internal error, load record not created');
var load = registerRegistry[key];
return load.instantiatePromise || (load.instantiatePromise = Promise.resolve(loader.instantiate(key, load.metadata))
.then(function(instantiation) {
// dynamic module
if (instantiation !== undefined) {
loader.registry._registry[key] = instantiation;
loader._registerRegistry[key] = undefined;
return instantiation;
}
// already linked but not in main registry is ignored
if (load && !load.module)
return instantiate(loader, load, load.linkRecord, registry, registerRegistry);
// run the cached loader.register declaration if there is one
ensureRegisterLinkRecord.call(loader, load);
var metadata = loader.createMetadata();
return resolve(loader, key, parentKey, metadata)
.catch(function (err) {
throw addToError(err, 'Resolving dependency "' + key + '" of ' + parentKey);
})
.then(function (resolvedKey) {
// main loader registry always takes preference
module = registry[resolvedKey];
if (module)
return module;
// metadata no longer needed
if (!loader.trace)
load.metadata = undefined;
load = registerRegistry[resolvedKey];
return load;
})
.catch(function(err) {
err = addToError(err, 'Instantiating ' + load.key);
// already has a module value but not already in the registry (load.module)
// means it was removed by registry.delete, so we should
// disgard the current load record creating a new one over it
// but keep any existing registration
if (!load || load.module)
load = createLoadRecord.call(loader, resolvedKey, load && load.registration);
// immediately clear the load record for an instantiation error
if (loader._registerRegistry[load.key] === load)
loader._registerRegistry[load.key] = undefined;
var link = load.linkRecord;
if (!link)
return load;
throw err;
}));
link.metadata = link.metadata || metadata;
return instantiate(loader, load, link, registry, registerRegistry);
});
}
// this only applies to load records with load.esLinkRecord set
function instantiateAllDeps(loader, load, seen) {
// skip if already linked
if (load.module)
return Promise.resolve();
function instantiate (loader, load, link, registry, registerRegistry) {
return link.instantiatePromise || (link.instantiatePromise =
// if there is already an existing registration, skip running instantiate
(load.registration ? Promise.resolve() : Promise.resolve().then(function () {
return loader.instantiate(load.key, link.metadata);
}))
.then(function (instantiation) {
// direct module return from instantiate -> we're done
if (instantiation !== undefined) {
if (!(instantiation instanceof Module))
throw new TypeError('Instantiate did not return a valid Module object.');
var esLinkRecord = load.esLinkRecord;
registerRegistry[load.key] = undefined;
if (loader.trace)
traceLoad(load, link);
return registry[load.key] = instantiation;
}
// no dependencies shortpath
if (!esLinkRecord.dependencies.length)
return Promise.resolve();
// run the cached loader.register declaration if there is one
var registration = load.registration;
// clear to allow new registrations for future loads (combined with registry delete)
load.registration = undefined;
if (!registration)
throw new TypeError('Module instantiation did not call an anonymous or correctly named System.register.');
// assumes seen does not contain load already
seen.push(load);
link.dependencies = registration[0];
var instantiateDepsPromises = Array(esLinkRecord.dependencies.length);
var registry = loader.registry._registry;
load.importerSetters = [];
// normalize dependencies
for (var i = 0; i < esLinkRecord.dependencies.length; i++) (function(i) {
// this resolve can potentially be cached on the link record, should be a measured optimization
instantiateDepsPromises[i] = loader[RESOLVE](esLinkRecord.dependencies[i], load.key)
.catch(function(err) {
throw addToError(err, 'Resolving ' + esLinkRecord.dependencies[i] + ' to ' + load.key);
})
.then(function(resolvedDepKey) {
if (loader.trace) {
esLinkRecord.depMap = esLinkRecord.depMap || {};
esLinkRecord.depMap[esLinkRecord.dependencies[i]] = resolvedDepKey;
}
// process System.registerDynamic declaration
if (registration[2])
registerDynamic(loader, load, link, registry, registerRegistry, registration[1]);
var existingNamespace = registry[resolvedDepKey];
if (existingNamespace) {
esLinkRecord.dependencyInstantiations[i] = existingNamespace;
// run setter to reference the module
if (esLinkRecord.setters[i])
esLinkRecord.setters[i](existingNamespace);
return Promise.resolve();
}
// process System.register declaration
else
registerDeclarative(loader, load, link, registration[1]);
return instantiate(loader, resolvedDepKey)
.then(function(instantiation) {
// instantiation is either a load record or a module namespace
esLinkRecord.dependencyInstantiations[i] = instantiation;
// shortpath to instantiateDeps
if (!link.dependencies.length) {
link.linked = true;
if (loader.trace)
traceLoad(load, link);
}
// dynamic module
if (instantiation instanceof ModuleNamespace) {
if (esLinkRecord.setters[i])
esLinkRecord.setters[i](instantiation);
return Promise.resolve();
}
return load;
})
.catch(function (err) {
throw link.error = addToError(err, 'Instantiating ' + load.key);
}));
}
// register setter with dependency
instantiation.importerSetters.push(esLinkRecord.setters[i]);
// like resolveInstantiate, but returning load records for linking
function resolveInstantiateDep (loader, key, parentKey, registry, registerRegistry, traceDepMap) {
// normalization shortpaths for already-normalized key
// could add a plain name filter, but doesn't yet seem necessary for perf
var load = registerRegistry[key];
var module = registry[key];
// run setter now to pick up the first bindings from the dependency
if (esLinkRecord.setters[i])
esLinkRecord.setters[i](instantiation.esLinkRecord.moduleObj);
if (module) {
if (traceDepMap)
traceDepMap[key] = key;
// circular
if (seen.indexOf(instantiation) !== -1)
return Promise.resolve();
// registry authority check in case module was deleted or replaced in main registry
if (load && load.module && load.module === module)
return load;
else
return module;
}
// es module load
// already linked but not in main registry is ignored
if (load && !load.module) {
if (traceDepMap)
traceDepMap[key] = key;
return instantiate(loader, load, load.linkRecord, registry, registerRegistry);
}
// if not already linked, instantiate dependencies
if (instantiation.esLinkRecord)
return instantiateAllDeps(loader, instantiation, seen);
});
})
})(i);
var metadata = loader.createMetadata();
return resolve(loader, key, parentKey, metadata)
.catch(function (err) {
throw addToError(err, 'Resolving dependency "' + key + '" of ' + parentKey);
})
.then(function (resolvedKey) {
if (traceDepMap)
traceDepMap[key] = key;
return Promise.all(instantiateDepsPromises)
.catch(function(err) {
err = addToError(err, 'Loading ' + load.key);
// similar logic to above
load = registerRegistry[resolvedKey];
module = registry[resolvedKey];
// throw up the instantiateAllDeps stack
// loads are then synchonously cleared at the top-level through the helper below
// this then ensures avoiding partially unloaded tree states
esLinkRecord.error = err;
// main loader registry always takes preference
if (module && (!load || load.module && module !== load.module))
return module;
throw err;
// already has a module value but not already in the registry (load.module)
// means it was removed by registry.delete, so we should
// disgard the current load record creating a new one over it
// but keep any existing registration
if (!load || !module && load.module)
load = createLoadRecord.call(loader, resolvedKey, load && load.registration);
var link = load.linkRecord;
if (!link)
return load;
link.metadata = link.metadata || metadata;
return instantiate(loader, load, link, registry, registerRegistry);
});
}
// clears an errored load and all its errored dependencies from the loads registry
function clearLoadErrors(loader, load) {
// clear from loads
if (loader._registerRegistry[load.key] === load)
loader._registerRegistry[load.key] = undefined;
function traceLoad (load, link) {
loader.loads[load.key] = {
key: load.key,
dependencies: link.dependencies,
depMap: link.depMap || {},
metadata: link.metadata
};
}
var esLinkRecord = load.esLinkRecord;
function registerDynamic (loader, load, link, registry, registerRegistry, execute) {
var moduleObj = link.moduleObj = {};
if (!esLinkRecord)
return;
// create a closure on dependencies and dependencyInstantiations only
var dependencyInstantiations = link.dependencyInstantiations = [];
var dependencies = link.dependencies;
var key = load.key;
esLinkRecord.dependencyInstantiations.forEach(function(depLoad, index) {
if (!depLoad || depLoad instanceof ModuleNamespace)
return;
// we can only require from already-known dependencies
function require (name) {
for (var i = 0; i < dependencies.length; i++) {
if (dependencies[i] === name) {
var depLoad = dependencyInstantiations[i];
var err;
if (depLoad.esLinkRecord && depLoad.esLinkRecord.error) {
// unregister setters for es dependency load records
var setterIndex = depLoad.importerSetters.indexOf(esLinkRecord.setters[index]);
depLoad.importerSetters.splice(setterIndex, 1);
var module;
// provides a circular reference check
if (loader._registerRegistry[depLoad.key] === depLoad)
clearLoadErrors(loader, depLoad);
if (depLoad instanceof Module)
module = depLoad;
else
module = ensureEvaluate(loader, depLoad, depLoad.linkRecord, registry, registerRegistry);
return module.__useDefault ? module.default : module;
}
}
});
}
throw new Error('Module ' + name + ' not declared as a System.registerDynamic dependency of ' + key);
}
function createESLinkRecord(dependencies, setters, module, moduleObj, execute) {
return {
dependencies: dependencies,
error: undefined,
// will be the dependency ES load record, or a module namespace
dependencyInstantiations: Array(dependencies.length),
setters: setters,
module: module,
moduleObj: moduleObj,
execute: execute
};
link.execute = function () {
var exports = moduleObj.default = {};
Object.defineProperty(moduleObj, '__useDefault', { value: true });
var module = { exports: exports, id: key };
// execute then copy the exports onto moduleObj
copyNamedExports(execute(require, exports, module) || module.exports, moduleObj);
}
}
/*
* System.register
* Places the status into the registry and a load into the loads list
* Convert a CJS module.exports into a valid object for new Module:
*
* new Module(getEsModule(module.exports))
*
* Sets the default value to the module, while also reading off named exports carefully.
*/
RegisterLoader.prototype.register = function(key, deps, declare) {
// anonymous modules get stored as lastAnon
if (declare === undefined)
this._registeredLastAnon = [key, deps];
// everything else registers into the register cache
else
(this._registerRegistry[key] || createLoadRecord(this, key)).defined = [deps, declare];
};
RegisterLoader.prototype.processRegisterContext = function(contextKey) {
if (!this._registeredLastAnon)
function copyNamedExports(exports, moduleObj) {
// don't trigger getters/setters in environments that support them
if ((typeof exports != 'object' && typeof exports != 'function') || exports === global)
return;
(this._registerRegistry[contextKey] || createLoadRecord(this, contextKey)).defined = this._registeredLastAnon;
this._registeredLastAnon = undefined;
};
for (var p in exports)
if (p !== 'default')
defineOrCopyProperty(moduleObj, exports, p);
function ensureRegisterLinkRecord(load) {
// ensure we already have a link record
if (load.esLinkRecord)
return;
moduleObj.default = exports;
}
var key = load.key;
var registrationPair = load.defined;
function defineOrCopyProperty(targetObj, sourceObj, propName) {
try {
var d;
if (d = Object.getOwnPropertyDescriptor(sourceObj, propName))
Object.defineProperty(targetObj, propName, d);
}
catch (ex) {
// Object.getOwnPropertyDescriptor threw an exception, fall back to normal set property
// we dont need hasOwnProperty here because getOwnPropertyDescriptor would have returned undefined above
targetObj[propName] = sourceObj[propName];
}
}
if (!registrationPair)
throw new TypeError('Module instantiation did not call an anonymous or correctly named System.register');
function registerDeclarative (loader, load, link, declare) {
var moduleObj = link.moduleObj = {};
var importerSetters = load.importerSetters;
load.defined = undefined;
var importerSetters = [];
var moduleObj = {};
var locked = false;
var declared = registrationPair[1].call(global, function(name, value) {
// closure especially not based on link to allow link record disposal
var declared = declare.call(global, function (name, value) {
// export setter propogation with locking to avoid cycles

@@ -355,139 +411,306 @@ if (locked)

if (importerSetters.length) {
locked = true;
for (var i = 0; i < importerSetters.length; i++)
// this object should be a defined module object
// but in order to do that we need the exports returned by declare
// for now we assume no exports in the implementation
importerSetters[i](moduleObj);
locked = true;
for (var i = 0; i < importerSetters.length; i++)
importerSetters[i](moduleObj);
locked = false;
locked = false;
}
return value;
}, new ContextualLoader(this, key));
}, new ContextualLoader(loader, load.key));
var setters, execute;
if (typeof declared !== 'function') {
setters = declared.setters;
execute = declared.execute;
link.setters = declared.setters;
link.execute = declared.execute;
}
else {
setters = [],
execute = declared;
link.setters = [];
link.execute = declared;
}
}
// TODO, pass module when we can create it here already via exports
load.importerSetters = importerSetters;
load.esLinkRecord = createESLinkRecord(registrationPair[0], setters, undefined, moduleObj, execute);
function instantiateDeps (loader, load, link, registry, registerRegistry, seen) {
return (link.depsInstantiatePromise || (link.depsInstantiatePromise = Promise.resolve()
.then(function () {
var depsInstantiatePromises = Array(link.dependencies.length);
for (var i = 0; i < link.dependencies.length; i++)
depsInstantiatePromises[i] = resolveInstantiateDep(loader, link.dependencies[i], load.key, registry, registerRegistry, loader.trace && (link.depMap = {}));
return Promise.all(depsInstantiatePromises);
})
.then(function (dependencyInstantiations) {
// for registerDynamic, we need dependencyInstantiations
// to work by reference as we have a closure on it
if (!link.setters) {
for (var i = 0; i < dependencyInstantiations.length; i++)
link.dependencyInstantiations[i] = dependencyInstantiations[i];
}
// run setters to set up bindings to instantiated dependencies
else {
link.dependencyInstantiations = dependencyInstantiations;
for (var i = 0; i < dependencyInstantiations.length; i++) {
var setter = link.setters[i];
if (setter) {
var instantiation = dependencyInstantiations[i];
if (instantiation instanceof Module) {
setter(instantiation);
}
else {
setter(instantiation.module || instantiation.linkRecord.moduleObj);
// this applies to both es and dynamic registrations
if (instantiation.importerSetters)
instantiation.importerSetters.push(setter);
}
}
}
}
})))
.then(function () {
// now deeply instantiateDeps on each dependencyInstantiation that is a load record
var deepDepsInstantiatePromises = [];
for (var i = 0; i < link.dependencies.length; i++) {
var depLoad = link.dependencyInstantiations[i];
var depLink = depLoad.linkRecord;
if (!depLink || depLink.linked)
continue;
if (seen.indexOf(depLoad) !== -1)
continue;
seen.push(depLoad);
deepDepsInstantiatePromises.push(instantiateDeps(loader, depLoad, depLoad.linkRecord, registry, registerRegistry, seen));
}
return Promise.all(deepDepsInstantiatePromises);
})
.then(function () {
// as soon as all dependencies instantiated, we are ready for evaluation so can add to the registry
// this can run multiple times, but so what
link.linked = true;
if (loader.trace)
traceLoad(load, link);
return load;
})
.catch(function (err) {
err = addToError(err, 'Loading ' + load.key);
// throw up the instantiateDeps stack
// loads are then synchonously cleared at the top-level through the clearLoadErrors helper below
// this then ensures avoiding partially unloaded tree states
link.error = link.error || err;
throw err;
});
}
// clears an errored load and all its errored dependencies from the loads registry
function clearLoadErrors (loader, load) {
// clear from loads
if (loader[REGISTER_REGISTRY][load.key] === load)
loader[REGISTER_REGISTRY][load.key] = undefined;
var link = load.linkRecord;
if (!link)
return;
if (link.dependencyInstantiations)
link.dependencyInstantiations.forEach(function (depLoad, index) {
if (!depLoad || depLoad instanceof Module)
return;
if (depLoad.linkRecord) {
if (depLoad.linkRecord.error) {
// provides a circular reference check
if (loader[REGISTER_REGISTRY][depLoad.key] === depLoad)
clearLoadErrors(loader, depLoad);
}
// unregister setters for es dependency load records that will remain
if (link.setters && depLoad.importerSetters) {
var setterIndex = depLoad.importerSetters.indexOf(link.setters[index]);
depLoad.importerSetters.splice(setterIndex, 1);
}
}
});
}
/*
* System.register
*/
RegisterLoader.prototype.register = function (key, deps, declare) {
// anonymous modules get stored as lastAnon
if (declare === undefined) {
this[REGISTERED_LAST_ANON] = [key, deps, false];
}
// everything else registers into the register cache
else {
var load = this[REGISTER_REGISTRY][key] || createLoadRecord.call(this, key, undefined);
load.registration = [deps, declare, false];
}
};
/*
* System.registerDyanmic
*/
RegisterLoader.prototype.registerDynamic = function (key, deps, execute) {
// anonymous modules get stored as lastAnon
if (typeof key !== 'string') {
this[REGISTERED_LAST_ANON] = [key, deps === true && execute || deps === false && makeNonExecutingRequire(key, execute) || deps, true];
}
// everything else registers into the register cache
else {
var load = this[REGISTER_REGISTRY][key] || createLoadRecord.call(this, key, undefined);
load.registration = [deps, execute === true && arguments[3] || execute === false && makeNonExecutingRequire(deps, arguments[3]) || execute, true];
}
};
function makeNonExecutingRequire (deps, execute) {
return function(require) {
// evaluate deps first
for (var i = 0; i < deps.length; i++)
require(deps[i]);
// then run execution function
return execute.apply(this, arguments);
};
}
RegisterLoader.prototype.processRegisterContext = function (contextKey) {
var registeredLastAnon = this[REGISTERED_LAST_ANON];
if (!registeredLastAnon)
return;
this[REGISTERED_LAST_ANON] = undefined;
// returning the defined value allows avoiding an extra lookup for custom instantiate
var load = this[REGISTER_REGISTRY][contextKey] || createLoadRecord.call(this, contextKey, undefined);
load.registration = registeredLastAnon;
};
// ContextualLoader class
// backwards-compatible with previous System.register context argument by exposing .id
function ContextualLoader(loader, key) {
function ContextualLoader (loader, key) {
this.loader = loader;
this.key = this.id = key;
}
ContextualLoader.prototype.constructor = function() {
ContextualLoader.prototype.constructor = function () {
throw new TypeError('Cannot subclass the contextual loader only Reflect.Loader.');
};
ContextualLoader.prototype.import = function(key) {
ContextualLoader.prototype.import = function (key) {
return this.loader.import(key, this.key);
};
ContextualLoader.prototype.resolve = function(key) {
ContextualLoader.prototype.resolve = function (key) {
return this.loader[Loader.resolve](key, this.key);
};
ContextualLoader.prototype.load = function(key) {
ContextualLoader.prototype.load = function (key) {
return this.loader.load(key, this.key);
};
// this is the execution function bound to the Module namespace record
function ensureEvaluate (loader, load, link, registry, registerRegistry) {
if (load.module)
return load.module;
if (link.error)
throw link.error;
var err = doEvaluate(load, link, registry, registerRegistry, []);
if (err) {
clearLoadErrors(loader, load);
throw err;
}
return load.module;
}
// ensures the given es load is evaluated
// returns the error if any
function ensureEvaluated(loader, load, seen) {
var esLinkRecord = load.esLinkRecord;
// no esLinkRecord means evaluated
if (!esLinkRecord)
return;
// assumes seen does not contain load already
function doEvaluate (load, link, registry, registerRegistry, seen) {
seen.push(load);
var err, depLoad;
var err;
for (var i = 0; i < esLinkRecord.dependencies.length; i++) {
depLoad = esLinkRecord.dependencyInstantiations[i];
// es modules evaluate dependencies first
// non es modules explicitly call moduleEvaluate through require
if (link.setters) {
var depLoad, depLink;
for (var i = 0; i < link.dependencies.length; i++) {
depLoad = link.dependencyInstantiations[i];
// non ES load
// custom Module returned from instantiate
// it is the responsibility of the executor to remove the module from the registry on failure
if (depLoad instanceof Module) {
err = nsEvaluate(depLoad);
}
// it is the responsibility of the executor to remove the module from the registry on failure
if (depLoad instanceof ModuleNamespace)
err = namespaceEvaluate(depLoad);
// ES or dynamic execute
else {
depLink = depLoad.linkRecord;
if (depLink && !depLink.module && seen.indexOf(depLoad) === -1) {
if (depLink.error)
err = depLink.error;
else
err = doEvaluate(depLoad, depLink, registry, registerRegistry, seen);
}
}
// ES load
else if (seen.indexOf(depLoad) === -1)
err = ensureEvaluated(loader, depLoad, seen);
if (err)
return addToError(err, 'Evaluating ' + load.key);
if (err)
return link.error = addToError(err, 'Evaluating ' + load.key);
}
}
// es load record evaluation
err = esEvaluate(esLinkRecord);
// link.execute won't exist for Module returns from instantiate on top-level load
if (link.execute)
// "this" is null in ES, exports in CJS
err = doExecute(link.execute, link.setters ? nullContext : link.moduleObj.default);
if (err)
return addToError(err, 'Evaluating ' + load.key);
return link.error = addToError(err, 'Evaluating ' + load.key);
load.module = new ModuleNamespace(esLinkRecord.moduleObj);
loader.registry._registry[load.key] = load.module;
// can clear link record now
if (!loader.trace)
load.esLinkRecord = undefined;
registry[load.key] = load.module = new Module(load.linkRecord.moduleObj);
// if not an esm module, run importer setters and clear them
// this allows dynamic modules to update themselves into es modules
// as soon as execution has completed
if (!link.setters) {
if (load.importerSetters)
for (var i = 0; i < load.importerSetters.length; i++)
load.importerSetters[i](load.module);
// once executed, non-es modules can be removed from the private registry
// since we don't need to store binding update metadata
if (registerRegistry[load.key] === load)
registerRegistry[load.key] = undefined;
}
load.linkRecord = undefined;
}
var execContext = {};
// {} is the closest we can get to call(undefined)
var nullContext = {};
if (Object.freeze)
Object.freeze(execContext);
function esEvaluate(esLinkRecord) {
Object.freeze(nullContext);
function doExecute (execute, context) {
try {
// {} is the closest we can get to call(undefined)
// this should really be blocked earlier though
esLinkRecord.execute.call(execContext);
execute.call(context);
}
catch(err) {
return err;
catch (e) {
return e;
}
}
function namespaceEvaluate(namespace) {
function nsEvaluate (ns) {
try {
Module.evaluate(namespace);
Module.evaluate(ns);
}
catch(err) {
return err;
catch (e) {
return e;
}
}
function traceLoadRecord(loader, load, seen) {
// its up to dynamic instantiate layers to ensure their own traces are present
if (load instanceof ModuleNamespace)
return;
seen.push(load);
if (!load.esLinkRecord || load.esLinkRecord.dependencies.length && !load.esLinkRecord.depMap)
throw new Error('Tracing error, ensure loader.trace is set before loading begins');
loader.loads[load.key] = {
key: load.key,
dependencies: load.esLinkRecord.dependencies,
depMap: load.esLinkRecord.depMap || {},
metadata: load.metadata
};
load.esLinkRecord.dependencyInstantiations.forEach(function(dep) {
if (seen.indexOf(dep) === -1)
traceLoadRecord(loader, dep, seen);
});
}

@@ -5,6 +5,6 @@ import { isNode } from './common.js';

* Optimized URL normalization assuming a syntax-valid URL parent
*/
export function resolveUrlToParentIfNotPlain(relUrl, parentUrl) {
*/
export function resolveUrlToParentIfNotPlain (relUrl, parentUrl) {
function throwResolveError() {
function throwResolveError () {
throw new RangeError('Unable to resolve "' + relUrl + '" to ' + parentUrl);

@@ -67,3 +67,3 @@ }

}
// join together and split for removal of .. and . segments

@@ -107,3 +107,3 @@ // looping the string instead of anything fancy for perf reasons

throwResolveError();
// trailing . or .. segment

@@ -121,7 +121,7 @@ if (i === segmented.length)

output.push(segmented.substr(segmentIndex, segmented.length - segmentIndex));
return parentUrl.substr(0, parentUrl.length - pathname.length) + output.join('');
}
// plain name -> return undefined
}
}
{
"name": "es-module-loader",
"description": "An ES Module Loader shim",
"version": "1.2.1",
"version": "1.3.0",
"homepage": "https://github.com/ModuleLoader/es-module-loader",

@@ -24,5 +24,5 @@ "author": {

"devDependencies": {
"babel-cli": "^6.11.4",
"babel-plugin-transform-async-to-generator": "^6.8.0",
"babel-plugin-transform-es2015-modules-systemjs": "^6.12.0",
"babel-cli": "^6.16.0",
"babel-plugin-transform-async-to-generator": "^6.16.0",
"babel-plugin-transform-es2015-modules-systemjs": "^6.14.0",
"benchmark": "^2.1.1",

@@ -29,0 +29,0 @@ "mocha": "^3.0.2",

# ES Module Loader Polyfill [![Build Status][travis-image]][travis-url]
Provides a polyfill and [low-level API](#loader-hooks) for the [WhatWG loader spec](https://github.com/whatwg/loader) to create a custom module loaders.
Provides [low-level hooks](#loader-hooks) for creating ES module loaders, roughly based on the API of the [WhatWG loader spec](https://github.com/whatwg/loader),
but with [various adjustments](#spec-differences) to match the current proposals for the HTML modules specification and [NodeJS ES module adoption](https://github.com/nodejs/node/issues/8866).
Supports the [System.register module format](https://github.com/ModuleLoader/es-module-loader/blob/master/docs/system-register.md) to provide identical module loading semantics as ES modules in environments today.
Supports the [System.register](docs/system-register.md) module format to provide exact module loading semantics for ES modules in environments today. In addition, support for the [System.registerDynamic](docs/system-register-dynamic.md) is provided to allow the linking
of module graphs consisting of inter-dependent ES modules and CommonJS modules with their respective semantics retained.
ES6 Module Loader Polyfill, the previous version of this project built to the [outdated ES6 loader specification](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts#august_24_2014_draft_rev_27) is available at the [0.17 branch](https://github.com/ModuleLoader/es-module-loader/tree/0.17).
This project aims to provide a [highly performant](#performance), minimal, unopinionated loader API on top of which custom loaders [can easily be built](#creating-a-loader). See the [spec differences](#spec-differences) section for a more detailed listing of the tradeoffs made.
ES6 Module Loader Polyfill, the previous version of this project was built to the [outdated ES6 loader specification](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts#august_24_2014_draft_rev_27) and can still be found at the [0.17 branch](https://github.com/ModuleLoader/es-module-loader/tree/0.17).
### Module Loader Examples

@@ -17,3 +21,3 @@

- [Node ES Module Loader](https://github.com/ModuleLoader/node-es-module-loader)
Allows loading ES modules with CommonJS interop in Node via `node-esml module/path.js` in line with the current Node
Allows loading ES modules with CommonJS interop in Node via `node-esml module/path.js` in line with the current Node
plans for implementing ES modules. Used to run the tests and benchmarks in this project.

@@ -34,10 +38,10 @@

The minimal polyfill loader is provided in `core/loader-polyfill.js`. On top of this the main API file is
'core/register-loader.js'` which provides the base loader class.
The minimal [polyfill loader API](@base-loader-polyfill-api) is provided in `core/loader-polyfill.js`. On top of this main API file is
`core/register-loader.js` which provides a base loader class with the non-spec `System.register` and `System.registerDynamic` support to enable the exact
linking semantics.
Helper functions are available in `core/resolve.js`, `core/common.js`, `core/fetch.js` and everything that is exported can be considered
Helper functions are available in `core/resolve.js` and `core/common.js`. Everything that is exported can be considered
part of the publicly versioned API of this project.
Any tool can be used to build the loader distribution file from these core modules - [Rollup](http://rollupjs.org) is used to do these builds in the example loaders above,
provided by the `rollup.config.js` file in the example loader repos listed above.
Any tool can be used to build the loader distribution file from these core modules - [Rollup](http://rollupjs.org) is used to do these builds in the example loaders above, provided by the `rollup.config.js` file in the example loader repos listed above.

@@ -63,3 +67,2 @@ ### Loader Hooks

[RegisterLoader.normalize](key, parentKey, metadata) {
// parent normalize is sync, providing relative normalization only
var relativeResolved = super[RegisterLoader.normalize](key, parentKey, metadata) || key;

@@ -81,14 +84,12 @@ return relativeResolved;

#### Normalize Hook
Relative normalization of the form `./x` is already performed using the internal resolver in `core/resolve.js`
so that the key provided into normalize will never be a relative URL - it will either be a plain / bare name
or an absolute URL.
The return value of `normalize` is the final key that is set in the registry (available and iterable as per the spec
at `loader.registry`).
The default normalization provided (`super[RegisterLoader.normalize]` above) is based on the principles of the HTML specification for modules, whereby _plain module names_ that are not valid URLs, and not starting with `./`, `../` or `/` return `undefined`.
So for example `lodash` will return `undefined`, while `./x` will resolve to `[baseURI]/x`. In NodeJS a `file:///` URL is used for the baseURI.
#### Instantiate Hook
##### Instantiating ES Modules via System.register
##### 1. Instantiating ES Modules via System.register

@@ -121,13 +122,18 @@ When instantiate returns `undefined`, it is assumed that the module key has already been registered through a

##### Instantiating Dynamic / Legacy Modules via ModuleNamespace
The `key` and `contextKey` provided to `register` or `processRegisterContext` must be the exact key to use in the registry. If not, the module will not be detected correctly.
Legacy module formats are not transpiled into `System.register`, rather they need to be executed according to their own semantics.
##### 2. Instantiating Legacy Modules via System.registerDynamic
The instantiate can handle its own execution pipeline for these legacy modules (like calling out to the Node require in the node-es-module-loader).
This is identical to the `System.register` process above, only running `loader.registerDynamic` instead of `loader.register`.
Having created a module instance, we wrap it in a `ModuleNamespace` object and can return that directly from instantiate:
For more information on the `System.registerDynamic` format [see the format explanation](docs/system-register-dynamic.md).
##### 3. Instantiating Dynamic Modules via ModuleNamespace
If the exact module definition is already known, or loaded through another method (like calling out fully to the Node require in the node-es-module-loader),
then the direct module namespace value can be returned from instantiate:
```javascript
import { InternalModuleNamespace } from 'es-module-loader/core/loader-polyfill.js'
import { ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js'

@@ -139,12 +145,43 @@ // ...

return new InternalModuleNamespace({ default: module });
return new ModuleNamespace({ default: module, customExport: 'value' });
}
```
Using these two types of return values, we can thus recreate ES module semantics interacting with legacy module formats.
Using these three types of return values for instantiate, we can thus recreate ES module semantics interacting with legacy module formats.
Note that `InternalModuleNamespace` is not provided in the WhatWG loader specification - the specification actually uses a `Module.Status` constructor.
We've chosen to take the route of implementing a custom private method over the spec, until that spec work can be fully stabilized, instead of having
Note that `ModuleNamespace` is not specified in the WhatWG loader specification - the specification actually uses a `Module.Status` constructor.
A custom private constructor is used over the spec until there is a stable proposal instead of having
to track small changes of this spec API over major versions of this project.
### Base Loader Polyfill API
The `Loader` and `Module` classes in `core/loader-polyfill.js` provide the basic spec API method shells for a loader instance `loader`:
- *`new Loader(baseKey)`*: Instantiate a new `loader` instance, with the given `baseKey` as the default parentKey for normalizations.
Defaults to environment baseURI detection in NodeJS and browsers.
- *`loader.import(key [, parentKey])`*: Promise for importing and executing a given module, returning its module instance.
- *`loader.resolve(key [, parentKey])`*: Promise for resolving the idempotent fully-normalized string key for a module.
- *`new Module(bindings)`*: Creates a new module namespace object instance for the given bindings object. The iterable properties
of the bindings object are created as getters returning the corresponding values from the bindings object.
- *`loader.registry.set(resolvedKey, namespace)`*: Set a module namespace into the registry.
- *`loader.registry.get(resolvedKey)`*: Get a module namespace (if any) from the registry.
- *`loader.registry.has(resolvedKey)`*: Boolean indicating whether the given key is present in the registry.
- *`loader.registry.delete(resolvedKey)``*: Removes the given module from the registry (if any), returning true or false.
- *`loader.registry.keys`*: Function returning the keys iterator for the registry.
- *`loader.registry.values`*: Function returning the values iterator for the registry.
- *`loader.registry.entries`*: Function returning the entries iterator for the registry (keys and values).
- *`loader.registry[Symbol.iterator]`*: In supported environments, provides registry entries iteration.
### Performance
A performance comparison loading System.register modules is provided in the `bench` folder comparing times between
the minimal [System Register Loader](https://github.com/ModuleLoader/system-register-loader) and SystemJS (which is built to the previous loader polyfill):
| Test | SystemJS | ES Module Loader 1.2 |
| ----------------------------------------- |:-----------:| :-------------------:|
| Importing multiple trees at the same time | 147 ops/sec | 705 ops/sec |
| Importing a deep tree of modules | 225 ops/sec | 4,713 ops/sec |
| Importing a single module with deps | 153 ops/sec | 9,652 ops/sec |
| Importing a single module without deps | 119 ops/sec | 16,279 ops/sec |
### Tracing API

@@ -167,6 +204,2 @@

Instantiate functions that return an `InternalModuleNamespace` instance directly are not included in the trace registry.
Custom loaders that want to share the same trace format, should populate the trace themselves using their internal knowledge of the legacy module dependency information.
### Spec Differences

@@ -177,14 +210,14 @@

Error handling is implemented as in the HTML specification for module loading, such that rejections reject the current load tree, but
Error handling is implemented as in the HTML specification for module loading, such that rejections reject the current in-progress load trees, but
are immediately removed from the registry to allow further loads to retry loading.
- Instead of storing a registry of ModuleStatus objects, we store a registry of Module Namespace objects. The reason for this is that asynchronous rejection of registry entries as a source of truth leads to partial inconsistent rejection states
- A direct `ModuleNamespace` constructor is provided over the `Module` mutator proposal in the WhatWG specification.
Instead of storing a registry of ModuleStatus objects, we then store a registry of Module Namespace objects. The reason for this is that asynchronous rejection of registry entries as a source of truth leads to partial inconsistent rejection states
(it is possible for the tick between the rejection of one load and its parent to have to deal with an overlapping in-progress tree),
so in order to have a predictable load error rejection process, loads are only stored in the registry as fully-linked Namespace objects
and not ModuleStatus objects as promises for Namespace objects (Module.evaluate is still supported though).
- `Loader` and `Module` are available as named exports from `core/loader-polyfill.js` but are not by default exported to the `global.Reflect` object.
- `Loader` is available as a named export from `core/loader-polyfill.js` but is not by default exported to the `global.Reflect` object.
This is to allow individual loader implementations to determine their own impact on the environment.
- A constructor argument is added to the loader that takes the environment baseKey to be used as the default normalization parent.
- An internal `Loader.prototype[Loader.instantiate]` hook is used as well as the `Loader.prototype[Loader.resolve]` hook
in order to ensure that uses of `loader.resolve` do not have to result in module loading and execution, as discussed in https://github.com/whatwg/loader/issues/147#issuecomment-230407764.
- A constructor argument is added to the loader that takes the environment `baseKey` to be used as the default normalization parent.
- The [WhatWG reduced specification proposal](https://github.com/whatwg/loader/issues/147) is to remove the loader hooks and simply have a single `resolve` hook, which could then set a module into the registry using the `registry.set` API as a side-effect to allow custom interception. As discussed in https://github.com/whatwg/loader/issues/147#issuecomment-230407764, this may cause unwanted execution of modules when only resolution is needed via `loader.resolve`, so the internal approach taken here is to still consider separate `resolve` and `instantiate` hooks in `core/loader-polyfill.js`.

@@ -191,0 +224,0 @@ ## License

@@ -44,8 +44,4 @@ import assert from 'assert';

var evaluated = false;
var mutator;
var module = new Module({
a: { value: 'asdf' }
}, function(_mutator) {
mutator = _mutator;
}, function() {
var mutator = { a: 'asdf' };
var module = new Module(mutator, function() {
evaluated = true;

@@ -55,4 +51,4 @@ mutator.a = 'b';

assert.equal(module.a, 'asdf');
assert.equal(module.a, undefined);
Module.evaluate(module);

@@ -63,2 +59,2 @@ assert(evaluated);

});
});
});
import assert from 'assert';
import path from 'path';
import Module from 'module';
import SystemRegisterLoader from './fixtures/system-register-loader.js';

@@ -7,3 +8,3 @@ import { pathToFileUrl, fileUrlToPath } from '../core/common.js';

describe('System Register Loader', function() {
var loader = new SystemRegisterLoader(path.resolve('test/fixtures/register-modules') + path.sep);
var loader = new SystemRegisterLoader(path.resolve('test/fixtures') + path.sep);

@@ -17,3 +18,3 @@ describe('Simple tests', function() {

it('Should import a module', async function() {
var m = await loader.import('./no-imports.js');
var m = await loader.import('./register-modules/no-imports.js');
assert(m);

@@ -24,4 +25,4 @@ assert.equal(m.asdf, 'asdf');

it('Should import a module cached', async function() {
var m1 = await loader.import('./no-imports.js');
var m2 = await loader.import('./no-imports.js');
var m1 = await loader.import('./register-modules/no-imports.js');
var m2 = await loader.import('./register-modules/no-imports.js');
assert.equal(m1.asdf, 'asdf');

@@ -32,3 +33,3 @@ assert.equal(m1, m2);

it('should import an es module with its dependencies', async function() {
var m = await loader.import('./es6-withdep.js');
var m = await loader.import('./register-modules/es6-withdep.js');
assert.equal(m.p, 'p');

@@ -38,3 +39,3 @@ });

it('should import without bindings', async function() {
var m = await loader.import('./direct.js');
var m = await loader.import('./register-modules/direct.js');
assert(!!m);

@@ -44,3 +45,3 @@ });

it('should support various es syntax', async function() {
var m = await loader.import('./es6-file.js');
var m = await loader.import('./register-modules/es6-file.js');

@@ -63,3 +64,3 @@ assert.equal(typeof m.q, 'function');

it('should resolve various import syntax', async function() {
var m = await loader.import('./import.js');
var m = await loader.import('./register-modules/import.js');
assert.equal(typeof m.a, 'function');

@@ -74,3 +75,3 @@ assert.equal(m.b, 4);

it('should support __moduleName', async function() {
var m = await loader.import('./moduleName.js');
var m = await loader.import('./register-modules/moduleName.js');
assert.equal(m.name, pathToFileUrl(path.resolve('test/fixtures/register-modules/moduleName.js')));

@@ -84,4 +85,4 @@ });

it('should resolve circular dependencies', async function() {
var m1 = await loader.import('./circular1.js');
var m2 = await loader.import('./circular2.js');
var m1 = await loader.import('./register-modules/circular1.js');
var m2 = await loader.import('./register-modules/circular2.js');

@@ -98,5 +99,4 @@

// pending https://github.com/babel/babel/pull/3650
it.skip('should update circular dependencies', async function() {
var m = await loader.import('./even.js');
it('should update circular dependencies', async function() {
var m = await loader.import('./register-modules/even.js');
assert.equal(m.counter, 1);

@@ -113,3 +113,3 @@ assert(m.even(10));

async function assertLoadOrder(module, exports) {
var m = await loader.import('./' + module);
var m = await loader.import('./register-modules/' + module);
exports.forEach(function(name) {

@@ -151,3 +151,3 @@ assert.equal(m[name], name);

it('should resolve different export syntax', async function() {
var m = await loader.import('./export.js');
var m = await loader.import('./register-modules/export.js');
assert.equal(m.p, 5);

@@ -163,3 +163,3 @@ assert.equal(typeof m.foo, 'function');

it('should resolve "export default"', async function() {
var m = await loader.import('./export-default.js');
var m = await loader.import('./register-modules/export-default.js');
assert.equal(m.default(), 'test');

@@ -169,3 +169,3 @@ });

it('should support simple re-exporting', async function() {
var m = await loader.import('./reexport1.js');
var m = await loader.import('./register-modules/reexport1.js');
assert.equal(m.p, 5);

@@ -175,4 +175,4 @@ });

it('should support re-exporting binding', async function() {
await loader.import('./reexport-binding.js');
var m = await loader.import('./rebinding.js');
await loader.import('./register-modules/reexport-binding.js');
var m = await loader.import('./register-modules/rebinding.js');
assert.equal(m.p, 4);

@@ -182,3 +182,3 @@ });

it('should support re-exporting with a new name', async function() {
var m = await loader.import('./reexport2.js');
var m = await loader.import('./register-modules/reexport2.js');
assert.equal(m.q, 4);

@@ -189,3 +189,3 @@ assert.equal(m.z, 5);

it('should support re-exporting', async function() {
var m = await loader.import('./export-star.js');
var m = await loader.import('./register-modules/export-star.js');
assert.equal(m.foo, 'foo');

@@ -196,3 +196,3 @@ assert.equal(m.bar, 'bar');

it.skip('should support re-exporting overwriting', async function() {
var m = await loader.import('./export-star2.js');
var m = await loader.import('./register-modules/export-star2.js');
assert.equal(m.bar, 'bar');

@@ -205,3 +205,3 @@ assert.equal(typeof m.foo, 'function');

var testPath = fileUrlToPath(loader.key);
var testPath = fileUrlToPath(loader.key) + 'register-modules/';

@@ -219,13 +219,13 @@ async function getImportError(module) {

it('should throw if on syntax error', async function() {
var err = await getImportError('./main.js');
assert.equal(err, 'Error: dep error\n Evaluating ' + testPath + 'deperror.js\n Evaluating ' + testPath + 'main.js\n Loading ./main.js');
var err = await getImportError('./register-modules/main.js');
assert.equal(err, 'Error: dep error\n Evaluating ' + testPath + 'deperror.js\n Evaluating ' + testPath + 'main.js\n Loading ./register-modules/main.js');
});
it('should throw what the script throws', async function() {
var err = await getImportError('./deperror.js');
assert.equal(err, 'Error: dep error\n Evaluating ' + testPath + 'deperror.js\n Loading ./deperror.js');
var err = await getImportError('./register-modules/deperror.js');
assert.equal(err, 'Error: dep error\n Evaluating ' + testPath + 'deperror.js\n Loading ./register-modules/deperror.js');
});
it('404 error', async function() {
var err = await getImportError('./load-non-existent.js');
var err = await getImportError('./register-modules/load-non-existent.js');
var lines = err.split('\n ');

@@ -236,3 +236,3 @@ assert(lines[0].startsWith('Error: '));

assert.equal(lines[2], 'Loading ' + testPath + 'load-non-existent.js');
assert.equal(lines[3], 'Loading ./load-non-existent.js');
assert.equal(lines[3], 'Loading ./register-modules/load-non-existent.js');
});

@@ -242,2 +242,18 @@

describe('Register dynamic', function () {
it('should load a System.registerDynamic module', async function () {
var m = await loader.import('./dynamic-modules/basic-exports.js');
assert.equal(m.default(), 'ok');
assert.equal(m.named, 'name!');
});
it('should load mixed bundles of register and registerDynamic', async function() {
new Module().require(path.resolve(fileUrlToPath(loader.key), 'dynamic-modules/mixed-bundle.js'));
var m = await loader.import('tree/first');
assert.equal(m.p, 5);
assert.equal(m.q, 4);
assert.equal(m.a.is, 'amd');
});
});
});

@@ -12,3 +12,3 @@ import RegisterLoader from '../../core/register-loader.js';

*/
function SystemRegisterLoader(baseKey) {
function SystemRegisterLoader (baseKey) {
baseKey = resolveUrlToParentIfNotPlain(baseKey || (isNode ? process.cwd() : '.'), baseURI) || baseKey;

@@ -18,8 +18,10 @@ RegisterLoader.call(this, baseKey);

var loader = this;
// ensure System.register is available
global.System = global.System || {};
if (typeof global.System.register == 'function')
if (typeof global.System.register === 'function')
var prevRegister = global.System.register;
global.System.register = function() {
if (typeof global.System.registerDynamic === 'function')
var prevRegisterDynamic = global.System.registerDynamic;
global.System.register = function () {
loader.register.apply(loader, arguments);

@@ -29,2 +31,7 @@ if (prevRegister)

};
global.System.registerDynamic = function () {
loader.registerDynamic.apply(loader, arguments);
if (prevRegisterDynamic)
prevRegisterDynamic.apply(this, arguments);
}
}

@@ -35,3 +42,3 @@ SystemRegisterLoader.prototype = Object.create(RegisterLoader.prototype);

// so we just need to do plain name detect to throw as in the WhatWG spec
SystemRegisterLoader.prototype[RegisterLoader.normalize] = function(key, parent, metadata) {
SystemRegisterLoader.prototype[RegisterLoader.normalize] = function (key, parent, metadata) {
var resolved = RegisterLoader.prototype.normalize.call(this, key, parent, metadata);

@@ -47,10 +54,10 @@ if (!resolved)

// so we load the module name as a URL, and expect that to run System.register
SystemRegisterLoader.prototype[RegisterLoader.instantiate] = function(key, metadata) {
SystemRegisterLoader.prototype[RegisterLoader.instantiate] = function (key, metadata) {
var thisLoader = this;
return new Promise(function(resolve, reject) {
return new Promise(function (resolve, reject) {
if (isNode)
Promise.resolve(fs || (fs = typeof require !== 'undefined' ? require('fs') : loader.import('fs').then(function(m){ return m.default })))
.then(function(fs) {
fs.readFile(fileUrlToPath(key), function(err, source) {
.then(function (fs) {
fs.readFile(fileUrlToPath(key), function (err, source) {
if (err)

@@ -70,3 +77,3 @@ return reject(err);

else if (isBrowser)
scriptLoad(key, function() {
scriptLoad(key, function () {
thisLoader.processRegisterContext(key);

@@ -80,3 +87,3 @@ resolve();

function scriptLoad(src, resolve, reject) {
function scriptLoad (src, resolve, reject) {
var script = document.createElement('script');

@@ -98,3 +105,3 @@ script.type = 'text/javascript';

function error(err) {
function error (err) {
cleanup();

@@ -104,3 +111,3 @@ reject(new Error('Fetching ' + src));

function cleanup() {
function cleanup () {
script.removeEventListener('load', load, false);

@@ -107,0 +114,0 @@ script.removeEventListener('error', error, false);

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc