From 11064a1134ed8268f46fb2422c9611d4fce49c12 Mon Sep 17 00:00:00 2001 From: Maciek Stasieluk Date: Thu, 24 Sep 2015 23:35:18 +0200 Subject: [PATCH 1/8] new 0.5 version - wip --- CHANGELOG.md | 17 + README.md | 47 +- build-plugin.js | 125 +- extensions/exports.js | 23 + package-level-vars.js | 21 - package.js | 28 +- polyfills/URLPolyfill.js | 67 + polyfills/require.js | 2 + require-polyfill.js | 1 - system-config.js | 119 -- system.js | 161 +++ vendor/system-polyfills.js | 1343 -------------------- vendor/system.js | 2414 +++++++++++++++++++++++------------- 13 files changed, 1911 insertions(+), 2457 deletions(-) create mode 100644 extensions/exports.js delete mode 100644 package-level-vars.js create mode 100644 polyfills/URLPolyfill.js create mode 100644 polyfills/require.js delete mode 100644 require-polyfill.js delete mode 100644 system-config.js create mode 100644 system.js delete mode 100644 vendor/system-polyfills.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f8fc75c..17f5736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,21 @@ +## 0.5 + +### 0.5.0 + +- [BREAKING CHANGE] Meteor 1.2 is required to work +- [BREAKING CHANGE] **All absolute paths to modules must start with `/`** +- [BREAKING CHANGE] `!vars` was replaced by `!exports` +- [BREAKING CHANGE] Remove backward compatibility for deprecated package syntax `author:package` + +- Change in internal module naming (Potentially breaking change) +- Make use of new build plugins API +- Update `SystemJS` to 0.19.0 +- Use Meteor Promise polyfill instead of this shipped with SystemJS +- Improve error handling (stack traces are now easier to read when errors are thrown inside module) +- Update `babel-compiler` to x +- Update `babel-runtime` to x + ## 0.4 ### 0.4.1 diff --git a/README.md b/README.md index 50123ee..1137658 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ - + + # Universe Modules @@ -66,7 +67,7 @@ Just add this package to your app: If you want to see it in action, see our todo example app: -- Source code: https://github.com/vazco/demo_modules +- Source code: https://github.com/vazco/demo_modules (note: this example is little outdated and will be updated soon) - Live demo: http://universe-modules-demo.meteor.com You can also check out great `meteor-react-example` app by [optilude](https://github.com/optilude). @@ -121,29 +122,23 @@ e.g. `client/components/finalComponent`. To load files from packages prefix path with full package name in brackets, e.g: - import Lib from '{author:package}/lib' + import foo from '{author:package}/foo' This syntax will be also introduced in Meteor 1.2 to allow importing less/stylus files between packages. -Packages can also register nice module names in SystemJS. - -An example could be [universe:react-bootstrap](https://atmospherejs.com/universe/react-bootstrap). -Once added to Meteor project, you can write: - - import { Button } from 'bootstrap'; - -and use components from [ReactBootstrap](https://react-bootstrap.github.io/) packaged for Meteor projects. - ### Loading package-level variables -To load exported variables by meteor package, prefix package name like before and add `!vars` on the end(after bracket): + +To load variables exported by Meteor package, add `!exports` after package name in brackets: ``` -import {DDP} from '{ddp}!vars' -import {UniCollection, UniUsers} from '{vazco:universe-collection}!vars' +import {DDP} from '{ddp}!exports' +import {UniCollection, UniUsers} from '{universe:collection}!exports' ``` -be sure that if you use import from another package, you must have dependency to this package. + +Remember that if you want to import from another package, you must have dependency on this package. ### Loading from npm repository + There is extension for this package that adds a possibility of importing from npm repositories. [universe:modules-npm](https://atmospherejs.com/universe/modules-npm) / [Github repo](https://github.com/vazco/meteor-universe-modules-npm/) @@ -169,15 +164,15 @@ You can map alternative name for a module, but remember that you have to provide SystemJS has a packages concept that plays well with Meteor idea of packages. -Example usage from [universe:react-bootstrap](https://atmospherejs.com/universe/react-bootstrap): +Example usage: System.config({ packages: { - bootstrap: { - main: 'main', + myPackage: { + main: 'index', format: 'register', map: { - '.': System.normalizeSync('{universe:react-bootstrap}') + '.': System.normalizeSync('{me:my-package}') } } } @@ -185,9 +180,9 @@ Example usage from [universe:react-bootstrap](https://atmospherejs.com/universe/ This will map: -- `bootstrap` -> `{universe:react-bootstrap}/main` (main is set as a default by... `main` config option :)) -- `bootstrap/foo` -> `{universe:react-bootstrap}/foo` -- `bootstrap/foo/bar` -> `{universe:react-bootstrap}/foo/bar` +- `myPackage` -> `{me:my-package}/index` (index is set as a default by `main` config option) +- `myPackage/foo` -> `{me:my-package}/foo` +- `myPackage/foo/bar` -> `{me:my-package}/foo/bar` etc... @@ -199,7 +194,7 @@ You misspelled import name/path. SystemJS tries to download this file from remot Check if all files are at their location and import paths are OK. -Unfortunately file loading order is still important! +**Unfortunately file loading order is still important!** You need to be sure that all `XXX.import.js` files you want to use are loaded before executing `System.import('XXX')`. This normally isn't a issue as putting them into subdirectory is enough (it doesn't have to be a `lib`!) @@ -211,13 +206,13 @@ You also don't have to worry about this when using `import` inside `*.import.js` ### Roadmap -- [ ] Full tests coverage - [ ] Allow opt-in for other Babel modules (decorators etc) - [ ] Support for lazy loading modules on the client instead of bundling them with main Meteor app +- [ ] Full tests coverage ### Changelog -You can find changelog in CHANGELOG.md file. +You can find changelog and breaking changes in CHANGELOG.md file. ### Issues diff --git a/build-plugin.js b/build-plugin.js index d99ddcd..732094b 100644 --- a/build-plugin.js +++ b/build-plugin.js @@ -1,62 +1,89 @@ -var handler = function (compileStep) { - var source = compileStep.read().toString('utf8'); - var outputFile = compileStep.inputPath + '.js'; +class UniverseBabelCompiler extends BabelCompiler { - var path = compileStep.inputPath.split('.import.'); - var moduleId = path[0]; - - if(process.platform === 'win32') { - // windows support, replace backslashes with forward slashes - moduleId = moduleId.replace(/\\/g, '/'); + processFilesForTarget (inputFiles) { + inputFiles.forEach(this.processFile); } - if (compileStep.packageName) { - // inside package, prefix module - moduleId = '{' + compileStep.packageName + '}/' + moduleId; - } + processFile (inputFile) { - var extraWhitelist = [ - 'es6.modules', - // @todo make this configurable: - 'es7.decorators' - ]; - if (path[1] === 'jsx') { - // add support for React in *.import.jsx files - extraWhitelist.push('react'); - } + // Full contents of the file as a string + const source = inputFile.getContentsAsString(); + + // Relative path of file to the package or app root directory (always uses forward slashes) + const filePath = inputFile.getPathInPackage(); + + // Options from api.addFile + const fileOptions = inputFile.getFileOptions(); + + // Name of the package or null if the file is not in a package. + const packageName = inputFile.getPackageName(); + + // moduleId - Module name (full patch without extension) + // ext - File extension (either js or jsx) + let [moduleId, ext] = filePath.split('.import.'); + + // prefix module name accordingly + if (packageName) { + // inside package + moduleId = '/_modules_/packages/' + packageName.replace(':', '/') + '/' + moduleId; + } else { + // inside main app + moduleId = '/_modules_/app/' + moduleId; + } - try { - var result = Babel.transformMeteor(source, { + const extraWhitelist = [ + 'es6.modules', + // @todo make this configurable: + 'es7.decorators' + ]; + + if (ext === 'jsx') { + // add support for React in *.import.jsx files + extraWhitelist.push('react'); + } + + const babelDefaultOptions = Babel.getDefaultOptions(this.extraFeatures); + + const babelOptions = _({}).extend(babelDefaultOptions, { sourceMap: true, - filename: compileStep.pathForSourceMap, - sourceMapName: compileStep.pathForSourceMap, - extraWhitelist: extraWhitelist, + filename: filePath, + sourceFileName: '/' + filePath, + sourceMapName: '/' + filePath + '.map', modules: 'system', moduleIds: true, - moduleId: moduleId + moduleId, + whitelist: _.union(babelDefaultOptions.whitelist, extraWhitelist) }); - } catch (e) { - if (e.loc) { - // Babel error - compileStep.error({ - message: e.message, - sourcePath: compileStep.inputPath, - line: e.loc.line, - column: e.loc.column - }); - return; - } else { + + try { + var result = Babel.compile(source, babelOptions); + } catch (e) { + if (e.loc) { + inputFile.error({ + message: e.message, + sourcePath: filePath, + line: e.loc.line, + column: e.loc.column + }); + return; + } throw e; } - } - compileStep.addJavaScript({ - path: outputFile, - sourcePath: compileStep.inputPath, - data: result.code, - sourceMap: JSON.stringify(result.map) - }); -}; + inputFile.addJavaScript({ + sourcePath: filePath, + path: filePath, + data: result.code, + hash: result.hash, + sourceMap: result.map, + bare: !!fileOptions.bare + }); + } +} -Plugin.registerSourceHandler('import.js', handler); -Plugin.registerSourceHandler('import.jsx', handler); +Plugin.registerCompiler({ + extensions: ['import.js', 'import.jsx'], + filenames: [] +}, function () { + return new UniverseBabelCompiler(); +}); \ No newline at end of file diff --git a/extensions/exports.js b/extensions/exports.js new file mode 100644 index 0000000..9574a6d --- /dev/null +++ b/extensions/exports.js @@ -0,0 +1,23 @@ +/** + * Following script allows to import variables exported from packages + * @example import {UniCollection, UniUsers} from '{universe:collection}!exports' + * import {DDP} from '{ddp}!exports' + */ +var packageRegex = /^\/modules\/packages\/([\w-]*?)\/([\w-]+)!exports$/; + +const {locate} = System; + +System.locate = function (data) { + console.info('locate', data); + if (packageRegex.test(data.name)) { + console.warn('inside', data.name); + let packageName = data.name.replace(packageRegex, '$1:$2'); + if (Package[packageName]) { + //Getting access for exported variables by meteor package + System.registerDynamic(data, [], true, function (require, exports, module) { + module.exports = Package[packageName]; + }); + } + } + return locate.apply(this, arguments); +}; \ No newline at end of file diff --git a/package-level-vars.js b/package-level-vars.js deleted file mode 100644 index 6611c2f..0000000 --- a/package-level-vars.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Following script, adds possibility of import of exported variables from packages - * @example import {UniCollection, UniUsers} from '{vazco:universe-collection}!vars' - * import {DDP} from '{ddp}!vars' - */ -var packageRegex = /^{((?:[\w-]*?):?(?:[\w-]+))}!vars$/; -var _normalize = System.normalize; - -System.normalize = function (name) { - var packageName; - if (packageRegex.test(name)) { - packageName = name.replace(packageRegex, '$1'); - if (Package[packageName]) { - //Getting access for exported variables by meteor package - System.registerDynamic(name, [], true, function (require, exports, module) { - module.exports = Package[packageName]; - }); - } - } - return _normalize.apply(this, arguments); -}; \ No newline at end of file diff --git a/package.js b/package.js index 7d4a8d2..b07851c 100644 --- a/package.js +++ b/package.js @@ -1,31 +1,41 @@ Package.describe({ name: 'universe:modules', - version: '0.4.2', + version: '0.5.0-alpha', summary: 'Use ES6 / ES2015 modules in Meteor with SystemJS!', git: 'https://github.com/vazco/universe-modules', documentation: 'README.md' }); Package.registerBuildPlugin({ - name: 'UniverseModulesBuilder', - use: ['babel-compiler@5.8.3_1'], + name: 'compile-universe-modules', + use: ['babel-compiler', 'ecmascript', 'underscore'], sources: ['build-plugin.js'] }); Package.onUse(function (api) { + api.versionsFrom('1.2.0.1'); + + // Write ES2015 code inside package itself + api.use('ecmascript'); + + // Use Meteor 1.2 build plugin + api.use('isobuild:compiler-plugin@1.0.0'); + + // Use Meteor-aware Promise polyfill + api.use('promise'); // Babel runtime is required for some compiled syntax to work - api.imply('babel-runtime@0.1.2'); + api.imply('babel-runtime'); // We need `require` for System.js to run on the server - api.addFiles('require-polyfill.js', 'server'); + api.addFiles('polyfills/require.js', 'server'); // Load SystemJS api.addFiles([ - 'vendor/system-polyfills.js', - 'vendor/system.js', - 'system-config.js', - 'package-level-vars.js' + 'polyfills/URLPolyfill.js', + 'vendor/system.js', // @todo: Use system as a dependency + 'system.js', + 'extensions/exports.js' ]); }); diff --git a/polyfills/URLPolyfill.js b/polyfills/URLPolyfill.js new file mode 100644 index 0000000..b346d44 --- /dev/null +++ b/polyfills/URLPolyfill.js @@ -0,0 +1,67 @@ +// from https://gist.github.com/Yaffle/1088850 +(function (global) { + "use strict"; + + function URLUtils (url, baseURL) { + var m = String(url).replace(/^\s+|\s+$/g, "").match(/^([^:\/?#]+:)?(?:\/\/(?:([^:@\/?#]*)(?::([^:@\/?#]*))?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/); + if (!m) { + throw new RangeError(); + } + var protocol = m[1] || ""; + var username = m[2] || ""; + var password = m[3] || ""; + var host = m[4] || ""; + var hostname = m[5] || ""; + var port = m[6] || ""; + var pathname = m[7] || ""; + var search = m[8] || ""; + var hash = m[9] || ""; + if (baseURL !== undefined) { + var base = new URLUtils(baseURL); + var flag = protocol === "" && host === "" && username === ""; + if (flag && pathname === "" && search === "") { + search = base.search; + } + if (flag && pathname.charAt(0) !== "/") { + pathname = (pathname !== "" ? (((base.host !== "" || base.username !== "") && base.pathname === "" ? "/" : "") + base.pathname.slice(0, base.pathname.lastIndexOf("/") + 1) + pathname) : base.pathname); + } + // dot segments removal + var output = []; + pathname.replace(/^(\.\.?(\/|$))+/, "") + .replace(/\/(\.(\/|$))+/g, "/") + .replace(/\/\.\.$/, "/../") + .replace(/\/?[^\/]*/g, function (p) { + if (p === "/..") { + output.pop(); + } else { + output.push(p); + } + }); + pathname = output.join("").replace(/^\//, pathname.charAt(0) === "/" ? "/" : ""); + if (flag) { + port = base.port; + hostname = base.hostname; + host = base.host; + password = base.password; + username = base.username; + } + if (protocol === "") { + protocol = base.protocol; + } + } + this.origin = protocol + (protocol !== "" || host !== "" ? "//" : "") + host; + this.href = protocol + (protocol !== "" || host !== "" ? "//" : "") + (username !== "" ? username + (password !== "" ? ":" + password : "") + "@" : "") + host + pathname + search + hash; + this.protocol = protocol; + this.username = username; + this.password = password; + this.host = host; + this.hostname = hostname; + this.port = port; + this.pathname = pathname; + this.search = search; + this.hash = hash; + } + + global.URLPolyfill = URLUtils; + +}(this)); \ No newline at end of file diff --git a/polyfills/require.js b/polyfills/require.js new file mode 100644 index 0000000..3a05095 --- /dev/null +++ b/polyfills/require.js @@ -0,0 +1,2 @@ +// Package level variable required for SystemJS +require = Npm.require; \ No newline at end of file diff --git a/require-polyfill.js b/require-polyfill.js deleted file mode 100644 index e6960ed..0000000 --- a/require-polyfill.js +++ /dev/null @@ -1 +0,0 @@ -require = Npm.require; \ No newline at end of file diff --git a/system-config.js b/system-config.js deleted file mode 100644 index eca54ff..0000000 --- a/system-config.js +++ /dev/null @@ -1,119 +0,0 @@ -// Backup original SystemJS methods -var _System = { - normalize: System.normalize, - normalizeSync: System.normalizeSync, - locate: System.locate, - fetch: System.fetch, - translate: System.translate, - instantiate: System.instantiate -}; - -// Make `register` the default module format -System.config({ - meta: { - '*': { - format: 'register' - } - } -}); - -// Regular expressions for Meteor package import syntax -var appRegex = /^\{}\//; -var packageRegex = /^{([\w-]*?):?([\w-]+)}/; -var packageRegexBC = /^([\w-]+):([\w-]+)/; - -/** - * Convert Meteor package syntax to System.normalize friendly string. - * The `__author_package/foo` syntax in an internal implementation that is subject to change. - * You should never rely on it! Instead pass all your module names through System.normalizeSync - * @param {string} name - unnormalized module name with Meteor package syntax - * @returns {string} - unnormalized module name without Meteor package syntax - */ -var normalizeMeteorPackageName = function (name) { - name = name - .replace(appRegex, '') // {}/foo -> foo - .replace(packageRegex, '__$1_$2'); // {author:package}/foo -> __author_package/foo - - if (packageRegexBC.test(name)) { - // provide temporary backward compatibility for versions < 0.4 package syntax - console.warn([ - '[Universe Modules]', - 'You are using deprecated syntax for importing modules from a package.', - 'Instead of', name, 'you should use', name.replace(packageRegexBC, '{$1:$2}') - ].join(' ')); - return name.replace(packageRegexBC, '__$1_$2'); // author:package/foo -> __author_package/foo - } - return name; -}; - -/* - * name: the unnormalized module name - * parentName: the canonical module name for the requesting module - * parentAddress: the address of the requesting module - */ -System.normalize = function (name, parentName, parentAddress) { - - // Allow foomodule.import syntax in import name (TypeScript support) - if (name.slice(-7) === '.import') { - name = name.slice(0, -7); - } - - // Load original normalize - return _System.normalize.call(this, normalizeMeteorPackageName(name), parentName, parentAddress); -}; - -/* - * name: the unnormalized module name - * parentName: the canonical module name for the requesting module - */ -System.normalizeSync = function (name, parentName) { - return _System.normalizeSync.call(this, normalizeMeteorPackageName(name), parentName); -}; - -/* - * load.name the canonical module name - * load.metadata a metadata object that can be used to store - * derived metadata for reference in other hooks - */ -//System.locate = function (load) { -// return _System.locate.call(this, load); -//}; - -/* - * load.name: the canonical module name - * load.address: the URL returned from locate - * load.metadata: the same metadata object by reference, which - * can be modified - */ -System.fetch = function (load) { - var promise = _System.fetch.call(this, load); - - if (!promise) { - // not really a promise - return promise; - } - - // Add our warning - return promise.catch(function () { - console.warn('[Universe Modules]: Module ' + load.name.replace(System.baseURL, '') + ' does not exist! You will probably see other errors in the console because of that.'); - - }); -}; - -/* - * load.name - * load.address - * load.metadata - * load.source: the fetched source - */ -//System.translate = function (load) { -// return _System.translate.call(this, load); -//}; - -/* - * load identical to previous hooks, but load.source - * is now the translated source - */ -//System.instantiate = function (load) { -// return _System.instantiate.call(this, load); -//}; \ No newline at end of file diff --git a/system.js b/system.js new file mode 100644 index 0000000..d49190a --- /dev/null +++ b/system.js @@ -0,0 +1,161 @@ +// Keep reference to original SystemJS methods +const { + normalize, + normalizeSync, + locate, + fetch, + translate, + instantiate, + 'import': importFn + } = System; + +// Make `register` the default module format +System.config({ + meta: { + '*': { + format: 'register' + } + } +}); + +// Regular expressions for Meteor package import syntax +const appRegex = /^\{}\//; +const packageRegex = /^{([\w-]*?):?([\w-]+)}/; +const normalizedRegex = /^\/_modules_\//; + +/** + * Convert Meteor-like friendly module name to real module name. + * The `/_modules_/packages/abc/xyz/` syntax in an internal implementation that is subject to change! + * You should never rely on it! Instead pass all your module names through System.normalizeSync + * @param {string} name - friendly module name with Meteor package syntax + * @returns {string} - real module name + */ +const convertMeteorModuleName = function convertMeteorModuleName (name, parentName) { + if (name.charAt(0) === '/') { + // absolute path + + if (normalizedRegex.test(name)) { + // already normalized name, leave it as is + return name; + } + + if (parentName) { + + let [, dir, type, author, packageName] = parentName.split('/'); + + if (dir !== '_modules_') { + // invalid parent name, not our module! + throw new Error(`[Universe Modules]: Invalid parent while loading module from absolute path: ${name} - ${parentName}`); + } + if (type === 'app') { + // inside app + return '/_modules_/app' + name; + } else if (type === 'packages') { + // inside a package + return `/_modules_/packages/${author}/${packageName}${name}`; + } else { + // invalid type + throw new Error(`[Universe Modules]: Cannot determine parent when loading module from absolute path: ${name} - ${parentName}`); + } + + } else { + // no parent provided, treat it as an app module, default behaviour + return '/_modules_/app' + name; + + } + + } else if (name.charAt(0) === '{') { + // Meteor syntax + + return name + // main app file + .replace(appRegex, '/_modules_/app/') // {}/foo -> /_modules_/app/foo + // package file + .replace(packageRegex, '/_modules_/packages/$1/$2'); // {author:package}/foo -> /_modules_/packages/author/package/foo + + } else { + // Other syntax, maybe relative path, leave it as is + return name; + } +}; + +/* + * name: the unnormalized module name + * parentName: the canonical module name for the requesting module + * parentAddress: the address of the requesting module + */ +System.normalize = function (name, parentName, parentAddress) { + + // Allow foomodule.import syntax in import name (TypeScript support) + if (name.slice(-7) === '.import') { + name = name.slice(0, -7); + } + + console.log('normalize', name, parentName, '->', convertMeteorModuleName(name, parentName)); + + // Load original normalize + return normalize.call(this, convertMeteorModuleName(name, parentName), parentName, parentAddress); +}; + +/* + * name: the unnormalized module name + * parentName: the canonical module name for the requesting module + */ +System.normalizeSync = function (name, parentName) { + console.log('normalizeSync', name, parentName, '->', convertMeteorModuleName(name, parentName)); + return normalizeSync.call(this, convertMeteorModuleName(name, parentName), parentName); +}; + +/* + * load.name the canonical module name + * load.metadata a metadata object that can be used to store + * derived metadata for reference in other hooks + */ +//System.locate = function (load) { +// return _System.locate.call(this, load); +//}; + +/* + * load.name: the canonical module name + * load.address: the URL returned from locate + * load.metadata: the same metadata object by reference, which + * can be modified + */ +System.fetch = function (load) { + var promise = fetch.call(this, load); + + if (!promise) { + // not really a promise + return promise; + } + + // Add our warning + return promise.catch(function () { + console.warn('[Universe Modules]: Module ' + load.name.replace(System.baseURL, '') + ' does not exist! You will probably see other errors in the console because of that.'); + }); +}; + +/* + * load.name + * load.address + * load.metadata + * load.source: the fetched source + */ +//System.translate = function (load) { +// return translate.call(this, load); +//}; + +/* + * load identical to previous hooks, but load.source + * is now the translated source + */ +//System.instantiate = function (load) { +// return instantiate.call(this, load); +//}; + +/* + * Wrap system.import and add default error reporting + */ +System.import = function (...args) { + return importFn.call(this, ...args).catch(console.error.bind(console)); +}; \ No newline at end of file diff --git a/vendor/system-polyfills.js b/vendor/system-polyfills.js deleted file mode 100644 index 1a4c128..0000000 --- a/vendor/system-polyfills.js +++ /dev/null @@ -1,1343 +0,0 @@ -/* - * SystemJS Polyfills for URL and Promise providing IE8+ Support - */ -// from https://gist.github.com/Yaffle/1088850 -(function(global) { - function URLPolyfill(url, baseURL) { - if (typeof url != 'string') - throw new TypeError('URL must be a string'); - var m = String(url).replace(/^\s+|\s+$/g, "").match(/^([^:\/?#]+:)?(?:\/\/(?:([^:@\/?#]*)(?::([^:@\/?#]*))?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/); - if (!m) { - throw new RangeError(); - } - var protocol = m[1] || ""; - var username = m[2] || ""; - var password = m[3] || ""; - var host = m[4] || ""; - var hostname = m[5] || ""; - var port = m[6] || ""; - var pathname = m[7] || ""; - var search = m[8] || ""; - var hash = m[9] || ""; - if (baseURL !== undefined) { - var base = baseURL instanceof URLPolyfill ? baseURL : new URLPolyfill(baseURL); - var flag = protocol === "" && host === "" && username === ""; - if (flag && pathname === "" && search === "") { - search = base.search; - } - if (flag && pathname.charAt(0) !== "/") { - pathname = (pathname !== "" ? (((base.host !== "" || base.username !== "") && base.pathname === "" ? "/" : "") + base.pathname.slice(0, base.pathname.lastIndexOf("/") + 1) + pathname) : base.pathname); - } - // dot segments removal - var output = []; - pathname.replace(/^(\.\.?(\/|$))+/, "") - .replace(/\/(\.(\/|$))+/g, "/") - .replace(/\/\.\.$/, "/../") - .replace(/\/?[^\/]*/g, function (p) { - if (p === "/..") { - output.pop(); - } else { - output.push(p); - } - }); - pathname = output.join("").replace(/^\//, pathname.charAt(0) === "/" ? "/" : ""); - if (flag) { - port = base.port; - hostname = base.hostname; - host = base.host; - password = base.password; - username = base.username; - } - if (protocol === "") { - protocol = base.protocol; - } - } - - // convert windows file URLs to use / - if (protocol == 'file:') - pathname = pathname.replace(/\\/g, '/'); - - this.origin = protocol + (protocol !== "" || host !== "" ? "//" : "") + host; - this.href = protocol + (protocol !== "" || host !== "" ? "//" : "") + (username !== "" ? username + (password !== "" ? ":" + password : "") + "@" : "") + host + pathname + search + hash; - this.protocol = protocol; - this.username = username; - this.password = password; - this.host = host; - this.hostname = hostname; - this.port = port; - this.pathname = pathname; - this.search = search; - this.hash = hash; - } - global.URLPolyfill = URLPolyfill; -})(typeof self != 'undefined' ? self : global);!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.Promise=e():"undefined"!=typeof global?global.Promise=e():"undefined"!=typeof self&&(self.Promise=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0) { - reported.splice(i, 1); - logInfo('Handled previous rejection [' + r.id + '] ' + format.formatObject(r.value)); - } - } - - function enqueue(f, x) { - tasks.push(f, x); - if(running === null) { - running = setTimer(flush, 0); - } - } - - function flush() { - running = null; - while(tasks.length > 0) { - tasks.shift()(tasks.shift()); - } - } - - return Promise; - }; - - function throwit(e) { - throw e; - } - - function noop() {} - - }); - }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); - -},{"../env":5,"../format":6}],5:[function(require,module,exports){ - /** @license MIT License (c) copyright 2010-2014 original author or authors */ - /** @author Brian Cavalier */ - /** @author John Hann */ - - /*global process,document,setTimeout,clearTimeout,MutationObserver,WebKitMutationObserver*/ - (function(define) { 'use strict'; - define(function(require) { - /*jshint maxcomplexity:6*/ - - // Sniff "best" async scheduling option - // Prefer process.nextTick or MutationObserver, then check for - // setTimeout, and finally vertx, since its the only env that doesn't - // have setTimeout - - var MutationObs; - var capturedSetTimeout = typeof setTimeout !== 'undefined' && setTimeout; - - // Default env - var setTimer = function(f, ms) { return setTimeout(f, ms); }; - var clearTimer = function(t) { return clearTimeout(t); }; - var asap = function (f) { return capturedSetTimeout(f, 0); }; - - // Detect specific env - if (isNode()) { // Node - asap = function (f) { return process.nextTick(f); }; - - } else if (MutationObs = hasMutationObserver()) { // Modern browser - asap = initMutationObserver(MutationObs); - - } else if (!capturedSetTimeout) { // vert.x - var vertxRequire = require; - var vertx = vertxRequire('vertx'); - setTimer = function (f, ms) { return vertx.setTimer(ms, f); }; - clearTimer = vertx.cancelTimer; - asap = vertx.runOnLoop || vertx.runOnContext; - } - - return { - setTimer: setTimer, - clearTimer: clearTimer, - asap: asap - }; - - function isNode () { - return typeof process !== 'undefined' && process !== null && - typeof process.nextTick === 'function'; - } - - function hasMutationObserver () { - return (typeof MutationObserver === 'function' && MutationObserver) || - (typeof WebKitMutationObserver === 'function' && WebKitMutationObserver); - } - - function initMutationObserver(MutationObserver) { - var scheduled; - var node = document.createTextNode(''); - var o = new MutationObserver(run); - o.observe(node, { characterData: true }); - - function run() { - var f = scheduled; - scheduled = void 0; - f(); - } - - var i = 0; - return function (f) { - scheduled = f; - node.data = (i ^= 1); - }; - } - }); - }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); - -},{}],6:[function(require,module,exports){ - /** @license MIT License (c) copyright 2010-2014 original author or authors */ - /** @author Brian Cavalier */ - /** @author John Hann */ - - (function(define) { 'use strict'; - define(function() { - - return { - formatError: formatError, - formatObject: formatObject, - tryStringify: tryStringify - }; - - /** - * Format an error into a string. If e is an Error and has a stack property, - * it's returned. Otherwise, e is formatted using formatObject, with a - * warning added about e not being a proper Error. - * @param {*} e - * @returns {String} formatted string, suitable for output to developers - */ - function formatError(e) { - var s = typeof e === 'object' && e !== null && e.stack ? e.stack : formatObject(e); - return e instanceof Error ? s : s + ' (WARNING: non-Error used)'; - } - - /** - * Format an object, detecting "plain" objects and running them through - * JSON.stringify if possible. - * @param {Object} o - * @returns {string} - */ - function formatObject(o) { - var s = String(o); - if(s === '[object Object]' && typeof JSON !== 'undefined') { - s = tryStringify(o, s); - } - return s; - } - - /** - * Try to return the result of JSON.stringify(x). If that fails, return - * defaultValue - * @param {*} x - * @param {*} defaultValue - * @returns {String|*} JSON.stringify(x) or defaultValue - */ - function tryStringify(x, defaultValue) { - try { - return JSON.stringify(x); - } catch(e) { - return defaultValue; - } - } - - }); - }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); - -},{}],7:[function(require,module,exports){ - /** @license MIT License (c) copyright 2010-2014 original author or authors */ - /** @author Brian Cavalier */ - /** @author John Hann */ - - (function(define) { 'use strict'; - define(function() { - - return function makePromise(environment) { - - var tasks = environment.scheduler; - var emitRejection = initEmitRejection(); - - var objectCreate = Object.create || - function(proto) { - function Child() {} - Child.prototype = proto; - return new Child(); - }; - - /** - * Create a promise whose fate is determined by resolver - * @constructor - * @returns {Promise} promise - * @name Promise - */ - function Promise(resolver, handler) { - this._handler = resolver === Handler ? handler : init(resolver); - } - - /** - * Run the supplied resolver - * @param resolver - * @returns {Pending} - */ - function init(resolver) { - var handler = new Pending(); - - try { - resolver(promiseResolve, promiseReject, promiseNotify); - } catch (e) { - promiseReject(e); - } - - return handler; - - /** - * Transition from pre-resolution state to post-resolution state, notifying - * all listeners of the ultimate fulfillment or rejection - * @param {*} x resolution value - */ - function promiseResolve (x) { - handler.resolve(x); - } - /** - * Reject this promise with reason, which will be used verbatim - * @param {Error|*} reason rejection reason, strongly suggested - * to be an Error type - */ - function promiseReject (reason) { - handler.reject(reason); - } - - /** - * @deprecated - * Issue a progress event, notifying all progress listeners - * @param {*} x progress event payload to pass to all listeners - */ - function promiseNotify (x) { - handler.notify(x); - } - } - - // Creation - - Promise.resolve = resolve; - Promise.reject = reject; - Promise.never = never; - - Promise._defer = defer; - Promise._handler = getHandler; - - /** - * Returns a trusted promise. If x is already a trusted promise, it is - * returned, otherwise returns a new trusted Promise which follows x. - * @param {*} x - * @return {Promise} promise - */ - function resolve(x) { - return isPromise(x) ? x - : new Promise(Handler, new Async(getHandler(x))); - } - - /** - * Return a reject promise with x as its reason (x is used verbatim) - * @param {*} x - * @returns {Promise} rejected promise - */ - function reject(x) { - return new Promise(Handler, new Async(new Rejected(x))); - } - - /** - * Return a promise that remains pending forever - * @returns {Promise} forever-pending promise. - */ - function never() { - return foreverPendingPromise; // Should be frozen - } - - /** - * Creates an internal {promise, resolver} pair - * @private - * @returns {Promise} - */ - function defer() { - return new Promise(Handler, new Pending()); - } - - // Transformation and flow control - - /** - * Transform this promise's fulfillment value, returning a new Promise - * for the transformed result. If the promise cannot be fulfilled, onRejected - * is called with the reason. onProgress *may* be called with updates toward - * this promise's fulfillment. - * @param {function=} onFulfilled fulfillment handler - * @param {function=} onRejected rejection handler - * @param {function=} onProgress @deprecated progress handler - * @return {Promise} new promise - */ - Promise.prototype.then = function(onFulfilled, onRejected, onProgress) { - var parent = this._handler; - var state = parent.join().state(); - - if ((typeof onFulfilled !== 'function' && state > 0) || - (typeof onRejected !== 'function' && state < 0)) { - // Short circuit: value will not change, simply share handler - return new this.constructor(Handler, parent); - } - - var p = this._beget(); - var child = p._handler; - - parent.chain(child, parent.receiver, onFulfilled, onRejected, onProgress); - - return p; - }; - - /** - * If this promise cannot be fulfilled due to an error, call onRejected to - * handle the error. Shortcut for .then(undefined, onRejected) - * @param {function?} onRejected - * @return {Promise} - */ - Promise.prototype['catch'] = function(onRejected) { - return this.then(void 0, onRejected); - }; - - /** - * Creates a new, pending promise of the same type as this promise - * @private - * @returns {Promise} - */ - Promise.prototype._beget = function() { - return begetFrom(this._handler, this.constructor); - }; - - function begetFrom(parent, Promise) { - var child = new Pending(parent.receiver, parent.join().context); - return new Promise(Handler, child); - } - - // Array combinators - - Promise.all = all; - Promise.race = race; - Promise._traverse = traverse; - - /** - * Return a promise that will fulfill when all promises in the - * input array have fulfilled, or will reject when one of the - * promises rejects. - * @param {array} promises array of promises - * @returns {Promise} promise for array of fulfillment values - */ - function all(promises) { - return traverseWith(snd, null, promises); - } - - /** - * Array> -> Promise> - * @private - * @param {function} f function to apply to each promise's value - * @param {Array} promises array of promises - * @returns {Promise} promise for transformed values - */ - function traverse(f, promises) { - return traverseWith(tryCatch2, f, promises); - } - - function traverseWith(tryMap, f, promises) { - var handler = typeof f === 'function' ? mapAt : settleAt; - - var resolver = new Pending(); - var pending = promises.length >>> 0; - var results = new Array(pending); - - for (var i = 0, x; i < promises.length && !resolver.resolved; ++i) { - x = promises[i]; - - if (x === void 0 && !(i in promises)) { - --pending; - continue; - } - - traverseAt(promises, handler, i, x, resolver); - } - - if(pending === 0) { - resolver.become(new Fulfilled(results)); - } - - return new Promise(Handler, resolver); - - function mapAt(i, x, resolver) { - if(!resolver.resolved) { - traverseAt(promises, settleAt, i, tryMap(f, x, i), resolver); - } - } - - function settleAt(i, x, resolver) { - results[i] = x; - if(--pending === 0) { - resolver.become(new Fulfilled(results)); - } - } - } - - function traverseAt(promises, handler, i, x, resolver) { - if (maybeThenable(x)) { - var h = getHandlerMaybeThenable(x); - var s = h.state(); - - if (s === 0) { - h.fold(handler, i, void 0, resolver); - } else if (s > 0) { - handler(i, h.value, resolver); - } else { - resolver.become(h); - visitRemaining(promises, i+1, h); - } - } else { - handler(i, x, resolver); - } - } - - Promise._visitRemaining = visitRemaining; - function visitRemaining(promises, start, handler) { - for(var i=start; i use that existing linkset + if (existingLoad.linkSets.length) + return existingLoad.linkSets[0].done.then(function() { + resolve(existingLoad); + }); } } - var load = createLoad(name); + var load = existingLoad || createLoad(name); load.metadata = stepState.moduleMetadata; @@ -513,6 +521,9 @@ } // 15.2.5.2.2 function addLoadToLinkSet(linkSet, load) { + if (load.status == 'failed') + return; + console.assert(load.status == 'loading' || load.status == 'loaded', 'loading or loaded on link set'); for (var i = 0, l = linkSet.loads.length; i < l; i++) @@ -530,6 +541,9 @@ var loader = linkSet.loader; for (var i = 0, l = load.dependencies.length; i < l; i++) { + if (!load.dependencies[i]) + continue; + var name = load.dependencies[i].value; if (loader.modules[name]) @@ -613,17 +627,30 @@ // 15.2.5.2.4 function linkSetFailed(linkSet, load, exc) { var loader = linkSet.loader; + var requests; - if (load) { - if (load && linkSet.loads[0].name != load.name) - exc = addToError(exc, 'Error loading ' + load.name + ' from ' + linkSet.loads[0].name); - - if (load) - exc = addToError(exc, 'Error loading ' + load.name); - } - else { - exc = addToError(exc, 'Error linking ' + linkSet.loads[0].name); - } + checkError: + if (load) { + if (linkSet.loads[0].name == load.name) { + exc = addToError(exc, 'Error loading ' + load.name); + } + else { + for (var i = 0; i < linkSet.loads.length; i++) { + var pLoad = linkSet.loads[i]; + for (var j = 0; j < pLoad.dependencies.length; j++) { + var dep = pLoad.dependencies[j]; + if (dep.value == load.name) { + exc = addToError(exc, 'Error loading ' + load.name + ' as "' + dep.key + '" from ' + pLoad.name); + break checkError; + } + } + } + exc = addToError(exc, 'Error loading ' + load.name + ' from ' + linkSet.loads[0].name); + } + } + else { + exc = addToError(exc, 'Error linking ' + linkSet.loads[0].name); + } var loads = linkSet.loads.concat([]); @@ -779,11 +806,17 @@ // 26.3.3.9 keys not implemented // 26.3.3.10 load: function(name, options) { - if (this._loader.modules[name]) { - doEnsureEvaluated(this._loader.modules[name], [], this._loader); - return Promise.resolve(this._loader.modules[name].module); + var loader = this._loader; + if (loader.modules[name]) { + doEnsureEvaluated(loader.modules[name], [], loader); + return Promise.resolve(loader.modules[name].module); } - return this._loader.importPromises[name] || createImportPromise(this, name, loadModule(this._loader, name, {})); + return loader.importPromises[name] || createImportPromise(this, name, + loadModule(loader, name, {}) + .then(function(load) { + delete loader.importPromises[name]; + return evaluateLoadedModule(loader, load); + })); }, // 26.3.3.11 module: function(source, options) { @@ -954,7 +987,7 @@ } var outPath = paths[pathMatch] || name; - if (wildcard) + if (typeof wildcard == 'string') outPath = outPath.replace('*', wildcard); return outPath; @@ -966,14 +999,7 @@ SystemLoader.prototype = new LoaderProto(); var fetchTextFromURL; if (typeof XMLHttpRequest != 'undefined') { - fetchTextFromURL = function(url, fulfill, reject) { - // percent encode just '#' in urls - // according to https://github.com/jorendorff/js-loaders/blob/master/browser-loader.js#L238 - // we should encode everything, but it breaks for servers that don't expect it - // like in (https://github.com/systemjs/systemjs/issues/168) - if (isBrowser) - url = url.replace(/#/g, '%23'); - + fetchTextFromURL = function(url, authorization, fulfill, reject) { var xhr = new XMLHttpRequest(); var sameDomain = true; var doTimeout = false; @@ -999,7 +1025,7 @@ fulfill(xhr.responseText); } function error() { - reject(xhr.statusText + ': ' + url || 'XHR error'); + reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url)); } xhr.onreadystatechange = function () { @@ -1013,6 +1039,16 @@ }; xhr.open("GET", url, true); + if (xhr.setRequestHeader) { + xhr.setRequestHeader('Accept', 'application/x-es-module, */*'); + // can set "authorization: true" to enable withCredentials only + if (authorization) { + if (typeof authorization == 'string') + xhr.setRequestHeader('Authorization', authorization); + xhr.withCredentials = true; + } + } + if (doTimeout) setTimeout(function() { xhr.send(); @@ -1023,17 +1059,18 @@ } else if (typeof require != 'undefined') { var fs; - fetchTextFromURL = function(url, fulfill, reject) { + fetchTextFromURL = function(url, authorization, fulfill, reject) { if (url.substr(0, 8) != 'file:///') - throw 'Only file URLs of the form file:/// allowed running in Node.'; + throw new Error('Unable to fetch "' + url + '". Only file URLs of the form file:/// allowed running in Node.'); fs = fs || require('fs'); if (isWindows) url = url.replace(/\//g, '\\').substr(8); else url = url.substr(7); return fs.readFile(url, function(err, data) { - if (err) + if (err) { return reject(err); + } else { // Strip Byte Order Mark out if it's the leading char var dataString = data + ''; @@ -1051,9 +1088,10 @@ SystemLoader.prototype.fetch = function(load) { return new Promise(function(resolve, reject) { - fetchTextFromURL(load.address, resolve, reject); + fetchTextFromURL(load.address, undefined, resolve, reject); }); - };/* + }; + /* * Traceur, Babel and TypeScript transpile hook for Loader */ var transpile = (function() { @@ -1079,7 +1117,7 @@ transpileFunction = babelTranspile; // note __moduleName will be part of the transformer meta in future when we have the spec for this - return 'var __moduleName = "' + load.name + '";' + transpileFunction.call(self, load, transpiler) + '\n//# sourceURL=' + load.address + '!transpiled'; + return '(function(__moduleName){' + transpileFunction.call(self, load, transpiler) + '\n})("' + load.name + '");\n//# sourceURL=' + load.address + '!transpiled'; }); }; @@ -1087,7 +1125,8 @@ var options = this.traceurOptions || {}; options.modules = 'instantiate'; options.script = false; - options.sourceMaps = 'inline'; + if (options.sourceMaps === undefined) + options.sourceMaps = 'inline'; options.filename = load.address; options.inputSourceMap = load.metadata.sourceMap; options.moduleName = false; @@ -1109,7 +1148,8 @@ function babelTranspile(load, babel) { var options = this.babelOptions || {}; options.modules = 'system'; - options.sourceMap = 'inline'; + if (options.sourceMap === undefined) + options.sourceMap = 'inline'; options.inputSourceMap = load.metadata.sourceMap; options.filename = load.address; options.code = true; @@ -1120,11 +1160,13 @@ function typescriptTranspile(load, ts) { var options = this.typescriptOptions || {}; - if (options.target === undefined) { - options.target = ts.ScriptTarget.ES5; - } + options.target = options.target || ts.ScriptTarget.ES5; + if (options.sourceMap === undefined) + options.sourceMap = true; + if (options.sourceMap) + options.inlineSourceMap = true; + options.module = ts.ModuleKind.System; - options.inlineSourceMap = true; return ts.transpile(load.source, options, load.address); } @@ -1165,6 +1207,8 @@ // 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); @@ -1195,6 +1239,12 @@ 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(); @@ -1208,6 +1258,8 @@ 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)); @@ -1231,6 +1283,7 @@ function SystemProto() {}; SystemProto.prototype = SystemLoader.prototype; SystemJSLoader.prototype = new SystemProto(); + SystemJSLoader.prototype.constructor = SystemJSLoader; var systemJSConstructor; @@ -1249,12 +1302,134 @@ return newDeps; } - function extend(a, b, underwrite) { + function group(deps) { + var names = []; + var indices = []; + for (var i = 0, l = deps.length; i < l; i++) { + var index = indexOf.call(names, deps[i]); + if (index === -1) { + names.push(deps[i]); + indices.push([i]); + } + else { + indices[index].push(i); + } + } + return { names: names, indices: indices }; + } + + var getOwnPropertyDescriptor = true; + try { + Object.getOwnPropertyDescriptor({ a: 0 }, 'a'); + } + catch(e) { + getOwnPropertyDescriptor = false; + } + +// converts any module.exports object into an object ready for System.newModule + function getESModule(exports) { + var esModule = {}; + // don't trigger getters/setters in environments that support them + if (typeof exports == 'object' || typeof exports == 'function') { + if (getOwnPropertyDescriptor) { + var d; + for (var p in exports) + if (d = Object.getOwnPropertyDescriptor(exports, p)) + defineProperty(esModule, p, d); + } + else { + var hasOwnProperty = exports && exports.hasOwnProperty; + for (var p in exports) { + if (!hasOwnProperty || exports.hasOwnProperty(p)) + esModule[p] = exports[p]; + } + } + } + esModule['default'] = exports; + defineProperty(esModule, '__useDefault', { + value: true + }); + return esModule; + } + + function extend(a, b, prepend) { for (var p in b) { - if (!underwrite || !(p in a)) + if (!prepend || !(p in a)) a[p] = b[p]; } - }var absURLRegEx = /^[^\/]+:\/\//; + return a; + } + +// package configuration options + var packageProperties = ['main', 'format', 'defaultExtension', 'meta', 'map', 'basePath', 'depCache']; + +// meta first-level extends where: +// array + array appends +// object + object extends +// other properties replace + function extendMeta(a, b, prepend) { + for (var p in b) { + var val = b[p]; + if (!(p in a)) + 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') + a[p] = extend(extend({}, a[p]), val, prepend); + else if (!prepend) + a[p] = val; + } + } + + function warn(msg) { + if (this.warnings && typeof console != 'undefined' && console.warn) + console.warn(msg); + }/* + SystemJS map support + + Provides map configuration through + System.map['jquery'] = 'some/module/map' + + Note that this applies for subpaths, just like RequireJS: + + jquery -> 'some/module/map' + jquery/path -> 'some/module/map/path' + bootstrap -> 'bootstrap' + + The most specific map is always taken, as longest path length + */ + hookConstructor(function(constructor) { + return function() { + constructor.call(this); + this.map = {}; + }; + }); + + 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; + } + } + + if (bestMatch) + name = this.map[bestMatch] + name.substr(bestMatch.length); + } + + // map is the first normalizer + return name; + }; + }); + var absURLRegEx = /^[^\/]+:\/\//; function readMemberExpression(p, value) { var pParts = p.split('.'); @@ -1281,267 +1456,912 @@ var baseURIObj = new URL(baseURI); - (function() { + hookConstructor(function(constructor) { + return function() { + constructor.call(this); - hookConstructor(function(constructor) { - return function() { - constructor.call(this); + // support baseURL + this.baseURL = baseURI.substr(0, baseURI.lastIndexOf('/') + 1); - // support baseURL - this.baseURL = baseURI.substr(0, baseURI.lastIndexOf('/') + 1); + // support the empty module, as a concept + this.set('@empty', this.newModule({})); - // 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; + }; + }); - /* - Normalization + /* + 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 + 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 package + normalization phases (applying before and after this one). - The paths normalization phase applies last (paths extension), which - defines the `normalizeSync` function and normalizes everything into - a URL. + The paths normalization phase applies last (paths extension), which + defines the `normalizeSync` function and normalizes everything into + a URL. - The final normalization - */ - hook('normalize', function() { - return function(name, parentName) { - // relative URL-normalization - if (name[0] == '.' || name[0] == '/') - return new URL(name, parentName || baseURIObj).href; - return name; - }; - }); + The final normalization + */ - /* - __useDefault + hook('normalize', function(normalize) { + return function(name, parentName) { + // dynamically load node-core modules when requiring `@node/fs` for example + if (name.substr(0, 6) == '@node/') { + if (!this._nodeRequire) + throw new TypeError('Can only load node core modules in Node.'); + this.set(name, this.newModule(getESModule(this._nodeRequire(name.substr(6))))); + } - When a module object looks like: - newModule( - __useDefault: true, - default: 'some-module' - }) + // first run map config + name = normalize.apply(this, arguments); - Then importing that module provides the 'some-module' - result directly instead of the full module. + // relative URL-normalization + if (name[0] == '.' || name[0] == '/') + return new URL(name, parentName || baseURIObj).href; + return name; + }; + }); - Useful for eg module.exports = function() {} - */ - hook('import', function(systemImport) { - return function(name, parentName, parentAddress) { - return systemImport.call(this, name, parentName, parentAddress).then(function(module) { - return module.__useDefault ? module['default'] : module; +// percent encode just '#' in urls + hook('locate', function(locate) { + return function(load) { + return Promise.resolve(locate.call(this, load)) + .then(function(address) { + return address.replace(/#/g, '%23'); }); - }; - }); + }; + }); - /* - Extend config merging one deep only + /* + * Fetch with authorization + */ + hook('fetch', function() { + return function(load) { + return new Promise(function(resolve, reject) { + fetchTextFromURL(load.address, load.metadata.authorization, resolve, reject); + }); + }; + }); - loader.config({ - some: 'random', - config: 'here', - deep: { - config: { too: 'too' } - } - }); + /* + __useDefault + + When a module object looks like: + newModule( + __useDefault: true, + default: 'some-module' + }) - <=> + Then importing that module provides the 'some-module' + result directly instead of the full module. - loader.some = 'random'; - loader.config = 'here' - loader.deep = loader.deep || {}; - loader.deep.config = { too: 'too' }; + Useful for eg module.exports = function() {} + */ + 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); + return systemImport.call(this, name, parentName, parentAddress).then(function(module) { + return module.__useDefault ? module['default'] : module; + }); + }; + }); + /* + Extend config merging one deep only - Normalizes meta and package configs allowing for: + loader.config({ + some: 'random', + config: 'here', + deep: { + config: { too: 'too' } + } + }); - System.config({ - meta: { - './index.js': {} - } - }); + <=> - To become + loader.some = 'random'; + loader.config = 'here' + loader.deep = loader.deep || {}; + loader.deep.config = { too: 'too' }; - System.meta['https://thissite.com/index.js'] = {}; - For easy normalization canonicalization with latest URL support. + Normalizes meta and package configs allowing for: - */ - SystemJSLoader.prototype.config = function(cfg) { + System.config({ + meta: { + './index.js': {} + } + }); - // always configure baseURL first - if (cfg.baseURL) { - var hasConfig = false; - function checkHasConfig(obj) { - 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.'); + To become - this.baseURL = cfg.baseURL; + System.meta['https://thissite.com/index.js'] = {}; - // sanitize baseURL - getBaseURLObj.call(this); - } + For easy normalization canonicalization with latest URL support. - if (cfg.paths) { - for (var p in cfg.paths) - this.paths[p] = cfg.paths[p]; + */ + SystemJSLoader.prototype.warnings = false; + SystemJSLoader.prototype.config = function(cfg) { + if ('warnings' in cfg) + this.warnings = cfg.warnings; + + // always configure baseURL first + if (cfg.baseURL) { + var hasConfig = false; + function checkHasConfig(obj) { + 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 (cfg.map) { - for (var p in cfg.map) { - var v = cfg.map[p]; + this.baseURL = cfg.baseURL; - // object map backwards-compat into packages configuration - if (typeof v !== 'string') { - var normalized = this.normalizeSync(p); + // sanitize baseURL + getBaseURLObj.call(this); + } - // 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); + if (cfg.defaultJSExtensions) { + this.defaultJSExtensions = cfg.defaultJSExtensions; + warn.call(this, 'The defaultJSExtensions configuration option is deprecated, use packages configuration instead.'); + } - // 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] == '/') - && 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 (cfg.pluginFirst) + this.pluginFirst = cfg.pluginFirst; - var pkg = this.packages[normalized] = this.packages[normalized] || {}; - pkg.map = v; - } - else { - this.map[p] = v; - } - } - } + if (cfg.paths) { + for (var p in cfg.paths) + this.paths[p] = cfg.paths[p]; + } - if (cfg.packages) { - for (var p in cfg.packages) { - var prop = this.normalizeSync(p); + if (cfg.map) { + var objMaps = ''; + for (var p in cfg.map) { + var v = cfg.map[p]; + + // 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') - prop = prop.substr(0, prop.length - 3); + normalized = normalized.substr(0, normalized.length - 3); + + // 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] == '/') + && 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); - this.packages[prop]= this.packages[prop] || {}; - for (var q in cfg.packages[p]) - this.packages[prop][q] = cfg.packages[p][q]; + var pkg = this.packages[normalized] = this.packages[normalized] || {}; + pkg.map = v; + } + else { + this.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: {...} } } }).'); + } - 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; - } + if (cfg.packageConfigPaths) { + var packageConfigPaths = []; + 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); } + this.packageConfigPaths = packageConfigPaths; + } - for (var c in cfg) { - var v = cfg[c]; - var normalizeProp = false, normalizeValArray = false; + if (cfg.packages) { + for (var p in cfg.packages) { + if (p.match(/^([^\/]+:)?\/\/$/)) + throw new TypeError('"' + p + '" is not a valid package name.'); - if (c == 'baseURL' || c == 'map' || c == 'packages' || c == 'bundles' || c == 'paths') - continue; + // request with trailing "/" to get package name exactly + var prop = this.normalizeSync(p + (p[p.length - 1] != '/' ? '/' : '')); + prop = prop.substr(0, prop.length - 1); - if (typeof v != 'object' || v instanceof Array) { - this[c] = v; - } - else { - this[c] = this[c] || {}; + // 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); - if (c == 'meta' || c == 'depCache') - normalizeProp = true; + 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); - for (var p in v) { - if (normalizeProp) - this[c][this.normalizeSync(p)] = v[p]; - else - this[c][p] = v[p]; - } - } + extendMeta(this.packages[prop], cfg.packages[p]); } - }; + } - })();/* - * Script tag fetch - * - * When load.metadata.scriptLoad is true, we load via script tag injection. - */ - (function() { + 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; + } + } - if (typeof document != 'undefined') - var head = document.getElementsByTagName('head')[0]; + for (var c in cfg) { + var v = cfg[c]; + var normalizeProp = false, normalizeValArray = false; - // call this functione everytime a wrapper executes - var curSystem; - // System clobbering protection for Traceur - SystemJSLoader.prototype.onScriptLoad = function() { - __global.System = curSystem; - }; + if (c == 'baseURL' || c == 'map' || c == 'packages' || c == 'bundles' || c == 'paths' || c == 'warnings' || c == 'packageConfigPaths') + continue; - function webWorkerImport(loader, load) { - return new Promise(function(resolve, reject) { - try { - importScripts(load.address); - } - catch(e) { - reject(e); - } + if (typeof v != 'object' || v instanceof Array) { + this[c] = v; + } + else { + this[c] = this[c] || {}; - loader.onScriptLoad(load); - // if nothing registered, then something went wrong - if (!load.metadata.registered) - reject(load.address + ' did not call System.register or AMD define'); + if (c == 'meta' || c == 'depCache') + normalizeProp = true; - resolve(''); - }); + 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); - // override fetch to use script injection - hook('fetch', function(fetch) { - return function(load) { - var loader = this; - - if (!load.metadata.scriptLoad || (!isBrowser && !isWorker)) - return fetch.call(this, load); - - if (isWorker) - return webWorkerImport(loader, load); - - return new Promise(function(resolve, reject) { - var s = document.createElement('script'); - s.async = true; + // if the module is in the registry already, use that + if (this.has(normalized)) + return normalized; - function complete(evt) { - if (s.readyState && s.readyState != 'loaded' && s.readyState != 'complete') - return; - cleanup(); + if (normalized.match(absURLRegEx)) { + // defaultJSExtensions backwards compatibility + if (this.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js') + normalized += '.js'; + return normalized; + } - // this runs synchronously after execution - // we now need to tell the wrapper handlers that - // this load record has just executed - loader.onScriptLoad(load); + // applyPaths implementation provided from ModuleLoader system.js source + normalized = applyPaths(this.paths, normalized) || normalized; - // if nothing registered, then something went wrong - if (!load.metadata.registered) - reject(load.address + ' did not call System.register or AMD define'); + // defaultJSExtensions backwards compatibility + if (this.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js') + normalized += '.js'; + + // ./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; + }; + });/* + * Package Configuration Extension + * + * Example: + * + * System.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: { + * '*.ts': { + * loader: 'typescript' + * }, + * 'vendor/sizzle.js': { + * format: 'global' + * } + * }, + * map: { + * // map internal require('sizzle') to local require('./vendor/sizzle') + * sizzle: './vendor/sizzle.js', + * // map any internal or external require of 'jquery/vendor/another' to 'another/index.js' + * './vendor/another.js': './another/index.js', + * // test.js / test -> lib/test.js + * './test.js': './lib/test.js', + * + * // environment-specific map configurations + * './index.js': { + * '~browser': './index-node.js' + * } + * }, + * // allows for setting package-prefixed depCache + * // keys are normalized module names relative to the package itself + * depCache: { + * // import 'package/index.js' loads in parallel package/lib/test.js,package/vendor/sizzle.js + * './index.js': ['./test'], + * './test.js': ['sizzle'] + * } + * } + * }; + * + * Then: + * import 'jquery' -> jquery/index.js + * import 'jquery/submodule' -> jquery/submodule.js + * import 'jquery/submodule.ts' -> jquery/submodule.ts loaded as typescript + * import 'jquery/vendor/another' -> another/index.js + * + * 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 + * - 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 + * + * 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 + * configuration from. This allows dynamic loading of non-predetermined code, a key use + * case in SystemJS. + * + * Example: + * + * System.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'); + * + * // will first request 'packages/test/package.json' before the main + * System.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 + * 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() { + + hookConstructor(function(constructor) { + return function() { + constructor.call(this); + this.packages = {}; + this.packageConfigPaths = {}; + }; + }); + + function getPackage(name) { + // 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] === '/')) { + pkgLen = p.split('/').length; + if (pkgLen > curPkgLen) { + curPkg = p; + curPkgLen = pkgLen; + } + } + } + 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 : ''; + if (basePath) { + if (basePath.substr(0, 2) == './') + basePath = basePath.substr(2); + if (basePath[basePath.length - 1] != '/') + basePath += '/'; + } + 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)); + + // 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 (matchDepth == 0 || metaPattern.lastIndexOf('*') != metaPattern.length - 1) + skipExtension = true; + }); + + var normalized = pkgName + '/' + basePath + subPath + (skipExtension ? '' : getDefaultExtension(pkg, subPath)); + + return sync ? normalized : booleanConditional.call(loader, normalized, pkgName + '/').then(function(name) { + return interpolateConditional.call(loader, name, pkgName + '/'); + }); + } + + 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; + } + return ''; + } + + function applyPackageConfig(normalized, pkgName, pkg, sync, isPlugin) { + var loader = this; + + var basePath = getBasePath(pkg); + + // 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); + + // allow for direct package name normalization with trailling "/" (no main) + if (normalized.length == pkgName.length + 1 && normalized[pkgName.length] == '/') + return normalized; + + // no submap if name is package itself + if (normalized.length == pkgName.length) + return normalized + (loader.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js' ? '.js' : ''); + + // 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]; + } + + 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); + } + + // apply non-environment map match + if (typeof mapped == 'string') + return doMap(mapped + subPath.substr(map.length)); + + // sync normalize does not apply environment map + if (sync || !mapped) + return toPackagePath(loader, pkgName, pkg, basePath, normalized.substr(pkgName.length + 1), sync, isPlugin); + + // 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 + if (loader.builder) + return pkgName + '#:' + map.substr(2); + + // environment map + return loader['import'](pkg.map['@env'] || '@system-env', pkgName) + .then(function(env) { + // first map condition to match is used + for (var e in mapped) { + var negate = e[0] == '~'; + + var value = readMemberExpression(negate ? e.substr(1) : e, env); + + if (!negate && value || negate && !value) + return mapped[e] + subPath.substr(map.length); + } + }) + .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); + }); + } + + function createPackageNormalize(normalize, sync) { + return function(name, parentName, isPlugin) { + isPlugin = isPlugin === true; + + // apply contextual package map first + 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)); + + 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 defaultJSExtension = this.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js'; + + // apply map, core, paths + var normalized = normalize.call(this, name, parentName); + + // undo defaultJSExtension + if (defaultJSExtension && normalized.substr(normalized.length - 3, 3) != '.js') + defaultJSExtension = false; + 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 + // that is normalize('pkg/') does not apply main, while normalize('./', 'pkg/') does + if (parentPackage && name[0] == '.' && normalized == parentPackage + '/') + normalized = parentPackage; + + var loader = this; + + 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]; + + if (pkg) + return applyPackageConfig.call(loader, normalized, pkgName, pkg, sync, isPlugin); + else + return normalized + (defaultJSExtension ? '.js' : ''); + } + + // do direct resolution if sync + if (sync) + return packageResolution(normalized); + + // first check if we are in a package + var pkgName = getPackage.call(this, normalized); + var pkg = pkgName && this.packages[pkgName]; + + // if so, and the package is configured, then do direct resolution + if (pkg && pkg.configured) + return packageResolution(normalized, pkgName, pkg); + + var pkgConfigMatch = pkgConfigPathMatch(loader, normalized); + + if (!pkgConfigMatch.pkgName) + return packageResolution(normalized, pkgName, pkg); + + // 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)) + + // 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; + } + }) + + // having loaded any bundles, attempt a package resolution now + .then(function() { + return packageResolution(normalized, pkgConfigMatch.pkgName); + }) + + .then(function(curResolution) { + // if that resolution is defined in the registry use it + if (curResolution in loader.defined) + return curResolution; + + // otherwise revert to loading configuration dynamically + return loadPackageConfigPaths(loader, pkgConfigMatch) + .then(function() { + // before doing a final resolution + return packageResolution(normalized); + }); + }); + }; + } + + var pkgBundlePromises = {}; + + // check if the given normalized name matches a packageConfigPath + // if so, loads the config + var packageConfigPathsRegExps = {}; + var pkgConfigPromises = {}; + + function pkgConfigPathMatch(loader, normalized) { + var pkgPath, pkgConfigPaths = []; + 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)); + } + } + return { + pkgName: pkgPath, + configPaths: pkgConfigPaths + }; + } + + function loadPackageConfigPaths(loader, pkgConfigMatch) { + var curPkgConfig = loader.packages[pkgConfigMatch.pkgName]; + + if (curPkgConfig && curPkgConfig.configured) + return Promise.resolve(); + + 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]; + } + + // support main array + if (cfg.main instanceof Array) + cfg.main = cfg.main[0]; + + // deeply-merge (to first level) config with any existing package config + if (curPkgConfig) + extendMeta(cfg, curPkgConfig); + + // support external depCache + if (cfg.depCache) + for (var d in cfg.depCache) { + if (d.substr(0, 2) == './') + continue; + + var dNormalized = loader.normalizeSync(d); + loader.depCache[dNormalized] = (loader.depCache[dNormalized] || []).concat(cfg.depCache[d]); + } + + curPkgConfig = loader.packages[pkgConfigMatch.pkgName] = cfg; + })); + })(pkgConfigMatch.configPaths[i]); + + return Promise.all(pkgConfigPromises); + }) + ); + } + + SystemJSLoader.prototype.normalizeSync = SystemJSLoader.prototype.normalize; + + hook('normalizeSync', function(normalize) { + return createPackageNormalize(normalize, true); + }); + + hook('normalize', function(normalize) { + return createPackageNormalize(normalize, false); + }); + + function getMetaMatches(pkgMeta, pkgName, subPath, matchFn) { + // wildcard meta + var meta = {}; + var wildcardIndex; + for (var module in pkgMeta) { + // allow meta to start with ./ for flexibility + var dotRel = module.substr(0, 2) == './' ? './' : ''; + if (dotRel) + module = module.substr(2); + + wildcardIndex = module.indexOf('*'); + if (wildcardIndex === -1) + continue; + + 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); + } + } + // exact meta + var exactMeta = pkgMeta[subPath] || pkgMeta['./' + subPath]; + if (exactMeta) + matchFn(exactMeta, exactMeta, 0); + } + + hook('locate', function(locate) { + return function(load) { + var loader = this; + return Promise.resolve(locate.call(this, load)) + .then(function(address) { + var pkgName = getPackage.call(loader, load.name); + if (pkgName) { + var pkg = loader.packages[pkgName]; + var basePath = getBasePath(pkg); + var subPath = load.name.substr(pkgName.length + basePath.length + 1); + + // format + 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) { + if (matchDepth > bestDepth) + bestDepth = matchDepth; + extendMeta(meta, matchMeta, matchDepth && bestDepth > matchDepth); + }); + + // allow alias and loader to be package-relative + if (meta.alias && meta.alias.substr(0, 2) == './') + meta.alias = pkgName + meta.alias.substr(1); + if (meta.loader && meta.loader.substr(0, 2) == './') + meta.loader = pkgName + meta.loader.substr(1); + extendMeta(load.metadata, meta); + } + } + + return address; + }); + }; + }); + + })();/* + * Script tag fetch + * + * When load.metadata.scriptLoad is true, we load via script tag injection. + */ + (function() { + + 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; + }; + + function webWorkerImport(loader, load) { + return new Promise(function(resolve, reject) { + if (load.metadata.integrity) + reject(new Error('Subresource integrity checking is not supported in web workers.')); + + try { + importScripts(load.address); + } + catch(e) { + reject(e); + } + + loader.onScriptLoad(load); + // if nothing registered, then something went wrong + if (!load.metadata.registered) + reject(load.address + ' did not call System.register or AMD define'); + + resolve(''); + }); + } + + // override fetch to use script injection + hook('fetch', function(fetch) { + return function(load) { + var loader = this; + + if (!load.metadata.scriptLoad || (!isBrowser && !isWorker)) + return fetch.call(this, load); + + if (isWorker) + return webWorkerImport(loader, load); + + return new Promise(function(resolve, reject) { + var s = document.createElement('script'); + s.async = true; + + function complete(evt) { + if (s.readyState && s.readyState != 'loaded' && s.readyState != 'complete') + return; + cleanup(); + + // this runs synchronously after execution + // we now need to tell the wrapper handlers that + // this load record has just executed + loader.onScriptLoad(load); + + // if nothing registered, then something went wrong + if (!load.metadata.registered) + reject(load.address + ' did not call System.register or AMD define'); resolve(''); } @@ -1562,6 +2382,10 @@ curSystem = __global.System; __global.System = loader; s.src = load.address; + + if (load.metadata.integrity) + s.setAttribute('integrity', load.metadata.integrity); + head.appendChild(s); function cleanup() { @@ -1608,21 +2432,24 @@ * */ var anonRegister; - var calledRegister; + var calledRegister = false; function doRegister(loader, name, register) { calledRegister = true; // named register if (name) { - name = loader.normalizeSync(name); + // ideally wouldn't apply map config to bundle names but + // dependencies go through map regardless so we can't restrict + // could reconsider in shift to new spec + name = (loader.normalizeSync || loader.normalize).call(loader, name); register.name = name; if (!(name in loader.defined)) loader.defined[name] = register; } // anonymous register - else if (register.declarative) { + else { if (anonRegister) - throw new TypeError('Multiple anonymous System.register calls in the same module file.'); + throw new TypeError('Invalid anonymous System.register module load. If loading a single module, ensure anonymous System.register is loaded via System.import. If loading a bundle, ensure all the System.register calls are named.'); anonRegister = register; } } @@ -1681,6 +2508,7 @@ * * For dynamic we track the es module with: * - esModule actual es module value + * - esmExports whether to extend the esModule with named exports * * Then for declarative only we track dynamic bindings with the 'module' records: * - name @@ -1707,11 +2535,11 @@ return function(load) { onScriptLoad.call(this, load); - // anonymous define - if (anonRegister) - load.metadata.entry = anonRegister; - if (calledRegister) { + // anonymous define + if (anonRegister) + load.metadata.entry = anonRegister; + load.metadata.format = load.metadata.format || 'defined'; load.metadata.registered = true; calledRegister = false; @@ -1788,11 +2616,18 @@ } // module binding records + function Module() {} + defineProperty(Module, 'toString', { + value: function() { + return 'Module'; + } + }); + function getOrCreateModuleRecord(name, moduleRecords) { return moduleRecords[name] || (moduleRecords[name] = { name: name, dependencies: [], - exports: {}, // start from an empty module and extend + exports: new Module(), // start from an empty module and extend importers: [] }); } @@ -1872,9 +2707,14 @@ module.dependencies.push(null); } - // run the setter for this dependency - if (module.setters[i]) - module.setters[i](depExports); + // run setters for all entries with the matching dependency name + var originalIndices = entry.originalIndices[i]; + for (var j = 0, len = originalIndices.length; j < len; ++j) { + var index = originalIndices[j]; + if (module.setters[index]) { + module.setters[index](depExports); + } + } } } @@ -1941,33 +2781,15 @@ // create the esModule object, which allows ES6 named imports of dynamics exports = module.exports; - if (exports && exports.__esModule) { + // __esModule flag treats as already-named + if (exports && exports.__esModule) entry.esModule = exports; - } - else { - entry.esModule = {}; - - // don't trigger getters/setters in environments that support them - if (typeof exports == 'object' || typeof exports == 'function') { - if (Object.getOwnPropertyDescriptor) { - var d; - for (var p in exports) - if (d = Object.getOwnPropertyDescriptor(exports, p)) - Object.defineProperty(entry.esModule, p, d); - } - else { - var hasOwnProperty = exports && exports.hasOwnProperty; - for (var p in exports) { - if (!hasOwnProperty || exports.hasOwnProperty(p)) - entry.esModule[p] = exports[p]; - } - } - } - entry.esModule['default'] = exports; - defineProperty(entry.esModule, '__useDefault', { - value: true - }); - } + // set module as 'default' export, then fake named exports by iterating properties + else if (entry.esmExports) + entry.esModule = getESModule(exports); + // just use the 'default' export + else + entry.esModule = { 'default': exports }; } /* @@ -2016,7 +2838,11 @@ }; }); - var registerRegEx = /^\s*(\/\*.*\*\/\s*|\/\/[^\n]*\s*)*System\.register(Dynamic)?\s*\(/; + var leadingCommentAndMetaRegEx = /^\s*(\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\s*\/\/[^\n]*|\s*"[^"]+"\s*;?|\s*'[^']+'\s*;?)*\s*/; + function detectRegisterFormat(source) { + var leadingCommentAndMeta = source.match(leadingCommentAndMetaRegEx); + return leadingCommentAndMeta && source.substr(leadingCommentAndMeta[0].length, 15) == 'System.register'; + } hook('fetch', function(fetch) { return function(load) { @@ -2029,7 +2855,7 @@ anonRegister = null; calledRegister = false; - if (load.metadata.format == 'register') + if (load.metadata.format == 'register' && !load.metadata.authorization) load.metadata.scriptLoad = true; // NB remove when "deps " is deprecated @@ -2049,7 +2875,7 @@ load.metadata.deps = load.metadata.deps || []; // run detection for register format - if (load.metadata.format == 'register' || !load.metadata.format && load.source.match(registerRegEx)) + if (load.metadata.format == 'register' || !load.metadata.format && detectRegisterFormat(load.source)) load.metadata.format = 'register'; return source; }); @@ -2083,11 +2909,17 @@ } // Contains System.register calls - else if (load.metadata.format == 'register' || load.metadata.format == 'esm' || load.metadata.format == 'es6') { + // (dont run bundles in the builder) + else if (!(loader.builder && load.metadata.bundle) + && (load.metadata.format == 'register' || load.metadata.format == 'esm' || load.metadata.format == 'es6')) { anonRegister = null; calledRegister = false; - __exec.call(loader, load); + if (typeof __exec != 'undefined') + __exec.call(loader, load); + + if (!calledRegister && !load.metadata.registered) + throw new TypeError(load.name + ' detected as System.register but didn\'t execute.'); if (anonRegister) entry = anonRegister; @@ -2097,8 +2929,8 @@ if (!entry && loader.defined[load.name]) entry = loader.defined[load.name]; - if (!calledRegister && !load.metadata.registered) - throw new TypeError(load.name + ' detected as System.register but didn\'t execute.'); + anonRegister = null; + calledRegister = false; } // named bundles are just an empty module @@ -2114,8 +2946,12 @@ // place this module onto defined for circular references loader.defined[load.name] = entry; - entry.deps = dedupe(entry.deps); + var grouped = group(entry.deps); + + entry.deps = grouped.names; + entry.originalIndices = grouped.indices; entry.name = load.name; + entry.esmExports = load.metadata.esmExports !== false; // first, normalize all dependencies var normalizePromises = []; @@ -2164,14 +3000,23 @@ .then(function(source) { // detect & transpile ES6 if (load.metadata.format == 'esm' || load.metadata.format == 'es6' || !load.metadata.format && source.match(esmRegEx)) { + if (load.metadata.format == 'es6') + warn.call(loader, 'Module ' + load.name + ' has metadata setting its format to "es6", which is deprecated.\nThis should be updated to "esm".'); load.metadata.format = 'esm'; + if (loader.transpiler === false) + throw new TypeError('Unable to dynamically transpile ES module as System.transpiler set to false.'); + // setting _loadedTranspiler = false tells the next block to // do checks for setting transpiler metadata loader._loadedTranspiler = loader._loadedTranspiler || false; if (loader.pluginLoader) loader.pluginLoader._loadedTranspiler = loader._loadedTranspiler || false; + // builder support + if (loader.builder) + load.metadata.originalSource = load.source; + // defined in es6-module-loader/src/transpile.js return transpile.call(loader, load) .then(function(source) { @@ -2261,7 +3106,7 @@ // A global with exports, no globals and no deps // can be loaded via a script tag - if (load.metadata.format == 'global' + if (load.metadata.format == 'global' && !load.metadata.authorization && load.metadata.exports && !load.metadata.globals && (!load.metadata.deps || load.metadata.deps.length == 0)) load.metadata.scriptLoad = true; @@ -2283,13 +3128,23 @@ if (!load.metadata.format) load.metadata.format = 'global'; - // add globals as dependencies - if (load.metadata.globals) - for (var g in load.metadata.globals) - load.metadata.deps.push(load.metadata.globals[g]); + // globals shorthand support for: + // globals = ['Buffer'] where we just require 'Buffer' in the current context + if (load.metadata.globals) { + if (load.metadata.globals instanceof Array) { + var globals = {}; + for (var i = 0; i < load.metadata.globals.length; i++) + globals[load.metadata.globals[i]] = load.metadata.globals[i]; + load.metadata.globals = globals; + } + } // global is a fallback module format if (load.metadata.format == 'global' && !load.metadata.registered) { + + for (var g in load.metadata.globals) + load.metadata.deps.push(load.metadata.globals[g]); + load.metadata.execute = function(require, exports, module) { var globals; @@ -2300,24 +3155,14 @@ } var exportName = load.metadata.exports; - var retrieveGlobal = loader.get('@@global-helpers').prepareGlobal(module.id, exportName, globals); if (exportName) load.source += '\n' + __globalName + '["' + exportName + '"] = ' + exportName + ';'; - // disable module detection - var define = __global.define; - var cRequire = __global.require; - - __global.define = undefined; - __global.module = undefined; - __global.exports = undefined; + var retrieveGlobal = loader.get('@@global-helpers').prepareGlobal(module.id, exportName, globals); __exec.call(loader, load); - __global.require = cRequire; - __global.define = define; - return retrieveGlobal(); } } @@ -2332,7 +3177,7 @@ var hasOwnProperty = Object.prototype.hasOwnProperty; // bare minimum ignores for IE8 - var ignoredGlobalProps = ['_g', 'sessionStorage', 'localStorage', 'clipboardData', 'frames', 'external']; + var ignoredGlobalProps = ['_g', 'sessionStorage', 'localStorage', 'clipboardData', 'frames', 'external', 'mozAnimationStartTime', 'webkitStorageInfo', 'webkitIndexedDB']; var globalSnapshot; @@ -2363,6 +3208,14 @@ loader.set('@@global-helpers', loader.newModule({ prepareGlobal: function(moduleName, exportName, globals) { + // disable module detection + var curDefine = __global.define; + + __global.define = undefined; + __global.exports = undefined; + if (__global.module && __global.module.exports) + __global.module = undefined; + // set globals var oldGlobals; if (globals) { @@ -2417,13 +3270,15 @@ for (var g in oldGlobals) __global[g] = oldGlobals[g]; } + __global.define = curDefine; return globalValue; }; } })); }; - });/* + }); + /* SystemJS CommonJS Format */ (function() { @@ -2435,34 +3290,35 @@ var commentRegEx = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg; function getCJSDeps(source) { - cjsRequireRegEx.lastIndex = 0; + cjsRequireRegEx.lastIndex = commentRegEx.lastIndex = 0; var deps = []; - // remove comments from the source first, if not minified - if (source.length / source.split('\n').length < 200) - source = source.replace(commentRegEx, ''); - + // track comments in the source var match; - while (match = cjsRequireRegEx.exec(source)) - deps.push(match[1].substr(1, match[1].length - 2)); + var commentLocations = []; + if (source.length / source.split('\n').length < 200) { + while (match = commentRegEx.exec(source)) + commentLocations.push([match.index, match.index + match[0].length]); + } + + while (match = cjsRequireRegEx.exec(source)) { + // ensure we're not in a comment location + var inComment = false; + for (var i = 0; i < commentLocations.length; i++) { + if (commentLocations[i][0] < match.index && commentLocations[i][1] > match.index + match[0].length) + inComment = true; + } + if (!inComment) + deps.push(match[1].substr(1, match[1].length - 2)); + } return deps; } - if (typeof window != 'undefined' && typeof document != 'undefined' && window.location) - var windowOrigin = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : ''); - - hookConstructor(function(constructor) { - return function() { - constructor.call(this); - - // include the node require since we're overriding it - if (typeof require != 'undefined' && require.resolve && typeof process != 'undefined') - this._nodeRequire = require; - }; - }); + if (typeof window != 'undefined' && typeof document != 'undefined' && window.location) + var windowOrigin = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : ''); hook('instantiate', function(instantiate) { return function(load) { @@ -2478,6 +3334,9 @@ var metaDeps = load.metadata.deps || []; load.metadata.deps = metaDeps.concat(getCJSDeps(load.source)); + for (var g in load.metadata.globals) + load.metadata.deps.push(load.metadata.globals[g]); + load.metadata.executingRequire = true; load.metadata.execute = function(require, exports, module) { @@ -2490,11 +3349,7 @@ dirname.pop(); dirname = dirname.join('/'); - if (windowOrigin && address.substr(0, windowOrigin.length) === windowOrigin) { - address = address.substr(windowOrigin.length); - dirname = dirname.substr(windowOrigin.length); - } - else if (address.substr(0, 8) == 'file:///') { + if (address.substr(0, 8) == 'file:///') { address = address.substr(7); dirname = dirname.substr(7); @@ -2504,6 +3359,10 @@ dirname = dirname.substr(1); } } + else if (windowOrigin && address.substr(0, windowOrigin.length) === windowOrigin) { + address = address.substr(windowOrigin.length); + dirname = dirname.substr(windowOrigin.length); + } // disable AMD detection var define = __global.define; @@ -2511,10 +3370,16 @@ __global.__cjsWrapper = { exports: exports, - args: [require, exports, module, address, dirname, __global] + args: [require, exports, module, address, dirname, __global, __global] }; - load.source = "(function(require, exports, module, __filename, __dirname, global) {" + var globals = ''; + if (load.metadata.globals) { + for (var g in load.metadata.globals) + globals += 'var ' + g + ' = require("' + load.metadata.globals[g] + '");'; + } + + load.source = "(function(require, exports, module, __filename, __dirname, global, GLOBAL) {" + globals + load.source + "\n}).apply(__cjsWrapper.exports, __cjsWrapper.args);"; __exec.call(loader, load); @@ -2593,13 +3458,15 @@ // commonjs require else if (typeof names == 'string') { - var module = loader.get(names); + var module = loader.get(loader.normalizeSync(names, referer)); + if (!module) + throw new Error('Module not already loaded loading "' + names + '" from "' + referer + '".'); return module.__useDefault ? module['default'] : module; } else throw new TypeError('Invalid require'); - }; + } function define(name, deps, factory) { if (typeof name != 'string') { @@ -2702,13 +3569,13 @@ } // named define else { - // if it has no dependencies and we don't have any other - // defines, then let this be an anonymous define + // if we don't have any other defines, + // then let this be an anonymous define // this is just to support single modules of the form: // define('jquery') // still loading anonymously // because it is done widely enough to be useful - if (deps.length == 0 && !lastModule.anonDefine && !lastModule.isBundle) { + if (!lastModule.anonDefine && !lastModule.isBundle) { lastModule.anonDefine = define; } // otherwise its a bundle only @@ -2799,7 +3666,7 @@ hook('fetch', function(fetch) { return function(load) { - if (load.metadata.format === 'amd') + if (load.metadata.format === 'amd' && !load.metadata.authorization) load.metadata.scriptLoad = true; if (load.metadata.scriptLoad) this.get('@@amd-helpers').createDefine(this); @@ -2812,378 +3679,43 @@ var loader = this; if (load.metadata.format == 'amd' || !load.metadata.format && load.source.match(amdRegEx)) { - load.metadata.format = 'amd'; - - if (loader.execute !== false) { - var removeDefine = this.get('@@amd-helpers').createDefine(loader); - - __exec.call(loader, load); - - removeDefine(loader); - - var lastModule = this.get('@@amd-helpers').lastModule; - - if (!lastModule.anonDefine && !lastModule.isBundle) - throw new TypeError('AMD module ' + load.name + ' did not define'); - - if (lastModule.anonDefine) { - load.metadata.deps = load.metadata.deps ? load.metadata.deps.concat(lastModule.anonDefine.deps) : lastModule.anonDefine.deps; - load.metadata.execute = lastModule.anonDefine.execute; - } - - lastModule.isBundle = false; - lastModule.anonDefine = null; - } - - return instantiate.call(loader, load); - } - - return instantiate.call(loader, load); - }; - }); - - })(); - /* - SystemJS map support - - Provides map configuration through - System.map['jquery'] = 'some/module/map' - - Note that this applies for subpaths, just like RequireJS: - - jquery -> 'some/module/map' - jquery/path -> 'some/module/map/path' - bootstrap -> 'bootstrap' - - The most specific map is always taken, as longest path length - */ - hookConstructor(function(constructor) { - return function() { - constructor.call(this); - this.map = {}; - }; - }); - - hook('normalize', function(normalize) { - return function(name, parentName, parentAddress) { - 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; - } - } - - if (bestMatch) - name = this.map[bestMatch] + name.substr(bestMatch.length); - } - - return normalize.call(this, name, parentName, parentAddress); - }; - }); - /* - * Paths extension - * - * Applies paths and normalizes to a full URL - */ - hook('normalize', function(normalize) { - - return function(name, parentName) { - var normalized = normalize.call(this, name, parentName); - - // 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; - - // defaultJSExtensions backwards compatibility - if (this.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js') - normalized += '.js'; - - // ./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; - }; - });/* - * Package Configuration Extension - * - * Example: - * - * System.packages = { - * jquery: { - * main: 'index.js', // when not set, package name is requested directly - * format: 'amd', - * defaultExtension: 'js', - * meta: { - * '*.ts': { - * loader: 'typescript' - * }, - * 'vendor/sizzle.js': { - * format: 'global' - * } - * }, - * map: { - * // map internal require('sizzle') to local require('./vendor/sizzle') - * sizzle: './vendor/sizzle.js', - * // map any internal or external require of 'jquery/vendor/another' to 'another/index.js' - * './vendor/another.js': './another/index.js', - * // test.js / test -> lib/test.js - * './test.js': './lib/test.js', - * }, - * env: { - * 'browser': { - * main: 'browser.js' - * } - * } - * } - * }; - * - * Then: - * import 'jquery' -> jquery/index.js - * import 'jquery/submodule' -> jquery/submodule.js - * import 'jquery/submodule.ts' -> jquery/submodule.ts loaded as typescript - * import 'jquery/vendor/another' -> another/index.js - * - * Detailed Behaviours - * - main is the only property where 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 - * - 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) - * - * In addition, the following meta properties will be allowed to be package - * -relative as well in the package meta config: - * - * - loader - * - alias - * - */ - (function() { - - hookConstructor(function(constructor) { - return function() { - constructor.call(this); - this.packages = {}; - }; - }); - - function getPackage(name) { - for (var p in this.packages) { - if (name.substr(0, p.length) === p && (name.length === p.length || name[p.length] === '/')) - return p; - } - } - - function getPackageConfig(loader, pkgName) { - var pkgConfig = loader.packages[pkgName]; - - if (!pkgConfig.env) - return Promise.resolve(pkgConfig); - - // check environment conditions - // default environment condition is '@env' in package or '@system-env' globally - return loader['import'](pkgConfig.map['@env'] || '@system-env', pkgName) - .then(function(env) { - // derived config object - var pkg = {}; - for (var p in pkgConfig) - if (p !== 'map' & p !== 'env') - pkg[p] = pkgConfig[p]; - - pkg.map = {}; - for (var p in pkgConfig.map) - pkg.map[p] = pkgConfig.map[p]; - - for (var e in pkgConfig.env) { - if (env[e]) { - var envConfig = pkgConfig.env[e]; - if (envConfig.main) - pkg.main = envConfig.main; - for (var m in envConfig.map) - pkg.map[m] = envConfig.map[m]; - } - } - - // store the derived environment config so we have this cached for next time - loader.packages[pkgName] = pkg; - - return pkg; - }); - } - - 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; - } - } - if (bestMatch) - return map[bestMatch] + name.substr(bestMatch.length); - } - - SystemJSLoader.prototype.normalizeSync = SystemJSLoader.prototype.normalize; - - hook('normalize', function(normalize) { - return function(name, parentName) { - // apply contextual package map first - 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)); - } - - if (parentPackage && name[0] !== '.') { - var parentMap = this.packages[parentPackage].map; - if (parentMap) { - name = applyMap(parentMap, name) || name; - - // relative maps are package-relative - if (name[0] === '.') - parentName = parentPackage + '/'; - } - } - - var defaultJSExtension = this.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js'; - - // apply global map, relative normalization - var normalized = normalize.call(this, name, parentName); - - // undo defaultJSExtension - if (normalized.substr(normalized.length - 3, 3) != '.js') - defaultJSExtension = false; - if (defaultJSExtension) - normalized = normalized.substr(0, normalized.length - 3); - - // check if we are inside a package - var pkgName = getPackage.call(this, normalized); - - if (pkgName) { - return getPackageConfig(this, pkgName) - .then(function(pkg) { - // main - if (pkgName === normalized && pkg.main) - normalized += '/' + (pkg.main.substr(0, 2) == './' ? pkg.main.substr(2) : pkg.main); - - if (normalized.substr(pkgName.length) == '/') - return normalized; - - // defaultExtension & defaultJSExtension - // if we have meta for this package, don't do defaultExtensions - var defaultExtension = ''; - if (!pkg.meta || !pkg.meta[normalized.substr(pkgName.length + 1)]) { - // apply defaultExtension - - if ('defaultExtension' in pkg) { - if (pkg.defaultExtension !== false && normalized.split('/').pop().indexOf('.') == -1) - defaultExtension = '.' + pkg.defaultExtension; - } - // apply defaultJSExtensions if defaultExtension not set - else if (defaultJSExtension) { - defaultExtension = '.js'; - } - } - - // apply submap checking without then with defaultExtension - var subPath = '.' + normalized.substr(pkgName.length); - var mapped = applyMap(pkg.map, subPath) || defaultExtension && applyMap(pkg.map, subPath + defaultExtension); - if (mapped) - normalized = mapped.substr(0, 2) == './' ? pkgName + mapped.substr(1) : mapped; - else - normalized += defaultExtension; - - - return normalized; - }); - } - - // add back defaultJSExtension if not a package - if (defaultJSExtension) - normalized += '.js'; - - return normalized; - }; - }); - - hook('locate', function(locate) { - return function(load) { - var loader = this; - return Promise.resolve(locate.call(this, load)) - .then(function(address) { - var pkgName = getPackage.call(loader, load.name); - if (pkgName) { - var pkg = loader.packages[pkgName]; + load.metadata.format = 'amd'; - // format - if (pkg.format) - load.metadata.format = load.metadata.format || pkg.format; + if (!loader.builder && loader.execute !== false) { + var removeDefine = this.get('@@amd-helpers').createDefine(loader); - // loader - if (pkg.loader) - load.metadata.loader = load.metadata.loader || pkg.loader; + __exec.call(loader, load); - if (pkg.meta) { - // wildcard meta - var meta = {}; - var bestDepth = 0; - var wildcardIndex; - for (var module in pkg.meta) { - wildcardIndex = module.indexOf('*'); - if (wildcardIndex === -1) - continue; - if (module.substr(0, wildcardIndex) === load.name.substr(0, wildcardIndex) - && module.substr(wildcardIndex + 1) === load.name.substr(load.name.length - module.length + wildcardIndex + 1)) { - var depth = module.split('/').length; - if (depth > bestDepth) - bestDetph = depth; - extend(meta, pkg.meta[module], bestDepth != depth); - } - } - // exact meta - var exactMeta = pkg.meta[load.name.substr(pkgName.length + 1)]; - if (exactMeta) - extend(meta, exactMeta); + removeDefine(loader); - // allow alias and loader to be package-relative - if (meta.alias && meta.alias.substr(0, 2) == './') - meta.alias = pkgName + meta.alias.substr(1); - if (meta.loader && meta.loader.substr(0, 2) == './') - meta.loader = pkgName + meta.loader.substr(1); + var lastModule = this.get('@@amd-helpers').lastModule; - extend(load.metadata, meta); - } + if (!lastModule.anonDefine && !lastModule.isBundle) + throw new TypeError('AMD module ' + load.name + ' did not define'); + + if (lastModule.anonDefine) { + load.metadata.deps = load.metadata.deps ? load.metadata.deps.concat(lastModule.anonDefine.deps) : lastModule.anonDefine.deps; + load.metadata.execute = lastModule.anonDefine.execute; } - return address; - }); + lastModule.isBundle = false; + lastModule.anonDefine = null; + } + else { + load.metadata.execute = function() { + return load.metadata.builderExecute.apply(this, arguments); + }; + } + + return instantiate.call(loader, load); + } + + return instantiate.call(loader, load); }; }); - })();/* + })(); + /* SystemJS Loader Plugin Support Supports plugin loader syntax with "!", or via metadata.loader @@ -3194,61 +3726,87 @@ (function() { // sync or async plugin normalize function - function normalizePlugin(normalize, name, parentName, sync) { + function normalizePlugin(normalize, name, parentName, isPlugin, sync) { var loader = this; // if parent is a plugin, normalize against the parent plugin argument only - var parentPluginIndex; - if (parentName && (parentPluginIndex = parentName.indexOf('!')) != -1) - parentName = parentName.substr(0, parentPluginIndex); + if (parentName) { + var parentPluginIndex; + if (loader.pluginFirst) { + if ((parentPluginIndex = parentName.lastIndexOf('!')) != -1) + parentName = parentName.substr(parentPluginIndex + 1); + } + else { + if ((parentPluginIndex = parentName.indexOf('!')) != -1) + parentName = parentName.substr(0, parentPluginIndex); + } + } // if this is a plugin, normalize the plugin name and the argument var pluginIndex = name.lastIndexOf('!'); if (pluginIndex != -1) { - var argumentName = name.substr(0, pluginIndex); - var pluginName = name.substr(pluginIndex + 1) || argumentName.substr(argumentName.lastIndexOf('.') + 1); + var argumentName; + var pluginName; + + if (loader.pluginFirst) { + argumentName = name.substr(pluginIndex + 1); + pluginName = name.substr(0, pluginIndex); + } + else { + argumentName = name.substr(0, pluginIndex); + pluginName = name.substr(pluginIndex + 1) || argumentName.substr(argumentName.lastIndexOf('.') + 1); + } // note if normalize will add a default js extension // if so, remove for backwards compat // this is strange and sucks, but will be deprecated var defaultExtension = loader.defaultJSExtensions && argumentName.substr(argumentName.length - 3, 3) != '.js'; + // put name back together after parts have been normalized + function normalizePluginParts(argumentName, pluginName) { + if (defaultExtension && argumentName.substr(argumentName.length - 3, 3) == '.js') + argumentName = argumentName.substr(0, argumentName.length - 3); + + if (loader.pluginFirst) { + return pluginName + '!' + argumentName; + } + else { + return argumentName + '!' + pluginName; + } + } + if (sync) { argumentName = loader.normalizeSync(argumentName, parentName); pluginName = loader.normalizeSync(pluginName, parentName); - if (defaultExtension && argumentName.substr(argumentName.length - 3, 3) == '.js') - argumentName = argumentName.substr(0, argumentName.length - 3); - - return argumentName + '!' + pluginName; + return normalizePluginParts(argumentName, pluginName); } else { + // third argument represents that this is a plugin call + // which in turn will skip default extension adding within packages return Promise.all([ - loader.normalize(argumentName, parentName), - loader.normalize(pluginName, parentName) + loader.normalize(argumentName, parentName, true), + loader.normalize(pluginName, parentName, true) ]) .then(function(normalized) { - argumentName = normalized[0]; - if (defaultExtension && argumentName.substr(argumentName.length - 3, 3) == '.js') - argumentName = argumentName.substr(0, argumentName.length - 3); - return argumentName + '!' + normalized[1]; + return normalizePluginParts(normalized[0], normalized[1]); }); } } else { - return normalize.call(loader, name, parentName); + return normalize.call(loader, name, parentName, isPlugin); } } // async plugin normalize hook('normalize', function(normalize) { - return function(name, parentName) { - return normalizePlugin.call(this, normalize, name, parentName, false); + return function(name, parentName, isPlugin) { + return normalizePlugin.call(this, normalize, name, parentName, isPlugin, false); }; }); hook('normalizeSync', function(normalizeSync) { - return function(name, parentName) { - return normalizePlugin.call(this, normalizeSync, name, parentName, true); + return function(name, parentName, isPlugin) { + return normalizePlugin.call(this, normalizeSync, name, parentName, isPlugin, true); }; }); @@ -3259,10 +3817,18 @@ var name = load.name; // plugin syntax - var pluginSyntaxIndex = name.lastIndexOf('!'); - if (pluginSyntaxIndex != -1) { - load.metadata.loader = name.substr(pluginSyntaxIndex + 1); - load.name = name.substr(0, pluginSyntaxIndex); + var pluginSyntaxIndex; + if (loader.pluginFirst) { + if ((pluginSyntaxIndex = name.indexOf('!')) != -1) { + load.metadata.loader = name.substr(0, pluginSyntaxIndex); + load.name = name.substr(pluginSyntaxIndex + 1); + } + } + else { + if ((pluginSyntaxIndex = name.lastIndexOf('!')) != -1) { + load.metadata.loader = name.substr(pluginSyntaxIndex + 1); + load.name = name.substr(0, pluginSyntaxIndex); + } } return locate.call(loader, load) @@ -3283,7 +3849,6 @@ .then(function(loaderModule) { // store the plugin module itself on the metadata load.metadata.loaderModule = loaderModule; - load.metadata.loaderArgument = name; load.address = address; if (loaderModule.locate) @@ -3327,6 +3892,27 @@ hook('instantiate', function(instantiate) { return function(load) { var loader = this; + + /* + * Source map sanitization for load.metadata.sourceMap + * Used to set browser and build-level source maps for + * translated sources in a general way. + */ + var sourceMap = load.metadata.sourceMap; + + // if an object not a JSON string do sanitizing + if (sourceMap && typeof sourceMap == 'object') { + var originalName = load.name.split('!')[0]; + + // force set the filename of the original file + sourceMap.file = originalName + '!transpiled'; + + // force set the sources list if only one source + if (!sourceMap.sources || sourceMap.sources.length == 1) + sourceMap.sources = [originalName]; + load.metadata.sourceMap = JSON.stringify(sourceMap); + } + if (load.metadata.loaderModule && load.metadata.loaderModule.instantiate) return Promise.resolve(load.metadata.loaderModule.instantiate.call(loader, load)).then(function(result) { load.metadata.format = 'defined'; @@ -3341,6 +3927,158 @@ }); })(); + /* + * Conditions Extension + * + * Allows a condition module to alter the resolution of an import via syntax: + * + * import $ from 'jquery/#{browser}'; + * + * Will first load the module 'browser' via `System.import('browser')` and + * take the default export of that module. + * If the default export is not a string, an error is thrown. + * + * We then substitute the string into the require to get the conditional resolution + * enabling environment-specific variations like: + * + * import $ from 'jquery/ie' + * import $ from 'jquery/firefox' + * import $ from 'jquery/chrome' + * import $ from 'jquery/safari' + * + * It can be useful for a condition module to define multiple conditions. + * This can be done via the `|` modifier to specify an export member expression: + * + * import 'jquery/#{./browser.js|grade.version}' + * + * Where the `grade` export `version` member in the `browser.js` module is substituted. + * + * + * Boolean Conditionals + * + * For polyfill modules, that are used as imports but have no module value, + * a binary conditional allows a module not to be loaded at all if not needed: + * + * import 'es5-shim#?./conditions.js|needs-es5shim' + * + * These conditions can also be negated via: + * + * import 'es5-shim#?~./conditions.js|es6' + * + */ + + function parseCondition(condition) { + var conditionExport, conditionModule, negation; + + var negation = condition[0] == '~'; + var conditionExportIndex = condition.lastIndexOf('|'); + if (conditionExportIndex != -1) { + conditionExport = condition.substr(conditionExportIndex + 1); + conditionModule = condition.substr(negation, conditionExportIndex - negation) || '@system-env'; + } + else { + conditionExport = null; + conditionModule = condition.substr(negation); + } + + return { + module: conditionModule, + prop: conditionExport, + negate: negation + }; + } + + function serializeCondition(conditionObj) { + return (conditionObj.negate ? '~' : '') + conditionObj.module + (conditionObj.prop ? '|' + conditionObj.prop : ''); + } + + function resolveCondition(conditionObj, parentName, bool) { + return this['import'](conditionObj.module, parentName) + .then(function(m) { + if (conditionObj.prop) + m = readMemberExpression(conditionObj.prop, m); + else if (typeof m == 'object' && m + '' == 'Module') + m = m['default']; + + return conditionObj.negate ? !m : m; + }); + } + + var interpolationRegEx = /#\{[^\}]+\}/; + function interpolateConditional(name, parentName) { + // first we normalize the conditional + var conditionalMatch = name.match(interpolationRegEx); + + if (!conditionalMatch) + return Promise.resolve(name); + + var conditionObj = parseCondition(conditionalMatch[0].substr(2, conditionalMatch[0].length - 3)); + + // in builds, return normalized conditional + if (this.builder) + return this['normalize'](conditionObj.module, parentName) + .then(function(conditionModule) { + conditionObj.conditionModule = conditionModule; + return name.replace(interpolationRegEx, '#{' + serializeCondition(conditionObj) + '}'); + }); + + return resolveCondition.call(this, conditionObj, parentName, false) + .then(function(conditionValue) { + if (typeof conditionValue !== 'string') + throw new TypeError('The condition value for ' + name + ' doesn\'t resolve to a string.'); + + return name.replace(interpolationRegEx, conditionValue); + }); + } + + function booleanConditional(name, parentName) { + // first we normalize the conditional + var booleanIndex = name.lastIndexOf('#?'); + + if (booleanIndex == -1) + return Promise.resolve(name); + + var conditionObj = parseCondition(name.substr(booleanIndex + 2)); + + // in builds, return normalized conditional + if (this.builder) + return this['normalize'](conditionObj.module, parentName) + .then(function(conditionModule) { + conditionObj.module = conditionModule; + return name.substr(0, booleanIndex) + '#?' + serializeCondition(conditionObj); + }); + + return resolveCondition.call(this, conditionObj, parentName, true) + .then(function(conditionValue) { + return conditionValue ? name.substr(0, booleanIndex) : '@empty'; + }); + } + + hookConstructor(function(constructor) { + return function() { + constructor.call(this); + + // standard environment module, starting small as backwards-compat matters! + this.set('@system-env', this.newModule({ + browser: isBrowser, + node: !!this._nodeRequire + })); + }; + }); + + // no normalizeSync + hook('normalize', function(normalize) { + return function(name, parentName, parentAddress) { + var loader = this; + return booleanConditional.call(loader, name, parentName) + .then(function(name) { + return normalize.call(loader, name, parentName, parentAddress); + }) + .then(function(normalized) { + return interpolateConditional.call(loader, normalized, parentName); + }); + }; + }); /* * Alias Extension * @@ -3354,11 +4092,12 @@ hook('fetch', function(fetch) { return function(load) { var alias = load.metadata.alias; + var aliasDeps = load.metadata.deps || []; if (alias) { load.metadata.format = 'defined'; this.defined[load.name] = { declarative: true, - deps: [alias], + deps: aliasDeps.concat([alias]), declare: function(_export) { return { setters: [function(module) { @@ -3390,16 +4129,16 @@ * 'my/module': { deps: ['jquery'] } * 'my/*': { format: 'amd' } * }); - * + * * Which in turn populates loader.metadata. * * load.metadata.deps and load.metadata.format will then be set * for 'my/module' * * The same meta could be set with a my/module.js file containing: - * + * * my/module.js - * "format amd"; + * "format amd"; * "deps[] jquery"; * "globals.some value" * console.log('this is my/module'); @@ -3415,7 +4154,7 @@ * loader.meta({ './app': { format: 'cjs' } }); * * Instead of needing to set against the absolute URL (https://site.com/app.js) - * + * */ (function() { @@ -3439,21 +4178,21 @@ var bestDepth = 0; var wildcardIndex; for (var module in meta) { - wildcardIndex = indexOf.call(module, '*'); + wildcardIndex = module.indexOf('*'); if (wildcardIndex === -1) continue; if (module.substr(0, wildcardIndex) === name.substr(0, wildcardIndex) && module.substr(wildcardIndex + 1) === name.substr(name.length - module.length + wildcardIndex + 1)) { var depth = module.split('/').length; if (depth > bestDepth) - bestDetph = depth; - extend(load.metadata, meta[module], bestDepth != depth); + bestDepth = depth; + extendMeta(load.metadata, meta[module], bestDepth != depth); } } // apply exact meta if (meta[name]) - extend(load.metadata, meta[name]); + extendMeta(load.metadata, meta[name]); return locate.call(this, load); }; @@ -3461,8 +4200,8 @@ // 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; + var metaRegEx = /^(\s*\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\s*\/\/[^\n]*|\s*"[^"]+"\s*;?|\s*'[^']+'\s*;?)+/; + var metaPartRegEx = /\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\/\/[^\n]*|"[^"]+"\s*;?|'[^']+'\s*;?/g; function setMetaProperty(target, p, value) { var pParts = p.split('.'); @@ -3503,13 +4242,19 @@ if (metaName.substr(metaName.length - 2, 2) == '[]') { metaName = metaName.substr(0, metaName.length - 2); load.metadata[metaName] = load.metadata[metaName] || []; + load.metadata[metaName].push(metaValue); } - - // temporary backwards compat for previous "deps" syntax - if (load.metadata[metaName] instanceof Array) + else if (load.metadata[metaName] instanceof Array) { + // temporary backwards compat for previous "deps" syntax + warn.call(this, 'Module ' + load.name + ' contains deprecated "deps ' + metaValue + '" meta syntax.\nThis should be updated to "deps[] ' + metaValue + '" for pushing to array meta.'); load.metadata[metaName].push(metaValue); - else + } + else { setMetaProperty(load.metadata, metaName, metaValue); + } + } + else { + load.metadata[metaString] = true; } } } @@ -3517,7 +4262,8 @@ return translate.call(this, load); }; }); - })();/* + })(); + /* System bundles Allows a bundle module to be specified which will be dynamically @@ -3531,6 +4277,24 @@ In this way, the bundle becomes the request that provides the module */ + function getBundleFor(loader, name) { + // check if it is in an already-loaded bundle + for (var b in loader.loadedBundles_) + if (indexOf.call(loader.bundles[b], name) != -1) + return Promise.resolve(b); + + // check if it is a new bundle + for (var b in loader.bundles) + if (indexOf.call(loader.bundles[b], name) != -1) + return loader.normalize(b) + .then(function(normalized) { + loader.bundles[normalized] = loader.bundles[b]; + loader.loadedBundles_[normalized] = true; + return normalized; + }); + + return Promise.resolve(); + } (function() { // bundles support (just like RequireJS) @@ -3546,51 +4310,25 @@ }; }); - function loadFromBundle(loader, bundle) { - return Promise.resolve(loader.normalize(bundle)) - .then(function(normalized) { - loader.loadedBundles_[normalized] = true; - loader.bundles[normalized] = loader.bundles[normalized] || loader.bundles[bundle]; - return loader.load(normalized); - }) - .then(function() { - return ''; - }); - } - // assign bundle metadata for bundle loads hook('locate', function(locate) { - return function(load) { - if (load.name in this.loadedBundles_ || load.name in this.bundles) - load.metadata.bundle = true; - - return locate.call(this, load); - }; - }); - - hook('fetch', function(fetch) { return function(load) { var loader = this; - if (loader.trace) - return fetch.call(loader, load); - - // if already defined, no need to load a bundle - if (load.name in loader.defined) - return ''; - - // check if it is in an already-loaded bundle - for (var b in loader.loadedBundles_) { - if (indexOf.call(loader.bundles[b], load.name) != -1) - return loadFromBundle(loader, b); - } + if (load.name in loader.loadedBundles_ || load.name in loader.bundles) + load.metadata.bundle = true; - // check if it is a new bundle - for (var b in loader.bundles) { - if (indexOf.call(loader.bundles[b], load.name) != -1) - return loadFromBundle(loader, b); - } + // if not already defined, check if we need to load a bundle + if (!(load.name in loader.defined)) + return getBundleFor(loader, load.name) + .then(function(bundleName) { + if (bundleName) + return loader.load(bundleName); + }) + .then(function() { + return locate.call(loader, load); + }); - return fetch.call(loader, load); + return locate.call(this, load); }; }); })(); @@ -3637,111 +4375,9 @@ }); })(); - /* - * Conditions Extension - * - * Allows a condition module to alter the resolution of an import via syntax: - * - * import $ from 'jquery/#{browser}'; - * - * Will first load the module 'browser' via `System.import('browser')` and - * take the default export of that module. - * If the default export is not a string, an error is thrown. - * - * We then substitute the string into the require to get the conditional resolution - * enabling environment-specific variations like: - * - * import $ from 'jquery/ie' - * import $ from 'jquery/firefox' - * import $ from 'jquery/chrome' - * import $ from 'jquery/safari' - * - * It can be useful for a condition module to define multiple conditions. - * This can be done via the `.` modifier to specify a member expression: - * - * import 'jquery/#{browser.grade}' - * - * Where the `grade` export of the `browser` module is taken for substitution. - * - * Note that `/` and a leading `.` are not permitted within conditional modules - * so that this syntax can be well-defined. - * - * - * Boolean Conditionals - * - * For polyfill modules, that are used as imports but have no module value, - * a binary conditional allows a module not to be loaded at all if not needed: - * - * import 'es5-shim#?conditions.needs-es5shim' - * - */ - (function() { - - var conditionalRegEx = /#\{[^\}]+\}|#\?.+$/; - - hookConstructor(function(constructor) { - return function() { - constructor.call(this); - - // standard environment module, starting small as backwards-compat matters! - this.set('@system-env', this.newModule({ - browser: isBrowser - })); - }; - }); - - hook('normalize', function(normalize) { - return function(name, parentName, parentAddress) { - var loader = this; - var conditionalMatch = name.match(conditionalRegEx); - if (conditionalMatch) { - var substitution = conditionalMatch[0][1] != '?'; - - var conditionModule = substitution ? conditionalMatch[0].substr(2, conditionalMatch[0].length - 3) : conditionalMatch[0].substr(2); - - if (conditionModule[0] == '.' || conditionModule.indexOf('/') != -1) - throw new TypeError('Invalid condition ' + conditionalMatch[0] + '\n\tCondition modules cannot contain . or / in the name.'); - - var conditionExport = 'default'; - var conditionExportIndex = conditionModule.indexOf('.'); - if (conditionExportIndex != -1) { - conditionExport = conditionModule.substr(conditionExportIndex + 1); - conditionModule = conditionModule.substr(0, conditionExportIndex); - } - - var booleanNegation = !substitution && conditionModule[0] == '~'; - if (booleanNegation) - conditionModule = conditionModule.substr(1); - - return loader['import'](conditionModule, parentName, parentAddress) - .then(function(m) { - var conditionValue = readMemberExpression(conditionExport, m); - - if (substitution) { - if (typeof conditionValue !== 'string') - throw new TypeError('The condition value for ' + conditionalMatch[0] + ' doesn\'t resolving to a string.'); - name = name.replace(conditionalRegEx, conditionValue); - } - else { - if (typeof conditionValue !== 'boolean') - throw new TypeError('The condition value for ' + conditionalMatch[0] + ' isn\'t resolving to a boolean.'); - if (booleanNegation) - conditionValue = !conditionValue; - if (!conditionValue) - name = '@empty'; - else - name = name.replace(conditionalRegEx, ''); - } - return normalize.call(loader, name, parentName, parentAddress); - }); - } - - return Promise.resolve(normalize.call(loader, name, parentName, parentAddress)); - }; - }); - - })();System = new SystemJSLoader(); - System.constructor = SystemJSLoader; // -- exporting -- + System = new SystemJSLoader(); + System.version = '0.19.0 Standard'; + // -- exporting -- if (typeof exports === 'object') module.exports = Loader; From 2b73c09d1957e74aab8bff7cff5cb76abfa41eeb Mon Sep 17 00:00:00 2001 From: Maciek Stasieluk Date: Fri, 25 Sep 2015 21:25:03 +0200 Subject: [PATCH 2/8] fix !exports extension --- CHANGELOG.md | 4 +- extensions/exports.js | 42 +- system.js | 7 +- vendor/system.js | 960 +++++++++++++++++++++++------------------- 4 files changed, 558 insertions(+), 455 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17f5736..1d561bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,12 @@ - [BREAKING CHANGE] Meteor 1.2 is required to work - [BREAKING CHANGE] **All absolute paths to modules must start with `/`** -- [BREAKING CHANGE] `!vars` was replaced by `!exports` +- [BREAKING CHANGE] `!vars` was rewritten and replaced by `!exports` - [BREAKING CHANGE] Remove backward compatibility for deprecated package syntax `author:package` - Change in internal module naming (Potentially breaking change) - Make use of new build plugins API -- Update `SystemJS` to 0.19.0 +- Update `SystemJS` to 0.19.3 - Use Meteor Promise polyfill instead of this shipped with SystemJS - Improve error handling (stack traces are now easier to read when errors are thrown inside module) - Update `babel-compiler` to x diff --git a/extensions/exports.js b/extensions/exports.js index 9574a6d..7da4413 100644 --- a/extensions/exports.js +++ b/extensions/exports.js @@ -1,23 +1,27 @@ -/** - * Following script allows to import variables exported from packages - * @example import {UniCollection, UniUsers} from '{universe:collection}!exports' - * import {DDP} from '{ddp}!exports' +/* + * Following script allows to import variables exported from Meteor packages + * @example `import {UniCollection, UniUsers} from '{universe:collection}!exports'` + * @example `import {DDP} from '{ddp}!exports'` */ -var packageRegex = /^\/modules\/packages\/([\w-]*?)\/([\w-]+)!exports$/; -const {locate} = System; +System.set('exports', System.newModule({ + fetch() { + // we don't need to fetch anything for this to work + return ''; + }, + instantiate ({name}) { + return new Promise((resolve, reject) => { + // grab author and package name + let [, author, packageName] = /^\/_modules_\/packages\/([\w-]*?)\/([\w-]+)!exports$/.exec(name); + let fullPackageName = (author ? author + ':' : '') + packageName; -System.locate = function (data) { - console.info('locate', data); - if (packageRegex.test(data.name)) { - console.warn('inside', data.name); - let packageName = data.name.replace(packageRegex, '$1:$2'); - if (Package[packageName]) { - //Getting access for exported variables by meteor package - System.registerDynamic(data, [], true, function (require, exports, module) { - module.exports = Package[packageName]; - }); - } + if (Package[fullPackageName]) { + // yay we got a package! + resolve(Package[fullPackageName]); + } else { + // there is no such package + reject(new Error(`[Universe Modules]: Cannot find Meteor package exports for {${fullPackageName}}`)); + } + }); } - return locate.apply(this, arguments); -}; \ No newline at end of file +})); \ No newline at end of file diff --git a/system.js b/system.js index d49190a..45691ef 100644 --- a/system.js +++ b/system.js @@ -91,8 +91,6 @@ System.normalize = function (name, parentName, parentAddress) { name = name.slice(0, -7); } - console.log('normalize', name, parentName, '->', convertMeteorModuleName(name, parentName)); - // Load original normalize return normalize.call(this, convertMeteorModuleName(name, parentName), parentName, parentAddress); }; @@ -102,7 +100,6 @@ System.normalize = function (name, parentName, parentAddress) { * parentName: the canonical module name for the requesting module */ System.normalizeSync = function (name, parentName) { - console.log('normalizeSync', name, parentName, '->', convertMeteorModuleName(name, parentName)); return normalizeSync.call(this, convertMeteorModuleName(name, parentName), parentName); }; @@ -122,8 +119,12 @@ System.normalizeSync = function (name, parentName) { * can be modified */ System.fetch = function (load) { + //console.log('fetch', load); + var promise = fetch.call(this, load); + //console.log('promise', promise, typeof promise); + if (!promise) { // not really a promise return promise; diff --git a/vendor/system.js b/vendor/system.js index 13bf78b..29a5148 100644 --- a/vendor/system.js +++ b/vendor/system.js @@ -1,5 +1,5 @@ /* - * SystemJS v0.19.0 + * SystemJS v0.19.3 */ (function() { function bootstrap() {(function(__global) { @@ -958,7 +958,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,11 +975,11 @@ } // 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); } @@ -1049,12 +1049,13 @@ } } - if (doTimeout) + if (doTimeout) { setTimeout(function() { xhr.send(); }, 0); - - xhr.send(null); + } else { + xhr.send(null); + } }; } else if (typeof require != 'undefined') { @@ -1173,105 +1174,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); @@ -1288,7 +1191,7 @@ 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() {}); @@ -1384,7 +1287,128 @@ function warn(msg) { if (this.warnings && typeof console != 'undefined' && console.warn) console.warn(msg); - }/* + }// we define a __exec for globally-scoped execution +// used by module format implementations + var __exec; + + (function() { + + // 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 = loader; + curLoad = load; + } + function postExec() { + if (--callCounter == 0) + __global.System = curSystem; + curLoad = undefined; + } + + // 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; + + this.reduceRegister_(curLoad, register); + return true; + }; + }); + + var hasBtoa = typeof btoa != 'undefined'; + + 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; + + return (wrap ? '(function(System) {' : '') + 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))) || '') + } + + // 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 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); + } + }; + } + + // 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, 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; + } + } + else { + // global scoped eval for node + var vmModule = 'vm'; + var vm = require(vmModule); + __exec = function(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); + } + }; + } + + })();/* SystemJS map support Provides map configuration through @@ -1501,18 +1525,25 @@ name = normalize.apply(this, arguments); // relative URL-normalization - if (name[0] == '.' || name[0] == '/') - return new URL(name, parentName || baseURIObj).href; + if (name[0] == '.' || name[0] == '/') { + if (parentName) + return new URL(name, parentName.replace(/#/g, '%05')).href.replace(/%05/g, '#'); + else + return new URL(name, baseURIObj).href; + } return name; }; }); -// 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; }); }; }); @@ -1679,7 +1710,8 @@ 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') + // (unless already a package which would have skipped extension) + if (!this.packages[prop] && this.defaultJSExtensions && p.substr(p.length - 3, 3) != '.js') prop = prop.substr(0, prop.length - 3); this.packages[prop]= this.packages[prop] || {}; @@ -1956,7 +1988,7 @@ if (normalized.length == pkgName.length + 1 && normalized[pkgName.length] == '/') return normalized; - // no submap if name is package itself + // also no submap if name is package itself (import 'pkg' -> 'path/to/pkg.js') if (normalized.length == pkgName.length) return normalized + (loader.defaultJSExtensions && normalized.substr(normalized.length - 3, 3) != '.js' ? '.js' : ''); @@ -2306,29 +2338,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: