/* * 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 `SystemJS.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' * */ var sysConditions = ['browser', 'node', 'dev', 'build', 'production', 'default']; 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); if (negation) warn.call(this, 'Condition negation form "' + condition + '" is deprecated for "' + conditionModule + '|~' + conditionExport + '"'); if (conditionExport[0] == '~') { negation = true; conditionExport = conditionExport.substr(1); } } else { conditionExport = 'default'; conditionModule = condition.substr(negation); if (sysConditions.indexOf(conditionModule) != -1) { conditionExport = conditionModule; conditionModule = null; } } return { module: conditionModule || '@system-env', prop: conditionExport, negate: negation }; } function serializeCondition(conditionObj) { return conditionObj.module + '|' + (conditionObj.negate ? '~' : '') + conditionObj.prop; } function resolveCondition(conditionObj, parentName, bool) { var self = this; return this.normalize(conditionObj.module, parentName) .then(function(normalizedCondition) { return self.load(normalizedCondition) .then(function(q) { var m = readMemberExpression(conditionObj.prop, self.get(normalizedCondition)); if (bool && typeof m != 'boolean') throw new TypeError('Condition ' + serializeCondition(conditionObj) + ' did not resolve to a boolean.'); 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.call(this, 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.module = 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.'); if (conditionValue.indexOf('/') != -1) throw new TypeError('Unabled to interpolate conditional ' + name + (parentName ? ' in ' + parentName : '') + '\n\tThe condition value ' + conditionValue + ' cannot contain a "/" separator.'); 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.call(this, 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'; }); } // normalizeSync does not parse conditionals at all although it could hook('normalize', function(normalize) { return function(name, parentName, skipExt) { var loader = this; return booleanConditional.call(loader, name, parentName) .then(function(name) { return normalize.call(loader, name, parentName, skipExt); }) .then(function(normalized) { return interpolateConditional.call(loader, normalized, parentName); }); }; });