diff --git a/index.js b/index.js index 8f2e570..73cf000 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ var Module = module.constructor.length > 1 : BuiltinModule var nodePath = require('path') +var nodeFS = require('fs') var modulePaths = [] var moduleAliases = {} @@ -28,20 +29,63 @@ Module._nodeModulePaths = function (from) { var oldResolveFilename = Module._resolveFilename Module._resolveFilename = function (request, parentModule, isMain, options) { - for (var i = moduleAliasNames.length; i-- > 0;) { + var found = false + for (var i = moduleAliasNames.length; i-- > 0 && !found;) { var alias = moduleAliasNames[i] if (isPathMatchesAlias(request, alias)) { - var aliasTarget = moduleAliases[alias] - // Custom function handler - if (typeof moduleAliases[alias] === 'function') { - var fromPath = parentModule.filename - aliasTarget = moduleAliases[alias](fromPath, request, alias) - if (!aliasTarget || typeof aliasTarget !== 'string') { - throw new Error('[module-alias] Expecting custom handler function to return path.') + var aliasTargets = moduleAliases[alias] + + for (var v = 0; v < aliasTargets.length && !found; v++) { + var aliasTarget = aliasTargets[v] + + // First check for a simple string + if (typeof aliasTarget === 'string') { + // No-op + // Custom function handler + } else if (typeof aliasTarget === 'function') { + var fromPath = parentModule.filename + aliasTarget = aliasTarget(fromPath, request, alias) + if (!aliasTarget || typeof aliasTarget !== 'string') { + throw new Error('[module-alias] Expecting custom handler function to return path.') + } + // And for everything else... + } else { + throw new Error('[module-alias] Expecting alias target to be string or function.') + } + + // Construct the path + request = nodePath.join(aliasTarget, request.substr(alias.length)) + + // Now, we need to decide whether or not we'll be continuing our loop + // through targets or stopping here. We're going to do this by checking + // for the existence of a file matching request or a directory + // containing a package.json matching the request. + + // If it's a directory and it contains a package.json, index, index.js, + // or index.node file, we're good to go. + if ( + isDirectory(request) && ( + isFile(nodePath.join(request, 'package.json')) || + isFile(nodePath.join(request, 'index')) || + isFile(nodePath.join(request, 'index.js')) || + isFile(nodePath.join(request, 'index.node')) + ) + ) { + found = true + break + // If it's a file as-is or when appended with .js or .node, rock and + // roll. + } else if ( + isFile(request) || + isFile(request + '.js') || + isFile(request + '.node') + ) { + found = true + break } } - request = nodePath.join(aliasTarget, request.substr(alias.length)) - // Only use the first match + + // There should be only one alias that matches, but you never know. break } } @@ -49,6 +93,26 @@ Module._resolveFilename = function (request, parentModule, isMain, options) { return oldResolveFilename.call(this, request, parentModule, isMain, options) } +function isFile (path) { + var stat + try { + stat = nodeFS.statSync(path) + } catch (err) { + return false + } + return stat.isFile() +} + +function isDirectory (path) { + var stat + try { + stat = nodeFS.statSync(path) + } catch (err) { + return false + } + return stat.isDirectory() +} + function isPathMatchesAlias (path, alias) { // Matching /^alias(\/|$)/ if (path.indexOf(alias) === 0) { @@ -103,8 +167,19 @@ function addAliases (aliases) { } } -function addAlias (alias, target) { - moduleAliases[alias] = target +function addAlias (alias, targets) { + if (moduleAliases[alias] === undefined) { + moduleAliases[alias] = [] + } + + if ((typeof targets === 'object') && (targets.constructor.name === 'Array')) { + moduleAliases[alias] = moduleAliases[alias].concat(targets) + } else if ((typeof targets === 'string') || (typeof targets === 'function')) { + moduleAliases[alias].push(targets) + } else { + throw new Error('[module-alias] Expecting alias targets to be string, function, or array of strings and/or functions.') + } + // Cost of sorting is lower here than during resolution moduleAliasNames = Object.keys(moduleAliases) moduleAliasNames.sort() diff --git a/test/specs.js b/test/specs.js index 4793827..bc08d08 100644 --- a/test/specs.js +++ b/test/specs.js @@ -84,6 +84,68 @@ describe('module-alias', function () { expect(something).to.equal('Hello from foo') }) + it('should register an alias (addAlias) with multiple targets', function () { + moduleAlias.addAlias('@bazfoo', [ + path.join(__dirname, 'src/bar/baz'), + path.join(__dirname, 'src/foo') + ]) + moduleAlias.addAlias('@foobaz', [ + path.join(__dirname, 'src/foo'), + path.join(__dirname, 'src/bar/baz') + ]) + + var value + + try { + value = require('@bazfoo') + } catch (e) {} + expect(value).to.equal('Hello from baz') + + try { + value = require('@foobaz') + } catch (e) {} + expect(value).to.equal('Hello from foo') + }) + + it('should register multiple aliases (addAliases) with multiple targets', function () { + moduleAlias.addAliases({ + '@srcfoobar': [ + path.join(__dirname, 'src'), + path.join(__dirname, 'src/foo/index.js'), + path.join(__dirname, 'src/bar') + ], + '@foobarsrc': [ + path.join(__dirname, 'src/foo/index.js'), + path.join(__dirname, 'src/bar'), + path.join(__dirname, 'src') + ], + '@barsrcfoo': [ + path.join(__dirname, 'src/bar'), + path.join(__dirname, 'src'), + path.join(__dirname, 'src/foo/index.js') + ], + 'something/foo': [ + path.join(__dirname, 'src/foo'), + path.join(__dirname, 'src/bar'), + path.join(__dirname, 'src'), + path.join(__dirname, 'src/foo/index.js') + ] + }) + + var src, foo, baz, something + try { + src = require('@srcfoobar/foo') + foo = require('@foobarsrc') + baz = require('@barsrcfoo/baz') + something = require('something/foo') + } catch (e) {} + + expect(src).to.equal('Hello from foo') + expect(foo).to.equal('Hello from foo') + expect(baz).to.equal('Hello from baz') + expect(something).to.equal('Hello from foo') + }) + describe('importing settings from package.json', function () { function expectAliasesToBeImported () { var src, foo, baz, some, someModule @@ -255,6 +317,55 @@ describe('module-alias', function () { expect(require('@bar/index.js')).to.equal('Hello from foo') }) + it('should addAlias with multiple targets', function () { + moduleAlias.addAlias('@src', [ + function (fromPath, request, alias) { + expect(fromPath).to.equal(__filename) + expect(request).to.equal('@src/baz') + expect(alias).to.equal('@src') + return path.join(__dirname, 'src/bar') + }, + function (fromPath, request, alias) { + expect(fromPath).to.equal(__filename) + expect(request).to.equal('@src/foo') + expect(alias).to.equal('@src') + return path.join(__dirname, 'src') + } + ]) + expect(require('@src/baz')).to.equal('Hello from baz') + expect(require('@src/foo')).to.equal('Hello from foo') + }) + + it('should addAliases with multiple targets', function () { + moduleAlias.addAliases({ + '@src': [ + function (fromPath, request, alias) { + expect(fromPath).to.equal(__filename) + expect(request).to.equal('@src/baz') + expect(alias).to.equal('@src') + return path.join(__dirname, 'src/bar') + }, + function (fromPath, request, alias) { + expect(fromPath).to.equal(__filename) + expect(request).to.equal('@src/foo') + expect(alias).to.equal('@src') + return path.join(__dirname, 'src') + } + ], + '@bar': [ + function (fromPath, request, alias) { + expect(fromPath).to.equal(__filename) + expect(request).to.equal('@bar/index.js') + expect(alias).to.equal('@bar') + return path.join(__dirname, 'src/foo') + } + ] + }) + expect(require('@src/baz')).to.equal('Hello from baz') + expect(require('@src/foo')).to.equal('Hello from foo') + expect(require('@bar/index.js')).to.equal('Hello from foo') + }) + it('should return npm package', function () { moduleAlias.addAlias('@src', function (fromPath, request, alias) { expect(fromPath).to.equal(__filename)