From 746443007af0bc4fd828bd3dd899aafe0f4dbbae Mon Sep 17 00:00:00 2001 From: Ian MacLeod Date: Wed, 25 Feb 2015 17:01:43 -0800 Subject: [PATCH] Support for relative dependencies for define(). Note that this implementation is a little naive; it completely ignores the id given to define, and always assumes that URLs will be relative to the script's document. That will not work for vulcanized bundles. Separate PR for that. --- src/lib/module.html | 53 +++++++++++++++++++++++++----- test/assets/modules/one.html | 19 +++++++++++ test/assets/modules/sub/three.html | 19 +++++++++++ test/assets/modules/two.html | 25 ++++++++++++++ test/unit/module.html | 28 +++++++++++++++- 5 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 test/assets/modules/one.html create mode 100644 test/assets/modules/sub/three.html create mode 100644 test/assets/modules/two.html diff --git a/src/lib/module.html b/src/lib/module.html index 5a727be9cc..6e38903490 100644 --- a/src/lib/module.html +++ b/src/lib/module.html @@ -44,15 +44,21 @@ dependencies = Array.isArray(id) ? id : []; } + var inferredId = _inferModuleId(); if (typeof id !== 'string') { - id = _inferModuleId(); + id = inferredId; + } + if (id.indexOf('\\') !== -1) { + throw new TypeError('Please use / as module path delimiters'); } if (id in _modules) { throw new Error('The module "' + id + '" has already been defined'); } - _modules[id] = _withDependencies(dependencies, factory); + // TODO(nevir): This is naive; doesn't support the vulcanize case. + var base = inferredId.match(/^(.*?)[^\/]*$/)[1]; + _modules[id] = _withDependencies(base, dependencies, factory); return _modules[id]; } // Semi-private. We expose this for tests & introspection. @@ -66,23 +72,25 @@ // Utility - // TODO(nevir): Temporary for anonymous module ids until we determine them via - // current script URL. - var _anonymousModuleCount = 0; - /** @return {string} A module id inferred from the current document/import. */ function _inferModuleId() { - return '__anonymous_' + _anonymousModuleCount++ + '__'; + var doc = document.currentScript && document.currentScript.ownerDocument || document; + if (!doc.baseURI) { + throw new Error('Unable to determine a module id: No baseURI for the document'); + } + return doc.baseURI; } /** * Calls `factory` with the exported values of `dependencies`. * + * @param {string} base * @param {Array} dependencies * @param {function(...*)} factory */ - function _withDependencies(dependencies, factory) { + function _withDependencies(base, dependencies, factory) { var modules = dependencies.map(function(id) { + id = _resolveRelativeId(base, id); if (!(id in _modules)) { throw new ReferenceError('The module "' + id + '" has not been loaded'); } @@ -91,6 +99,34 @@ return factory.apply(null, modules); } + /** + * @param {string} base The module path/URI that acts as the relative base. + * @param {string} id The module ID that should be relatively resolved. + * @return {string} The expanded module ID. + */ + function _resolveRelativeId(base, id) { + if (id[0] !== '.') return id; + // We need to be careful to only process the path of URLs. + var match = base.match(/^([^\/]*\/\/[^\/]+\/)?(.*?)\/?$/); + var prefix = match[1] || ''; + // We start with the base, and then mutate it into the final path. + var terms = match[2] ? match[2].split('/') : []; + var idTerms = id.match(/^\/?(.*?)\/?$/)[1].split('/'); + + for (var i = 0; i < idTerms.length; i++) { + var idTerm = idTerms[i]; + if (idTerm === '.') { + continue; + } else if (idTerm === '..') { + terms.pop(); + } else { + terms.push(idTerm); + } + } + + return prefix + terms.join('/'); + } + // Exports scope.define = define; @@ -111,6 +147,5 @@ define(dependencies, factory); }; - })(this); diff --git a/test/assets/modules/one.html b/test/assets/modules/one.html new file mode 100644 index 0000000000..d4de2ec7f9 --- /dev/null +++ b/test/assets/modules/one.html @@ -0,0 +1,19 @@ + + + + + diff --git a/test/assets/modules/sub/three.html b/test/assets/modules/sub/three.html new file mode 100644 index 0000000000..2b7db90d81 --- /dev/null +++ b/test/assets/modules/sub/three.html @@ -0,0 +1,19 @@ + + + + + diff --git a/test/assets/modules/two.html b/test/assets/modules/two.html new file mode 100644 index 0000000000..c7fc5ae329 --- /dev/null +++ b/test/assets/modules/two.html @@ -0,0 +1,25 @@ + + + + + + + diff --git a/test/unit/module.html b/test/unit/module.html index f0606231d0..06a6f07693 100644 --- a/test/unit/module.html +++ b/test/unit/module.html @@ -14,6 +14,8 @@ + + @@ -22,7 +24,11 @@ suite('define()', function() { beforeEach(function() { - for (var key in define._modules) { delete define._modules[key]; }; + for (var key in define._modules) { + if (/\/assets\/modules\//.test(key)) continue; + delete define._modules[key]; + } + define('a', function() { return 'module A'; }); define('b', function() { return 'module B'; }); }); @@ -86,6 +92,26 @@ }, Error, /"a".*defined/i); }); + suite('relative dependencies', function() { + + test('loads modules relative to the current', function(done) { + define(['../assets/modules/one.html'], function(one) { + assert.equal(one, 'module 1'); + done(); + }); + }); + + test('loads relative modules transitively', function(done) { + define(['../assets/modules/two.html'], function(two) { + assert.equal(two.one, 'module 1'); + assert.equal(two.me, 'module 2'); + assert.equal(two.three, 'module 3'); + done(); + }); + }); + + }); + });