From e0a83b55d335f0a8c8bd53f5c24f6a47dedf14d4 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Sun, 13 Oct 2019 17:33:42 -0700 Subject: [PATCH] Add `--build` flag, improve TS module error (#896) --- README.md | 1 + src/bin.ts | 8 ++++-- src/index.spec.ts | 14 +++++++-- src/index.ts | 59 +++++++++++++++++++++----------------- tests/.gitignore | 1 + tests/emit-compiled.ts | 4 +-- tests/from-node-modules.ts | 1 + tests/node_modules/test.ts | 3 ++ tests/tsconfig.json | 5 ++-- tests/tsconfig.json5 | 11 ------- 10 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 tests/.gitignore create mode 100644 tests/from-node-modules.ts create mode 100644 tests/node_modules/test.ts delete mode 100644 tests/tsconfig.json5 diff --git a/README.md b/README.md index 5e5afdbde..d569d561e 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,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`) +* `--build` Emit output files into `.ts-node` directory (`TS_NODE_BUILD`, default: `false`) * `--prefer-ts-exts` Re-order file extensions so that TypeScript imports are preferred (`TS_NODE_PREFER_TS_EXTS`, default: `false`) ### Programmatic Only Options diff --git a/src/bin.ts b/src/bin.ts index 79f700278..3c16cbb34 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -34,6 +34,7 @@ const args = arg({ '--skip-ignore': Boolean, '--prefer-ts-exts': Boolean, '--log-error': Boolean, + '--build': Boolean, // Aliases. '-e': '--eval', @@ -41,6 +42,7 @@ const args = arg({ '-r': '--require', '-h': '--help', '-v': '--version', + '-B': '--build', '-T': '--transpile-only', '-I': '--ignore', '-P': '--project', @@ -66,7 +68,8 @@ const { '--skip-project': skipProject = DEFAULTS.skipProject, '--skip-ignore': skipIgnore = DEFAULTS.skipIgnore, '--prefer-ts-exts': preferTsExts = DEFAULTS.preferTsExts, - '--log-error': logError = DEFAULTS.logError + '--log-error': logError = DEFAULTS.logError, + '--build': build = DEFAULTS.build } = args if (help) { @@ -118,16 +121,17 @@ const EVAL_INSTANCE = { input: '', output: '', version: 0, lines: 0 } // Register the TypeScript compiler instance. const service = register({ + build, files, pretty, typeCheck, transpileOnly, ignore, project, - skipIgnore, preferTsExts, logError, skipProject, + skipIgnore, compiler, ignoreDiagnostics, compilerOptions, diff --git a/src/index.spec.ts b/src/index.spec.ts index 4fd66b986..6b8349b73 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -8,7 +8,7 @@ import { register, VERSION } from './index' const TEST_DIR = join(__dirname, '../tests') const EXEC_PATH = join(__dirname, '../dist/bin') -const PROJECT = join(TEST_DIR, semver.gte(ts.version, '2.5.0') ? 'tsconfig.json5' : 'tsconfig.json') +const PROJECT = join(TEST_DIR, 'tsconfig.json') const BIN_EXEC = `node "${EXEC_PATH}" --project "${PROJECT}"` const SOURCE_MAP_REGEXP = /\/\/# sourceMappingURL=data:application\/json;charset=utf\-8;base64,[\w\+]+=*$/ @@ -165,7 +165,7 @@ describe('ts-node', function () { }) }) - it.skip('eval should work with source maps', function (done) { + it('eval should work with source maps', function (done) { exec(`${BIN_EXEC} -pe "import './tests/throw'"`, function (err) { if (err === null) { return done('Command was expected to fail, but it succeeded.') @@ -305,6 +305,16 @@ describe('ts-node', function () { return done() }) }) + + it('should give ts error for invalid node_modules', function (done) { + exec(`${BIN_EXEC} --skip-ignore tests/from-node-modules`, function (err, stdout) { + if (err === null) return done('Expected an error') + + expect(err.message).to.contain('Unable to compile file from external library') + + return done() + }) + }) }) describe('register', function () { diff --git a/src/index.ts b/src/index.ts index f246847e5..df60ae343 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,6 +56,7 @@ export const VERSION = require('../package.json').version * Registration options. */ export interface Options { + build?: boolean | null pretty?: boolean | null typeCheck?: boolean | null transpileOnly?: boolean | null @@ -64,8 +65,8 @@ export interface Options { compiler?: string ignore?: string[] project?: string - skipIgnore?: boolean | null skipProject?: boolean | null + skipIgnore?: boolean | null preferTsExts?: boolean | null compilerOptions?: object ignoreDiagnostics?: Array @@ -95,19 +96,20 @@ export interface TypeInfo { * Default register options. */ export const DEFAULTS: Options = { - files: yn(process.env['TS_NODE_FILES']), - pretty: yn(process.env['TS_NODE_PRETTY']), - compiler: process.env['TS_NODE_COMPILER'], - compilerOptions: parse(process.env['TS_NODE_COMPILER_OPTIONS']), - ignore: split(process.env['TS_NODE_IGNORE']), - 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']), - logError: yn(process.env['TS_NODE_LOG_ERROR']) + files: yn(process.env.TS_NODE_FILES), + pretty: yn(process.env.TS_NODE_PRETTY), + compiler: process.env.TS_NODE_COMPILER, + compilerOptions: parse(process.env.TS_NODE_COMPILER_OPTIONS), + ignore: split(process.env.TS_NODE_IGNORE), + project: process.env.TS_NODE_PROJECT, + skipProject: yn(process.env.TS_NODE_SKIP_PROJECT), + skipIgnore: yn(process.env.TS_NODE_SKIP_IGNORE), + 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), + logError: yn(process.env.TS_NODE_LOG_ERROR), + build: yn(process.env.TS_NODE_BUILD) } /** @@ -191,7 +193,7 @@ function cachedLookup (fn: (arg: string) => T): (arg: string) => T { * Register TypeScript compiler. */ export function register (opts: Options = {}): Register { - const options = Object.assign({}, DEFAULTS, opts) + const options = { ...DEFAULTS, ...opts } const originalJsHandler = require.extensions['.js'] // tslint:disable-line const ignoreDiagnostics = [ @@ -201,9 +203,7 @@ export function register (opts: Options = {}): Register { ...(options.ignoreDiagnostics || []) ].map(Number) - const ignore = options.skipIgnore ? [] : ( - options.ignore || ['/node_modules/'] - ).map(str => new RegExp(str)) + const ignore = options.skipIgnore ? [] : (options.ignore || ['/node_modules/']).map(str => new RegExp(str)) // Require the TypeScript compiler and configuration. const cwd = process.cwd() @@ -369,31 +369,37 @@ export function register (opts: Options = {}): Register { const sourceFile = builderProgram.getSourceFile(fileName) if (!sourceFile) throw new TypeError(`Unable to read file: ${fileName}`) - const diagnostics = ts.getPreEmitDiagnostics(builderProgram.getProgram(), sourceFile) + const program = builderProgram.getProgram() + const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile) const diagnosticList = filterDiagnostics(diagnostics, ignoreDiagnostics) if (diagnosticList.length) reportTSError(diagnosticList) - const result = builderProgram.emit(sourceFile, (path, file) => { + const result = builderProgram.emit(sourceFile, (path, file, writeByteOrderMark) => { if (path.endsWith('.map')) { output[1] = file } else { output[0] = file } + + if (options.build) sys.writeFile(path, file, writeByteOrderMark) }, undefined, undefined, getCustomTransformers()) if (result.emitSkipped) { throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`) } - // Throw an error when requiring `.d.ts` files. + // Throw an error when requiring files that cannot be compiled. if (output[0] === '') { + if (program.isSourceFileFromExternalLibrary(sourceFile)) { + throw new TypeError(`Unable to compile file from external library: ${relative(cwd, fileName)}`) + } + throw new TypeError( - 'Unable to require `.d.ts` file.\n' + + `Unable to require file: ${relative(cwd, fileName)}\n` + 'This is usually the result of a faulty configuration or import. ' + - 'Make sure there is a `.js`, `.json` or another executable extension and ' + - 'loader (attached before `ts-node`) available alongside ' + - `\`${basename(fileName)}\`.` + 'Make sure there is a `.js`, `.json` or other executable extension with ' + + 'loader attached before `ts-node` available.' ) } @@ -420,7 +426,8 @@ export function register (opts: Options = {}): Register { } } - if (config.options.incremental) { + // Write `.tsbuildinfo` when `--build` is enabled. + if (options.build && config.options.incremental) { process.on('exit', () => { // Emits `.tsbuildinfo` to filesystem. (builderProgram.getProgram() as any).emitBuildInfo() diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 000000000..ddf342489 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +!node_modules/ diff --git a/tests/emit-compiled.ts b/tests/emit-compiled.ts index a2e22c7c8..7708b8551 100644 --- a/tests/emit-compiled.ts +++ b/tests/emit-compiled.ts @@ -4,9 +4,9 @@ extensions.forEach(ext => { const old = require.extensions[ext] require.extensions[ext] = (m, path) => { - const _compile = m._compile + const _compile = (m as any)._compile - m._compile = (code, path) => { + ;(m as any)._compile = (code, path) => { console.error(code) return _compile.call(this, code, path) } diff --git a/tests/from-node-modules.ts b/tests/from-node-modules.ts new file mode 100644 index 000000000..9fc3e3f58 --- /dev/null +++ b/tests/from-node-modules.ts @@ -0,0 +1 @@ +import 'test' diff --git a/tests/node_modules/test.ts b/tests/node_modules/test.ts new file mode 100644 index 000000000..9bf72d93a --- /dev/null +++ b/tests/node_modules/test.ts @@ -0,0 +1,3 @@ +const message: string = 'hello world' + +console.log(message) diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 526a7e0bf..12fb856f1 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -2,9 +2,10 @@ "compilerOptions": { "jsx": "react", "noEmit": true, + // Global type definitions. "typeRoots": [ "./typings", - "../node_modules/@types" - ] + "../node_modules/@types", + ], } } diff --git a/tests/tsconfig.json5 b/tests/tsconfig.json5 deleted file mode 100644 index 12fb856f1..000000000 --- a/tests/tsconfig.json5 +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react", - "noEmit": true, - // Global type definitions. - "typeRoots": [ - "./typings", - "../node_modules/@types", - ], - } -}