mirror of
https://github.com/systemjs/systemjs.git
synced 2026-01-25 14:57:38 +00:00
870 lines
30 KiB
JavaScript
870 lines
30 KiB
JavaScript
import RegisterLoader from 'es-module-loader/core/register-loader.js';
|
|
import { getMapMatch, readMemberExpression, extendMeta, addToError, resolveIfNotPlain,
|
|
baseURI, CONFIG, METADATA, applyPaths, resolvedPromise, getPackage } from './common.js';
|
|
import { setPkgConfig, createPackage } from './config.js';
|
|
import fetch from './fetch.js';
|
|
|
|
export function createMetadata () {
|
|
return {
|
|
pluginKey: undefined,
|
|
pluginArgument: undefined,
|
|
pluginModule: undefined,
|
|
packageKey: undefined,
|
|
packageConfig: undefined,
|
|
load: undefined
|
|
};
|
|
}
|
|
|
|
function getCoreParentMetadata (loader, config, parentKey) {
|
|
var parentMetadata = createMetadata();
|
|
|
|
if (parentKey) {
|
|
// detect parent plugin
|
|
// we just need pluginKey to be truthy for package configurations
|
|
// so we duplicate it as pluginArgument - although not correct its not used
|
|
var parentPluginIndex;
|
|
if (config.pluginFirst) {
|
|
if ((parentPluginIndex = parentKey.lastIndexOf('!')) !== -1)
|
|
parentMetadata.pluginArgument = parentMetadata.pluginKey = parentKey.substr(0, parentPluginIndex);
|
|
}
|
|
else {
|
|
if ((parentPluginIndex = parentKey.indexOf('!')) !== -1)
|
|
parentMetadata.pluginArgument = parentMetadata.pluginKey = parentKey.substr(parentPluginIndex + 1);
|
|
}
|
|
}
|
|
|
|
return parentMetadata;
|
|
}
|
|
|
|
function getParentMetadata (loader, config, parentKey) {
|
|
var parentMetadata = createMetadata();
|
|
|
|
if (parentKey) {
|
|
// detect parent plugin
|
|
// we just need pluginKey to be truthy for package configurations
|
|
// so we duplicate it as pluginArgument - although not correct its not used
|
|
var parentPluginIndex;
|
|
if (config.pluginFirst) {
|
|
if ((parentPluginIndex = parentKey.lastIndexOf('!')) !== -1)
|
|
parentMetadata.pluginArgument = parentMetadata.pluginKey = parentKey.substr(0, parentPluginIndex);
|
|
}
|
|
else {
|
|
if ((parentPluginIndex = parentKey.indexOf('!')) !== -1)
|
|
parentMetadata.pluginArgument = parentMetadata.pluginKey = parentKey.substr(parentPluginIndex + 1);
|
|
}
|
|
|
|
// detect parent package
|
|
parentMetadata.packageKey = getMapMatch(config.packages, parentKey);
|
|
if (parentMetadata.packageKey)
|
|
parentMetadata.packageConfig = config.packages[parentMetadata.packageKey];
|
|
}
|
|
|
|
return parentMetadata;
|
|
}
|
|
|
|
export function normalize (key, parentKey) {
|
|
var config = this[CONFIG];
|
|
|
|
var metadata = createMetadata();
|
|
var parentMetadata = getParentMetadata(this, config, parentKey);
|
|
|
|
var loader = this;
|
|
|
|
return Promise.resolve()
|
|
|
|
// boolean conditional
|
|
.then(function () {
|
|
// first we normalize the conditional
|
|
var booleanIndex = key.lastIndexOf('#?');
|
|
|
|
if (booleanIndex === -1)
|
|
return Promise.resolve(key);
|
|
|
|
var conditionObj = parseCondition.call(loader, key.substr(booleanIndex + 2));
|
|
|
|
// in builds, return normalized conditional
|
|
/*if (this.builder)
|
|
return this.resolve(conditionObj.module, parentKey)
|
|
.then(function (conditionModule) {
|
|
conditionObj.module = conditionModule;
|
|
return key.substr(0, booleanIndex) + '#?' + serializeCondition(conditionObj);
|
|
});*/
|
|
|
|
return resolveCondition.call(loader, conditionObj, parentKey, true)
|
|
.then(function (conditionValue) {
|
|
return conditionValue ? key.substr(0, booleanIndex) : '@empty';
|
|
});
|
|
})
|
|
|
|
// plugin
|
|
.then(function (key) {
|
|
var parsed = parsePlugin(config.pluginFirst, key);
|
|
|
|
if (!parsed)
|
|
return packageResolve.call(loader, config, key, parentMetadata && parentMetadata.pluginArgument || parentKey, metadata, parentMetadata, false);
|
|
|
|
metadata.pluginKey = parsed.plugin;
|
|
|
|
return Promise.all([
|
|
packageResolve.call(loader, config, parsed.argument, parentMetadata && parentMetadata.pluginArgument || parentKey, metadata, parentMetadata, true),
|
|
loader.resolve(parsed.plugin, parentKey)
|
|
])
|
|
.then(function (normalized) {
|
|
metadata.pluginArgument = normalized[0];
|
|
metadata.pluginKey = normalized[1];
|
|
|
|
// don't allow a plugin to load itself
|
|
if (metadata.pluginArgument === metadata.pluginKey)
|
|
throw new Error('Plugin ' + metadata.pluginArgument + ' cannot load itself, make sure it is excluded from any wildcard meta configuration via a custom loader: false rule.');
|
|
|
|
return combinePluginParts(config.pluginFirst, normalized[0], normalized[1]);
|
|
});
|
|
})
|
|
.then(function (normalized) {
|
|
return interpolateConditional.call(loader, normalized, parentKey, parentMetadata);
|
|
})
|
|
.then(function (normalized) {
|
|
setMeta.call(loader, config, normalized, metadata);
|
|
|
|
if (metadata.pluginKey || !metadata.load.loader)
|
|
return normalized;
|
|
|
|
// loader by configuration
|
|
// normalizes to parent to support package loaders
|
|
return loader.resolve(metadata.load.loader, normalized)
|
|
.then(function (pluginKey) {
|
|
metadata.pluginKey = pluginKey;
|
|
metadata.pluginArgument = normalized;
|
|
return normalized;
|
|
});
|
|
})
|
|
.then(function (normalized) {
|
|
loader[METADATA][normalized] = metadata;
|
|
return normalized;
|
|
});
|
|
}
|
|
|
|
// normalization function used for registry keys
|
|
// just does coreResolve without map
|
|
export function decanonicalize (config, key) {
|
|
var parsed = parsePlugin(config.pluginFirst, key);
|
|
|
|
// plugin
|
|
if (parsed) {
|
|
var pluginKey = decanonicalize.call(this, config, parsed.plugin);
|
|
return combinePluginParts(config.pluginFirst, coreResolve.call(this, config, parsed.argument, undefined, false, false), pluginKey);
|
|
}
|
|
|
|
return coreResolve.call(this, config, key, undefined, false, false);
|
|
}
|
|
|
|
export function normalizeSync (key, parentKey) {
|
|
var config = this[CONFIG];
|
|
|
|
// normalizeSync is metadataless, so create metadata
|
|
var metadata = createMetadata();
|
|
var parentMetadata = parentMetadata || getParentMetadata(this, config, parentKey);
|
|
|
|
var parsed = parsePlugin(config.pluginFirst, key);
|
|
|
|
// plugin
|
|
if (parsed) {
|
|
metadata.pluginKey = normalizeSync.call(this, parsed.plugin, parentKey);
|
|
return combinePluginParts(config.pluginFirst,
|
|
packageResolveSync.call(this, config, parsed.argument, parentMetadata.pluginArgument || parentKey, metadata, parentMetadata, !!metadata.pluginKey),
|
|
metadata.pluginKey);
|
|
}
|
|
|
|
return packageResolveSync.call(this, config, key, parentMetadata.pluginArgument || parentKey, metadata, parentMetadata, !!metadata.pluginKey);
|
|
}
|
|
|
|
export function coreResolve (config, key, parentKey, doMap, packageName) {
|
|
var relativeResolved = resolveIfNotPlain(key, parentKey || baseURI);
|
|
|
|
// standard URL resolution
|
|
if (relativeResolved)
|
|
return applyPaths(config.baseURL, config.paths, relativeResolved);
|
|
|
|
// plain keys not starting with './', 'x://' and '/' go through custom resolution
|
|
if (doMap) {
|
|
var mapMatch = getMapMatch(config.map, key);
|
|
|
|
if (mapMatch) {
|
|
key = config.map[mapMatch] + key.substr(mapMatch.length);
|
|
|
|
relativeResolved = resolveIfNotPlain(key, baseURI);
|
|
if (relativeResolved)
|
|
return applyPaths(config.baseURL, config.paths, relativeResolved);
|
|
}
|
|
}
|
|
|
|
if (this.registry.has(key))
|
|
return key;
|
|
|
|
if (key.substr(0, 6) === '@node/')
|
|
return key;
|
|
|
|
var trailingSlash = packageName && key[key.length - 1] !== '/';
|
|
var resolved = applyPaths(config.baseURL, config.paths, trailingSlash ? key + '/' : key);
|
|
if (trailingSlash)
|
|
return resolved.substr(0, resolved.length - 1);
|
|
return resolved;
|
|
}
|
|
|
|
function packageResolveSync (config, key, parentKey, metadata, parentMetadata, skipExtensions) {
|
|
// ignore . since internal maps handled by standard package resolution
|
|
if (parentMetadata && parentMetadata.packageConfig && key[0] !== '.') {
|
|
var parentMap = parentMetadata.packageConfig.map;
|
|
var parentMapMatch = parentMap && getMapMatch(parentMap, key);
|
|
|
|
if (parentMapMatch && typeof parentMap[parentMapMatch] === 'string') {
|
|
var mapped = doMapSync(this, config, parentMetadata.packageConfig, parentMetadata.packageKey, parentMapMatch, key, metadata, skipExtensions);
|
|
if (mapped)
|
|
return mapped;
|
|
}
|
|
}
|
|
|
|
var normalized = coreResolve.call(this, config, key, parentKey, true, true);
|
|
|
|
var pkgConfigMatch = getPackageConfigMatch(config, normalized);
|
|
metadata.packageKey = pkgConfigMatch && pkgConfigMatch.packageKey || getMapMatch(config.packages, normalized);
|
|
|
|
if (!metadata.packageKey)
|
|
return normalized;
|
|
|
|
if (config.packageConfigKeys.indexOf(normalized) !== -1) {
|
|
metadata.packageKey = undefined;
|
|
return normalized;
|
|
}
|
|
|
|
metadata.packageConfig = config.packages[metadata.packageKey] || (config.packages[metadata.packageKey] = createPackage());
|
|
|
|
var subPath = normalized.substr(metadata.packageKey.length + 1);
|
|
|
|
return applyPackageConfigSync(this, config, metadata.packageConfig, metadata.packageKey, subPath, metadata, skipExtensions);
|
|
}
|
|
|
|
function packageResolve (config, key, parentKey, metadata, parentMetadata, skipExtensions) {
|
|
var loader = this;
|
|
|
|
return resolvedPromise
|
|
.then(function () {
|
|
// ignore . since internal maps handled by standard package resolution
|
|
if (parentMetadata && parentMetadata.packageConfig && key.substr(0, 2) !== './') {
|
|
var parentMap = parentMetadata.packageConfig.map;
|
|
var parentMapMatch = parentMap && getMapMatch(parentMap, key);
|
|
|
|
if (parentMapMatch)
|
|
return doMap(loader, config, parentMetadata.packageConfig, parentMetadata.packageKey, parentMapMatch, key, metadata, skipExtensions);
|
|
}
|
|
|
|
return resolvedPromise;
|
|
})
|
|
.then(function (mapped) {
|
|
if (mapped)
|
|
return mapped;
|
|
|
|
// apply map, core, paths, contextual package map
|
|
var normalized = coreResolve.call(loader, config, key, parentKey, true, true);
|
|
|
|
var pkgConfigMatch = getPackageConfigMatch(config, normalized);
|
|
metadata.packageKey = pkgConfigMatch && pkgConfigMatch.packageKey || getMapMatch(config.packages, normalized);
|
|
|
|
if (!metadata.packageKey)
|
|
return Promise.resolve(normalized);
|
|
|
|
if (config.packageConfigKeys.indexOf(normalized) !== -1) {
|
|
metadata.packageKey = undefined;
|
|
metadata.load = createMeta();
|
|
metadata.load.format = 'json';
|
|
// ensure no loader
|
|
metadata.load.loader = '';
|
|
return Promise.resolve(normalized);
|
|
}
|
|
|
|
metadata.packageConfig = config.packages[metadata.packageKey] || (config.packages[metadata.packageKey] = createPackage());
|
|
|
|
// load configuration when it matches packageConfigPaths, not already configured, and not the config itself
|
|
var loadConfig = pkgConfigMatch && !metadata.packageConfig.configured;
|
|
|
|
return (loadConfig ? loadPackageConfigPath(loader, config, pkgConfigMatch.configPath, metadata) : resolvedPromise)
|
|
.then(function () {
|
|
var subPath = normalized.substr(metadata.packageKey.length + 1);
|
|
|
|
return applyPackageConfig(loader, config, metadata.packageConfig, metadata.packageKey, subPath, metadata, skipExtensions);
|
|
});
|
|
});
|
|
}
|
|
|
|
function createMeta () {
|
|
return {
|
|
extension: '',
|
|
deps: undefined,
|
|
format: undefined,
|
|
loader: undefined,
|
|
scriptLoad: undefined,
|
|
globals: undefined,
|
|
nonce: undefined,
|
|
integrity: undefined,
|
|
sourceMap: undefined,
|
|
exports: undefined,
|
|
encapsulateGlobal: false,
|
|
crossOrigin: undefined,
|
|
cjsRequireDetection: true,
|
|
cjsDeferDepsExecute: false,
|
|
esModule: false
|
|
};
|
|
}
|
|
|
|
function setMeta (config, key, metadata) {
|
|
metadata.load = metadata.load || createMeta();
|
|
|
|
// apply wildcard metas
|
|
var bestDepth = 0;
|
|
var wildcardIndex;
|
|
for (var module in config.meta) {
|
|
wildcardIndex = module.indexOf('*');
|
|
if (wildcardIndex === -1)
|
|
continue;
|
|
if (module.substr(0, wildcardIndex) === key.substr(0, wildcardIndex)
|
|
&& module.substr(wildcardIndex + 1) === key.substr(key.length - module.length + wildcardIndex + 1)) {
|
|
var depth = module.split('/').length;
|
|
if (depth > bestDepth)
|
|
bestDepth = depth;
|
|
extendMeta(metadata.load, config.meta[module], bestDepth !== depth);
|
|
}
|
|
}
|
|
|
|
// apply exact meta
|
|
if (config.meta[key])
|
|
extendMeta(metadata.load, config.meta[key], false);
|
|
|
|
// apply package meta
|
|
if (metadata.packageKey) {
|
|
var subPath = key.substr(metadata.packageKey.length + 1);
|
|
|
|
var meta = {};
|
|
if (metadata.packageConfig.meta) {
|
|
var bestDepth = 0;
|
|
getMetaMatches(metadata.packageConfig.meta, subPath, function (metaPattern, matchMeta, matchDepth) {
|
|
if (matchDepth > bestDepth)
|
|
bestDepth = matchDepth;
|
|
extendMeta(meta, matchMeta, matchDepth && bestDepth > matchDepth);
|
|
});
|
|
|
|
extendMeta(metadata.load, meta, false);
|
|
}
|
|
|
|
// format
|
|
if (metadata.packageConfig.format && !metadata.pluginKey && !metadata.load.loader)
|
|
metadata.load.format = metadata.load.format || metadata.packageConfig.format;
|
|
}
|
|
}
|
|
|
|
function parsePlugin (pluginFirst, key) {
|
|
var argumentKey;
|
|
var pluginKey;
|
|
|
|
var pluginIndex = pluginFirst ? key.indexOf('!') : key.lastIndexOf('!');
|
|
|
|
if (pluginIndex === -1)
|
|
return;
|
|
|
|
if (pluginFirst) {
|
|
argumentKey = key.substr(pluginIndex + 1);
|
|
pluginKey = key.substr(0, pluginIndex);
|
|
}
|
|
else {
|
|
argumentKey = key.substr(0, pluginIndex);
|
|
pluginKey = key.substr(pluginIndex + 1) || argumentKey.substr(argumentKey.lastIndexOf('.') + 1);
|
|
}
|
|
|
|
return {
|
|
argument: argumentKey,
|
|
plugin: pluginKey
|
|
};
|
|
}
|
|
|
|
// put key back together after parts have been normalized
|
|
function combinePluginParts (pluginFirst, argumentKey, pluginKey) {
|
|
if (pluginFirst)
|
|
return pluginKey + '!' + argumentKey;
|
|
else
|
|
return argumentKey + '!' + pluginKey;
|
|
}
|
|
|
|
/*
|
|
* Package Configuration Extension
|
|
*
|
|
* Example:
|
|
*
|
|
* SystemJS.packages = {
|
|
* jquery: {
|
|
* main: 'index.js', // when not set, package key is requested directly
|
|
* format: 'amd',
|
|
* defaultExtension: 'ts', // defaults to 'js', can be set to false
|
|
* modules: {
|
|
* '*.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',
|
|
* './custom-condition.js|~export': './index-custom.js'
|
|
* }
|
|
* },
|
|
* // allows for setting package-prefixed depCache
|
|
* // keys are normalized module keys 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': ['external-dep'],
|
|
* 'external-dep/path.js': ['./another.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 can have a leading "./" can be added optionally
|
|
* - map and defaultExtension are applied to the main
|
|
* - defaultExtension adds the extension only if the exact extension is not present
|
|
|
|
* - if a meta value is available for a module, map and defaultExtension are skipped
|
|
* - like global map, package map also applies to subpaths (sizzle/x, ./vendor/another/sub)
|
|
* - condition module map is '@env' module in package or '@system-env' globally
|
|
* - map targets support conditional interpolation ('./x': './x.#{|env}.js')
|
|
* - internal package map targets cannot use boolean conditionals
|
|
*
|
|
* 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 key to derive the package
|
|
* configuration from. This allows dynamic loading of non-predetermined code, a key use
|
|
* case in SystemJS.
|
|
*
|
|
* Example:
|
|
*
|
|
* SystemJS.packageConfigPaths = ['packages/test/package.json', 'packages/*.json'];
|
|
*
|
|
* // will first request 'packages/new-package/package.json' for the package config
|
|
* // before completing the package request to 'packages/new-package/path'
|
|
* SystemJS.import('packages/new-package/path');
|
|
*
|
|
* // will first request 'packages/test/package.json' before the main
|
|
* SystemJS.import('packages/test');
|
|
*
|
|
* When a package matches packageConfigPaths, it will always send a config request for
|
|
* the package configuration.
|
|
* The package key itself is taken to be the match up to and including the last wildcard
|
|
* or trailing slash.
|
|
* The most specific package config path will be used.
|
|
* Any existing package configurations for the package will deeply merge with the
|
|
* package config, with the existing package configurations taking preference.
|
|
* To opt-out of the package configuration request for a package that matches
|
|
* packageConfigPaths, use the { configured: true } package config option.
|
|
*
|
|
*/
|
|
|
|
function addDefaultExtension (config, pkg, pkgKey, subPath, skipExtensions) {
|
|
// don't apply extensions to folders or if defaultExtension = false
|
|
if (!subPath || !pkg.defaultExtension || subPath[subPath.length - 1] === '/' || skipExtensions)
|
|
return subPath;
|
|
|
|
var metaMatch = false;
|
|
|
|
// exact meta or meta with any content after the last wildcard skips extension
|
|
if (pkg.meta)
|
|
getMetaMatches(pkg.meta, subPath, function (metaPattern, matchMeta, matchDepth) {
|
|
if (matchDepth === 0 || metaPattern.lastIndexOf('*') !== metaPattern.length - 1)
|
|
return metaMatch = true;
|
|
});
|
|
|
|
// exact global meta or meta with any content after the last wildcard skips extension
|
|
if (!metaMatch && config.meta)
|
|
getMetaMatches(config.meta, pkgKey + '/' + subPath, function (metaPattern, matchMeta, matchDepth) {
|
|
if (matchDepth === 0 || metaPattern.lastIndexOf('*') !== metaPattern.length - 1)
|
|
return metaMatch = true;
|
|
});
|
|
|
|
if (metaMatch)
|
|
return subPath;
|
|
|
|
// work out what the defaultExtension is and add if not there already
|
|
var defaultExtension = '.' + pkg.defaultExtension;
|
|
if (subPath.substr(subPath.length - defaultExtension.length) !== defaultExtension)
|
|
return subPath + defaultExtension;
|
|
else
|
|
return subPath;
|
|
}
|
|
|
|
function applyPackageConfigSync (loader, config, pkg, pkgKey, subPath, metadata, skipExtensions) {
|
|
// main
|
|
if (!subPath) {
|
|
if (pkg.main)
|
|
subPath = pkg.main.substr(0, 2) === './' ? pkg.main.substr(2) : pkg.main;
|
|
else
|
|
// also no submap if key is package itself (import 'pkg' -> 'path/to/pkg.js')
|
|
// NB can add a default package main convention here
|
|
// if it becomes internal to the package then it would no longer be an exit path
|
|
return pkgKey;
|
|
}
|
|
|
|
// map config checking without then with extensions
|
|
if (pkg.map) {
|
|
var mapPath = './' + subPath;
|
|
|
|
var mapMatch = getMapMatch(pkg.map, mapPath);
|
|
|
|
// we then check map with the default extension adding
|
|
if (!mapMatch) {
|
|
mapPath = './' + addDefaultExtension(loader, pkg, pkgKey, subPath, skipExtensions);
|
|
if (mapPath !== './' + subPath)
|
|
mapMatch = getMapMatch(pkg.map, mapPath);
|
|
}
|
|
if (mapMatch) {
|
|
var mapped = doMapSync(loader, config, pkg, pkgKey, mapMatch, mapPath, metadata, skipExtensions);
|
|
if (mapped)
|
|
return mapped;
|
|
}
|
|
}
|
|
|
|
// normal package resolution
|
|
return pkgKey + '/' + addDefaultExtension(loader, pkg, pkgKey, subPath, skipExtensions);
|
|
}
|
|
|
|
function validMapping (mapMatch, mapped, path) {
|
|
// allow internal ./x -> ./x/y or ./x/ -> ./x/y recursive maps
|
|
// but only if the path is exactly ./x and not ./x/z
|
|
if (mapped.substr(0, mapMatch.length) === mapMatch && path.length > mapMatch.length)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
function doMapSync (loader, config, pkg, pkgKey, mapMatch, path, metadata, skipExtensions) {
|
|
if (path[path.length - 1] === '/')
|
|
path = path.substr(0, path.length - 1);
|
|
var mapped = pkg.map[mapMatch];
|
|
|
|
if (typeof mapped === 'object')
|
|
throw new Error('Synchronous conditional normalization not supported sync normalizing ' + mapMatch + ' in ' + pkgKey);
|
|
|
|
if (!validMapping(mapMatch, mapped, path) || typeof mapped !== 'string')
|
|
return;
|
|
|
|
return packageResolveSync.call(this, config, mapped + path.substr(mapMatch.length), pkgKey + '/', metadata, metadata, skipExtensions);
|
|
}
|
|
|
|
function applyPackageConfig (loader, config, pkg, pkgKey, subPath, metadata, skipExtensions) {
|
|
// main
|
|
if (!subPath) {
|
|
if (pkg.main)
|
|
subPath = pkg.main.substr(0, 2) === './' ? pkg.main.substr(2) : pkg.main;
|
|
// also no submap if key is package itself (import 'pkg' -> 'path/to/pkg.js')
|
|
else
|
|
// NB can add a default package main convention here
|
|
// if it becomes internal to the package then it would no longer be an exit path
|
|
return Promise.resolve(pkgKey);
|
|
}
|
|
|
|
// map config checking without then with extensions
|
|
var mapPath, mapMatch;
|
|
|
|
if (pkg.map) {
|
|
mapPath = './' + subPath;
|
|
mapMatch = getMapMatch(pkg.map, mapPath);
|
|
|
|
// we then check map with the default extension adding
|
|
if (!mapMatch) {
|
|
mapPath = './' + addDefaultExtension(loader, pkg, pkgKey, subPath, skipExtensions);
|
|
if (mapPath !== './' + subPath)
|
|
mapMatch = getMapMatch(pkg.map, mapPath);
|
|
}
|
|
}
|
|
|
|
return (mapMatch ? doMap(loader, config, pkg, pkgKey, mapMatch, mapPath, metadata, skipExtensions) : resolvedPromise)
|
|
.then(function (mapped) {
|
|
if (mapped)
|
|
return Promise.resolve(mapped);
|
|
|
|
// normal package resolution / fallback resolution for no conditional match
|
|
return Promise.resolve(pkgKey + '/' + addDefaultExtension(loader, pkg, pkgKey, subPath, skipExtensions));
|
|
});
|
|
}
|
|
|
|
function doMap (loader, config, pkg, pkgKey, mapMatch, path, metadata, skipExtensions) {
|
|
if (path[path.length - 1] === '/')
|
|
path = path.substr(0, path.length - 1);
|
|
|
|
var mapped = pkg.map[mapMatch];
|
|
|
|
if (typeof mapped === 'string') {
|
|
if (!validMapping(mapMatch, mapped, path))
|
|
return resolvedPromise;
|
|
return packageResolve.call(loader, config, mapped + path.substr(mapMatch.length), pkgKey + '/', metadata, metadata, skipExtensions)
|
|
.then(function (normalized) {
|
|
return interpolateConditional.call(loader, normalized, pkgKey + '/', metadata);
|
|
});
|
|
}
|
|
|
|
// we use a special conditional syntax to allow the builder to handle conditional branch points further
|
|
/*if (loader.builder)
|
|
return Promise.resolve(pkgKey + '/#:' + path);*/
|
|
|
|
// we load all conditions upfront
|
|
var conditionPromises = [];
|
|
var conditions = [];
|
|
for (var e in mapped) {
|
|
var c = parseCondition(e);
|
|
conditions.push({
|
|
condition: c,
|
|
map: mapped[e]
|
|
});
|
|
conditionPromises.push(RegisterLoader.prototype.import.call(loader, c.module, pkgKey));
|
|
}
|
|
|
|
// map object -> conditional map
|
|
return Promise.all(conditionPromises)
|
|
.then(function (conditionValues) {
|
|
// first map condition to match is used
|
|
for (var i = 0; i < conditions.length; i++) {
|
|
var c = conditions[i].condition;
|
|
var value = readMemberExpression(c.prop, '__useDefault' in conditionValues[i] ? conditionValues[i].__useDefault : conditionValues[i]);
|
|
if (!c.negate && value || c.negate && !value)
|
|
return conditions[i].map;
|
|
}
|
|
})
|
|
.then(function (mapped) {
|
|
if (mapped) {
|
|
if (!validMapping(mapMatch, mapped, path))
|
|
return resolvedPromise;
|
|
return packageResolve.call(loader, config, mapped + path.substr(mapMatch.length), pkgKey + '/', metadata, metadata, skipExtensions)
|
|
.then(function (normalized) {
|
|
return interpolateConditional.call(loader, normalized, pkgKey + '/', metadata);
|
|
});
|
|
}
|
|
|
|
// no environment match -> fallback to original subPath by returning undefined
|
|
});
|
|
}
|
|
|
|
// check if the given normalized key matches a packageConfigPath
|
|
// if so, loads the config
|
|
var packageConfigPaths = {};
|
|
|
|
// data object for quick checks against package paths
|
|
function createPkgConfigPathObj (path) {
|
|
var lastWildcard = path.lastIndexOf('*');
|
|
var length = Math.max(lastWildcard + 1, path.lastIndexOf('/'));
|
|
return {
|
|
length: length,
|
|
regEx: new RegExp('^(' + path.substr(0, length).replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^\\/]+') + ')(\\/|$)'),
|
|
wildcard: lastWildcard !== -1
|
|
};
|
|
}
|
|
|
|
// most specific match wins
|
|
function getPackageConfigMatch (config, normalized) {
|
|
var pkgKey, exactMatch = false, configPath;
|
|
for (var i = 0; i < config.packageConfigPaths.length; i++) {
|
|
var packageConfigPath = config.packageConfigPaths[i];
|
|
var p = packageConfigPaths[packageConfigPath] || (packageConfigPaths[packageConfigPath] = createPkgConfigPathObj(packageConfigPath));
|
|
if (normalized.length < p.length)
|
|
continue;
|
|
var match = normalized.match(p.regEx);
|
|
if (match && (!pkgKey || (!(exactMatch && p.wildcard) && pkgKey.length < match[1].length))) {
|
|
pkgKey = match[1];
|
|
exactMatch = !p.wildcard;
|
|
configPath = pkgKey + packageConfigPath.substr(p.length);
|
|
}
|
|
}
|
|
|
|
if (!pkgKey)
|
|
return;
|
|
|
|
return {
|
|
packageKey: pkgKey,
|
|
configPath: configPath
|
|
};
|
|
}
|
|
|
|
function loadPackageConfigPath (loader, config, pkgConfigPath, metadata, normalized) {
|
|
var configLoader = loader.pluginLoader || loader;
|
|
|
|
// ensure we note this is a package config file path
|
|
// it will then be skipped from getting other normalizations itself to ensure idempotency
|
|
if (config.packageConfigKeys.indexOf(pkgConfigPath) === -1)
|
|
config.packageConfigKeys.push(pkgConfigPath);
|
|
|
|
return configLoader.import(pkgConfigPath)
|
|
.then(function (pkgConfig) {
|
|
setPkgConfig(metadata.packageConfig, pkgConfig, metadata.packageKey, true, config);
|
|
metadata.packageConfig.configured = true;
|
|
})
|
|
.catch(function (err) {
|
|
throw addToError(err, 'Unable to fetch package configuration file ' + pkgConfigPath);
|
|
});
|
|
}
|
|
|
|
function getMetaMatches (pkgMeta, subPath, matchFn) {
|
|
// wildcard 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)) {
|
|
// alow match function to return true for an exit path
|
|
if (matchFn(module, pkgMeta[dotRel + module], module.split('/').length))
|
|
return;
|
|
}
|
|
}
|
|
// exact meta
|
|
var exactMeta = pkgMeta[subPath] && Object.hasOwnProperty.call(pkgMeta, subPath) ? pkgMeta[subPath] : pkgMeta['./' + subPath];
|
|
if (exactMeta)
|
|
matchFn(exactMeta, exactMeta, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
var conditionExportIndex = condition.lastIndexOf('|');
|
|
if (conditionExportIndex !== -1) {
|
|
conditionExport = condition.substr(conditionExportIndex + 1);
|
|
conditionModule = condition.substr(0, conditionExportIndex);
|
|
|
|
if (conditionExport[0] === '~') {
|
|
negation = true;
|
|
conditionExport = conditionExport.substr(1);
|
|
}
|
|
}
|
|
else {
|
|
negation = condition[0] === '~';
|
|
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 resolveCondition (conditionObj, parentKey, bool) {
|
|
// import without __useDefault handling here
|
|
return RegisterLoader.prototype.import.call(this, conditionObj.module, parentKey)
|
|
.then(function (condition) {
|
|
var m = readMemberExpression(conditionObj.prop, condition);
|
|
|
|
if (bool && typeof m !== 'boolean')
|
|
throw new TypeError('Condition did not resolve to a boolean.');
|
|
|
|
return conditionObj.negate ? !m : m;
|
|
});
|
|
}
|
|
|
|
var interpolationRegEx = /#\{[^\}]+\}/;
|
|
function interpolateConditional (key, parentKey, parentMetadata) {
|
|
// first we normalize the conditional
|
|
var conditionalMatch = key.match(interpolationRegEx);
|
|
|
|
if (!conditionalMatch)
|
|
return Promise.resolve(key);
|
|
|
|
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, parentKey, createMetadata(), parentMetadata)
|
|
.then(function (conditionModule) {
|
|
conditionObj.module = conditionModule;
|
|
return key.replace(interpolationRegEx, '#{' + serializeCondition(conditionObj) + '}');
|
|
});*/
|
|
|
|
return resolveCondition.call(this, conditionObj, parentKey, false)
|
|
.then(function (conditionValue) {
|
|
if (typeof conditionValue !== 'string')
|
|
throw new TypeError('The condition value for ' + key + ' doesn\'t resolve to a string.');
|
|
|
|
if (conditionValue.indexOf('/') !== -1)
|
|
throw new TypeError('Unabled to interpolate conditional ' + key + (parentKey ? ' in ' + parentKey : '') + '\n\tThe condition value ' + conditionValue + ' cannot contain a "/" separator.');
|
|
|
|
return key.replace(interpolationRegEx, conditionValue);
|
|
});
|
|
}
|