Skip to content

Adds support for aliases that have multiple targets #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 87 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var Module = module.constructor.length > 1
: BuiltinModule

var nodePath = require('path')
var nodeFS = require('fs')

var modulePaths = []
var moduleAliases = {}
Expand All @@ -28,27 +29,90 @@ 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++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why 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') {
Comment on lines +42 to +45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the noop branch (see comment about the else branch).

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.')
}
Comment on lines +51 to +54
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given tghe addAlias method, this should never happen.
I'd remove the else branch, unless we can write a failing test for it.


// 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
}
}

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) {
Expand Down Expand Up @@ -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')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of using

Suggested change
if ((typeof targets === 'object') && (targets.constructor.name === 'Array')) {
if (targets instanceof 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()
Expand Down
111 changes: 111 additions & 0 deletions test/specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down