147 lines
5.1 KiB
JavaScript
147 lines
5.1 KiB
JavaScript
|
/*
|
||
|
* 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'
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
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'];
|
||
|
|
||
|
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(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(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, 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);
|
||
|
});
|
||
|
};
|
||
|
});
|