mendel-resolver
Advanced tools
Comparing version 4.0.0-alpha.0 to 4.0.0-alpha.1
@@ -15,4 +15,7 @@ const VariationalModuleResolver = require('./variational-resolver'); | ||
resolve(moduleName) { | ||
const cacheKey = CachedBisourceVariationalResolver.isNodeModule(moduleName) ? | ||
moduleName : path.resolve(this.basedir, moduleName); | ||
const cacheKey = CachedBisourceVariationalResolver.isNodeModule( | ||
moduleName | ||
) | ||
? moduleName | ||
: path.resolve(this.basedir, moduleName); | ||
@@ -22,4 +25,3 @@ if (this._cache.has(cacheKey)) | ||
return super.resolve(moduleName) | ||
.then(deps => { | ||
return super.resolve(moduleName).then((deps) => { | ||
this._cache.set(cacheKey, deps); | ||
@@ -34,7 +36,7 @@ return deps; | ||
return Promise.resolve() | ||
.then(() => this.biSourceHas(filePath)) | ||
.then(result => { | ||
if (result) return filePath; | ||
return super.fileExists(filePath); | ||
}); | ||
.then(() => this.biSourceHas(filePath)) | ||
.then((result) => { | ||
if (result) return filePath; | ||
return super.fileExists(filePath); | ||
}); | ||
} | ||
@@ -41,0 +43,0 @@ } |
304
index.js
const path = require('path'); | ||
const {stat, readFile} = require('fs'); | ||
const { stat, readFile } = require('fs'); | ||
@@ -16,7 +16,7 @@ function withPrefix(path) { | ||
constructor({ | ||
cwd=process.cwd(), | ||
basedir=process.cwd(), | ||
extensions=['.js'], | ||
runtimes=['main', 'module', 'browser'], | ||
recordPackageJson=false, | ||
cwd = process.cwd(), | ||
basedir = process.cwd(), | ||
extensions = ['.js'], | ||
runtimes = ['main', 'module', 'browser'], | ||
recordPackageJson = false, | ||
} = {}) { | ||
@@ -40,3 +40,3 @@ this.extensions = extensions; | ||
static pReadFile(filePath, options={}) { | ||
static pReadFile(filePath, options = {}) { | ||
return new Promise((resolve, reject) => { | ||
@@ -50,4 +50,15 @@ readFile(filePath, options, (err, result) => { | ||
/*** | ||
* isNodeModule refers to detecting npm/yarn/pnpm modules. | ||
* It does not check for 'node_modules' because its input is | ||
* the string in `require('mod')` or `import * from 'mod'`. | ||
* If it is not one of the following, it is a module: | ||
* * ./relative/path | ||
* * ../another/relative/path | ||
* * /absolute/path | ||
* * C:\absolute\windows\path | ||
* * C:/another/windows/path | ||
***/ | ||
static isNodeModule(name) { | ||
return !/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[\\\/])/.test(name); | ||
return !/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[\\/])/.test(name); | ||
} | ||
@@ -66,43 +77,61 @@ | ||
const moduleAbsPath = path.resolve(this.basedir, moduleName); | ||
promise = this.resolveFile(moduleAbsPath) | ||
.catch(() => this.resolveDir(moduleAbsPath)); | ||
promise = this.resolveFile(moduleAbsPath).catch(() => | ||
this.resolveDir(moduleAbsPath) | ||
); | ||
} else { | ||
promise = this.resolveNodeModules(moduleName); | ||
} | ||
return promise | ||
// Post process | ||
.then((deps) => { | ||
// Make the path relative to the `basedir`. | ||
Object.keys(deps) | ||
.filter(rt => deps[rt]) | ||
.forEach(rt => { | ||
if (typeof deps[rt] === 'string') { | ||
// It can be module name without real path for default | ||
// node modules (like "path") | ||
if (deps[rt].indexOf('/') < 0) return; | ||
deps[rt] = withPrefix(path.relative(this.cwd, deps[rt])); | ||
} else if (typeof deps[rt] === 'object') { | ||
const rtDep = deps[rt]; | ||
Object.keys(rtDep) | ||
.filter(key => rtDep[key]) | ||
.forEach(depKey => { | ||
const newKey = depKey.indexOf('/') < 0 ? depKey : | ||
withPrefix(path.relative(this.cwd, depKey)); | ||
const newValue = rtDep[depKey].indexOf('/') < 0 ? | ||
rtDep[depKey] : | ||
withPrefix(path.relative(this.cwd, rtDep[depKey])); | ||
delete rtDep[depKey]; | ||
rtDep[newKey] = newValue; | ||
}); | ||
} | ||
}); | ||
return deps; | ||
}) | ||
.catch(() => { | ||
throw new Error(`${moduleName} failed to resolve.`); | ||
}); | ||
return ( | ||
promise | ||
// Post process | ||
.then((deps) => { | ||
// Make the path relative to the `basedir`. | ||
Object.keys(deps) | ||
.filter((rt) => deps[rt]) | ||
.forEach((rt) => { | ||
if (typeof deps[rt] === 'string') { | ||
// It can be module name without real path for default | ||
// node modules (like "path") | ||
if (deps[rt].indexOf('/') < 0) return; | ||
deps[rt] = withPrefix( | ||
path.relative(this.cwd, deps[rt]) | ||
); | ||
} else if (typeof deps[rt] === 'object') { | ||
const rtDep = deps[rt]; | ||
Object.keys(rtDep) | ||
.filter((key) => rtDep[key]) | ||
.forEach((depKey) => { | ||
const newKey = | ||
depKey.indexOf('/') < 0 | ||
? depKey | ||
: withPrefix( | ||
path.relative( | ||
this.cwd, | ||
depKey | ||
) | ||
); | ||
const newValue = | ||
rtDep[depKey].indexOf('/') < 0 | ||
? rtDep[depKey] | ||
: withPrefix( | ||
path.relative( | ||
this.cwd, | ||
rtDep[depKey] | ||
) | ||
); | ||
delete rtDep[depKey]; | ||
rtDep[newKey] = newValue; | ||
}); | ||
} | ||
}); | ||
return deps; | ||
}) | ||
.catch(() => { | ||
throw new Error(`${moduleName} failed to resolve.`); | ||
}) | ||
); | ||
} | ||
fileExists(filePath) { | ||
return ModuleResolver.pStat(filePath).then(stat => { | ||
return ModuleResolver.pStat(filePath).then((stat) => { | ||
if (stat.isFile() || stat.isFIFO()) return filePath; | ||
@@ -118,7 +147,7 @@ throw new Error({ | ||
let promise = this.fileExists(moduleName); | ||
this.extensions.forEach(ext => { | ||
this.extensions.forEach((ext) => { | ||
promise = promise.catch(() => this.fileExists(moduleName + ext)); | ||
}); | ||
return promise.then(filePath => { | ||
return promise.then((filePath) => { | ||
const reduced = this.runtimes.reduce((reduced, name) => { | ||
@@ -133,4 +162,5 @@ reduced[name] = filePath; | ||
resolveDir(moduleName) { | ||
return this.resolvePackageJson(moduleName) | ||
.catch(() => this.resolveFile(path.join(moduleName, 'index'))); | ||
return this.resolvePackageJson(moduleName).catch(() => | ||
this.resolveFile(path.join(moduleName, 'index')) | ||
); | ||
} | ||
@@ -140,13 +170,14 @@ | ||
return ModuleResolver.pStat(dirName) | ||
.then(stat => { | ||
if (stat.isFile()) return ModuleResolver.pReadFile(dirName, 'utf8'); | ||
throw new Error({ | ||
message: `${dirName} does not have package.json as a File.`, | ||
code: 'ENOENT', | ||
.then((stat) => { | ||
if (stat.isFile()) | ||
return ModuleResolver.pReadFile(dirName, 'utf8'); | ||
throw new Error({ | ||
message: `${dirName} does not have package.json as a File.`, | ||
code: 'ENOENT', | ||
}); | ||
}) | ||
.then((packageStr) => { | ||
// if fails to parse, we will hit catch | ||
return JSON.parse(packageStr); | ||
}); | ||
}) | ||
.then((packageStr) => { | ||
// if fails to parse, we will hit catch | ||
return JSON.parse(packageStr); | ||
}); | ||
} | ||
@@ -157,69 +188,71 @@ | ||
return this.readPackageJson(packagePath) | ||
.then(pkg => { | ||
if (this.runtimes.every(name => !pkg[name])) | ||
throw new Error('package.json without "main"'); | ||
.then((pkg) => { | ||
if (this.runtimes.every((name) => !pkg[name])) | ||
throw new Error('package.json without "main"'); | ||
const consider = new Map(); | ||
// A "package.json" can have below data structure | ||
// { | ||
// "main": "./foo", | ||
// "browser": { | ||
// "./foo": "./bar", | ||
// "moduleA": "moduleB", | ||
// "./baz": false, | ||
// "./abc": "./xyz.js" | ||
// } | ||
// } | ||
// In case of the main, it should resolve to either "./foo.js" or "./foo/index.js" | ||
// In case of browser runtime, it should anything that requires "./foo" should map to "./bar.js" or "./bar/index.js" | ||
this.runtimes.filter(name => pkg[name]) | ||
.forEach(name => { | ||
if (typeof pkg[name] === 'string') consider.set(pkg[name]); | ||
else if (typeof pkg[name] === 'object') { | ||
Object.keys(pkg[name]).forEach(fromPath => { | ||
consider.set(fromPath); | ||
if (typeof pkg[name][fromPath] === 'string') | ||
consider.set(pkg[name][fromPath]); | ||
const consider = new Map(); | ||
// A "package.json" can have below data structure | ||
// { | ||
// "main": "./foo", | ||
// "browser": { | ||
// "./foo": "./bar", | ||
// "moduleA": "moduleB", | ||
// "./baz": false, | ||
// "./abc": "./xyz.js" | ||
// } | ||
// } | ||
// In case of the main, it should resolve to either "./foo.js" or "./foo/index.js" | ||
// In case of browser runtime, it should anything that requires "./foo" should map to "./bar.js" or "./bar/index.js" | ||
this.runtimes | ||
.filter((name) => pkg[name]) | ||
.forEach((name) => { | ||
if (typeof pkg[name] === 'string') | ||
consider.set(pkg[name]); | ||
else if (typeof pkg[name] === 'object') { | ||
Object.keys(pkg[name]).forEach((fromPath) => { | ||
consider.set(fromPath); | ||
if (typeof pkg[name][fromPath] === 'string') | ||
consider.set(pkg[name][fromPath]); | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
const furtherPaths = Array.from(consider.keys()); | ||
const furtherResolve = furtherPaths.map(depPath => { | ||
let promise = this.resolve(path.join(moduleName, depPath)); | ||
if (ModuleResolver.isNodeModule(depPath)) | ||
promise = promise.catch(() => this.resolve(depPath)); | ||
return promise.catch(() => false); | ||
}); | ||
return Promise.all(furtherResolve).then(resolves => { | ||
if (resolves.every(resolve => resolve === false)) | ||
throw new Error('None of the path declared resolves'); | ||
resolves.forEach((resolved, index) => { | ||
consider.set(furtherPaths[index], resolved); | ||
const furtherPaths = Array.from(consider.keys()); | ||
const furtherResolve = furtherPaths.map((depPath) => { | ||
let promise = this.resolve(path.join(moduleName, depPath)); | ||
if (ModuleResolver.isNodeModule(depPath)) | ||
promise = promise.catch(() => this.resolve(depPath)); | ||
return promise.catch(() => false); | ||
}); | ||
return {deps: consider, pkg}; | ||
}); | ||
}) | ||
.then(({pkg, deps}) => { | ||
const resolved = this.runtimes.reduce((reduced, name) => { | ||
const runtimeVal = pkg[name] || pkg.main; | ||
if (deps.has(runtimeVal)) | ||
reduced[name] = deps.get(runtimeVal)[name]; | ||
else if (typeof runtimeVal === 'object') { | ||
const obj = reduced[name] = {}; | ||
Object.keys(runtimeVal).forEach(key => { | ||
const val = runtimeVal[key]; | ||
if (!deps.get(key) && !deps.get(val)) return; | ||
if (!deps.get(key) && deps.get(val)) | ||
return obj[key] = deps.get(val)[name]; | ||
if (deps.get(key) && typeof val !== 'string') | ||
return obj[deps.get(key)[name]] = false; | ||
obj[deps.get(key)[name]] = deps.get(val)[name]; | ||
return Promise.all(furtherResolve).then((resolves) => { | ||
if (resolves.every((resolve) => resolve === false)) | ||
throw new Error('None of the path declared resolves'); | ||
resolves.forEach((resolved, index) => { | ||
consider.set(furtherPaths[index], resolved); | ||
}); | ||
} | ||
return reduced; | ||
}, {}); | ||
return { deps: consider, pkg }; | ||
}); | ||
}) | ||
.then(({ pkg, deps }) => { | ||
const resolved = this.runtimes.reduce((reduced, name) => { | ||
const runtimeVal = pkg[name] || pkg.main; | ||
if (deps.has(runtimeVal)) | ||
reduced[name] = deps.get(runtimeVal)[name]; | ||
else if (typeof runtimeVal === 'object') { | ||
const obj = (reduced[name] = {}); | ||
Object.keys(runtimeVal).forEach((key) => { | ||
const val = runtimeVal[key]; | ||
if (!deps.get(key) && !deps.get(val)) return; | ||
if (!deps.get(key) && deps.get(val)) | ||
return (obj[key] = deps.get(val)[name]); | ||
if (deps.get(key) && typeof val !== 'string') | ||
return (obj[deps.get(key)[name]] = false); | ||
obj[deps.get(key)[name]] = deps.get(val)[name]; | ||
}); | ||
} | ||
return reduced; | ||
}, {}); | ||
if (this.recordPackageJson) resolved.packageJson = packagePath; | ||
return resolved; | ||
}); | ||
if (this.recordPackageJson) resolved.packageJson = packagePath; | ||
return resolved; | ||
}); | ||
} | ||
@@ -232,11 +265,16 @@ | ||
promise = promise.catch(() => { | ||
return ModuleResolver.pStat(nodeModulePath).then(stat => { | ||
if (!stat.isDirectory()) throw new Error({ | ||
message: `${nodeModulePath} is not a directory.`, | ||
code: 'ENOENT', | ||
}); | ||
return ModuleResolver.pStat(nodeModulePath).then((stat) => { | ||
if (!stat.isDirectory()) | ||
throw new Error({ | ||
message: `${nodeModulePath} is not a directory.`, | ||
code: 'ENOENT', | ||
}); | ||
const moduleFullPath = path.join(nodeModulePath, moduleName); | ||
return this.resolveFile(moduleFullPath) | ||
.catch(() => this.resolveDir(moduleFullPath)); | ||
const moduleFullPath = path.join( | ||
nodeModulePath, | ||
moduleName | ||
); | ||
return this.resolveFile(moduleFullPath).catch(() => | ||
this.resolveDir(moduleFullPath) | ||
); | ||
}); | ||
@@ -264,3 +302,3 @@ }); | ||
const splitRe = process.platform === 'win32' ? /[\/\\]/ : /\/+/; | ||
const splitRe = process.platform === 'win32' ? /[/\\]/ : /\/+/; | ||
const parts = start.split(splitRe); | ||
@@ -271,6 +309,12 @@ | ||
if (modules === parts[i]) continue; | ||
dirs.push(prefix + path.join(path.join.apply(path, parts.slice(0, i + 1)), modules)); | ||
dirs.push( | ||
prefix + | ||
path.join( | ||
path.join.apply(path, parts.slice(0, i + 1)), | ||
modules | ||
) | ||
); | ||
} | ||
if (process.platform === 'win32'){ | ||
if (process.platform === 'win32') { | ||
dirs[dirs.length - 1] = dirs[dirs.length - 1].replace(':', ':\\'); | ||
@@ -277,0 +321,0 @@ } |
{ | ||
"name": "mendel-resolver", | ||
"version": "4.0.0-alpha.0", | ||
"version": "4.0.0-alpha.1", | ||
"description": "node-resolve + browser-resolve that is promise based in an OOP fashion", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "tap test" | ||
"test": "tap test/*.js --coverage-map=../../.tap.coverage.map.js --no-check-coverage" | ||
}, | ||
@@ -13,7 +13,7 @@ "author": "Stephan Lee <stephanwlee@gmail.com>", | ||
"type": "git", | ||
"url": "https://github.com/yahoo/mendel" | ||
"url": "https://github.com/considerinc/mendel" | ||
}, | ||
"dependencies": { | ||
"debug": "^4.3.4", | ||
"mendel-development": "^4.0.0-alpha.0" | ||
"mendel-development": "^4.0.0-alpha.1" | ||
}, | ||
@@ -23,3 +23,3 @@ "devDependencies": { | ||
}, | ||
"gitHead": "e91812ab9f13c60d431be5ce5e024d3a68073f42" | ||
"gitHead": "6e24d114f4ff273753654df1aa5e6c124f626e25" | ||
} |
@@ -35,3 +35,3 @@ const ModuleResolver = require('./index'); | ||
if (match) { | ||
const absChain = match.variation.chain.map(varDir => { | ||
const absChain = match.variation.chain.map((varDir) => { | ||
return path.resolve(path.resolve(this.projectRoot, varDir)); | ||
@@ -56,3 +56,3 @@ }); | ||
isNonProjectSource(modulePath) { | ||
return this.varDirs.every(dir => { | ||
return this.varDirs.every((dir) => { | ||
return modulePath.indexOf(dir) === -1; | ||
@@ -65,3 +65,3 @@ }); | ||
this.isBasePath(modulePath) || | ||
isNodeModule(modulePath) || | ||
isNodeModuleFile(modulePath) || | ||
this.isNonProjectSource(modulePath) | ||
@@ -72,3 +72,2 @@ ) { | ||
const moduleId = this.getModuleId(modulePath); | ||
@@ -87,18 +86,25 @@ return this.variationChain.reduce((promise, variation) => { | ||
const resolveFiles = this.runtimes | ||
.filter(name => pkg[name]) | ||
.map(name => { | ||
return this.resolveFile(path.join(moduleName, pkg[name])) | ||
// `resolveFile` returns Object with all values the same and that is useless for us. | ||
.then(fileResolved => ({name, path: fileResolved[name]})) | ||
// Even if file does not resolve, let's not make the promise all fail fast. | ||
.catch(() => {}); | ||
.filter((name) => pkg[name]) | ||
.map((name) => { | ||
return ( | ||
this.resolveFile(path.join(moduleName, pkg[name])) | ||
// `resolveFile` returns Object with all values the same and that is useless for us. | ||
.then((fileResolved) => ({ | ||
name, | ||
path: fileResolved[name], | ||
})) | ||
// Even if file does not resolve, let's not make the promise all fail fast. | ||
.catch(() => {}) | ||
); | ||
}); | ||
return Promise.all(resolveFiles).then(resolves => { | ||
return Promise.all(resolveFiles).then((resolves) => { | ||
const resolved = {}; | ||
// for failed case, we returned undefined in the catch above so lets filter that out. | ||
resolves.filter(Boolean).forEach(({name, path}) => { | ||
resolves.filter(Boolean).forEach(({ name, path }) => { | ||
resolved[name] = path; | ||
}); | ||
this.runtimes.filter(name => !resolved[name]).forEach(name => resolved[name] = resolved.main); | ||
this.runtimes | ||
.filter((name) => !resolved[name]) | ||
.forEach((name) => (resolved[name] = resolved.main)); | ||
return resolved; | ||
@@ -109,18 +115,24 @@ }); | ||
resolveDir(moduleName) { | ||
if (this.isBasePath(moduleName) || isNodeModule(moduleName)) return super.resolveDir(moduleName); | ||
if (this.isBasePath(moduleName) || isNodeModuleFile(moduleName)) | ||
return super.resolveDir(moduleName); | ||
const moduleId = this.getModuleId(moduleName); | ||
let promise = Promise.reject(); | ||
this.variationChain.forEach(variation => { | ||
this.variationChain.forEach((variation) => { | ||
const packagePath = path.join(variation, moduleId, '/package.json'); | ||
promise = promise.catch(() => { | ||
return this.readPackageJson(packagePath).then(varPackageJson => this._processPackageJson(moduleName, varPackageJson)); | ||
return this.readPackageJson(packagePath).then( | ||
(varPackageJson) => | ||
this._processPackageJson(moduleName, varPackageJson) | ||
); | ||
}); | ||
}); | ||
return promise.catch(() => this.resolveFile(path.join(moduleName, 'index'))); | ||
return promise.catch(() => | ||
this.resolveFile(path.join(moduleName, 'index')) | ||
); | ||
} | ||
} | ||
function isNodeModule(id) { | ||
function isNodeModuleFile(id) { | ||
return id.indexOf('node_modules') >= 0; | ||
@@ -127,0 +139,0 @@ } |
64292
10
442