diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bcbf15..7a09459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.6 +## 0.6.8 +- Updated SystemJS to v0.19.16 +- Added autoLoad function +- New Package `universe:modules-ecmascript` + ### 0.6.2 - Cache performance improving by rewriting on Meteor Cache Compiler instead of Babel Cache diff --git a/package.js b/package.js index 6a7749c..b3afc10 100644 --- a/package.js +++ b/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'universe:modules', - version: '0.6.7', + version: '0.6.8', summary: 'Use ES6 / ES2015 modules in Meteor with SystemJS!', git: 'https://github.com/vazco/universe-modules', documentation: 'README.md' @@ -13,7 +13,7 @@ Package.registerBuildPlugin({ }); Package.onUse(function (api) { - api.versionsFrom('1.2.0.2'); + api.versionsFrom('1.2.1'); // Write ES2015 code inside package itself api.use('ecmascript'); diff --git a/vendor/system.js b/vendor/system.js index 13bf78b..945f78c 100644 --- a/vendor/system.js +++ b/vendor/system.js @@ -1,12 +1,12 @@ /* - * SystemJS v0.19.0 + * SystemJS v0.19.16 */ (function() { function bootstrap() {(function(__global) { var isWorker = typeof window == 'undefined' && typeof self != 'undefined' && typeof importScripts != 'undefined'; var isBrowser = typeof window != 'undefined' && typeof document != 'undefined'; - var isWindows = typeof process != 'undefined' && !!process.platform.match(/^win/); + var isWindows = typeof process != 'undefined' && typeof process.platform != 'undefined' && !!process.platform.match(/^win/); if (!__global.console) __global.console = { assert: function() {} }; @@ -40,7 +40,7 @@ function addToError(err, msg) { var newErr; if (err instanceof Error) { - var newErr = new Error(err.message, err.fileName, err.lineNumber); + newErr = new Error(err.message, err.fileName, err.lineNumber); if (isBrowser) { newErr.message = err.message + '\n\t' + msg; newErr.stack = err.stack; @@ -273,7 +273,7 @@ return new Promise(function(resolve, reject) { resolve(loader.loaderObj.normalize(request, refererName, refererAddress)); }) - // 15.2.4.2.2 GetOrCreateLoad + // 15.2.4.2.2 GetOrCreateLoad .then(function(name) { var load; if (loader.modules[name]) { @@ -316,7 +316,7 @@ function proceedToFetch(loader, load, p) { proceedToTranslate(loader, load, p - // 15.2.4.4.1 CallFetch + // 15.2.4.4.1 CallFetch .then(function(address) { // adjusted, see https://bugs.ecmascript.org/show_bug.cgi?id=2602 if (load.status != 'loading') @@ -333,7 +333,7 @@ // 15.2.4.5 function proceedToTranslate(loader, load, p) { p - // 15.2.4.5.1 CallTranslate + // 15.2.4.5.1 CallTranslate .then(function(source) { if (load.status != 'loading') return; @@ -391,7 +391,7 @@ loadPromises.push( requestLoad(loader, request, load.name, load.address) - // 15.2.4.6.1 AddDependencyLoad (load is parentLoad) + // 15.2.4.6.1 AddDependencyLoad (load is parentLoad) .then(function(depLoad) { // adjusted from spec to maintain dependency order @@ -462,13 +462,14 @@ if (loader.loads[i].name == name) { existingLoad = loader.loads[i]; - if(step == 'translate' && !existingLoad.source) { + if (step == 'translate' && !existingLoad.source) { existingLoad.address = stepState.moduleAddress; proceedToTranslate(loader, existingLoad, Promise.resolve(stepState.moduleSource)); } - // a primary load -> use that existing linkset - if (existingLoad.linkSets.length) + // a primary load -> use that existing linkset if it is for the direct load here + // otherwise create a new linkset unit + if (existingLoad.linkSets.length && existingLoad.linkSets[0].loads[0].name == existingLoad.name) return existingLoad.linkSets[0].done.then(function() { resolve(existingLoad); }); @@ -805,18 +806,21 @@ }, // 26.3.3.9 keys not implemented // 26.3.3.10 - load: function(name, options) { + load: function(name) { var loader = this._loader; - if (loader.modules[name]) { - doEnsureEvaluated(loader.modules[name], [], loader); - return Promise.resolve(loader.modules[name].module); - } - return loader.importPromises[name] || createImportPromise(this, name, - loadModule(loader, name, {}) - .then(function(load) { - delete loader.importPromises[name]; - return evaluateLoadedModule(loader, load); - })); + if (loader.modules[name]) + return Promise.resolve(); + return loader.importPromises[name] || createImportPromise(this, name, new Promise(asyncStartLoadPartwayThrough({ + step: 'locate', + loader: loader, + moduleName: name, + moduleMetadata: {}, + moduleSource: undefined, + moduleAddress: undefined + })) + .then(function() { + delete loader.importPromises[name]; + })); }, // 26.3.3.11 module: function(source, options) { @@ -836,19 +840,14 @@ if (typeof obj != 'object') throw new TypeError('Expected object'); - // we do this to be able to tell if a module is a module privately in ES5 - // by doing m instanceof Module var m = new Module(); - var pNames; - if (Object.getOwnPropertyNames && obj != null) { + var pNames = []; + if (Object.getOwnPropertyNames && obj != null) pNames = Object.getOwnPropertyNames(obj); - } - else { - pNames = []; + else for (var key in obj) pNames.push(key); - } for (var i = 0; i < pNames.length; i++) (function(key) { defineProperty(m, key, { @@ -860,9 +859,6 @@ }); })(pNames[i]); - if (Object.preventExtensions) - Object.preventExtensions(m); - return m; }, // 26.3.3.14 @@ -958,7 +954,7 @@ // NB no specification provided for System.paths, used ideas discussed in https://github.com/jorendorff/js-loaders/issues/25 function applyPaths(paths, name) { // most specific (most number of slashes in path) match wins - var pathMatch = '', wildcard, maxSlashCount = 0; + var pathMatch = '', wildcard, maxWildcardPrefixLen = 0; // check to see if we have a paths entry for (var p in paths) { @@ -975,18 +971,18 @@ } // wildcard path match else { - var slashCount = p.split('/').length; - if (slashCount >= maxSlashCount && + var wildcardPrefixLen = pathParts[0].length; + if (wildcardPrefixLen >= maxWildcardPrefixLen && name.substr(0, pathParts[0].length) == pathParts[0] && name.substr(name.length - pathParts[1].length) == pathParts[1]) { - maxSlashCount = slashCount; + maxWildcardPrefixLen = wildcardPrefixLen; pathMatch = p; wildcard = name.substr(pathParts[0].length, name.length - pathParts[1].length - pathParts[0].length); } } } - var outPath = paths[pathMatch] || name; + var outPath = paths[pathMatch]; if (typeof wildcard == 'string') outPath = outPath.replace('*', wildcard); @@ -1030,9 +1026,22 @@ xhr.onreadystatechange = function () { if (xhr.readyState === 4) { - if (xhr.status === 200 || (xhr.status == 0 && xhr.responseText)) { + // in Chrome on file:/// URLs, status is 0 + if (xhr.status == 0) { + if (xhr.responseText) { + load(); + } + else { + // when responseText is empty, wait for load or error event + // to inform if it is a 404 or empty file + xhr.addEventListener('error', error); + xhr.addEventListener('load', load); + } + } + else if (xhr.status === 200) { load(); - } else { + } + else { error(); } } @@ -1049,15 +1058,16 @@ } } - if (doTimeout) + if (doTimeout) { setTimeout(function() { xhr.send(); }, 0); - - xhr.send(null); + } else { + xhr.send(null); + } }; } - else if (typeof require != 'undefined') { + else if (typeof require != 'undefined' && typeof process != 'undefined') { var fs; fetchTextFromURL = function(url, authorization, fulfill, reject) { if (url.substr(0, 8) != 'file:///') @@ -1082,6 +1092,29 @@ }); }; } + else if (typeof self != 'undefined' && typeof self.fetch != 'undefined') { + fetchTextFromURL = function(url, authorization, fulfill, reject) { + var opts = { + headers: {'Accept': 'application/x-es-module, */*'} + }; + + if (authorization) { + if (typeof authorization == 'string') + opts.headers['Authorization'] = authorization; + opts.credentials = 'include'; + } + + fetch(url, opts) + .then(function (r) { + if (r.ok) { + return r.text(); + } else { + throw new Error('Fetch error: ' + r.status + ' ' + r.statusText); + } + }) + .then(fulfill, reject); + } + } else { throw new TypeError('No environment fetch API available.'); } @@ -1103,7 +1136,7 @@ var self = this; return Promise.resolve(__global[self.transpiler == 'typescript' ? 'ts' : self.transpiler] - || (self.pluginLoader || self)['import'](self.transpiler)) + || (self.pluginLoader || self)['import'](self.transpiler)) .then(function(transpiler) { if (transpiler.__useDefault) transpiler = transpiler['default']; @@ -1140,8 +1173,12 @@ return compiler.compile(source, filename); } catch(e) { - // traceur throws an error array - throw e[0]; + // on older versions of traceur (<0.9.3), an array of errors is thrown + // rather than a single error. + if (e.length) { + throw e[0]; + } + throw e; } } @@ -1163,7 +1200,7 @@ options.target = options.target || ts.ScriptTarget.ES5; if (options.sourceMap === undefined) options.sourceMap = true; - if (options.sourceMap) + if (options.sourceMap && options.inlineSourceMap !== false) options.inlineSourceMap = true; options.module = ts.ModuleKind.System; @@ -1173,105 +1210,7 @@ return transpile; })(); -// we define a __exec for globally-scoped execution -// used by module format implementations - var __exec; - - (function() { - - // System clobbering protection (mostly for Traceur) - var curSystem; - function preExec(loader) { - curSystem = __global.System; - __global.System = loader; - } - function postExec() { - __global.System = curSystem; - } - - var hasBtoa = typeof btoa != 'undefined'; - - function getSource(load) { - var lastLineIndex = load.source.lastIndexOf('\n'); - - return load.source - // adds the sourceURL comment if not already present - + (load.source.substr(lastLineIndex, 15) != '\n//# sourceURL=' - ? '\n//# sourceURL=' + load.address + (load.metadata.sourceMap ? '!transpiled' : '') : '') - // add sourceMappingURL if load.metadata.sourceMap is set - + (load.metadata.sourceMap && hasBtoa && - '\n//# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(load.metadata.sourceMap))) || '') - } - - // Web Worker and Chrome Extensions use original ESML eval - // this may lead to some global module execution differences (eg var not defining onto global) - if (isWorker || isBrowser && window.chrome && window.chrome.extension) { - __exec = function(load) { - if (load.metadata.integrity) - throw new Error('Subresource integrity checking is not supported in Web Workers or Chrome Extensions.'); - try { - preExec(this); - new Function(getSource(load)).call(__global); - postExec(); - } - catch(e) { - throw addToError(e, 'Evaluating ' + load.address); - } - }; - } - - // use script injection eval to get identical global script behaviour - else if (typeof document != 'undefined') { - var head; - - var scripts = document.getElementsByTagName('script'); - $__curScript = scripts[scripts.length - 1]; - - __exec = function(load) { - if (!head) - head = document.head || document.body || document.documentElement; - - var script = document.createElement('script'); - script.text = getSource(load); - var onerror = window.onerror; - var e; - window.onerror = function(_e) { - e = addToError(_e, 'Evaluating ' + load.address); - } - preExec(this); - - if (load.metadata.integrity) - script.setAttribute('integrity', load.metadata.integrity); - if (load.metadata.nonce) - script.setAttribute('nonce', load.metadata.nonce); - - head.appendChild(script); - head.removeChild(script); - postExec(); - window.onerror = onerror; - if (e) - throw e; - } - } - else { - // global scoped eval for node - var vmModule = 'vm'; - var vm = require(vmModule); - __exec = function(load) { - if (load.metadata.integrity) - throw new Error('Subresource integrity checking is unavailable in Node.'); - try { - preExec(this); - vm.runInThisContext(getSource(load)); - postExec(); - } - catch(e) { - throw addToError(e.toString(), 'Evaluating ' + load.address); - } - }; - } - - })();// SystemJS Loader Class and Extension helpers +// SystemJS Loader Class and Extension helpers function SystemJSLoader() { SystemLoader.call(this); @@ -1285,10 +1224,13 @@ SystemJSLoader.prototype = new SystemProto(); SystemJSLoader.prototype.constructor = SystemJSLoader; +// remove ESML instantiate + SystemJSLoader.prototype.instantiate = function() {}; + var systemJSConstructor; function hook(name, hook) { - SystemJSLoader.prototype[name] = hook(SystemJSLoader.prototype[name]); + SystemJSLoader.prototype[name] = hook(SystemJSLoader.prototype[name] || function() {}); } function hookConstructor(hook) { systemJSConstructor = hook(systemJSConstructor || function() {}); @@ -1326,7 +1268,7 @@ getOwnPropertyDescriptor = false; } -// converts any module.exports object into an object ready for System.newModule +// converts any module.exports object into an object ready for SystemJS.newModule function getESModule(exports) { var esModule = {}; // don't trigger getters/setters in environments that support them @@ -1374,7 +1316,7 @@ a[p] = val; else if (val instanceof Array && a[p] instanceof Array) a[p] = [].concat(prepend ? val : a[p]).concat(prepend ? a[p] : val); - else if (typeof val == 'object' && typeof a[p] == 'object') + else if (typeof val == 'object' && val !== null && typeof a[p] == 'object') a[p] = extend(extend({}, a[p]), val, prepend); else if (!prepend) a[p] = val; @@ -1384,52 +1326,136 @@ function warn(msg) { if (this.warnings && typeof console != 'undefined' && console.warn) console.warn(msg); - }/* - SystemJS map support + }// we define a __exec for globally-scoped execution +// used by module format implementations + var __exec; - Provides map configuration through - System.map['jquery'] = 'some/module/map' + (function() { - Note that this applies for subpaths, just like RequireJS: + // System clobbering protection (mostly for Traceur) + var curSystem; + var callCounter = 0; + var curLoad; + function preExec(loader, load) { + if (callCounter++ == 0) + curSystem = __global.System; + __global.System = __global.SystemJS = loader; + curLoad = load; + } + function postExec() { + if (--callCounter == 0) + __global.System = __global.SystemJS = curSystem; + curLoad = undefined; + } - jquery -> 'some/module/map' - jquery/path -> 'some/module/map/path' - bootstrap -> 'bootstrap' + // System.register, System.registerDynamic, AMD define pipeline + // if currently evalling code here, immediately reduce the registered entry against the load record + hook('pushRegister_', function() { + return function(register) { + if (!curLoad) + return false; - The most specific map is always taken, as longest path length - */ - hookConstructor(function(constructor) { - return function() { - constructor.call(this); - this.map = {}; - }; - }); + this.reduceRegister_(curLoad, register); + return true; + }; + }); - hook('normalize', function() { - return function(name, parentName) { - if (name.substr(0, 1) != '.' && name.substr(0, 1) != '/' && !name.match(absURLRegEx)) { - var bestMatch, bestMatchLength = 0; - - // now do the global map - for (var p in this.map) { - if (name.substr(0, p.length) == p && (name.length == p.length || name[p.length] == '/')) { - var curMatchLength = p.split('/').length; - if (curMatchLength <= bestMatchLength) - continue; - bestMatch = p; - bestMatchLength = curMatchLength; - } - } + var hasBtoa = typeof btoa != 'undefined'; + + // used to support leading #!/usr/bin/env in scripts as supported in Node + var hashBangRegEx = /^\#\!.*/; + + function getSource(load) { + var lastLineIndex = load.source.lastIndexOf('\n'); + + // wrap ES formats with a System closure for System global encapsulation + var wrap = load.metadata.format == 'esm' || load.metadata.format == 'register' || load.metadata.bundle; - if (bestMatch) - name = this.map[bestMatch] + name.substr(bestMatch.length); + return (wrap ? '(function(System) {' : '') + (load.metadata.format == 'cjs' ? load.source.replace(hashBangRegEx, '') : load.source) + (wrap ? '\n})(System);' : '') + // adds the sourceURL comment if not already present + + (load.source.substr(lastLineIndex, 15) != '\n//# sourceURL=' + ? '\n//# sourceURL=' + load.address + (load.metadata.sourceMap ? '!transpiled' : '') : '') + // add sourceMappingURL if load.metadata.sourceMap is set + + (load.metadata.sourceMap && hasBtoa && + '\n//# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(load.metadata.sourceMap))) || '') + } + + function evalExec(load) { + if (load.metadata.integrity) + throw new TypeError('Subresource integrity checking is not supported in Web Workers or Chrome Extensions.'); + try { + preExec(this, load); + new Function(getSource(load)).call(__global); + postExec(); } + catch(e) { + postExec(); + throw addToError(e, 'Evaluating ' + load.address); + } + } - // map is the first normalizer - return name; - }; - }); - var absURLRegEx = /^[^\/]+:\/\//; + // use script injection eval to get identical global script behaviour + if (typeof document != 'undefined' && document.getElementsByTagName) { + var head; + + var scripts = document.getElementsByTagName('script'); + $__curScript = scripts[scripts.length - 1]; + __exec = function(load) { + if (!this.globalEvaluationScope) + return evalExec.call(this, load); + + if (!head) + head = document.head || document.body || document.documentElement; + + var script = document.createElement('script'); + script.text = getSource(load); + var onerror = window.onerror; + var e; + window.onerror = function(_e) { + e = addToError(_e, 'Evaluating ' + load.address); + } + preExec(this, load); + + if (load.metadata.integrity) + script.setAttribute('integrity', load.metadata.integrity); + if (load.metadata.nonce) + script.setAttribute('nonce', load.metadata.nonce); + + head.appendChild(script); + head.removeChild(script); + postExec(); + window.onerror = onerror; + if (e) + throw e; + }; + } + + // global scoped eval for node + else if (typeof require != 'undefined') { + var vmModule = 'vm'; + var vm = require(vmModule); + __exec = function vmExec(load) { + if (!this.globalEvaluationScope) + return evalExec.call(this, load); + + if (load.metadata.integrity) + throw new TypeError('Subresource integrity checking is unavailable in Node.'); + try { + preExec(this, load); + vm.runInThisContext(getSource(load)); + postExec(); + } + catch(e) { + postExec(); + throw addToError(e.toString(), 'Evaluating ' + load.address); + } + }; + } + else { + __exec = evalExec; + } + + })();var absURLRegEx = /^[^\/]+:\/\//; function readMemberExpression(p, value) { var pParts = p.split('.'); @@ -1454,6 +1480,32 @@ return (baseURLCache[this.baseURL] = baseURL); } + function getMapMatch(map, name) { + var bestMatch, bestMatchLength = 0; + + for (var p in map) { + if (name.substr(0, p.length) == p && (name.length == p.length || name[p.length] == '/')) { + var curMatchLength = p.split('/').length; + if (curMatchLength <= bestMatchLength) + continue; + bestMatch = p; + bestMatchLength = curMatchLength; + } + } + + return bestMatch; + } + + function setConditional(mode) { + this.set('@system-env', this.newModule({ + browser: isBrowser, + node: !!this._nodeRequire, + env: mode, + production: mode == 'production', + development: mode == 'development' + })); + } + var baseURIObj = new URL(baseURI); hookConstructor(function(constructor) { @@ -1463,56 +1515,116 @@ // support baseURL this.baseURL = baseURI.substr(0, baseURI.lastIndexOf('/') + 1); + // support map + this.map = {}; + + // global behaviour flags + this.warnings = false; + this.defaultJSExtensions = false; + this.globalEvaluationScope = true; + this.pluginFirst = false; + + // by default load ".json" files as json + // leading * meta doesn't need normalization + // NB add this in next breaking release + // this.meta['*.json'] = { format: 'json' }; + + // Default settings for globalEvaluationScope: + // Disabled for WebWorker, Chrome Extensions and jsdom + if (isWorker + || isBrowser && window.chrome && window.chrome.extension + || isBrowser && navigator.userAgent.match(/^Node\.js/)) + this.globalEvaluationScope = false; + // support the empty module, as a concept this.set('@empty', this.newModule({})); - // include the node require since we're overriding it - if (typeof require != 'undefined' && require.resolve && typeof process != 'undefined') - this._nodeRequire = require; + setConditional.call(this, 'development'); }; }); +// include the node require since we're overriding it + if (typeof require != 'undefined' && typeof process != 'undefined' && !process.browser) + SystemJSLoader.prototype._nodeRequire = require; + + var nodeCoreModules = ['assert', 'buffer', 'child_process', 'cluster', 'console', 'constants', + 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'module', 'net', 'os', 'path', + 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'sys', 'timers', + 'tls', 'tty', 'url', 'util', 'vm', 'zlib']; + /* - Normalization + Core SystemJS Normalization If a name is relative, we apply URL normalization to the page If a name is an absolute URL, we leave it as-is - Plain names (neither of the above) run through the map and package - normalization phases (applying before and after this one). + Plain names (neither of the above) run through the map and paths + normalization phases. The paths normalization phase applies last (paths extension), which - defines the `normalizeSync` function and normalizes everything into + defines the `decanonicalize` function and normalizes everything into a URL. - - The final normalization */ - hook('normalize', function(normalize) { return function(name, parentName) { + // first run map config + if (name[0] != '.' && name[0] != '/' && !name.match(absURLRegEx)) { + var mapMatch = getMapMatch(this.map, name); + if (mapMatch) + name = this.map[mapMatch] + name.substr(mapMatch.length); + } + // dynamically load node-core modules when requiring `@node/fs` for example - if (name.substr(0, 6) == '@node/') { + if (name.substr(0, 6) == '@node/' && nodeCoreModules.indexOf(name.substr(6)) != -1) { if (!this._nodeRequire) - throw new TypeError('Can only load node core modules in Node.'); + throw new TypeError('Error loading ' + name + '. Can only load node core modules in Node.'); this.set(name, this.newModule(getESModule(this._nodeRequire(name.substr(6))))); } - // first run map config - name = normalize.apply(this, arguments); - // relative URL-normalization + if (name[0] == '.' || name[0] == '/') { + if (parentName) + name = new URL(name, parentName.replace(/#/g, '%05')).href.replace(/%05/g, '#'); + else + name = new URL(name, baseURIObj).href; + } + + // if the module is in the registry already, use that + if (this.has(name)) + return name; + + if (name.match(absURLRegEx)) { + // defaultJSExtensions backwards compatibility + if (this.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js') + name += '.js'; + return name; + } + + // applyPaths implementation provided from ModuleLoader system.js source + name = applyPaths(this.paths, name) || name; + + // defaultJSExtensions backwards compatibility + if (this.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js') + name += '.js'; + + // ./x, /x -> page-relative if (name[0] == '.' || name[0] == '/') - return new URL(name, parentName || baseURIObj).href; - return name; + return new URL(name, baseURIObj).href; + // x -> baseURL-relative + else + return new URL(name, getBaseURLObj.call(this)).href; }; }); -// percent encode just '#' in urls +// percent encode just '#' in urls if using HTTP requests + var httpRequest = typeof XMLHttpRequest != 'undefined'; hook('locate', function(locate) { return function(load) { return Promise.resolve(locate.call(this, load)) .then(function(address) { - return address.replace(/#/g, '%23'); + if (httpRequest) + return address.replace(/#/g, '%23'); + return address; }); }; }); @@ -1545,13 +1657,63 @@ hook('import', function(systemImport) { return function(name, parentName, parentAddress) { if (parentName && parentName.name) - warn.call(this, 'System.import(name, { name: parentName }) is deprecated for System.import(name, parentName), while importing ' + name + ' from ' + parentName.name); + warn.call(this, 'SystemJS.import(name, { name: parentName }) is deprecated for SystemJS.import(name, parentName), while importing ' + name + ' from ' + parentName.name); return systemImport.call(this, name, parentName, parentAddress).then(function(module) { return module.__useDefault ? module['default'] : module; }); }; }); + /* + * Allow format: 'detect' meta to enable format detection + */ + hook('translate', function(systemTranslate) { + return function(load) { + if (load.metadata.format == 'detect') + load.metadata.format = undefined; + return systemTranslate.call(this, load); + }; + }); + + + /* + * JSON format support + * + * Supports loading JSON files as a module format itself + * + * Usage: + * + * SystemJS.config({ + * meta: { + * '*.json': { format: 'json' } + * } + * }); + * + * Module is returned as if written: + * + * export default {JSON} + * + * No named exports are provided + * + * Files ending in ".json" are treated as json automatically by SystemJS + */ + hook('instantiate', function(instantiate) { + return function(load) { + if (load.metadata.format == 'json' && !this.builder) { + var entry = load.metadata.entry = createEntry(); + entry.deps = []; + entry.execute = function() { + try { + return JSON.parse(load.source); + } + catch(e) { + throw new Error("Invalid JSON file " + load.name); + } + }; + } + }; + }) + /* Extend config merging one deep only @@ -1573,7 +1735,7 @@ Normalizes meta and package configs allowing for: - System.config({ + SystemJS.config({ meta: { './index.js': {} } @@ -1581,15 +1743,22 @@ To become - System.meta['https://thissite.com/index.js'] = {}; + SystemJS.meta['https://thissite.com/index.js'] = {}; For easy normalization canonicalization with latest URL support. */ - SystemJSLoader.prototype.warnings = false; + SystemJSLoader.prototype.env = 'development'; + SystemJSLoader.prototype.config = function(cfg) { + var loader = this; + if ('warnings' in cfg) - this.warnings = cfg.warnings; + loader.warnings = cfg.warnings; + + // transpiler deprecation path + if (cfg.transpilerRuntime === false) + loader._loader.loadedTranspilerRuntime = true; // always configure baseURL first if (cfg.baseURL) { @@ -1598,26 +1767,32 @@ for (var p in obj) return true; } - if (checkHasConfig(this.packages) || checkHasConfig(this.meta) || checkHasConfig(this.depCache) || checkHasConfig(this.bundles)) - throw new TypeError('baseURL should only be configured once and must be configured first.'); + if (checkHasConfig(loader.packages) || checkHasConfig(loader.meta) || checkHasConfig(loader.depCache) || checkHasConfig(loader.bundles) || checkHasConfig(loader.packageConfigPaths)) + throw new TypeError('Incorrect configuration order. The baseURL must be configured with the first SystemJS.config call.'); - this.baseURL = cfg.baseURL; + loader.baseURL = cfg.baseURL; // sanitize baseURL - getBaseURLObj.call(this); + getBaseURLObj.call(loader); } if (cfg.defaultJSExtensions) { - this.defaultJSExtensions = cfg.defaultJSExtensions; - warn.call(this, 'The defaultJSExtensions configuration option is deprecated, use packages configuration instead.'); + loader.defaultJSExtensions = cfg.defaultJSExtensions; + warn.call(loader, 'The defaultJSExtensions configuration option is deprecated, use packages configuration instead.'); } if (cfg.pluginFirst) - this.pluginFirst = cfg.pluginFirst; + loader.pluginFirst = cfg.pluginFirst; + + if (cfg.env) { + if (cfg.env != 'production' && cfg.env != 'development') + throw new TypeError('The config environment must be set to "production" or "development".'); + setConditional.call(loader, cfg.env); + } if (cfg.paths) { for (var p in cfg.paths) - this.paths[p] = cfg.paths[p]; + loader.paths[p] = cfg.paths[p]; } if (cfg.map) { @@ -1628,32 +1803,33 @@ // object map backwards-compat into packages configuration if (typeof v !== 'string') { objMaps += (objMaps.length ? ', ' : '') + '"' + p + '"'; - var normalized = this.normalizeSync(p); - // if doing default js extensions, undo to get package name - if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js') - normalized = normalized.substr(0, normalized.length - 3); + var prop = loader.decanonicalize(p + (p[p.length - 1] != '/' ? '/' : '')); + + // allow trailing '/' in package config + if (prop[prop.length - 1] == '/') + prop = prop.substr(0, prop.length - 1); // if a package main, revert it var pkgMatch = ''; - for (var pkg in this.packages) { - if (normalized.substr(0, pkg.length) == pkg - && (!normalized[pkg.length] || normalized[pkg.length] == '/') + for (var pkg in loader.packages) { + if (prop.substr(0, pkg.length) == pkg + && (!prop[pkg.length] || prop[pkg.length] == '/') && pkgMatch.split('/').length < pkg.split('/').length) pkgMatch = pkg; } - if (pkgMatch && this.packages[pkgMatch].main) - normalized = normalized.substr(0, normalized.length - this.packages[pkgMatch].main.length - 1); + if (pkgMatch && loader.packages[pkgMatch].main) + prop = prop.substr(0, prop.length - loader.packages[pkgMatch].main.length - 1); - var pkg = this.packages[normalized] = this.packages[normalized] || {}; + var pkg = loader.packages[prop] = loader.packages[prop] || {}; pkg.map = v; } else { - this.map[p] = v; + loader.map[p] = v; } } if (objMaps) - warn.call(this, 'The map configuration for ' + objMaps + ' uses object submaps, which is deprecated in global map.\nUpdate this to use package contextual map with configs like System.config({ packages: { "' + p + '": { map: {...} } } }).'); + warn.call(loader, 'The map configuration for ' + objMaps + ' uses object submaps, which is deprecated in global map.\nUpdate this to use package contextual map with configs like SystemJS.config({ packages: { "' + p + '": { map: {...} } } }).'); } if (cfg.packageConfigPaths) { @@ -1661,12 +1837,20 @@ for (var i = 0; i < cfg.packageConfigPaths.length; i++) { var path = cfg.packageConfigPaths[i]; var packageLength = Math.max(path.lastIndexOf('*') + 1, path.lastIndexOf('/')); - var normalized = this.normalizeSync(path.substr(0, packageLength) + '/'); - if (this.defaultJSExtensions && path.substr(path.length - 3, 3) != '.js') - normalized = normalized.substr(0, normalized.length - 3); - packageConfigPaths[i] = normalized.substr(0, normalized.length - 1) + path.substr(packageLength); + var normalized = loader.decanonicalize(path.substr(0, packageLength) + '/'); + normalized = normalized.substr(0, normalized.length - 1) + path.substr(packageLength); + packageConfigPaths[i] = normalized; + } + loader.packageConfigPaths = packageConfigPaths; + } + + if (cfg.bundles) { + for (var p in cfg.bundles) { + var bundle = []; + for (var i = 0; i < cfg.bundles[p].length; i++) + bundle.push(loader.decanonicalize(cfg.bundles[p][i])); + loader.bundles[p] = bundle; } - this.packageConfigPaths = packageConfigPaths; } if (cfg.packages) { @@ -1674,104 +1858,69 @@ if (p.match(/^([^\/]+:)?\/\/$/)) throw new TypeError('"' + p + '" is not a valid package name.'); - // request with trailing "/" to get package name exactly - var prop = this.normalizeSync(p + (p[p.length - 1] != '/' ? '/' : '')); - prop = prop.substr(0, prop.length - 1); + // trailing slash allows paths matches here + var prop = loader.decanonicalize(p + (p[p.length - 1] != '/' ? '/' : '')); + + // allow trailing '/' in package config + if (prop[prop.length - 1] == '/') + prop = prop.substr(0, prop.length - 1); - // if doing default js extensions, undo to get package name - if (this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js') - prop = prop.substr(0, prop.length - 3); + loader.packages[prop] = loader.packages[prop] || {}; + + // meta backwards compatibility + if (cfg.packages[p].modules) { + warn.call(loader, 'Package ' + p + ' is configured with "modules", which is deprecated as it has been renamed to "meta".'); + cfg.packages[p].meta = cfg.packages[p].modules; + delete cfg.packages[p].modules; + } - this.packages[prop]= this.packages[prop] || {}; for (var q in cfg.packages[p]) if (indexOf.call(packageProperties, q) == -1) - warn.call(this, '"' + q + '" is not a valid package configuration option in package ' + p); + warn.call(loader, '"' + q + '" is not a valid package configuration option in package ' + p); - extendMeta(this.packages[prop], cfg.packages[p]); - } - } - - if (cfg.bundles) { - for (var p in cfg.bundles) { - var bundle = []; - for (var i = 0; i < cfg.bundles[p].length; i++) - bundle.push(this.normalizeSync(cfg.bundles[p][i])); - this.bundles[p] = bundle; + extendMeta(loader.packages[prop], cfg.packages[p]); } } for (var c in cfg) { var v = cfg[c]; - var normalizeProp = false, normalizeValArray = false; + var normalizeProp = false; if (c == 'baseURL' || c == 'map' || c == 'packages' || c == 'bundles' || c == 'paths' || c == 'warnings' || c == 'packageConfigPaths') continue; if (typeof v != 'object' || v instanceof Array) { - this[c] = v; + loader[c] = v; } else { - this[c] = this[c] || {}; - - if (c == 'meta' || c == 'depCache') - normalizeProp = true; - - for (var p in v) { - if (c == 'meta' && p[0] == '*') - this[c][p] = v[p]; - else if (normalizeProp) - this[c][this.normalizeSync(p)] = v[p]; - else - this[c][p] = v[p]; - } - } - } - };/* - * Paths extension - * - * Applies paths and normalizes to a full URL - */ - hook('normalize', function(normalize) { - return function(name, parentName) { - var normalized = normalize.apply(this, arguments); - - // if the module is in the registry already, use that - if (this.has(normalized)) - return normalized; - - if (normalized.match(absURLRegEx)) { - // defaultJSExtensions backwards compatibility - if (this.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js') - normalized += '.js'; - return normalized; - } - - // applyPaths implementation provided from ModuleLoader system.js source - normalized = applyPaths(this.paths, normalized) || normalized; + loader[c] = loader[c] || {}; - // defaultJSExtensions backwards compatibility - if (this.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js') - normalized += '.js'; + if (c == 'meta' || c == 'depCache') + normalizeProp = true; - // ./x, /x -> page-relative - if (normalized[0] == '.' || normalized[0] == '/') - return new URL(normalized, baseURIObj).href; - // x -> baseURL-relative - else - return new URL(normalized, getBaseURLObj.call(this)).href; - }; - });/* + for (var p in v) { + // base-level wildcard meta does not normalize to retain catch-all quality + if (c == 'meta' && p[0] == '*') + loader[c][p] = v[p]; + else if (normalizeProp) + loader[c][loader.decanonicalize(p)] = v[p]; + else + loader[c][p] = v[p]; + } + } + } + };/* * Package Configuration Extension * * Example: * - * System.packages = { + * SystemJS.packages = { * jquery: { * basePath: 'lib', // optionally only use a subdirectory within the package * main: 'index.js', // when not set, package name is requested directly * format: 'amd', * defaultExtension: 'ts', // defaults to 'js', can be set to false - * meta: { + * modules: { * '*.ts': { * loader: 'typescript' * }, @@ -1797,8 +1946,9 @@ * depCache: { * // import 'package/index.js' loads in parallel package/lib/test.js,package/vendor/sizzle.js * './index.js': ['./test'], - * './test.js': ['sizzle'] - * } + * './test.js': ['external-dep'], + * 'external-dep/path.js': ['./another.js'] + * } * } * }; * @@ -1811,21 +1961,23 @@ * Detailed Behaviours * - main can have a leading "./" can be added optionally * - map and defaultExtension are applied to the main - * - defaultExtension adds the extension only if no other extension is present + * - defaultExtension adds the extension only if the exact extension is not present * - defaultJSExtensions applies after map when defaultExtension is not set * - if a meta value is available for a module, map and defaultExtension are skipped * - like global map, package map also applies to subpaths (sizzle/x, ./vendor/another/sub) * - condition module map is '@env' module in package or '@system-env' globally + * - map targets support conditional interpolation ('./x': './x.#{|env}.js') + * - internal package map targets cannot use boolean conditionals + * + * In addition, the following modules properties will be allowed to be package + * -relative as well in the package module config: * - * In addition, the following meta properties will be allowed to be package - * -relative as well in the package meta config: - * * - loader * - alias * * * Package Configuration Loading - * + * * Not all packages may already have their configuration present in the System config * For these cases, a list of packageConfigPaths can be provided, which when matched against * a request, will first request a ".json" file by the package name to derive the package @@ -1833,26 +1985,26 @@ * case in SystemJS. * * Example: - * - * System.packageConfigPaths = ['packages/test/package.json', 'packages/*.json']; + * + * SystemJS.packageConfigPaths = ['packages/test/package.json', 'packages/*.json']; * * // will first request 'packages/new-package/package.json' for the package config * // before completing the package request to 'packages/new-package/path' - * System.import('packages/new-package/path'); + * SystemJS.import('packages/new-package/path'); * * // will first request 'packages/test/package.json' before the main - * System.import('packages/test'); + * SystemJS.import('packages/test'); * * When a package matches packageConfigPaths, it will always send a config request for * the package configuration. * The package name itself is taken to be the match up to and including the last wildcard * or trailing slash. - * Package config paths are ordered - matching is done based on the first match found. - * Any existing package configurations for the package will deeply merge with the + * The most specific package config path will be used. + * Any existing package configurations for the package will deeply merge with the * package config, with the existing package configurations taking preference. * To opt-out of the package configuration request for a package that matches * packageConfigPaths, use the { configured: true } package config option. - * + * */ (function() { @@ -1864,11 +2016,11 @@ }; }); - function getPackage(name) { + function getPackage(loader, normalized) { // use most specific package var curPkg, curPkgLen = 0, pkgLen; - for (var p in this.packages) { - if (name.substr(0, p.length) === p && (name.length === p.length || name[p.length] === '/')) { + for (var p in loader.packages) { + if (normalized.substr(0, p.length) === p && (normalized.length === p.length || normalized[p.length] === '/')) { pkgLen = p.split('/').length; if (pkgLen > curPkgLen) { curPkg = p; @@ -1879,22 +2031,6 @@ return curPkg; } - function applyMap(map, name) { - var bestMatch, bestMatchLength = 0; - - for (var p in map) { - if (name.substr(0, p.length) == p && (name.length == p.length || name[p.length] == '/')) { - var curMatchLength = p.split('/').length; - if (curMatchLength <= bestMatchLength) - continue; - bestMatch = p; - bestMatchLength = curMatchLength; - } - } - - return bestMatch; - } - function getBasePath(pkg) { // sanitize basePath var basePath = pkg.basePath && pkg.basePath != '.' ? pkg.basePath : ''; @@ -1907,93 +2043,166 @@ return basePath; } - // given the package subpath, return the resultant combined path - // defaultExtension is only added if the path does not have - // loader package meta or exact package meta - // We also re-incorporate package-level conditional syntax at this point - // allowing package map and package mains to point to conditionals - // when conditionals are present, - function toPackagePath(loader, pkgName, pkg, basePath, subPath, sync, isPlugin) { - // skip if its a plugin call already, or we have boolean / interpolation conditional syntax in subPath - var skipExtension = !!(isPlugin || subPath.indexOf('#?') != -1 || subPath.match(interpolationRegEx)); + function addDefaultExtension(loader, pkg, pkgName, basePath, subPath, skipExtensions) { + // don't apply extensions to folders or if defaultExtension = false + if (!subPath || subPath[subPath.length - 1] == '/' || skipExtensions || pkg.defaultExtension === false) + return subPath; + + // NB are you sure about this? + // skip if we have interpolation conditional syntax in subPath? + if (subPath.match(interpolationRegEx)) + return subPath; + + var metaMatch = false; // exact meta or meta with any content after the last wildcard skips extension - if (!skipExtension && pkg.meta) - getMetaMatches(pkg.meta, pkgName, subPath, function(metaPattern, matchMeta, matchDepth) { + if (pkg.meta) + getMetaMatches(pkg.meta, subPath, function(metaPattern, matchMeta, matchDepth) { + if (matchDepth == 0 || metaPattern.lastIndexOf('*') != metaPattern.length - 1) + return metaMatch = true; + }); + + // exact global meta or meta with any content after the last wildcard skips extension + if (!metaMatch && loader.meta) + getMetaMatches(loader.meta, pkgName + '/' + basePath + subPath, function(metaPattern, matchMeta, matchDepth) { if (matchDepth == 0 || metaPattern.lastIndexOf('*') != metaPattern.length - 1) - skipExtension = true; + return metaMatch = true; }); - var normalized = pkgName + '/' + basePath + subPath + (skipExtension ? '' : getDefaultExtension(pkg, subPath)); + if (metaMatch) + return subPath; - return sync ? normalized : booleanConditional.call(loader, normalized, pkgName + '/').then(function(name) { - return interpolateConditional.call(loader, name, pkgName + '/'); - }); + // work out what the defaultExtension is and add if not there already + // NB reconsider if default should really be ".js"? + var defaultExtension = '.' + (pkg.defaultExtension || 'js'); + if (subPath.substr(subPath.length - defaultExtension.length) != defaultExtension) + return subPath + defaultExtension; + else + return subPath; } - function getDefaultExtension(pkg, subPath) { - // don't apply extensions to folders or if defaultExtension = false - if (subPath[subPath.length - 1] != '/' && pkg.defaultExtension !== false) { - // work out what the defaultExtension is and add if not there already - var defaultExtension = '.' + (pkg.defaultExtension || 'js'); - if (subPath.substr(subPath.length - defaultExtension.length) != defaultExtension) - return defaultExtension; + function applyPackageConfigSync(loader, pkg, pkgName, subPath, skipExtensions) { + // main + if (!subPath) { + if (pkg.main) + subPath = pkg.main.substr(0, 2) == './' ? pkg.main.substr(2) : pkg.main; + // also no submap if name is package itself (import 'pkg' -> 'path/to/pkg.js') + else + // NB can add a default package main convention here when defaultJSExtensions is deprecated + // if it becomes internal to the package then it would no longer be an exit path + return pkgName + (loader.defaultJSExtensions ? '.js' : ''); + } + + var basePath = getBasePath(pkg); + + // map config checking without then with extensions + if (pkg.map) { + var mapPath = './' + subPath; + + var mapMatch = getMapMatch(pkg.map, mapPath); + + // we then check map with the default extension adding + if (!mapMatch) { + mapPath = './' + addDefaultExtension(loader, pkg, pkgName, basePath, subPath, skipExtensions); + if (mapPath != './' + subPath) + mapMatch = getMapMatch(pkg.map, mapPath); + } + if (mapMatch) + return doMapSync(loader, pkg, pkgName, basePath, mapMatch, mapPath, skipExtensions); } - return ''; + + // normal package resolution + return pkgName + '/' + basePath + addDefaultExtension(loader, pkg, pkgName, basePath, subPath, skipExtensions); } - function applyPackageConfig(normalized, pkgName, pkg, sync, isPlugin) { - var loader = this; + function doMapSync(loader, pkg, pkgName, basePath, mapMatch, path, skipExtensions) { + var mapped = pkg.map[mapMatch]; - var basePath = getBasePath(pkg); + // ignore conditionals in sync + if (typeof mapped != 'string') + mapped = mapMatch = path; + + // package map to main / base-level + if (mapped == '.') + mapped = pkgName; + + // internal package map + else if (mapped.substr(0, 2) == './') + return pkgName + '/' + basePath + addDefaultExtension(loader, pkg, pkgName, basePath, mapped.substr(2) + path.substr(mapMatch.length), skipExtensions); + + // external map reference + return loader.normalizeSync(mapped + path.substr(mapMatch.length), pkgName + '/'); + } + function applyPackageConfig(loader, pkg, pkgName, subPath, skipExtensions) { // main - // NB can add a default package main convention here - if (pkgName === normalized && pkg.main) - normalized += '/' + (pkg.main.substr(0, 2) == './' ? pkg.main.substr(2) : pkg.main); + if (!subPath) { + if (pkg.main) + subPath = pkg.main.substr(0, 2) == './' ? pkg.main.substr(2) : pkg.main; + // also no submap if name is package itself (import 'pkg' -> 'path/to/pkg.js') + else + // NB can add a default package main convention here when defaultJSExtensions is deprecated + // if it becomes internal to the package then it would no longer be an exit path + return Promise.resolve(pkgName + (loader.defaultJSExtensions ? '.js' : '')); + } - // allow for direct package name normalization with trailling "/" (no main) - if (normalized.length == pkgName.length + 1 && normalized[pkgName.length] == '/') - return normalized; + var basePath = getBasePath(pkg); - // no submap if name is package itself - if (normalized.length == pkgName.length) - return normalized + (loader.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js' ? '.js' : ''); + // map config checking without then with extensions + var mapPath, mapMatch; - // apply map, checking without then with defaultExtension if (pkg.map) { - var subPath = '.' + normalized.substr(pkgName.length); - var map = applyMap(pkg.map, subPath) || !isPlugin && applyMap(pkg.map, (subPath += getDefaultExtension(pkg, subPath.substr(2)))); - var mapped = pkg.map[map]; + mapPath = './' + subPath; + mapMatch = getMapMatch(pkg.map, mapPath); + + // we then check map with the default extension adding + if (!mapMatch) { + mapPath = './' + addDefaultExtension(loader, pkg, pkgName, basePath, subPath, skipExtensions); + if (mapPath != './' + subPath) + mapMatch = getMapMatch(pkg.map, mapPath); + } } - function doMap(mapped) { - // '.' as a target is the package itself (with package main check) - if (mapped == '.') - return pkgName; - // internal package map - else if (mapped.substr(0, 2) == './') - return toPackagePath(loader, pkgName, pkg, basePath, mapped.substr(2), sync, isPlugin); - // global package map - else - return (sync ? loader.normalizeSync : loader.normalize).call(loader, mapped); - } + return (mapMatch ? doMap(loader, pkg, pkgName, basePath, mapMatch, mapPath, skipExtensions) : Promise.resolve()) + .then(function(mapped) { + if (mapped) + return Promise.resolve(mapped); - // apply non-environment map match - if (typeof mapped == 'string') - return doMap(mapped + subPath.substr(map.length)); + // normal package resolution / fallback resolution for no conditional match + return Promise.resolve(pkgName + '/' + basePath + addDefaultExtension(loader, pkg, pkgName, basePath, subPath, skipExtensions)); + }); + } + + function doStringMap(loader, pkg, pkgName, basePath, mapMatch, mapped, path, skipExtensions) { + // NB the interpolation cases should strictly skip subsequent interpolation - // sync normalize does not apply environment map - if (sync || !mapped) - return toPackagePath(loader, pkgName, pkg, basePath, normalized.substr(pkgName.length + 1), sync, isPlugin); + // package map to main / base-level + if (mapped == '.') + mapped = pkgName; - // environment map build support - // -> we return [package-name]#[conditional-map] ("jquery#:index.js" in example above) - // to indicate this unique conditional branch to builder in all of its possibilities + // internal package map + else if (mapped.substr(0, 2) == './') + return Promise.resolve(pkgName + '/' + basePath + addDefaultExtension(loader, pkg, pkgName, basePath, mapped.substr(2) + path.substr(mapMatch.length), skipExtensions)) + .then(function(name) { + return interpolateConditional.call(loader, name, pkgName + '/'); + }); + + // external map reference + // NB deprecate the use of the second argument here -> should be fully global reference + return loader.normalize(mapped + path.substr(mapMatch.length), pkgName + '/'); + } + + function doMap(loader, pkg, pkgName, basePath, mapMatch, path, skipExtensions) { + var mapped = pkg.map[mapMatch]; + + if (typeof mapped == 'string') + return doStringMap(loader, pkg, pkgName, basePath, mapMatch, mapped, path, skipExtensions); + + // we use a special conditional syntax to allow the builder to handle conditional branch points further if (loader.builder) - return pkgName + '#:' + map.substr(2); + return Promise.resolve(pkgName + '/#:' + path); - // environment map + // map object -> conditional map return loader['import'](pkg.map['@env'] || '@system-env', pkgName) .then(function(env) { // first map condition to match is used @@ -2003,52 +2212,76 @@ var value = readMemberExpression(negate ? e.substr(1) : e, env); if (!negate && value || negate && !value) - return mapped[e] + subPath.substr(map.length); + return mapped[e]; } }) .then(function(mapped) { - // no environment match - if (!mapped) - return toPackagePath(loader, pkgName, pkg, basePath, normalized.substr(pkgName.length + 1), sync, isPlugin); - else - return doMap(mapped); + if (mapped) + return doStringMap(loader, pkg, pkgName, basePath, mapMatch, mapped, path, skipExtensions); + + // no environment match -> fallback to original subPath by returning undefined }); } - function createPackageNormalize(normalize, sync) { + // normalizeSync = decanonicalize + package resolution + SystemJSLoader.prototype.normalizeSync = SystemJSLoader.prototype.decanonicalize = SystemJSLoader.prototype.normalize; + + // decanonicalize must JUST handle package defaultExtension: false case when defaultJSExtensions is set + // to be deprecated! + hook('decanonicalize', function(decanonicalize) { + return function(name, parentName) { + var decanonicalized = decanonicalize.call(this, name, parentName); + + if (!this.defaultJSExtensions) + return decanonicalized; + + var pkgName = getPackage(this, decanonicalized); + + var defaultExtension = name[name.length - 1] == '/' ? false : pkgName && this.packages[pkgName].defaultExtension; + + if ((defaultExtension === false || defaultExtension && defaultExtension != '.js') && name.substr(name.length - 3, 3) != '.js' && decanonicalized.substr(decanonicalized.length - 3, 3) == '.js') + decanonicalized = decanonicalized.substr(0, decanonicalized.length - 3); + + return decanonicalized; + }; + }); + + hook('normalizeSync', function(normalizeSync) { return function(name, parentName, isPlugin) { + warn.call(this, 'SystemJS.normalizeSync has been deprecated for SystemJS.decanonicalize.'); + + var loader = this; isPlugin = isPlugin === true; // apply contextual package map first + // (we assume the parent package config has already been loaded) if (parentName) - var parentPackage = getPackage.call(this, parentName) || - this.defaultJSExtensions && parentName.substr(parentName.length - 3, 3) == '.js' && - getPackage.call(this, parentName.substr(0, parentName.length - 3)); + var parentPackageName = getPackage(loader, parentName) || + loader.defaultJSExtensions && parentName.substr(parentName.length - 3, 3) == '.js' && + getPackage(loader, parentName.substr(0, parentName.length - 3)); + var parentPackage = parentPackageName && loader.packages[parentPackageName]; + + // remove any parent basePath from parentName if (parentPackage) { - // remove any parent package base path for normalization - var parentBasePath = getBasePath(this.packages[parentPackage]); - if (parentBasePath && parentName.substr(parentPackage.length + 1, parentBasePath.length) == parentBasePath) - parentName = parentPackage + parentName.substr(parentPackage.length + parentBasePath.length); - - if (name[0] !== '.') { - var parentMap = this.packages[parentPackage].map; - if (parentMap) { - var map = applyMap(parentMap, name); - if (map) { - name = parentMap[map] + name.substr(map.length); - // relative maps are package-relative - if (name[0] === '.') - parentName = parentPackage + '/'; - } - } - } + var parentBasePath = getBasePath(parentPackage); + if (parentBasePath && parentName.substr(parentPackageName.length + 1, parentBasePath.length) == parentBasePath) + parentName = parentPackageName + parentName.substr(parentPackageName.length + parentBasePath.length); + } + + // ignore . since internal maps handled by standard package resolution + if (parentPackage && name[0] != '.') { + var parentMap = parentPackage.map; + var parentMapMatch = parentMap && getMapMatch(parentMap, name); + + if (parentMapMatch && typeof parentMap[parentMapMatch] == 'string') + return doMapSync(loader, parentPackage, parentPackageName, getBasePath(parentPackage), parentMapMatch, name, isPlugin); } - var defaultJSExtension = this.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js'; + var defaultJSExtension = loader.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js'; - // apply map, core, paths - var normalized = normalize.call(this, name, parentName); + // apply map, core, paths, contextual package map + var normalized = normalizeSync.call(loader, name, parentName); // undo defaultJSExtension if (defaultJSExtension && normalized.substr(normalized.length - 3, 3) != '.js') @@ -2056,172 +2289,190 @@ if (defaultJSExtension) normalized = normalized.substr(0, normalized.length - 3); - // if we requested a relative path, and got the package name, remove the trailing '/' to apply main + var pkgConfigMatch = getPackageConfigMatch(loader, normalized); + var pkgName = pkgConfigMatch && pkgConfigMatch.packageName || getPackage(loader, normalized); + + if (!pkgName) + return normalized + (defaultJSExtension ? '.js' : ''); + + var subPath = normalized.substr(pkgName.length + 1); + + // allow for direct package name normalization with trailling "/" (no main) // that is normalize('pkg/') does not apply main, while normalize('./', 'pkg/') does - if (parentPackage && name[0] == '.' && normalized == parentPackage + '/') - normalized = parentPackage; + if (!subPath && normalized.length == pkgName.length + 1 && name[0] != '.') + return pkgName + subPath; + return applyPackageConfigSync(loader, loader.packages[pkgName] || {}, pkgName, subPath, isPlugin); + }; + }); + + hook('normalize', function(normalize) { + return function(name, parentName, isPlugin) { var loader = this; + isPlugin = isPlugin === true; - function packageResolution(normalized, pkgName, pkg) { - // check if we are inside a package - pkgName = pkgName || getPackage.call(loader, normalized); - var pkg = pkg || pkgName && loader.packages[pkgName]; + return Promise.resolve() + .then(function() { + // apply contextual package map first + // (we assume the parent package config has already been loaded) + if (parentName) + var parentPackageName = getPackage(loader, parentName) || + loader.defaultJSExtensions && parentName.substr(parentName.length - 3, 3) == '.js' && + getPackage(loader, parentName.substr(0, parentName.length - 3)); + + var parentPackage = parentPackageName && loader.packages[parentPackageName]; + + // remove any parent basePath from parentName + if (parentPackage) { + var parentBasePath = getBasePath(parentPackage); + if (parentBasePath && parentName.substr(parentPackageName.length + 1, parentBasePath.length) == parentBasePath) + parentName = parentPackageName + parentName.substr(parentPackageName.length + parentBasePath.length); + } - if (pkg) - return applyPackageConfig.call(loader, normalized, pkgName, pkg, sync, isPlugin); - else - return normalized + (defaultJSExtension ? '.js' : ''); - } + // ignore . since internal maps handled by standard package resolution + if (parentPackage && name.substr(0, 2) != './') { + var parentMap = parentPackage.map; + var parentMapMatch = parentMap && getMapMatch(parentMap, name); - // do direct resolution if sync - if (sync) - return packageResolution(normalized); + if (parentMapMatch) + return doMap(loader, parentPackage, parentPackageName, parentBasePath, parentMapMatch, name, isPlugin); + } - // first check if we are in a package - var pkgName = getPackage.call(this, normalized); - var pkg = pkgName && this.packages[pkgName]; + return Promise.resolve(); + }) + .then(function(mapped) { + if (mapped) + return mapped; - // if so, and the package is configured, then do direct resolution - if (pkg && pkg.configured) - return packageResolution(normalized, pkgName, pkg); + var defaultJSExtension = loader.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js'; - var pkgConfigMatch = pkgConfigPathMatch(loader, normalized); + // apply map, core, paths, contextual package map + var normalized = normalize.call(loader, name, parentName); - if (!pkgConfigMatch.pkgName) - return packageResolution(normalized, pkgName, pkg); + // undo defaultJSExtension + if (defaultJSExtension && normalized.substr(normalized.length - 3, 3) != '.js') + defaultJSExtension = false; + if (defaultJSExtension) + normalized = normalized.substr(0, normalized.length - 3); - // if we're within a known package path, then halt on - // further package configuration steps from bundles and config files - return Promise.resolve(getBundleFor(loader, normalized)) + var pkgConfigMatch = getPackageConfigMatch(loader, normalized); + var pkgName = pkgConfigMatch && pkgConfigMatch.packageName || getPackage(loader, normalized); - // ensure that any bundles in this package are triggered, and - // all that are triggered block any further loads in the package - .then(function(bundle) { - if (bundle || pkgBundlePromises[pkgConfigMatch.pkgName]) { - var pkgBundleLoads = pkgBundlePromises[pkgConfigMatch.pkgName] = pkgBundlePromises[pkgConfigMatch.pkgName] || { bundles: [], promise: Promise.resolve() }; - if (bundle && indexOf.call(pkgBundleLoads.bundles, bundle) == -1) { - pkgBundleLoads.bundles.push(bundle); - pkgBundleLoads.promise = Promise.all([pkgBundleLoads.promise, loader.load(bundle)]); - } - return pkgBundleLoads.promise; - } - }) + if (!pkgName) + return Promise.resolve(normalized + (defaultJSExtension ? '.js' : '')); - // having loaded any bundles, attempt a package resolution now - .then(function() { - return packageResolution(normalized, pkgConfigMatch.pkgName); - }) + var pkg = loader.packages[pkgName]; + + // if package is already configured or not a dynamic config package, use existing package config + var isConfigured = pkg && (pkg.configured || !pkgConfigMatch); + return (isConfigured ? Promise.resolve(pkg) : loadPackageConfigPath(loader, pkgName, pkgConfigMatch.configPath)) + .then(function(pkg) { + var subPath = normalized.substr(pkgName.length + 1); - .then(function(curResolution) { - // if that resolution is defined in the registry use it - if (curResolution in loader.defined) - return curResolution; + // allow for direct package name normalization with trailling "/" (no main) + // that is normalize('pkg/') does not apply main, while normalize('./', 'pkg/') does + if (!subPath && normalized.length == pkgName.length + 1 && name[0] != '.') + return Promise.resolve(pkgName + subPath); - // otherwise revert to loading configuration dynamically - return loadPackageConfigPaths(loader, pkgConfigMatch) - .then(function() { - // before doing a final resolution - return packageResolution(normalized); + return applyPackageConfig(loader, pkg, pkgName, subPath, isPlugin); }); }); }; - } - - var pkgBundlePromises = {}; + }); // check if the given normalized name matches a packageConfigPath // if so, loads the config - var packageConfigPathsRegExps = {}; - var pkgConfigPromises = {}; + var packageConfigPaths = {}; + + // data object for quick checks against package paths + function createPkgConfigPathObj(path) { + var lastWildcard = path.lastIndexOf('*'); + var length = Math.max(lastWildcard + 1, path.lastIndexOf('/')); + return { + length: length, + // NB handle regex control character escapes or simply create a test function here + regEx: new RegExp('^(' + path.substr(0, length).replace(/\*/g, '[^\\/]+') + ')(\\/|$)'), + wildcard: lastWildcard != -1 + }; + } - function pkgConfigPathMatch(loader, normalized) { - var pkgPath, pkgConfigPaths = []; + // most specific match wins + function getPackageConfigMatch(loader, normalized) { + var pkgName, exactMatch = false, configPath; for (var i = 0; i < loader.packageConfigPaths.length; i++) { - var p = loader.packageConfigPaths[i]; - var pPkgLen = Math.max(p.lastIndexOf('*') + 1, p.lastIndexOf('/')); - var match = normalized.match(packageConfigPathsRegExps[p] || - (packageConfigPathsRegExps[p] = new RegExp('^(' + p.substr(0, pPkgLen).replace(/\*/g, '[^\\/]+') + ')(\/|$)'))); - if (match && (!pkgPath || pkgPath == match[1])) { - pkgPath = match[1]; - pkgConfigPaths.push(pkgPath + p.substr(pPkgLen)); + var packageConfigPath = loader.packageConfigPaths[i]; + var p = packageConfigPaths[packageConfigPath] || (packageConfigPaths[packageConfigPath] = createPkgConfigPathObj(packageConfigPath)); + if (normalized.length < p.length) + continue; + var match = normalized.match(p.regEx); + if (match && (!pkgName || (!(exactMatch && p.wildcard) && pkgName.length < match[1].length))) { + pkgName = match[1]; + exactMatch = !p.wildcard; + configPath = pkgName + packageConfigPath.substr(p.length); } } + + if (!pkgName) + return; + return { - pkgName: pkgPath, - configPaths: pkgConfigPaths + packageName: pkgName, + configPath: configPath }; } - function loadPackageConfigPaths(loader, pkgConfigMatch) { - var curPkgConfig = loader.packages[pkgConfigMatch.pkgName]; - - if (curPkgConfig && curPkgConfig.configured) - return Promise.resolve(); + function loadPackageConfigPath(loader, pkgName, pkgConfigPath) { + var configLoader = loader.pluginLoader || loader; - return pkgConfigPromises[pkgConfigMatch.pkgName] || ( - pkgConfigPromises[pkgConfigMatch.pkgName] = Promise.resolve() - .then(function() { - var pkgConfigPromises = []; - for (var i = 0; i < pkgConfigMatch.configPaths.length; i++) (function(pkgConfigPath) { - pkgConfigPromises.push(loader['fetch']({ name: pkgConfigPath, address: pkgConfigPath, metadata: {} }) - .then(function(source) { - try { - return JSON.parse(source); - } - catch(e) { - throw new Error('Invalid JSON in package configuration file ' + pkgConfigPath); - } - }) - .then(function(cfg) { - // support "systemjs" prefixing - if (cfg.systemjs) - cfg = cfg.systemjs; - - // remove any non-system properties if generic config file (eg package.json) - for (var p in cfg) { - if (indexOf.call(packageProperties, p) == -1) - delete cfg[p]; - } + // NB remove this when json is default + (configLoader.meta[pkgConfigPath] = configLoader.meta[pkgConfigPath] || {}).format = 'json'; - // support main array - if (cfg.main instanceof Array) - cfg.main = cfg.main[0]; + return configLoader.load(pkgConfigPath) + .then(function() { + pkgConfig = configLoader.get(pkgConfigPath); - // deeply-merge (to first level) config with any existing package config - if (curPkgConfig) - extendMeta(cfg, curPkgConfig); + var cfg = pkgConfig['default']; - // support external depCache - if (cfg.depCache) - for (var d in cfg.depCache) { - if (d.substr(0, 2) == './') - continue; + // support "systemjs" prefixing + if (cfg.systemjs) + cfg = cfg.systemjs; - var dNormalized = loader.normalizeSync(d); - loader.depCache[dNormalized] = (loader.depCache[dNormalized] || []).concat(cfg.depCache[d]); - } + // modules backwards compatibility + if (cfg.modules) { + cfg.meta = cfg.modules; + warn.call(loader, 'Package config file ' + pkgConfigPath + ' is configured with "modules", which is deprecated as it has been renamed to "meta".'); + } - curPkgConfig = loader.packages[pkgConfigMatch.pkgName] = cfg; - })); - })(pkgConfigMatch.configPaths[i]); + // remove any non-system properties if generic config file (eg package.json) + for (var p in cfg) { + if (indexOf.call(packageProperties, p) == -1) + delete cfg[p]; + } - return Promise.all(pkgConfigPromises); - }) - ); - } + // deeply-merge (to first level) config with any existing package config + var pkg = loader.packages[pkgName] = loader.packages[pkgName] || {}; + extendMeta(pkg, cfg, true); - SystemJSLoader.prototype.normalizeSync = SystemJSLoader.prototype.normalize; + // support external depCache + var basePath = getBasePath(pkg); + if (cfg.depCache) { + for (var d in cfg.depCache) { + var dNormalized; - hook('normalizeSync', function(normalize) { - return createPackageNormalize(normalize, true); - }); + if (d.substr(0, 2) == './') + dNormalized = pkgName + '/' + basePath + d.substr(2); + else + dNormalized = coreResolve.call(loader, d); + loader.depCache[dNormalized] = (loader.depCache[dNormalized] || []).concat(cfg.depCache[d]); + } + delete cfg.depCache; + } - hook('normalize', function(normalize) { - return createPackageNormalize(normalize, false); - }); + return pkg; + }); + } - function getMetaMatches(pkgMeta, pkgName, subPath, matchFn) { + function getMetaMatches(pkgMeta, subPath, matchFn) { // wildcard meta var meta = {}; var wildcardIndex; @@ -2237,7 +2488,9 @@ if (module.substr(0, wildcardIndex) == subPath.substr(0, wildcardIndex) && module.substr(wildcardIndex + 1) == subPath.substr(subPath.length - module.length + wildcardIndex + 1)) { - matchFn(module, pkgMeta[dotRel + module], module.split('/').length); + // alow match function to return true for an exit path + if (matchFn(module, pkgMeta[dotRel + module], module.split('/').length)) + return; } } // exact meta @@ -2251,7 +2504,7 @@ var loader = this; return Promise.resolve(locate.call(this, load)) .then(function(address) { - var pkgName = getPackage.call(loader, load.name); + var pkgName = getPackage(loader, load.name); if (pkgName) { var pkg = loader.packages[pkgName]; var basePath = getBasePath(pkg); @@ -2261,22 +2514,12 @@ if (pkg.format) load.metadata.format = load.metadata.format || pkg.format; - // depCache for packages - if (pkg.depCache) { - for (var d in pkg.depCache) { - if (d != './' + subPath) - continue; - - var deps = pkg.depCache[d]; - for (var i = 0; i < deps.length; i++) - loader['import'](deps[i], pkgName + '/'); - } - } - var meta = {}; if (pkg.meta) { var bestDepth = 0; - getMetaMatches(pkg.meta, pkgName, subPath, function(metaPattern, matchMeta, matchDepth) { + + // NB support a main shorthand in meta here? + getMetaMatches(pkg.meta, subPath, function(metaPattern, matchMeta, matchDepth) { if (matchDepth > bestDepth) bestDepth = matchDepth; extendMeta(meta, matchMeta, matchDepth && bestDepth > matchDepth); @@ -2296,7 +2539,8 @@ }; }); - })();/* + })(); + /* * Script tag fetch * * When load.metadata.scriptLoad is true, we load via script tag injection. @@ -2306,29 +2550,89 @@ if (typeof document != 'undefined') var head = document.getElementsByTagName('head')[0]; - // call this functione everytime a wrapper executes var curSystem; - // System clobbering protection for Traceur - SystemJSLoader.prototype.onScriptLoad = function() { - __global.System = curSystem; - }; + + // if doing worker executing, this is set to the load record being executed + var workerLoad = null; + + // interactive mode handling method courtesy RequireJS + var ieEvents = head && (function() { + var s = document.createElement('script'); + var isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]'; + return s.attachEvent && !(s.attachEvent.toString && s.attachEvent.toString().indexOf('[native code') < 0) && !isOpera; + })(); + + // IE interactive-only part + // we store loading scripts array as { script: