diff --git a/src/index.ts b/src/index.ts index 51e5b972b..8d03b5f31 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { relative, basename, extname, resolve, dirname, join } from 'path' -import { readdirSync, writeFileSync, readFileSync, statSync } from 'fs' +import { readdirSync, writeFileSync, readFileSync, statSync, existsSync } from 'fs' import { EOL, tmpdir } from 'os' import sourceMapSupport = require('source-map-support') import extend = require('xtend') @@ -186,6 +186,11 @@ export function register (options: Options = {}): () => Register { const configDiagnostics = filterDiagnostics(config.errors, ignoreWarnings, disableWarnings) const extensions = ['.ts', '.tsx'] + // Augment loading of node modules according to paths in tsconfig + if (config.options.baseUrl && config.options.paths) { + installModuleLoadForPaths(config.options.baseUrl, config.options.paths) + } + const cachedir = join( resolve(cwd, cacheDirectory), getCompilerDigest({ version: ts.version, fast, ignoreWarnings, disableWarnings, config, compiler }) @@ -650,3 +655,60 @@ export class TSError extends BaseError { } } + +/** + * Matches pattern with a single star against search. + * Star must match at least one character to be considered a match. + * @returns the part of search that * matches, or undefined if no match. + */ +function matchStar(pattern: string, search: string): string | undefined { + if (search.length < pattern.length) { return undefined } + if (pattern === '*') { return search } + const star = pattern.indexOf('*') + if (star === -1) { return undefined } + const part1 = pattern.substring(0, star) + const part2 = pattern.substring(star + 1) + if (search.substr(0, star) !== part1) { return undefined } + if (search.substr(search.length - part2.length) !== part2) { return undefined } + return search.substr(star, search.length - part2.length) +} + +/** + * Finds a path from tsconfig that matches a module load request. + * @returns the found path, or undefined if no path was found. + */ +function findConfigPath(request: string, baseUrl: string, paths: {[key: string]: Array}) { + + if (request[0] !== '.' && request[0] !== '/') { + for (const key of Object.keys(paths)) { + const starReplace = key === request ? '' : matchStar(key, request) + if (starReplace !== undefined) { + for (const pathToTry of paths[key]) { + const file = pathToTry.replace('*', starReplace) + if (existsSync(join(baseUrl, file)) + || existsSync(join(baseUrl, file + '.ts')) + || existsSync(join(baseUrl, file + '.tsx'))) { + return file + } + } + } + } + } + return undefined + +} + +/** + * Installs a custom module load function that can adhere to paths in tsconfig. + */ +function installModuleLoadForPaths(baseUrl: string, paths: {[key: string]: Array}): void { + + const Module = require('module') + const originalLoader = Module._load + Module._load = function (request: string, parent: any) { + const found = findConfigPath(request, baseUrl, paths) + if (found) { arguments[0] = found } + return originalLoader.apply(this, arguments) + } + +}