615 lines
23 KiB
JavaScript
615 lines
23 KiB
JavaScript
/*
|
|
* Package Configuration Extension
|
|
*
|
|
* Example:
|
|
*
|
|
* SystemJS.packages = {
|
|
* jquery: {
|
|
* main: 'index.js', // when not set, package name 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'
|
|
* }
|
|
* },
|
|
* // 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': ['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
|
|
* - defaultJSExtensions applies after map when defaultExtension is not set
|
|
* - if a meta value is available for a module, map and defaultExtension are skipped
|
|
* - like global map, package map also applies to subpaths (sizzle/x, ./vendor/another/sub)
|
|
* - condition module map is '@env' module in package or '@system-env' globally
|
|
* - map targets support conditional interpolation ('./x': './x.#{|env}.js')
|
|
* - internal package map targets cannot use boolean conditionals
|
|
*
|
|
* 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:
|
|
*
|
|
* 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 name 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() {
|
|
|
|
hookConstructor(function(constructor) {
|
|
return function() {
|
|
constructor.call(this);
|
|
this.packages = {};
|
|
this.packageConfigPaths = [];
|
|
};
|
|
});
|
|
|
|
function getPackage(loader, normalized) {
|
|
// use most specific package
|
|
var curPkg, curPkgLen = 0, pkgLen;
|
|
for (var p in loader.packages) {
|
|
if (normalized.substr(0, p.length) === p && (normalized.length === p.length || normalized[p.length] === '/')) {
|
|
pkgLen = p.split('/').length;
|
|
if (pkgLen > curPkgLen) {
|
|
curPkg = p;
|
|
curPkgLen = pkgLen;
|
|
}
|
|
}
|
|
}
|
|
return curPkg;
|
|
}
|
|
|
|
function addDefaultExtension(loader, pkg, pkgName, subPath, skipExtensions) {
|
|
// don't apply extensions to folders or if defaultExtension = false
|
|
if (!subPath || subPath[subPath.length - 1] == '/' || skipExtensions || pkg.defaultExtension === false)
|
|
return subPath;
|
|
|
|
// NB are you sure about this?
|
|
// skip if we have interpolation conditional syntax in subPath?
|
|
if (subPath.match(interpolationRegEx))
|
|
return subPath;
|
|
|
|
var metaMatch = false;
|
|
|
|
// exact meta or meta with any content after the last wildcard skips extension
|
|
if (pkg.meta)
|
|
getMetaMatches(pkg.meta, subPath, function(metaPattern, matchMeta, matchDepth) {
|
|
if (matchDepth == 0 || metaPattern.lastIndexOf('*') != metaPattern.length - 1)
|
|
return metaMatch = true;
|
|
});
|
|
|
|
// exact global meta or meta with any content after the last wildcard skips extension
|
|
if (!metaMatch && loader.meta)
|
|
getMetaMatches(loader.meta, pkgName + '/' + 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
|
|
// NB reconsider if default should really be ".js"?
|
|
var defaultExtension = '.' + (pkg.defaultExtension || 'js');
|
|
if (subPath.substr(subPath.length - defaultExtension.length) != defaultExtension)
|
|
return subPath + defaultExtension;
|
|
else
|
|
return subPath;
|
|
}
|
|
|
|
function applyPackageConfigSync(loader, pkg, pkgName, subPath, skipExtensions) {
|
|
// main
|
|
if (!subPath) {
|
|
if (pkg.main)
|
|
subPath = pkg.main.substr(0, 2) == './' ? pkg.main.substr(2) : pkg.main;
|
|
// also no submap if name is package itself (import 'pkg' -> 'path/to/pkg.js')
|
|
else
|
|
// NB can add a default package main convention here when defaultJSExtensions is deprecated
|
|
// if it becomes internal to the package then it would no longer be an exit path
|
|
return pkgName + (loader.defaultJSExtensions ? '.js' : '');
|
|
}
|
|
|
|
// map config checking without then with extensions
|
|
if (pkg.map) {
|
|
var mapPath = './' + subPath;
|
|
|
|
var mapMatch = getMapMatch(pkg.map, mapPath);
|
|
|
|
// we then check map with the default extension adding
|
|
if (!mapMatch) {
|
|
mapPath = './' + addDefaultExtension(loader, pkg, pkgName, subPath, skipExtensions);
|
|
if (mapPath != './' + subPath)
|
|
mapMatch = getMapMatch(pkg.map, mapPath);
|
|
}
|
|
if (mapMatch)
|
|
return doMapSync(loader, pkg, pkgName, mapMatch, mapPath, skipExtensions);
|
|
}
|
|
|
|
// normal package resolution
|
|
return pkgName + '/' + addDefaultExtension(loader, pkg, pkgName, subPath, skipExtensions);
|
|
}
|
|
|
|
function validateMapping(mapMatch, mapped, pkgName) {
|
|
// disallow internal to subpath maps
|
|
if (mapMatch == '.')
|
|
throw new Error('Package ' + pkgName + ' has a map entry for "." which is not permitted.');
|
|
// disallow internal ./x -> ./x/y recursive maps
|
|
else if (mapped.substr(0, mapMatch.length) == mapMatch && (mapMatch[mapMatch.length - 1] != '/' && mapped[mapMatch.length] == '/'))
|
|
throw new Error('Package ' + pkgName + ' has a recursive map for "' + mapMatch + '" which is not permitted.');
|
|
}
|
|
|
|
function doMapSync(loader, pkg, pkgName, mapMatch, path, skipExtensions) {
|
|
var mapped = pkg.map[mapMatch];
|
|
|
|
if (typeof mapped == 'object')
|
|
throw new Error('Synchronous conditional normalization not supported sync normalizing ' + mapMatch + ' in ' + pkgName);
|
|
|
|
validateMapping(mapMatch, mapped, pkgName);
|
|
|
|
// ignore conditionals in sync
|
|
if (typeof mapped != 'string')
|
|
mapped = mapMatch = path;
|
|
|
|
validateMapping(mapMatch, mapped, pkgName);
|
|
|
|
// package map to main / base-level
|
|
if (mapped == '.')
|
|
mapped = pkgName;
|
|
|
|
// internal package map
|
|
else if (mapped.substr(0, 2) == './')
|
|
return pkgName + '/' + addDefaultExtension(loader, pkg, pkgName, mapped.substr(2) + path.substr(mapMatch.length), skipExtensions);
|
|
|
|
// external map reference
|
|
return loader.normalizeSync(mapped + path.substr(mapMatch.length), pkgName + '/');
|
|
}
|
|
|
|
function applyPackageConfig(loader, pkg, pkgName, subPath, skipExtensions) {
|
|
// main
|
|
if (!subPath) {
|
|
if (pkg.main)
|
|
subPath = pkg.main.substr(0, 2) == './' ? pkg.main.substr(2) : pkg.main;
|
|
// also no submap if name is package itself (import 'pkg' -> 'path/to/pkg.js')
|
|
else
|
|
// NB can add a default package main convention here when defaultJSExtensions is deprecated
|
|
// if it becomes internal to the package then it would no longer be an exit path
|
|
return Promise.resolve(pkgName + (loader.defaultJSExtensions ? '.js' : ''));
|
|
}
|
|
|
|
// 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, pkgName, subPath, skipExtensions);
|
|
if (mapPath != './' + subPath)
|
|
mapMatch = getMapMatch(pkg.map, mapPath);
|
|
}
|
|
}
|
|
|
|
return (mapMatch ? doMap(loader, pkg, pkgName, mapMatch, mapPath, skipExtensions) : Promise.resolve())
|
|
.then(function(mapped) {
|
|
if (mapped)
|
|
return Promise.resolve(mapped);
|
|
|
|
// normal package resolution / fallback resolution for no conditional match
|
|
return Promise.resolve(pkgName + '/' + addDefaultExtension(loader, pkg, pkgName, subPath, skipExtensions));
|
|
});
|
|
}
|
|
|
|
function doStringMap(loader, pkg, pkgName, mapMatch, mapped, path, skipExtensions) {
|
|
// NB the interpolation cases should strictly skip subsequent interpolation
|
|
|
|
// package map to main / base-level
|
|
if (mapped == '.')
|
|
mapped = pkgName;
|
|
|
|
// internal package map
|
|
else if (mapped.substr(0, 2) == './')
|
|
return Promise.resolve(pkgName + '/' + addDefaultExtension(loader, pkg, pkgName, mapped.substr(2) + path.substr(mapMatch.length), skipExtensions))
|
|
.then(function(name) {
|
|
return interpolateConditional.call(loader, name, pkgName + '/');
|
|
});
|
|
|
|
// external map reference
|
|
return loader.normalize(mapped + path.substr(mapMatch.length), pkgName + '/');
|
|
}
|
|
|
|
function doMap(loader, pkg, pkgName, mapMatch, path, skipExtensions) {
|
|
var mapped = pkg.map[mapMatch];
|
|
|
|
if (typeof mapped == 'string') {
|
|
validateMapping(mapMatch, mapped, pkgName);
|
|
return doStringMap(loader, pkg, pkgName, mapMatch, mapped, path, skipExtensions);
|
|
}
|
|
|
|
// we use a special conditional syntax to allow the builder to handle conditional branch points further
|
|
if (loader.builder)
|
|
return Promise.resolve(pkgName + '/#:' + path);
|
|
|
|
// map object -> conditional 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];
|
|
}
|
|
})
|
|
.then(function(mapped) {
|
|
if (mapped) {
|
|
if (typeof mapped != 'string')
|
|
throw new Error('Unable to map a package conditional to a package conditional.');
|
|
validateMapping(mapMatch, mapped, pkgName);
|
|
return doStringMap(loader, pkg, pkgName, mapMatch, mapped, path, skipExtensions);
|
|
}
|
|
|
|
// no environment match -> fallback to original subPath by returning undefined
|
|
});
|
|
}
|
|
|
|
// normalizeSync = decanonicalize + package resolution
|
|
SystemJSLoader.prototype.normalizeSync = SystemJSLoader.prototype.decanonicalize = SystemJSLoader.prototype.normalize;
|
|
|
|
// decanonicalize must JUST handle package defaultExtension: false case when defaultJSExtensions is set
|
|
// to be deprecated!
|
|
hook('decanonicalize', function(decanonicalize) {
|
|
return function(name, parentName) {
|
|
if (this.builder)
|
|
return decanonicalize.call(this, name, parentName, true);
|
|
|
|
var decanonicalized = decanonicalize.call(this, name, parentName);
|
|
|
|
if (!this.defaultJSExtensions)
|
|
return decanonicalized;
|
|
|
|
var pkgName = getPackage(this, decanonicalized);
|
|
|
|
var pkg = this.packages[pkgName];
|
|
var defaultExtension = pkg && pkg.defaultExtension;
|
|
|
|
if (defaultExtension == undefined && pkg && pkg.meta)
|
|
getMetaMatches(pkg.meta, decanonicalized.substr(pkgName), function(metaPattern, matchMeta, matchDepth) {
|
|
if (matchDepth == 0 || metaPattern.lastIndexOf('*') != metaPattern.length - 1) {
|
|
defaultExtension = false;
|
|
return true;
|
|
}
|
|
});
|
|
|
|
if ((defaultExtension === false || defaultExtension && defaultExtension != '.js') && name.substr(name.length - 3, 3) != '.js' && decanonicalized.substr(decanonicalized.length - 3, 3) == '.js')
|
|
decanonicalized = decanonicalized.substr(0, decanonicalized.length - 3);
|
|
|
|
return decanonicalized;
|
|
};
|
|
});
|
|
|
|
hook('normalizeSync', function(normalizeSync) {
|
|
return function(name, parentName, isPlugin) {
|
|
warn.call(this, 'SystemJS.normalizeSync has been deprecated for SystemJS.decanonicalize.');
|
|
|
|
var loader = this;
|
|
isPlugin = isPlugin === true;
|
|
|
|
// apply contextual package map first
|
|
// (we assume the parent package config has already been loaded)
|
|
if (parentName)
|
|
var parentPackageName = getPackage(loader, parentName) ||
|
|
loader.defaultJSExtensions && parentName.substr(parentName.length - 3, 3) == '.js' &&
|
|
getPackage(loader, parentName.substr(0, parentName.length - 3));
|
|
|
|
var parentPackage = parentPackageName && loader.packages[parentPackageName];
|
|
|
|
// ignore . since internal maps handled by standard package resolution
|
|
if (parentPackage && name[0] != '.') {
|
|
var parentMap = parentPackage.map;
|
|
var parentMapMatch = parentMap && getMapMatch(parentMap, name);
|
|
|
|
if (parentMapMatch && typeof parentMap[parentMapMatch] == 'string')
|
|
return doMapSync(loader, parentPackage, parentPackageName, parentMapMatch, name, isPlugin);
|
|
}
|
|
|
|
var defaultJSExtension = loader.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js';
|
|
|
|
// apply map, core, paths, contextual package map
|
|
var normalized = normalizeSync.call(loader, name, parentName);
|
|
|
|
// undo defaultJSExtension
|
|
if (defaultJSExtension && normalized.substr(normalized.length - 3, 3) != '.js')
|
|
defaultJSExtension = false;
|
|
if (defaultJSExtension)
|
|
normalized = normalized.substr(0, normalized.length - 3);
|
|
|
|
var pkgConfigMatch = getPackageConfigMatch(loader, normalized);
|
|
var pkgName = pkgConfigMatch && pkgConfigMatch.packageName || getPackage(loader, normalized);
|
|
|
|
if (!pkgName)
|
|
return normalized + (defaultJSExtension ? '.js' : '');
|
|
|
|
var subPath = normalized.substr(pkgName.length + 1);
|
|
|
|
return applyPackageConfigSync(loader, loader.packages[pkgName] || {}, pkgName, subPath, isPlugin);
|
|
};
|
|
});
|
|
|
|
hook('normalize', function(normalize) {
|
|
return function(name, parentName, isPlugin) {
|
|
var loader = this;
|
|
isPlugin = isPlugin === true;
|
|
|
|
return Promise.resolve()
|
|
.then(function() {
|
|
// apply contextual package map first
|
|
// (we assume the parent package config has already been loaded)
|
|
if (parentName)
|
|
var parentPackageName = getPackage(loader, parentName) ||
|
|
loader.defaultJSExtensions && parentName.substr(parentName.length - 3, 3) == '.js' &&
|
|
getPackage(loader, parentName.substr(0, parentName.length - 3));
|
|
|
|
var parentPackage = parentPackageName && loader.packages[parentPackageName];
|
|
|
|
// ignore . since internal maps handled by standard package resolution
|
|
if (parentPackage && name.substr(0, 2) != './') {
|
|
var parentMap = parentPackage.map;
|
|
var parentMapMatch = parentMap && getMapMatch(parentMap, name);
|
|
|
|
if (parentMapMatch)
|
|
return doMap(loader, parentPackage, parentPackageName, parentMapMatch, name, isPlugin);
|
|
}
|
|
|
|
return Promise.resolve();
|
|
})
|
|
.then(function(mapped) {
|
|
if (mapped)
|
|
return mapped;
|
|
|
|
var defaultJSExtension = loader.defaultJSExtensions && name.substr(name.length - 3, 3) != '.js';
|
|
|
|
// apply map, core, paths, contextual package map
|
|
var normalized = normalize.call(loader, name, parentName);
|
|
|
|
// undo defaultJSExtension
|
|
if (defaultJSExtension && normalized.substr(normalized.length - 3, 3) != '.js')
|
|
defaultJSExtension = false;
|
|
if (defaultJSExtension)
|
|
normalized = normalized.substr(0, normalized.length - 3);
|
|
|
|
var pkgConfigMatch = getPackageConfigMatch(loader, normalized);
|
|
var pkgName = pkgConfigMatch && pkgConfigMatch.packageName || getPackage(loader, normalized);
|
|
|
|
if (!pkgName)
|
|
return Promise.resolve(normalized + (defaultJSExtension ? '.js' : ''));
|
|
|
|
var pkg = loader.packages[pkgName];
|
|
|
|
// if package is already configured or not a dynamic config package, use existing package config
|
|
var isConfigured = pkg && (pkg.configured || !pkgConfigMatch);
|
|
return (isConfigured ? Promise.resolve(pkg) : loadPackageConfigPath(loader, pkgName, pkgConfigMatch.configPath))
|
|
.then(function(pkg) {
|
|
var subPath = normalized.substr(pkgName.length + 1);
|
|
|
|
return applyPackageConfig(loader, pkg, pkgName, subPath, isPlugin);
|
|
});
|
|
});
|
|
};
|
|
});
|
|
|
|
// check if the given normalized name 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(loader, normalized) {
|
|
var pkgName, exactMatch = false, configPath;
|
|
for (var i = 0; i < loader.packageConfigPaths.length; i++) {
|
|
var packageConfigPath = loader.packageConfigPaths[i];
|
|
var p = packageConfigPaths[packageConfigPath] || (packageConfigPaths[packageConfigPath] = createPkgConfigPathObj(packageConfigPath));
|
|
if (normalized.length < p.length)
|
|
continue;
|
|
var match = normalized.match(p.regEx);
|
|
if (match && (!pkgName || (!(exactMatch && p.wildcard) && pkgName.length < match[1].length))) {
|
|
pkgName = match[1];
|
|
exactMatch = !p.wildcard;
|
|
configPath = pkgName + packageConfigPath.substr(p.length);
|
|
}
|
|
}
|
|
|
|
if (!pkgName)
|
|
return;
|
|
|
|
return {
|
|
packageName: pkgName,
|
|
configPath: configPath
|
|
};
|
|
}
|
|
|
|
function loadPackageConfigPath(loader, pkgName, pkgConfigPath) {
|
|
var configLoader = loader.pluginLoader || loader;
|
|
|
|
// NB remove this when json is default
|
|
(configLoader.meta[pkgConfigPath] = configLoader.meta[pkgConfigPath] || {}).format = 'json';
|
|
|
|
return configLoader.load(pkgConfigPath)
|
|
.then(function() {
|
|
var cfg = configLoader.get(pkgConfigPath)['default'];
|
|
|
|
// support "systemjs" prefixing
|
|
if (cfg.systemjs)
|
|
cfg = cfg.systemjs;
|
|
|
|
// modules backwards compatibility
|
|
if (cfg.modules) {
|
|
cfg.meta = cfg.modules;
|
|
warn.call(loader, 'Package config file ' + pkgConfigPath + ' is configured with "modules", which is deprecated as it has been renamed to "meta".');
|
|
}
|
|
|
|
// 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];
|
|
}
|
|
|
|
// deeply-merge (to first level) config with any existing package config
|
|
var pkg = loader.packages[pkgName] = loader.packages[pkgName] || {};
|
|
extendMeta(pkg, cfg, true);
|
|
|
|
// support external depCache
|
|
if (cfg.depCache) {
|
|
for (var d in cfg.depCache) {
|
|
var dNormalized;
|
|
|
|
if (d.substr(0, 2) == './')
|
|
dNormalized = pkgName + '/' + d.substr(2);
|
|
else
|
|
dNormalized = coreResolve.call(loader, d);
|
|
loader.depCache[dNormalized] = (loader.depCache[dNormalized] || []).concat(cfg.depCache[d]);
|
|
}
|
|
delete cfg.depCache;
|
|
}
|
|
|
|
// main object becomes main map
|
|
if (typeof pkg.main == 'object') {
|
|
pkg.map = pkg.map || {};
|
|
pkg.map['./@main'] = pkg.main;
|
|
pkg.main['default'] = pkg.main['default'] || './';
|
|
pkg.main = '@main';
|
|
}
|
|
|
|
return pkg;
|
|
});
|
|
}
|
|
|
|
function getMetaMatches(pkgMeta, 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)) {
|
|
// 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] && pkgMeta.hasOwnProperty && pkgMeta.hasOwnProperty(subPath) ? 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(loader, load.name);
|
|
if (pkgName) {
|
|
var pkg = loader.packages[pkgName];
|
|
var subPath = load.name.substr(pkgName.length + 1);
|
|
|
|
// format
|
|
if (pkg.format)
|
|
load.metadata.format = load.metadata.format || pkg.format;
|
|
|
|
var meta = {};
|
|
if (pkg.meta) {
|
|
var bestDepth = 0;
|
|
|
|
// NB support a main shorthand in meta here?
|
|
getMetaMatches(pkg.meta, subPath, function(metaPattern, matchMeta, matchDepth) {
|
|
if (matchDepth > bestDepth)
|
|
bestDepth = matchDepth;
|
|
extendMeta(meta, matchMeta, matchDepth && bestDepth > matchDepth);
|
|
});
|
|
|
|
extendMeta(load.metadata, meta);
|
|
}
|
|
}
|
|
|
|
return address;
|
|
});
|
|
};
|
|
});
|
|
|
|
})();
|