systemjs/src/instantiate.js

593 lines
21 KiB
JavaScript

import { scriptLoad, isBrowser, isWorker, global, cjsRequireRegEx, addToError, loadNodeModule,
warn, CONFIG, METADATA, emptyModule, protectedCreateNamespace, resolvedPromise, preloadScript } from './common.js';
import { evaluate } from './evaluate.js';
import fetch from './fetch.js';
import { getGlobalValue, getCJSDeps, requireResolve, getPathVars, prepareGlobal, clearLastDefine, registerLastDefine } from './format-helpers.js';
var supportsScriptLoad = (isBrowser || isWorker) && typeof navigator !== 'undefined' && navigator.userAgent && !navigator.userAgent.match(/MSIE (9|10).0/);
// include the node require since we're overriding it
export var nodeRequire;
if (typeof require !== 'undefined' && typeof process !== 'undefined' && !process.browser)
nodeRequire = require;
function setMetaEsModule (metadata, moduleValue) {
if (metadata.load.esModule && (typeof moduleValue === 'object' || typeof moduleValue === 'function') &&
!('__esModule' in moduleValue))
Object.defineProperty(moduleValue, '__esModule', {
value: true
});
}
export function instantiate (key, processAnonRegister) {
var loader = this;
var config = this[CONFIG];
var metadata = this[METADATA][key];
// first do bundles and depCache
return (loadBundlesAndDepCache(config, this, key) || resolvedPromise)
.then(function () {
if (processAnonRegister())
return;
// node module loading
if (key.substr(0, 6) === '@node/') {
if (!loader._nodeRequire)
throw new TypeError('Error loading ' + key + '. Can only load node core modules in Node.');
loader.registerDynamic([], false, function () {
return loadNodeModule.call(loader, key.substr(6), loader.baseURL);
});
processAnonRegister();
return;
}
if (metadata.load.scriptLoad ) {
if (metadata.load.pluginKey || !supportsScriptLoad) {
metadata.load.scriptLoad = false;
warn.call(config, 'scriptLoad not supported for "' + key + '"');
}
}
else if (metadata.load.scriptLoad !== false && !metadata.load.pluginKey && supportsScriptLoad) {
// auto script load AMD, global without deps
if (!metadata.load.deps && !metadata.load.globals &&
(metadata.load.format === 'system' || metadata.load.format === 'register' || metadata.load.format === 'global' && metadata.load.exports))
metadata.load.scriptLoad = true;
}
// fetch / translate / instantiate pipeline
if (!metadata.load.scriptLoad)
return initializePlugin(loader, key, metadata)
.then(function () {
return runFetchPipeline(loader, key, metadata, processAnonRegister, config.wasm);
})
// just script loading
return new Promise(function (resolve, reject) {
if (metadata.load.format === 'amd' && global.define !== loader.amdDefine)
throw new Error('Loading AMD with scriptLoad requires setting the global `' + globalName + '.define = SystemJS.amdDefine`');
scriptLoad(key, metadata.load.crossOrigin, metadata.load.integrity, function () {
if (!processAnonRegister()) {
metadata.load.format = 'global';
var globalValue = metadata.load.exports && getGlobalValue(metadata.load.exports);
loader.registerDynamic([], false, function () {
setMetaEsModule(metadata, globalValue);
return globalValue;
});
processAnonRegister();
}
resolve();
}, reject);
});
})
.then(function (instantiated) {
delete loader[METADATA][key];
return instantiated;
});
};
function initializePlugin (loader, key, metadata) {
if (!metadata.pluginKey)
return resolvedPromise;
return loader.import(metadata.pluginKey).then(function (plugin) {
metadata.pluginModule = plugin;
metadata.pluginLoad = {
name: key,
address: metadata.pluginArgument,
source: undefined,
metadata: metadata.load
};
metadata.load.deps = metadata.load.deps || [];
});
}
function loadBundlesAndDepCache (config, loader, key) {
// load direct deps, in turn will pick up their trace trees
var deps = config.depCache[key];
if (deps) {
for (var i = 0; i < deps.length; i++)
loader.normalize(deps[i], key).then(preloadScript);
}
else {
var matched = false;
for (var b in config.bundles) {
for (var i = 0; i < config.bundles[b].length; i++) {
var curModule = config.bundles[b][i];
if (curModule == key) {
matched = true;
break;
}
// wildcard in bundles includes / boundaries
if (curModule.indexOf('*') != -1) {
var parts = curModule.split('*');
if (parts.length != 2) {
config.bundles[b].splice(i--, 1);
continue;
}
if (key.substring(0, parts[0].length) == parts[0] &&
key.substr(key.length - parts[1].length, parts[1].length) == parts[1]) {
matched = true;
break;
}
}
}
if (matched)
return loader.import(b);
}
}
}
function runFetchPipeline (loader, key, metadata, processAnonRegister, wasm) {
if (metadata.load.exports && !metadata.load.format)
metadata.load.format = 'global';
return resolvedPromise
// locate
.then(function () {
if (!metadata.pluginModule || !metadata.pluginModule.locate)
return;
return Promise.resolve(metadata.pluginModule.locate.call(loader, metadata.pluginLoad))
.then(function (address) {
if (address)
metadata.pluginLoad.address = address;
});
})
// fetch
.then(function () {
if (!metadata.pluginModule)
return fetch(key, metadata.load.authorization, metadata.load.integrity, wasm);
wasm = false;
if (!metadata.pluginModule.fetch)
return fetch(metadata.pluginLoad.address, metadata.load.authorization, metadata.load.integrity, false);
return metadata.pluginModule.fetch.call(loader, metadata.pluginLoad, function (load) {
return fetch(load.address, metadata.load.authorization, metadata.load.integrity, false);
});
})
.then(function (fetched) {
// fetch is already a utf-8 string if not doing wasm detection
if (!wasm || typeof fetched === 'string')
return translateAndInstantiate(loader, key, fetched, metadata, processAnonRegister);
var bytes = new Uint8Array(fetched);
// detect by leading bytes
if (bytes[0] === 0 && bytes[1] === 97 && bytes[2] === 115) {
return WebAssembly.compile(bytes).then(function (m) {
var deps = [];
var setters = [];
var importObj = {};
// we can only set imports if supported (eg Safari doesnt support)
if (WebAssembly.Module.imports)
WebAssembly.Module.imports(m).forEach(function (i) {
var key = i.module;
setters.push(function (m) {
importObj[key] = m;
});
if (deps.indexOf(key) === -1)
deps.push(key);
});
loader.register(deps, function (_export) {
return {
setters: setters,
execute: function () {
_export(new WebAssembly.Instance(m, importObj).exports);
}
};
});
processAnonRegister();
});
}
// not wasm -> convert buffer into utf-8 string to execute as a module
// TextDecoder compatibility matches WASM currently. Need to keep checking this.
// The TextDecoder interface is documented at http://encoding.spec.whatwg.org/#interface-textdecoder
var stringSource = new TextDecoder('utf-8').decode(bytes);
return translateAndInstantiate(loader, key, stringSource, metadata, processAnonRegister);
})
}
function translateAndInstantiate (loader, key, source, metadata, processAnonRegister) {
return Promise.resolve(source)
// translate
.then(function (source) {
if (metadata.load.format === 'detect')
metadata.load.format = undefined;
readMetaSyntax(source, metadata);
if (!metadata.pluginModule || !metadata.pluginModule.translate)
return source;
metadata.pluginLoad.source = source;
return Promise.resolve(metadata.pluginModule.translate.call(loader, metadata.pluginLoad, metadata.traceOpts))
.then(function (translated) {
if (metadata.load.sourceMap) {
if (typeof metadata.load.sourceMap !== 'object')
throw new Error('metadata.load.sourceMap must be set to an object.');
sanitizeSourceMap(metadata.pluginLoad.address, metadata.load.sourceMap);
}
if (typeof translated === 'string')
return translated;
else
return metadata.pluginLoad.source;
});
})
.then(function (source) {
if (metadata.load.format === 'register' || !metadata.load.format && detectRegisterFormat(source)) {
metadata.load.format = 'register';
return source;
}
if (metadata.load.format !== 'esm' && (metadata.load.format || !source.match(esmRegEx))) {
return source;
}
metadata.load.format = 'esm';
return transpile(loader, source, key, metadata, processAnonRegister);
})
// instantiate
.then(function (translated) {
if (typeof translated !== 'string' || !metadata.pluginModule || !metadata.pluginModule.instantiate)
return translated;
var calledInstantiate = false;
metadata.pluginLoad.source = translated;
return Promise.resolve(metadata.pluginModule.instantiate.call(loader, metadata.pluginLoad, function (load) {
translated = load.source;
metadata.load = load.metadata;
if (calledInstantiate)
throw new Error('Instantiate must only be called once.');
calledInstantiate = true;
}))
.then(function (result) {
if (calledInstantiate)
return translated;
return protectedCreateNamespace(result);
});
})
.then(function (source) {
// plugin instantiate result case
if (typeof source !== 'string')
return source;
if (!metadata.load.format)
metadata.load.format = detectLegacyFormat(source);
var registered = false;
switch (metadata.load.format) {
case 'esm':
case 'register':
case 'system':
var err = evaluate(loader, source, metadata.load.sourceMap, key, metadata.load.integrity, metadata.load.nonce, false);
if (err)
throw err;
if (!processAnonRegister())
return emptyModule;
return;
break;
case 'json':
// warn.call(config, '"json" module format is deprecated.');
return loader.newModule({ default: JSON.parse(source), __useDefault: true });
case 'amd':
var curDefine = global.define;
global.define = loader.amdDefine;
clearLastDefine(metadata.load.deps, metadata.load.esModule);
var err = evaluate(loader, source, metadata.load.sourceMap, key, metadata.load.integrity, metadata.load.nonce, false);
// if didn't register anonymously, use the last named define if only one
registered = processAnonRegister();
if (!registered) {
registerLastDefine(loader);
registered = processAnonRegister();
}
global.define = curDefine;
if (err)
throw err;
break;
case 'cjs':
var metaDeps = metadata.load.deps;
var deps = (metadata.load.deps || []).concat(metadata.load.cjsRequireDetection ? getCJSDeps(source) : []);
for (var g in metadata.load.globals)
if (metadata.load.globals[g])
deps.push(metadata.load.globals[g]);
loader.registerDynamic(deps, true, function (require, exports, module) {
require.resolve = function (key) {
return requireResolve.call(loader, key, module.id);
};
// support module.paths ish
module.paths = [];
module.require = require;
// ensure meta deps execute first
if (!metadata.load.cjsDeferDepsExecute && metaDeps)
for (var i = 0; i < metaDeps.length; i++)
require(metaDeps[i]);
var pathVars = getPathVars(module.id);
var __cjsWrapper = {
exports: exports,
args: [require, exports, module, pathVars.filename, pathVars.dirname, global, global]
};
var cjsWrapper = "(function (require, exports, module, __filename, __dirname, global, GLOBAL";
// add metadata.globals to the wrapper arguments
if (metadata.load.globals)
for (var g in metadata.load.globals) {
__cjsWrapper.args.push(require(metadata.load.globals[g]));
cjsWrapper += ", " + g;
}
// disable AMD detection
var define = global.define;
global.define = undefined;
global.__cjsWrapper = __cjsWrapper;
source = cjsWrapper + ") {" + source.replace(hashBangRegEx, '') + "\n}).apply(__cjsWrapper.exports, __cjsWrapper.args);";
var err = evaluate(loader, source, metadata.load.sourceMap, key, metadata.load.integrity, metadata.load.nonce, false);
if (err)
throw err;
setMetaEsModule(metadata, exports);
global.__cjsWrapper = undefined;
global.define = define;
});
registered = processAnonRegister();
break;
case 'global':
var deps = metadata.load.deps || [];
for (var g in metadata.load.globals) {
var gl = metadata.load.globals[g];
if (gl)
deps.push(gl);
}
loader.registerDynamic(deps, false, function (require, exports, module) {
var globals;
if (metadata.load.globals) {
globals = {};
for (var g in metadata.load.globals)
if (metadata.load.globals[g])
globals[g] = require(metadata.load.globals[g]);
}
var exportName = metadata.load.exports;
if (exportName)
source += '\n' + globalName + '["' + exportName + '"] = ' + exportName + ';';
var retrieveGlobal = prepareGlobal(module.id, exportName, globals, metadata.load.encapsulateGlobal);
var err = evaluate(loader, source, metadata.load.sourceMap, key, metadata.load.integrity, metadata.load.nonce, true);
if (err)
throw err;
var output = retrieveGlobal();
setMetaEsModule(metadata, output);
return output;
});
registered = processAnonRegister();
break;
default:
throw new TypeError('Unknown module format "' + metadata.load.format + '" for "' + key + '".' + (metadata.load.format === 'es6' ? ' Use "esm" instead here.' : ''));
}
if (!registered)
throw new Error('Module ' + key + ' detected as ' + metadata.load.format + ' but didn\'t execute correctly.');
});
}
var globalName = typeof self != 'undefined' ? 'self' : 'global';
// good enough ES6 module detection regex - format detections not designed to be accurate, but to handle the 99% use case
export var esmRegEx = /(^\s*|[}\);\n]\s*)(import\s*(['"]|(\*\s+as\s+)?[^"'\(\)\n;]+\s*from\s*['"]|\{)|export\s+\*\s+from\s+["']|export\s*(\{|default|function|class|var|const|let|async\s+function))/;
var leadingCommentAndMetaRegEx = /^(\s*\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\s*\/\/[^\n]*|\s*"[^"]+"\s*;?|\s*'[^']+'\s*;?)*\s*/;
export function detectRegisterFormat(source) {
var leadingCommentAndMeta = source.match(leadingCommentAndMetaRegEx);
return leadingCommentAndMeta && source.substr(leadingCommentAndMeta[0].length, 15) === 'System.register';
}
// AMD Module Format Detection RegEx
// define([.., .., ..], ...)
// define(varName); || define(function(require, exports) {}); || define({})
var amdRegEx = /(?:^\uFEFF?|[^$_a-zA-Z\xA0-\uFFFF.])define\s*\(\s*("[^"]+"\s*,\s*|'[^']+'\s*,\s*)?\s*(\[(\s*(("[^"]+"|'[^']+')\s*,|\/\/.*\r?\n|\/\*(.|\s)*?\*\/))*(\s*("[^"]+"|'[^']+')\s*,?)?(\s*(\/\/.*\r?\n|\/\*(.|\s)*?\*\/))*\s*\]|function\s*|{|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*\))/;
/// require('...') || exports[''] = ... || exports.asd = ... || module.exports = ...
var cjsExportsRegEx = /(?:^\uFEFF?|[^$_a-zA-Z\xA0-\uFFFF.])(exports\s*(\[['"]|\.)|module(\.exports|\['exports'\]|\["exports"\])\s*(\[['"]|[=,\.]))/;
var commentRegEx = /(^|[^\\])(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg;
var stringRegEx = /("[^"\\\n\r]*(\\.[^"\\\n\r]*)*"|'[^'\\\n\r]*(\\.[^'\\\n\r]*)*')/g;
// used to support leading #!/usr/bin/env in scripts as supported in Node
var hashBangRegEx = /^\#\!.*/;
export function detectLegacyFormat (source) {
if (source.match(amdRegEx))
return 'amd';
cjsExportsRegEx.lastIndex = 0;
cjsRequireRegEx.lastIndex = 0;
if (cjsRequireRegEx.exec(source) || cjsExportsRegEx.exec(source))
return 'cjs';
// global is the fallback format
return 'global';
}
function sanitizeSourceMap (address, sourceMap) {
var originalName = address.split('!')[0];
// force set the filename of the original file
if (!sourceMap.file || sourceMap.file == address)
sourceMap.file = originalName + '!transpiled';
// force set the sources list if only one source
if (!sourceMap.sources || sourceMap.sources.length <= 1 && (!sourceMap.sources[0] || sourceMap.sources[0] === address))
sourceMap.sources = [originalName];
}
function transpile (loader, source, key, metadata, processAnonRegister) {
if (!loader.transpiler)
throw new TypeError('Unable to dynamically transpile ES module\n A loader plugin needs to be configured via `SystemJS.config({ transpiler: \'transpiler-module\' })`.');
// deps support for es transpile
if (metadata.load.deps) {
var depsPrefix = '';
for (var i = 0; i < metadata.load.deps.length; i++)
depsPrefix += 'import "' + metadata.load.deps[i] + '"; ';
source = depsPrefix + source;
}
// do transpilation
return loader.import.call(loader, loader.transpiler)
.then(function (transpiler) {
if (transpiler.__useDefault)
transpiler = transpiler.default;
// translate hooks means this is a transpiler plugin instead of a raw implementation
if (!transpiler.translate)
throw new Error(loader.transpiler + ' is not a valid transpiler plugin.');
// if transpiler is the same as the plugin loader, then don't run twice
if (transpiler === metadata.pluginModule)
return source;
// convert the source map into an object for transpilation chaining
if (typeof metadata.load.sourceMap === 'string')
metadata.load.sourceMap = JSON.parse(metadata.load.sourceMap);
metadata.pluginLoad = metadata.pluginLoad || {
name: key,
address: key,
source: source,
metadata: metadata.load
};
metadata.load.deps = metadata.load.deps || [];
return Promise.resolve(transpiler.translate.call(loader, metadata.pluginLoad, metadata.traceOpts))
.then(function (source) {
// sanitize sourceMap if an object not a JSON string
var sourceMap = metadata.load.sourceMap;
if (sourceMap && typeof sourceMap === 'object')
sanitizeSourceMap(key, sourceMap);
if (metadata.load.format === 'esm' && detectRegisterFormat(source))
metadata.load.format = 'register';
return source;
});
}, function (err) {
throw addToError(err, 'Unable to load transpiler to transpile ' + key);
});
}
// detect any meta header syntax
// only set if not already set
var metaRegEx = /^(\s*\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\s*\/\/[^\n]*|\s*"[^"]+"\s*;?|\s*'[^']+'\s*;?)+/;
var metaPartRegEx = /\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\/\/[^\n]*|"[^"]+"\s*;?|'[^']+'\s*;?/g;
function setMetaProperty(target, p, value) {
var pParts = p.split('.');
var curPart;
while (pParts.length > 1) {
curPart = pParts.shift();
target = target[curPart] = target[curPart] || {};
}
curPart = pParts.shift();
if (target[curPart] === undefined)
target[curPart] = value;
}
function readMetaSyntax (source, metadata) {
var meta = source.match(metaRegEx);
if (!meta)
return;
var metaParts = meta[0].match(metaPartRegEx);
for (var i = 0; i < metaParts.length; i++) {
var curPart = metaParts[i];
var len = curPart.length;
var firstChar = curPart.substr(0, 1);
if (curPart.substr(len - 1, 1) == ';')
len--;
if (firstChar != '"' && firstChar != "'")
continue;
var metaString = curPart.substr(1, curPart.length - 3);
var metaName = metaString.substr(0, metaString.indexOf(' '));
if (metaName) {
var metaValue = metaString.substr(metaName.length + 1, metaString.length - metaName.length - 1);
if (metaName === 'deps')
metaName = 'deps[]';
if (metaName.substr(metaName.length - 2, 2) === '[]') {
metaName = metaName.substr(0, metaName.length - 2);
metadata.load[metaName] = metadata.load[metaName] || [];
metadata.load[metaName].push(metaValue);
}
// "use strict" is not meta
else if (metaName !== 'use') {
setMetaProperty(metadata.load, metaName, metaValue);
}
}
else {
metadata.load[metaString] = true;
}
}
}