UNPKG

systemjs

Version:

System loader extension for flexible AMD & CommonJS support

567 lines (465 loc) 17.6 kB
/* * 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 * * Works as a standalone extension, but benefits from having a more * advanced __eval defined like in SystemJS polyfill-wrapper-end.js * * 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. * */ function register(loader) { if (typeof indexOf == 'undefined') indexOf = Array.prototype.indexOf; if (typeof __eval == 'undefined' || typeof document != 'undefined' && !document.addEventListener) __eval = 0 || eval; // uglify breaks without the 0 || loader._extensions = loader._extensions || []; loader._extensions.push(register); // define exec for easy evaluation of a load record (load.name, load.source, load.address) // main feature is source maps support handling var curSystem; function exec(load) { var loader = this; // support sourceMappingURL (efficiently) var sourceMappingURL; var lastLineIndex = load.source.lastIndexOf('\n'); if (lastLineIndex != -1) { if (load.source.substr(lastLineIndex + 1, 21) == '//# sourceMappingURL=') { sourceMappingURL = load.source.substr(lastLineIndex + 22, load.source.length - lastLineIndex - 22); if (typeof toAbsoluteURL != 'undefined') sourceMappingURL = toAbsoluteURL(load.address, sourceMappingURL); } } __eval(load.source, load.address, sourceMappingURL); } loader.__exec = exec; function dedupe(deps) { var newDeps = []; for (var i = 0, l = deps.length; i < l; i++) if (indexOf.call(newDeps, deps[i]) == -1) newDeps.push(deps[i]) return newDeps; } /* * 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.register for dynamic modules (3-4 params) - System.register([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. * */ // loader.register sets loader.defined for declarative modules var anonRegister; var calledRegister; function registerModule(name, deps, declare, execute) { if (typeof name != 'string') { execute = declare; declare = deps; deps = name; name = null; } calledRegister = true; var register; // dynamic if (typeof declare == 'boolean') { register = { declarative: false, deps: deps, execute: execute, executingRequire: declare }; } else { // ES6 declarative register = { declarative: true, deps: deps, declare: declare }; } // named register if (name) { register.name = name; // we never overwrite an existing define if (!(name in loader.defined)) loader.defined[name] = register; } // anonymous register else if (register.declarative) { if (anonRegister) throw new TypeError('Multiple anonymous System.register calls in the same module file.'); anonRegister = register; } } /* * Registry side table - 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 System.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 * * Then for declarative only we track dynamic bindings with the records: * - name * - setters declarative setter functions * - exports actual module values * - 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 * */ function defineRegister(loader) { if (loader.register) return; loader.register = registerModule; if (!loader.defined) loader.defined = {}; // script injection mode calls this function synchronously on load var onScriptLoad = loader.onScriptLoad; loader.onScriptLoad = function(load) { onScriptLoad(load); // anonymous define if (anonRegister) load.metadata.entry = anonRegister; if (calledRegister) { load.metadata.format = load.metadata.format || 'register'; load.metadata.registered = true; } } } defineRegister(loader); 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 === undefined || depEntry.groupIndex < depGroupIndex) { // if already in a group, remove from the old group if (depEntry.groupIndex !== undefined) { 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 TypeError("Mixed dependency cycle detected"); } depEntry.groupIndex = depGroupIndex; } buildGroups(depEntry, loader, groups); } } function link(name, loader) { var startEntry = loader.defined[name]; // 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 var moduleRecords = {}; function getOrCreateModuleRecord(name) { return moduleRecords[name] || (moduleRecords[name] = { name: name, dependencies: [], exports: {}, // 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 module = entry.module = getOrCreateModuleRecord(entry.name); var exports = entry.module.exports; var declaration = entry.declare.call(loader.global, function(name, value) { module.locked = true; 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); importerModule.setters[importerIndex](exports); } } module.locked = false; return value; }); 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) { if (depEntry.module.exports && depEntry.module.exports.__esModule) depExports = depEntry.module.exports; else depExports = { 'default': depEntry.module.exports, '__useDefault': true }; } // 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 the setter for this dependency if (module.setters[i]) module.setters[i](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, [], 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]; var depEntry = loader.defined[depName]; if (depEntry) linkDynamicModule(depEntry, loader); } } // now execute entry.evaluated = true; var output = entry.execute.call(loader.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); } throw new TypeError('Module ' + name + ' not declared as a dependency.'); }, exports, module); if (output) module.exports = output; } /* * 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, seen, loader) { var entry = loader.defined[moduleName]; // 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, seen, loader); } } if (entry.evaluated) return; entry.evaluated = true; entry.module.execute.call(loader.global); } var registerRegEx = /System\.register/; var loaderFetch = loader.fetch; loader.fetch = function(load) { var loader = this; defineRegister(loader); if (loader.defined[load.name]) { load.metadata.format = 'defined'; return ''; } anonRegister = null; calledRegister = false; // the above get picked up by onScriptLoad return loaderFetch.call(loader, load); } var loaderTranslate = loader.translate; loader.translate = function(load) { this.register = registerModule; this.__exec = exec; load.metadata.deps = load.metadata.deps || []; // we run the meta detection here (register is after meta) return Promise.resolve(loaderTranslate.call(this, load)).then(function(source) { // dont run format detection for globals shimmed // ideally this should be in the global extension, but there is // currently no neat way to separate it if (load.metadata.init || load.metadata.exports) load.metadata.format = load.metadata.format || 'global'; // run detection for register format if (load.metadata.format == 'register' || !load.metadata.format && load.source.match(registerRegEx)) load.metadata.format = 'register'; return source; }); } var loaderInstantiate = loader.instantiate; loader.instantiate = function(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]; entry.deps = entry.deps.concat(load.metadata.deps); } // picked up already by a script injection else if (load.metadata.entry) entry = load.metadata.entry; // otherwise check if it is dynamic else if (load.metadata.execute) { entry = { declarative: false, deps: load.metadata.deps || [], execute: load.metadata.execute, executingRequire: load.metadata.executingRequire // NodeJS-style requires or not }; } // Contains System.register calls else if (load.metadata.format == 'register') { anonRegister = null; calledRegister = false; var curSystem = loader.global.System; loader.global.System = loader; loader.__exec(load); loader.global.System = curSystem; if (anonRegister) entry = anonRegister; if (!entry && System.defined[load.name]) entry = System.defined[load.name]; if (!calledRegister && !load.metadata.registered) throw new TypeError(load.name + ' detected as System.register but didn\'t execute.'); } // named bundles are just an empty module if (!entry && load.metadata.format != 'es6') return { deps: load.metadata.deps, execute: function() { return loader.newModule({}); } }; // place this module onto defined for circular references if (entry) loader.defined[load.name] = entry; // no entry -> treat as ES6 else return loaderInstantiate.call(this, load); entry.deps = dedupe(entry.deps); entry.name = load.name; // 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, loader); // now handle dependency execution in correct order ensureEvaluated(load.name, [], loader); // remove from the registry loader.defined[load.name] = undefined; var module = entry.module.exports; if (!module || !entry.declarative && module.__esModule !== true) module = { 'default': module, __useDefault: true }; // return the defined module object return loader.newModule(module); } }; }); } }