From a1f57f79bfb9cbd7fcc32cdba7dc3bf42f4583aa Mon Sep 17 00:00:00 2001 From: guybedford Date: Wed, 6 May 2015 20:58:29 +0200 Subject: [PATCH] new normalization algorithm --- Makefile | 7 +- lib/config.js | 63 ----------------- lib/core.js | 24 +------ lib/map.js | 32 +++++---- lib/misc.js | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 105 deletions(-) delete mode 100644 lib/config.js create mode 100644 lib/misc.js diff --git a/Makefile b/Makefile index f72da46f..f2ece7e8 100755 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ dist/system.src.js: lib/*.js $(ESML)/*.js $(ESML)/transpiler.js \ lib/global-eval.js \ lib/core.js \ - lib/config.js \ + lib/misc.js \ lib/scriptLoader.js \ lib/meta.js \ lib/register.js \ @@ -90,7 +90,7 @@ dist/system-prod.src.js: lib/*.js $(ESML)/*.js $(ESML)/dynamic-only.js \ $(ESML)/system.js \ lib/core.js \ - lib/config.js \ + lib/misc.js \ lib/scriptLoader.js \ lib/meta.js \ lib/scriptOnly.js \ @@ -115,9 +115,6 @@ dist/system-register-only.src.js: lib/*.js $(ESML)/*.js $(ESML)/loader.js \ $(ESML)/dynamic-only.js \ $(ESML)/system.js \ - lib/core.js \ - lib/scriptLoader.js \ - lib/scriptOnly.js \ lib/register.js \ lib/createSystem.js \ $(ESML)/wrapper-end.js \ diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index 4460c3d2..00000000 --- a/lib/config.js +++ /dev/null @@ -1,63 +0,0 @@ - /* - * Config - */ -(function() { - /* - Extend config merging one deep only - - loader.config({ - some: 'random', - config: 'here', - deep: { - config: { too: 'too' } - } - }); - - <=> - - loader.some = 'random'; - loader.config = 'here' - loader.deep = loader.deep || {}; - loader.deep.config = { too: 'too' }; - */ - SystemLoader.prototype.config = function(cfg) { - for (var c in cfg) { - var v = cfg[c]; - if (typeof v == 'object' && !(v instanceof Array)) { - this[c] = this[c] || {}; - for (var p in v) - this[c][p] = v[p]; - } - else - this[c] = v; - } - }; - - var baseURL; - hookConstructor(function(constructor) { - return function() { - var loader = this; - constructor.call(loader); - baseURL = loader.baseURL; - - // support the empty module, as a concept - loader.set('@empty', loader.newModule({})); - }; - }); - - // allow baseURL to be a relative URL - var normalizedBaseURL; - hook('locate', function(locate) { - return function(load) { - if (this.baseURL != normalizedBaseURL) { - normalizedBaseURL = new URL(this.baseURL, baseURL).href; - - if (normalizedBaseURL.substr(normalizedBaseURL.length - 1, 1) != '/') - normalizedBaseURL += '/'; - this.baseURL = normalizedBaseURL; - } - - return Promise.resolve(locate.call(this, load)); - }; - }); -})(); \ No newline at end of file diff --git a/lib/core.js b/lib/core.js index cf9818e4..675367cf 100644 --- a/lib/core.js +++ b/lib/core.js @@ -26,26 +26,4 @@ function dedupe(deps) { if (indexOf.call(newDeps, deps[i]) == -1) newDeps.push(deps[i]) return newDeps; -} - -/* - __useDefault - - When a module object looks like: - newModule( - __useDefault: true, - default: 'some-module' - }) - - Then importing that module provides the 'some-module' - result directly instead of the full module. - - Useful for eg module.exports = function() {} -*/ -hook('import', function(systemImport) { - return function(name, parentName) { - return systemImport.call(this, name, parentName).then(function(module) { - return module.__useDefault ? module['default'] : module; - }); - }; -}); \ No newline at end of file +} \ No newline at end of file diff --git a/lib/map.js b/lib/map.js index 0f4f25e6..425e3949 100644 --- a/lib/map.js +++ b/lib/map.js @@ -19,29 +19,33 @@ hookConstructor(function(constructor) { }; }); +var baseURLCache = {}; + 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; + if (name.substr(0, 1) != '.' && name.substr(0, 1) != '/' && !name.match(absURLRegEx)) { + var bestMatch, bestMatchLength = 0; - // now do the global map - for (var p in loader.map) { - if (typeof loader.map[p] != 'string') - throw new TypeError('Map configuration no longer permits object submaps. Use package map instead (`System.packages[name].map`).'); + // now do the global map + for (var p in loader.map) { + if (typeof loader.map[p] != 'string') + throw new TypeError('Map configuration no longer permits object submaps. Use package map instead (`System.packages[name].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 (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) - return loader.map[bestMatch] + name.substr(bestMatch.length); + if (bestMatch) + return loader.map[bestMatch] + name.substr(bestMatch.length); + } return name; }); diff --git a/lib/misc.js b/lib/misc.js new file mode 100644 index 00000000..7f91b135 --- /dev/null +++ b/lib/misc.js @@ -0,0 +1,183 @@ +var absURLRegEx = /^[^\/]+:\/\//; + +(function() { + +var baseURI; + +hookConstructor(function(constructor) { + return function() { + constructor.call(this); + + // by default ModuleLoader sets this.baseURL to baseURI + baseURI = this.baseURL; + + // support the empty module, as a concept + this.set('@empty', this.newModule({})); + }; +}); + +// caches baseURL URL object +// also allows baseURL to be relative to the baseURI +function getAbsBaseURL(baseURL) { + return baseURLCache[baseURL] = baseURLCache[baseURL] || new URL(baseURL + (baseURL[baseURL.length - 1] != '/' ? '/' : ''), baseURI); +} + +/* + Normalization + + Dot-Normalization is applied for relative module names, when the parent name + is not a URL itself. + ../, ./ are resolved relative to plain path parentNames + where "plain paths" are defined as not beginning with protocol://, /, ./ + Inner ../ or ./ are not supported + Names that dot below the parent name skip to use URL normalization + + The goal is for normalize to normalize all names into a space containing a union + of "plain paths", and absolute URLs. + + Plain paths will then either be packages or paths in locate + or they will be baseURL-relative in locate + + Absolute paths corresponding to paths within baseURL are converted + back into plain paths to ensure a unique representation in this space. + + In SystemJS we assume and define normalize to never output a module + name not an absolute URL or plain path. + */ +hook('normalize', function() { + return function(name, parentName, parentAddress) { + // percent encode just '#' in module names + // according to https://github.com/jorendorff/js-loaders/blob/master/browser-loader.js#L238 + // we should encode everything, but it breaks for servers that don't expect it + // like in (https://github.com/systemjs/systemjs/issues/168) + if (isBrowser) + name = name.replace(/#/g, '%23'); + + // Relative Normalization + if (name[0] == '.' && parentName) { + // if parent is a URL, apply URL normalization + if (parentName.match(absURLRegEx)) + return new URL(name, parentName).href; + + // Dot Normalization + + // skip leading ./ + var parts = name.split('/'); + var i = 0; + var dotdots = 0; + while (parts[i] == '.') { + i++; + if (i == parts.length) + throw new TypeError('Invalid module name'); + } + + // count dot dots + while (parts[i] == '..') { + i++; + dotdots++; + if (i == parts.length) + throw new TypeError('Invalid module name'); + } + + var parentParts = parentName.split('/'); + + parts = parts.splice(i, parts.length - i); + + // if backtracking below the parent name, just set to the base-level like URLs + // NB if parentAddress is supported in the spec, we can URL normalize against it here instead + if (dotdots <= parentParts.length) + parts = parentParts.splice(0, parentParts.length - dotdots - 1).concat(parts); + + name = parts.join('/'); + } + + // if url-relative, normalize to baseURL + // if within baseURL, convert into corresponding plain path + if (name[0] == '.' || name[0] == '/') { + var baseURL = getAbsBaseURL(this.baseURL); + var normalizedURL = new URL(name, baseURL); + if (normalizedURL.origin == baseURL.origin && normalizedURL.pathname.substr(0, baseURL.pathname.length) == baseURL.pathname) + name = normalizedURL.pathname.substr(baseURL.pathname.length); + else + name = normalizedURL.href; + } + + return name; + }; +}); + + +/* + Locate + + Any plain names from normalize are normalized into absolute URLs + First they are checked against paths + Paths is moved from normalize in the module spec to locate in the System spec + This isn't inconsistent because the module spec normalizes everything to absolute URLs, + while the System spec defines its own intermediate union of URLs and plain paths + */ +hook('locate', function() { + return function(load) { + if (load.name.match(absURLRegEx)) + return load.name; + + name = applyPaths(this, load.name); + + return new URL(name, getAbsBaseURL(this.baseURL)).href; + }; +}); + +/* + __useDefault + + When a module object looks like: + newModule( + __useDefault: true, + default: 'some-module' + }) + + Then importing that module provides the 'some-module' + result directly instead of the full module. + + Useful for eg module.exports = function() {} +*/ +hook('import', function(systemImport) { + return function(name, parentName, parentAddress) { + return systemImport.call(this, name, parentName, parentAddress).then(function(module) { + return module.__useDefault ? module['default'] : module; + }); + }; +}); + +/* + Extend config merging one deep only + + loader.config({ + some: 'random', + config: 'here', + deep: { + config: { too: 'too' } + } + }); + + <=> + + loader.some = 'random'; + loader.config = 'here' + loader.deep = loader.deep || {}; + loader.deep.config = { too: 'too' }; +*/ +SystemLoader.prototype.config = function(cfg) { + for (var c in cfg) { + var v = cfg[c]; + if (typeof v == 'object' && !(v instanceof Array)) { + this[c] = this[c] || {}; + for (var p in v) + this[c][p] = v[p]; + } + else + this[c] = v; + } +}; + +})(); \ No newline at end of file