package config adjustments

This commit is contained in:
guybedford 2015-04-17 19:43:58 +02:00
parent f5791c79ca
commit 686f65cd35
6 changed files with 182 additions and 235 deletions

View File

@ -70,9 +70,9 @@ dist/system.src.js: lib/*.js $(ESML)/*.js
lib/cjs.js \
lib/amd.js \
lib/map.js \
lib/package.js \
lib/plugins.js \
lib/alias.js \
lib/package.js \
lib/bundles.js \
lib/depCache.js \
lib/conditionals.js \
@ -97,9 +97,9 @@ dist/system-prod.src.js: lib/*.js $(ESML)/*.js
lib/register.js \
lib/global-helpers.js \
lib/map.js \
lib/package.js \
lib/plugins.js \
lib/alias.js \
lib/package.js \
lib/bundles.js \
lib/depCache.js \
lib/conditionals.js \

View File

@ -4,134 +4,46 @@
Provides map configuration through
System.map['jquery'] = 'some/module/map'
As well as contextual map config through
System.map['bootstrap'] = {
jquery: 'some/module/map2'
}
Note that this applies for subpaths, just like RequireJS
Note that this applies for subpaths, just like RequireJS:
jquery -> 'some/module/map'
jquery/path -> 'some/module/map/path'
bootstrap -> 'bootstrap'
Inside any module name of the form 'bootstrap' or 'bootstrap/*'
jquery -> 'some/module/map2'
jquery/p -> 'some/module/map2/p'
Maps are carefully applied from most specific contextual map, to least specific global map
The most specific map is always taken, as longest path length
*/
(function() {
hookConstructor(function(constructor) {
return function() {
constructor.call(this);
this.map = {};
};
});
hookConstructor(function(constructor) {
return function() {
constructor.call(this);
this.map = {};
};
});
hook('normalize', function(normalize) {
return function(name, parentName, parentAddress) {
var loader = this;
return Promise.resolve(normalize.call(loader, name, parentName, parentAddress))
.then(function(name) {
var bestMatch, bestMatchLength = 0;
// return if prefix parts (separated by '/') match the name
// eg prefixMatch('jquery/some/thing', 'jquery') -> true
// prefixMatch('jqueryhere/', 'jquery') -> false
function prefixMatch(name, prefix) {
if (name.length < prefix.length)
return false;
if (name.substr(0, prefix.length) != prefix)
return false;
if (name[prefix.length] && name[prefix.length] != '/')
return false;
return true;
}
// get the depth of a given path
// eg pathLen('some/name') -> 2
function pathLen(name) {
var len = 1;
for (var i = 0, l = name.length; i < l; i++)
if (name[i] === '/')
len++;
return len;
}
function doMap(name, matchLen, map) {
return map + name.substr(matchLen);
}
// given a relative-resolved module name and normalized parent name,
// apply the map configuration
function applyMap(name, parentName, loader) {
var curMatch, curMatchLength = 0;
var curParent, curParentMatchLength = 0;
var tmpParentLength, tmpPrefixLength;
var subPath;
var nameParts;
// first find most specific contextual match
if (parentName) {
// now do the global map
for (var p in loader.map) {
var curMap = loader.map[p];
if (typeof curMap != 'object')
continue;
if (typeof loader.map[p] != 'string')
throw new TypeError('Map configuration no longer permits object submaps. Use package map instead (`System.packages[name].map`).');
// most specific parent match wins first
if (!prefixMatch(parentName, p))
continue;
tmpParentLength = pathLen(p);
if (tmpParentLength <= curParentMatchLength)
continue;
for (var q in curMap) {
// most specific name match wins
if (!prefixMatch(name, q))
if (name.substr(0, p.length) == p && (name.length == p.length || name[p.length] == '/')) {
var curMatchLength = p.split('/').length;
if (curMatchLength <= bestMatchLength)
continue;
tmpPrefixLength = pathLen(q);
if (tmpPrefixLength <= curMatchLength)
continue;
curMatch = q;
curMatchLength = tmpPrefixLength;
curParent = p;
curParentMatchLength = tmpParentLength;
bestMatch = p;
bestMatchLength = curMatchLength;
}
}
}
// if we found a contextual match, apply it now
if (curMatch)
return doMap(name, curMatch.length, loader.map[curParent][curMatch]);
if (bestMatch)
return loader.map[bestMatch] + name.substr(bestMatch.length);
// now do the global map
for (var p in loader.map) {
var curMap = loader.map[p];
if (typeof curMap != 'string')
continue;
if (!prefixMatch(name, p))
continue;
var tmpPrefixLength = pathLen(p);
if (tmpPrefixLength <= curMatchLength)
continue;
curMatch = p;
curMatchLength = tmpPrefixLength;
}
if (curMatch)
return doMap(name, curMatch.length, loader.map[curMatch]);
return name;
}
hook('normalize', function(normalize) {
return function(name, parentName, parentAddress) {
var loader = this;
return Promise.resolve(normalize.call(loader, name, parentName, parentAddress))
.then(function(name) {
return applyMap(name, parentName, loader);
});
};
});
})();
return name;
});
};
});

View File

@ -7,47 +7,35 @@
*
* Example:
*
* System.package('jquery', {
* main: 'index.js',
* format: 'amd',
* defaultJSExtension: true,
* modules: {
* 'vendor/sizzle.js': {
* format: 'global'
* }
* },
* map: {
* // map internal require('sizzle') to local require('./vendor/sizzle')
* sizzle: './vendor/sizzle.js',
*
* // map external require of 'jquery/vendor/another.js' to 'another/index.js'
* './vendor/another.js': 'another/index.js'
* }
* });
*
* Is equivalent to:
*
* System.config({
* meta: {
* 'jquery/*': {
* format: 'amd',
* }
* 'jquery/vendor/sizzle.js': {
* format: 'global'
* }
* },
* map: {
* 'jquery/vendor/another.js': 'another/index.js'
* packages: {
* jquery: {
* sizzle: 'jquery/vendor/sizzle.js'
* main: 'index.js', // this main is actually set by default
* format: 'amd',
* defaultExtension: 'js',
* meta: {
* '*.ts': {
* plugin: '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': 'another/index.js'
* }
* }
* }
* });
*
* With normalization hooks providing main and defaultExtension support
* so that:
* import 'jquery' => import 'jquery/index.js'
* import 'jquery/module' => import 'jquery/module.js'
* Then:
* import 'jquery' -> jquery/index.js
* import 'jquery/submodule' -> jquery/submodule.js
* import 'jquery/vendor/another' -> jquery/another/index.js
*
* In addition, the following meta properties will be allowed to be package
* -relative as well in the package meta config:
@ -61,86 +49,136 @@
hookConstructor(function(constructor) {
return function() {
constructor.call(this);
this.packageMains = {};
this.packageDefaultJSExtensions = {};
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 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)
name = map[bestMatch] + name.substr(bestMatch.length);
return name;
}
hook('normalize', function(normalize) {
return function(name, parentName, parentAddress) {
var loader = this;
// apply contextual package map first
if (parentName)
var parentPackage = getPackage.call(loader, parentName);
if (parentPackage && name[0] !== '.') {
var parentMap = loader.packages[parentPackage].map;
if (parentMap) {
name = applyMap(parentMap, name);
// relative maps are package-relative
if (name[0] === '.')
parentName = parentPackage;
}
}
// apply global map, relative normalization
return normalize.call(loader, name, parentName, parentAddress)
.then(function(normalized) {
// main
if (loader.packageMains[normalized])
normalized = normalized + '/' + loader.packageMains[normalized];
// check if we are inside a package
var pkgName = getPackage.call(loader, normalized);
// defaultJSExtension
if (normalized.split('/').pop().indexOf('.') == -1) {
for (var d in loader.packageDefaultExtensions) {
if (normalized.substr(0, d.length + 1) == d + '/') {
normalized += '.js';
break;
}
if (pkgName) {
var pkg = loader.packages[pkgName];
// main
if (pkgName === normalized)
normalized += '/' + (pkg.main || 'index.js');
// relative maps
if (pkg.map) {
normalized = pkgName + applyMap(pkg.map, '.' + normalized.substr(pkgName.length)).substr(1);
// normalize package-relative maps
if (normalized.substr(0, 2) == './')
normalized = pkgName + normalized.substr(1);
}
// defaultExtension
if (pkg.defaultExtension
&& (!pkg.meta || !pkg.meta[normalized.substr(pkgName.length + 1)])
&& normalized.split('/').pop().indexOf('.') == -1)
normalized += '.' + pkg.defaultExtension;
}
return normalized;
});
};
});
SystemJSLoader.prototype.package = function(p, pkgConfig) {
// main
var main = pkgCfg.main;
if (main)
this.packageMains[p] = main.substr(0, 2) == './' ? main.substr(2) : main;
// defaultJSExtension
if (pkgCfg.defaultJSExtension)
this.packageDefaultJSExtensions[p] = true;
// format
if (pkgCfg.format) {
var formatMeta = this.meta[p + '/*'] || {};
formatMeta.format = pkgCfg.format;
this.meta[p + '/*'] = formatMeta;
function extend(a, b, overwrite) {
for (var p in b) {
if (overwrite || !(p in a))
a[p] = b[p];
}
}
// meta
for (var m in pkgCfg.modules) {
var meta = this.meta[p + '/' + m] || {};
var pkgMeta = pkgCfg.modules[m];
// alias shorthand
if (typeof pkgMeta === 'string')
pkgMeta = { alias: pkgMeta };
for (var p in pkgMeta) {
// plugin and alias can be package-relative
if ((p === 'plugin' || p === 'alias')
&& pkgMeta[p].substr(0, 2) == './')
meta[p] = p + pkgMeta[p].substr(1);
else
meta[p] = pkgMeta[p];
}
this.meta[p + '/' + m] = meta;
}
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];
// format
if (pkg.format)
load.metadata.format = load.metadata.format || pkg.format;
// map
for (var m in pkgCfg.map) {
var target = pkgCfg.map[m];
if (target.substr(0, 2) == './')
target = p + target.substr(1);
if (pkg.meta) {
// wildcard meta
var meta = {};
var bestDepth = 0;
var wildcardIndex;
for (var module in pkg.meta) {
wildcardIndex = indexOf.call(module, '*');
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
if (pkg.meta[load.name])
extend(meta, meta[load.name], true);
extend(load.metadata, meta);
}
}
// package-relative base map
if (m.substr(0, 2) == './') {
this.map[p + m.substr(1)] = target;
}
// package-contextual map
else {
this.map[p] = this.map[p] || {};
this.map[p][m] = target;
}
}
};
return address;
});
};
});
})();

