/* * Instantiate registry extension * * Supports Traceur System.register 'instantiate' output for loading ES6 as ES5. * * - Creates the loader.register function * - Also supports metadata.format = 'register' in instantiate for anonymous register modules * - Also supports metadata.deps, metadata.execute and metadata.executingRequire * for handling dynamic modules alongside register-transformed ES6 modules * * * The code here replicates the ES6 linking groups algorithm to ensure that * circular ES6 compiled into System.register can work alongside circular AMD * and CommonJS, identically to the actual ES6 loader. * */ /* * Registry side table entries in loader.defined * Registry Entry Contains: * - name * - deps * - declare for declarative modules * - execute for dynamic modules, different to declarative execute on module * - executingRequire indicates require drives execution for circularity of dynamic modules * - declarative optional boolean indicating which of the above * * Can preload modules directly on SystemJS.defined['my/module'] = { deps, execute, executingRequire } * * Then the entry gets populated with derived information during processing: * - normalizedDeps derived from deps, created in instantiate * - groupIndex used by group linking algorithm * - evaluated indicating whether evaluation has happend * - module the module record object, containing: * - exports actual module exports * * For dynamic we track the es module with: * - esModule actual es module value * - esmExports whether to extend the esModule with named exports * * Then for declarative only we track dynamic bindings with the 'module' records: * - name * - exports * - setters declarative setter functions * - dependencies, module records of dependencies * - importers, module records of dependents * * After linked and evaluated, entries are removed, declarative module records remain in separate * module binding table * */ var leadingCommentAndMetaRegEx = /^(\s*\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\s*\/\/[^\n]*|\s*"[^"]+"\s*;?|\s*'[^']+'\s*;?)*\s*/; function detectRegisterFormat(source) { var leadingCommentAndMeta = source.match(leadingCommentAndMetaRegEx); return leadingCommentAndMeta && source.substr(leadingCommentAndMeta[0].length, 15) == 'System.register'; } function createEntry() { return { name: null, deps: null, originalIndices: null, declare: null, execute: null, executingRequire: false, declarative: false, normalizedDeps: null, groupIndex: null, evaluated: false, module: null, esModule: null, esmExports: false }; } (function() { /* * There are two variations of System.register: * 1. System.register for ES6 conversion (2-3 params) - System.register([name, ]deps, declare) * see https://github.com/ModuleLoader/es6-module-loader/wiki/System.register-Explained * * 2. System.registerDynamic for dynamic modules (3-4 params) - System.registerDynamic([name, ]deps, executingRequire, execute) * the true or false statement * * this extension implements the linking algorithm for the two variations identical to the spec * allowing compiled ES6 circular references to work alongside AMD and CJS circular references. * */ SystemJSLoader.prototype.register = function(name, deps, declare) { if (typeof name != 'string') { declare = deps; deps = name; name = null; } // dynamic backwards-compatibility // can be deprecated eventually if (typeof declare == 'boolean') return this.registerDynamic.apply(this, arguments); var entry = createEntry(); // ideally wouldn't apply map config to bundle names but // dependencies go through map regardless so we can't restrict // could reconsider in shift to new spec entry.name = name && (this.decanonicalize || this.normalize).call(this, name); entry.declarative = true; entry.deps = deps; entry.declare = declare; this.pushRegister_({ amd: false, entry: entry }); }; SystemJSLoader.prototype.registerDynamic = function(name, deps, declare, execute) { if (typeof name != 'string') { execute = declare; declare = deps; deps = name; name = null; } // dynamic var entry = createEntry(); entry.name = name && (this.decanonicalize || this.normalize).call(this, name); entry.deps = deps; entry.execute = execute; entry.executingRequire = declare; this.pushRegister_({ amd: false, entry: entry }); }; hook('reduceRegister_', function() { return function(load, register) { if (!register) return; var entry = register.entry; var curMeta = load && load.metadata; // named register if (entry.name) { if (!(entry.name in this.defined)) this.defined[entry.name] = entry; if (curMeta) curMeta.bundle = true; } // anonymous register if (!entry.name || load && !curMeta.entry && entry.name == load.name) { if (!curMeta) throw new TypeError('Invalid System.register call. Anonymous System.register calls can only be made by modules loaded by SystemJS.import and not via script tags.'); if (curMeta.entry) { if (curMeta.format == 'register') throw new Error('Multiple anonymous System.register calls in module ' + load.name + '. If loading a bundle, ensure all the System.register calls are named.'); else throw new Error('Module ' + load.name + ' interpreted as ' + curMeta.format + ' module format, but called System.register.'); } if (!curMeta.format) curMeta.format = 'register'; curMeta.entry = entry; } }; }); hookConstructor(function(constructor) { return function() { constructor.call(this); this.defined = {}; this._loader.moduleRecords = {}; }; }); function buildGroups(entry, loader, groups) { groups[entry.groupIndex] = groups[entry.groupIndex] || []; if (indexOf.call(groups[entry.groupIndex], entry) != -1) return; groups[entry.groupIndex].push(entry); for (var i = 0, l = entry.normalizedDeps.length; i < l; i++) { var depName = entry.normalizedDeps[i]; var depEntry = loader.defined[depName]; // not in the registry means already linked / ES6 if (!depEntry || depEntry.evaluated) continue; // now we know the entry is in our unlinked linkage group var depGroupIndex = entry.groupIndex + (depEntry.declarative != entry.declarative); // the group index of an entry is always the maximum if (depEntry.groupIndex === null || depEntry.groupIndex < depGroupIndex) { // if already in a group, remove from the old group if (depEntry.groupIndex !== null) { groups[depEntry.groupIndex].splice(indexOf.call(groups[depEntry.groupIndex], depEntry), 1); // if the old group is empty, then we have a mixed depndency cycle if (groups[depEntry.groupIndex].length == 0) throw new Error("Mixed dependency cycle detected"); } depEntry.groupIndex = depGroupIndex; } buildGroups(depEntry, loader, groups); } } function link(name, startEntry, loader) { // skip if already linked if (startEntry.module) return; startEntry.groupIndex = 0; var groups = []; buildGroups(startEntry, loader, groups); var curGroupDeclarative = !!startEntry.declarative == groups.length % 2; for (var i = groups.length - 1; i >= 0; i--) { var group = groups[i]; for (var j = 0; j < group.length; j++) { var entry = group[j]; // link each group if (curGroupDeclarative) linkDeclarativeModule(entry, loader); else linkDynamicModule(entry, loader); } curGroupDeclarative = !curGroupDeclarative; } } // module binding records function ModuleRecord() {} defineProperty(ModuleRecord, 'toString', { value: function() { return 'Module'; } }); function getOrCreateModuleRecord(name, moduleRecords) { return moduleRecords[name] || (moduleRecords[name] = { name: name, dependencies: [], exports: new ModuleRecord(), // start from an empty module and extend importers: [] }); } function linkDeclarativeModule(entry, loader) { // only link if already not already started linking (stops at circular) if (entry.module) return; var moduleRecords = loader._loader.moduleRecords; var module = entry.module = getOrCreateModuleRecord(entry.name, moduleRecords); var exports = entry.module.exports; var declaration = entry.declare.call(__global, function(name, value) { module.locked = true; if (typeof name == 'object') { for (var p in name) exports[p] = name[p]; } else { exports[name] = value; } for (var i = 0, l = module.importers.length; i < l; i++) { var importerModule = module.importers[i]; if (!importerModule.locked) { var importerIndex = indexOf.call(importerModule.dependencies, module); var setter = importerModule.setters[importerIndex]; if (setter) setter(exports); } } module.locked = false; return value; }, { id: entry.name }); if (typeof declaration == 'function') declaration = { setters: [], execute: declaration }; // allowing undefined declaration was a mistake! To be deprecated. declaration = declaration || { setters: [], execute: function() {} }; module.setters = declaration.setters; module.execute = declaration.execute; if (!module.setters || !module.execute) { throw new TypeError('Invalid System.register form for ' + entry.name); } // now link all the module dependencies for (var i = 0, l = entry.normalizedDeps.length; i < l; i++) { var depName = entry.normalizedDeps[i]; var depEntry = loader.defined[depName]; var depModule = moduleRecords[depName]; // work out how to set depExports based on scenarios... var depExports; if (depModule) { depExports = depModule.exports; } // dynamic, already linked in our registry else if (depEntry && !depEntry.declarative) { depExports = depEntry.esModule; } // in the loader registry else if (!depEntry) { depExports = loader.get(depName); } // we have an entry -> link else { linkDeclarativeModule(depEntry, loader); depModule = depEntry.module; depExports = depModule.exports; } // only declarative modules have dynamic bindings if (depModule && depModule.importers) { depModule.importers.push(module); module.dependencies.push(depModule); } else { module.dependencies.push(null); } // run setters for all entries with the matching dependency name var originalIndices = entry.originalIndices[i]; for (var j = 0, len = originalIndices.length; j < len; ++j) { var index = originalIndices[j]; if (module.setters[index]) { module.setters[index](depExports); } } } } // An analog to loader.get covering execution of all three layers (real declarative, simulated declarative, simulated dynamic) function getModule(name, loader) { var exports; var entry = loader.defined[name]; if (!entry) { exports = loader.get(name); if (!exports) throw new Error('Unable to load dependency ' + name + '.'); } else { if (entry.declarative) ensureEvaluated(name, entry, [], loader); else if (!entry.evaluated) linkDynamicModule(entry, loader); exports = entry.module.exports; } if ((!entry || entry.declarative) && exports && exports.__useDefault) return exports['default']; return exports; } function linkDynamicModule(entry, loader) { if (entry.module) return; var exports = {}; var module = entry.module = { exports: exports, id: entry.name }; // AMD requires execute the tree first if (!entry.executingRequire) { for (var i = 0, l = entry.normalizedDeps.length; i < l; i++) { var depName = entry.normalizedDeps[i]; // we know we only need to link dynamic due to linking algorithm var depEntry = loader.defined[depName]; if (depEntry) linkDynamicModule(depEntry, loader); } } // now execute entry.evaluated = true; var output = entry.execute.call(__global, function(name) { for (var i = 0, l = entry.deps.length; i < l; i++) { if (entry.deps[i] != name) continue; return getModule(entry.normalizedDeps[i], loader); } // try and normalize the dependency to see if we have another form var nameNormalized = loader.normalizeSync(name, entry.name); if (indexOf.call(entry.normalizedDeps, nameNormalized) != -1) return getModule(nameNormalized, loader); throw new Error('Module ' + name + ' not declared as a dependency of ' + entry.name); }, exports, module); if (output !== undefined) module.exports = output; // create the esModule object, which allows ES6 named imports of dynamics exports = module.exports; // __esModule flag treats as already-named if (exports && (exports.__esModule || exports instanceof Module)) entry.esModule = loader.newModule(exports); // set module as 'default' export, then fake named exports by iterating properties else if (entry.esmExports && exports !== __global) entry.esModule = loader.newModule(getESModule(exports)); // just use the 'default' export else entry.esModule = loader.newModule({ 'default': exports, __useDefault: true }); } /* * Given a module, and the list of modules for this current branch, * ensure that each of the dependencies of this module is evaluated * (unless one is a circular dependency already in the list of seen * modules, in which case we execute it) * * Then we evaluate the module itself depth-first left to right * execution to match ES6 modules */ function ensureEvaluated(moduleName, entry, seen, loader) { // if already seen, that means it's an already-evaluated non circular dependency if (!entry || entry.evaluated || !entry.declarative) return; // this only applies to declarative modules which late-execute seen.push(moduleName); for (var i = 0, l = entry.normalizedDeps.length; i < l; i++) { var depName = entry.normalizedDeps[i]; if (indexOf.call(seen, depName) == -1) { if (!loader.defined[depName]) loader.get(depName); else ensureEvaluated(depName, loader.defined[depName], seen, loader); } } if (entry.evaluated) return; entry.evaluated = true; entry.module.execute.call(__global); } // override the delete method to also clear the register caches hook('delete', function(del) { return function(name) { delete this._loader.moduleRecords[name]; delete this.defined[name]; return del.call(this, name); }; }); hook('fetch', function(fetch) { return function(load) { if (this.defined[load.name]) { load.metadata.format = 'defined'; return ''; } load.metadata.deps = load.metadata.deps || []; return fetch.call(this, load); }; }); hook('translate', function(translate) { // we run the meta detection here (register is after meta) return function(load) { load.metadata.deps = load.metadata.deps || []; return Promise.resolve(translate.apply(this, arguments)).then(function(source) { // run detection for register format if (load.metadata.format == 'register' || !load.metadata.format && detectRegisterFormat(load.source)) load.metadata.format = 'register'; return source; }); }; }); // implement a perforance shortpath for System.load with no deps hook('load', function(doLoad) { return function(normalized) { var loader = this; var entry = loader.defined[normalized]; if (!entry || entry.deps.length) return doLoad.apply(this, arguments); entry.originalIndices = entry.normalizedDeps = []; // recursively ensure that the module and all its // dependencies are linked (with dependency group handling) link(normalized, entry, loader); // now handle dependency execution in correct order ensureEvaluated(normalized, entry, [], loader); if (!entry.esModule) entry.esModule = loader.newModule(entry.module.exports); // remove from the registry if (!loader.trace) loader.defined[normalized] = undefined; // return the defined module object loader.set(normalized, entry.esModule); return Promise.resolve(); }; }); hook('instantiate', function(instantiate) { return function(load) { if (load.metadata.format == 'detect') load.metadata.format = undefined; // assumes previous instantiate is sync // (core json support) instantiate.call(this, load); var loader = this; var entry; // first we check if this module has already been defined in the registry if (loader.defined[load.name]) { entry = loader.defined[load.name]; // don't support deps for ES modules if (!entry.declarative) entry.deps = entry.deps.concat(load.metadata.deps); entry.deps = entry.deps.concat(load.metadata.deps); } // picked up already by an anonymous System.register script injection // or via the dynamic formats else if (load.metadata.entry) { entry = load.metadata.entry; entry.deps = entry.deps.concat(load.metadata.deps); } // Contains System.register calls // (dont run bundles in the builder) else if (!(loader.builder && load.metadata.bundle) && (load.metadata.format == 'register' || load.metadata.format == 'esm' || load.metadata.format == 'es6')) { if (typeof __exec != 'undefined') __exec.call(loader, load); if (!load.metadata.entry && !load.metadata.bundle) throw new Error(load.name + ' detected as ' + load.metadata.format + ' but didn\'t execute.'); entry = load.metadata.entry; // support metadata deps for System.register if (entry && load.metadata.deps) entry.deps = entry.deps.concat(load.metadata.deps); } // named bundles are just an empty module if (!entry) { entry = createEntry(); entry.deps = load.metadata.deps; entry.execute = function() {}; } // place this module onto defined for circular references loader.defined[load.name] = entry; var grouped = group(entry.deps); entry.deps = grouped.names; entry.originalIndices = grouped.indices; entry.name = load.name; entry.esmExports = load.metadata.esmExports !== false; // first, normalize all dependencies var normalizePromises = []; for (var i = 0, l = entry.deps.length; i < l; i++) normalizePromises.push(Promise.resolve(loader.normalize(entry.deps[i], load.name))); return Promise.all(normalizePromises).then(function(normalizedDeps) { entry.normalizedDeps = normalizedDeps; return { deps: entry.deps, execute: function() { // recursively ensure that the module and all its // dependencies are linked (with dependency group handling) link(load.name, entry, loader); // now handle dependency execution in correct order ensureEvaluated(load.name, entry, [], loader); if (!entry.esModule) entry.esModule = loader.newModule(entry.module.exports); // remove from the registry if (!loader.trace) loader.defined[load.name] = undefined; // return the defined module object return entry.esModule; } }; }); }; }); })();