diff --git a/README.md b/README.md index 690a2f1c3..5c1cd44b3 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ _Environment variable denoted in parentheses._ * `--skip-project` Skip project config resolution and loading (`TS_NODE_SKIP_PROJECT`, default: `false`) * `--skip-ignore` Skip ignore checks (`TS_NODE_SKIP_IGNORE`, default: `false`) * `--log-error` Logs errors of types instead of exit the process (`TS_NODE_LOG_ERROR`, default: `false`) +* `--prefer-ts-exts` Changes the order of file extensions used when matching file imports so that `.ts` files are preferred over `.js` (`TS_PREFER_TS_EXTS`, default: `false`) ### Programmatic Only Options diff --git a/src/bin.ts b/src/bin.ts index b86c9e775..1c5241e07 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -32,6 +32,7 @@ const args = arg({ '--pretty': Boolean, '--skip-project': Boolean, '--skip-ignore': Boolean, + '--prefer-ts-exts': Boolean, '--log-error': Boolean, // Aliases. @@ -64,6 +65,7 @@ const { '--pretty': pretty = DEFAULTS.pretty, '--skip-project': skipProject = DEFAULTS.skipProject, '--skip-ignore': skipIgnore = DEFAULTS.skipIgnore, + '--prefer-ts-exts': preferTsExts = DEFAULTS.preferTsExts, '--log-error': logError = DEFAULTS.logError } = args @@ -91,6 +93,7 @@ Options: --pretty Use pretty diagnostic formatter --skip-project Skip reading \`tsconfig.json\` --skip-ignore Skip \`--ignore\` checks + --prefer-ts-exts Prefer importing TypeScript files over JavaScript files `) process.exit(0) @@ -115,6 +118,7 @@ const service = register({ ignore, project, skipIgnore, + preferTsExts, logError, skipProject, compiler, diff --git a/src/index.spec.ts b/src/index.spec.ts index 17e3810a2..c9bf83ee4 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -269,6 +269,33 @@ describe('ts-node', function () { return done() }) }) + + it('should import js before ts by default', function (done) { + exec(`${BIN_EXEC} tests/import-order/compiled`, function (err, stdout) { + expect(err).to.equal(null) + expect(stdout).to.equal('Hello, JavaScript!\n') + + return done() + }) + }) + + it('should import ts before js when --prefer-ts-exts flag is present', function (done) { + exec(`${BIN_EXEC} --prefer-ts-exts tests/import-order/compiled`, function (err, stdout) { + expect(err).to.equal(null) + expect(stdout).to.equal('Hello, TypeScript!\n') + + return done() + }) + }) + + it('should ignore .d.ts files', function (done) { + exec(`${BIN_EXEC} tests/import-order/importer`, function (err, stdout) { + expect(err).to.equal(null) + expect(stdout).to.equal('Hello, World!\n') + + return done() + }) + }) }) describe('register', function () { diff --git a/src/index.ts b/src/index.ts index 31aa4d798..ef786cca8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,6 +66,7 @@ export interface Options { project?: string skipIgnore?: boolean | null skipProject?: boolean | null + preferTsExts?: boolean | null compilerOptions?: object ignoreDiagnostics?: Array readFile?: (path: string) => string | undefined @@ -105,6 +106,7 @@ export const DEFAULTS: Options = { project: process.env['TS_NODE_PROJECT'], skipIgnore: yn(process.env['TS_NODE_SKIP_IGNORE']), skipProject: yn(process.env['TS_NODE_SKIP_PROJECT']), + preferTsExts: yn(process.env['TS_NODE_PREFER_TS_EXTS']), ignoreDiagnostics: split(process.env['TS_NODE_IGNORE_DIAGNOSTICS']), typeCheck: yn(process.env['TS_NODE_TYPE_CHECK']), transpileOnly: yn(process.env['TS_NODE_TRANSPILE_ONLY']), @@ -399,9 +401,7 @@ export function register (opts: Options = {}): Register { const register: Register = { cwd, compile, getTypeInfo, extensions, ts } // Register the extensions. - extensions.forEach(extension => { - registerExtension(extension, ignore, register, originalJsHandler) - }) + registerExtensions(opts, extensions, ignore, register, originalJsHandler) return register } @@ -415,6 +415,44 @@ function shouldIgnore (filename: string, ignore: RegExp[]) { return ignore.some(x => x.test(relname)) } +/** + * "Refreshes" an extension on `require.extentions`. + * + * @param {string} ext + */ +function refreshRequireExtension (ext: string) { + const old = require.extensions[ext] // tslint:disable-line + delete require.extensions[ext] // tslint:disable-line + require.extensions[ext] = old // tslint:disable-line +} + +/** + * Register the extensions to support when importing files. + */ +function registerExtensions ( + opts: Options, + extensions: string[], + ignore: RegExp[], + register: Register, + originalJsHandler: (m: NodeModule, filename: string) => any +) { + if (opts.preferTsExts) { + extensions.unshift( + '.ts', + '.tsx', + ...Object.keys(require.extensions), // tslint:disable-line + ) + } + + // @todo a better way with options + Array.from(new Set(extensions)) + .forEach(ext => { + registerExtension(ext, ignore, register, originalJsHandler) + + if (ext in require.extensions) refreshRequireExtension(ext) // tslint:disable-line + }) +} + /** * Register the extension for node. */ diff --git a/tests/import-order/compiled.js b/tests/import-order/compiled.js new file mode 100644 index 000000000..950e532ec --- /dev/null +++ b/tests/import-order/compiled.js @@ -0,0 +1 @@ +console.log('Hello, JavaScript!'); diff --git a/tests/import-order/compiled.ts b/tests/import-order/compiled.ts new file mode 100644 index 000000000..2a508d1c4 --- /dev/null +++ b/tests/import-order/compiled.ts @@ -0,0 +1 @@ +console.log('Hello, TypeScript!') diff --git a/tests/import-order/defined.d.ts b/tests/import-order/defined.d.ts new file mode 100644 index 000000000..3bdda61f2 --- /dev/null +++ b/tests/import-order/defined.d.ts @@ -0,0 +1,3 @@ +declare const v = 'Hello, World!' + +export default v diff --git a/tests/import-order/defined.js b/tests/import-order/defined.js new file mode 100644 index 000000000..f25261a2a --- /dev/null +++ b/tests/import-order/defined.js @@ -0,0 +1 @@ +module.exports = 'Hello, World!'; diff --git a/tests/import-order/importer.ts b/tests/import-order/importer.ts new file mode 100644 index 000000000..f20693337 --- /dev/null +++ b/tests/import-order/importer.ts @@ -0,0 +1,3 @@ +const v = require('./defined') + +console.log(v)