View File

@ -155,34 +155,31 @@ asyncTest('Map configuration subpath', function() {
}, err);
});
asyncTest('Contextual map configuration', function() {
System.map['tests/contextual-map.js'] = {
maptest: 'tests/contextual-map-dep.js'
asyncTest('Package map configuration', function() {
System.packages['tests/contextual-test'] = {
main: 'contextual-map.js',
map: {
maptest: 'tests/contextual-map-dep.js'
}
};
System['import']('tests/contextual-map.js').then(function(m) {
System['import']('tests/contextual-test').then(function(m) {
ok(m.mapdep == 'mapdep', 'Contextual map dep not loaded');
start();
}, err);
});
asyncTest('Submodule contextual map configuration', function() {
System.map['tests/subcontextual-map'] = {
dep: 'tests/subcontextual-mapdep.js'
asyncTest('Package map with shim', function() {
System.packages['tests/shim-package'] = {
meta: {
'*': {
deps: ['shim-map-dep']
}
},
map: {
'shim-map-dep': 'tests/shim-map-test-dep.js'
}
};
System['import']('tests/subcontextual-map/submodule.js').then(function(m) {
ok(m == 'submapdep', 'Submodule contextual map not loaded');
start();
}, err);
});
asyncTest('Contextual map with shim', function() {
System.meta['tests/shim-map-test.js'] = {
deps: ['shim-map-dep']
};
System.map['tests/shim-map-test.js'] = {
'shim-map-dep': 'tests/shim-map-test-dep.js'
};
System['import']('tests/shim-map-test.js').then(function(m) {
System['import']('tests/shim-package/shim-map-test.js').then(function(m) {
ok(m == 'depvalue', 'shim dep not loaded');
start();
}, err);