Skip to content

Commit

Permalink
Allow specifying "require" option via tsconfig (#925)
Browse files Browse the repository at this point in the history
  • Loading branch information
cspotcode committed Jul 30, 2020
1 parent f03f09d commit 436e3a8
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 11 deletions.
29 changes: 29 additions & 0 deletions dist-raw/node-createrequire.js
@@ -0,0 +1,29 @@
// Extracted from https://github.com/nodejs/node/blob/ec2ffd6b9d255e19818b6949d2f7dc7ac70faee9/lib/internal/modules/cjs/loader.js
// then modified to suit our needs

const path = require('path');
const Module = require('module');

exports.createRequireFromPath = createRequireFromPath;

function createRequireFromPath(filename) {
// Allow a directory to be passed as the filename
const trailingSlash =
filename.endsWith('/') || (isWindows && filename.endsWith('\\'));

const proxyPath = trailingSlash ?
path.join(filename, 'noop.js') :
filename;

const m = new Module(proxyPath);
m.filename = proxyPath;

m.paths = Module._nodeModulePaths(m.path);
return makeRequireFunction(m, proxyPath);
}

// This trick is much smaller than copy-pasting from https://github.com/nodejs/node/blob/ec2ffd6b9d255e19818b6949d2f7dc7ac70faee9/lib/internal/modules/cjs/helpers.js#L32-L101
function makeRequireFunction(module, filename) {
module._compile('module.exports = require;', filename)
return mod.exports
}
6 changes: 2 additions & 4 deletions src/bin.ts
Expand Up @@ -90,7 +90,7 @@ export function main (argv: string[]) {
'--help': help = false,
'--script-mode': scriptMode = false,
'--version': version = 0,
'--require': requires = [],
'--require': argsRequire = [],
'--eval': code = undefined,
'--print': print = false,
'--interactive': interactive = false,
Expand Down Expand Up @@ -176,6 +176,7 @@ export function main (argv: string[]) {
compiler,
ignoreDiagnostics,
compilerOptions,
require: argsRequire,
readFile: code !== undefined
? (path: string) => {
if (path === state.path) return state.input
Expand Down Expand Up @@ -212,9 +213,6 @@ export function main (argv: string[]) {
module.filename = state.path
module.paths = (Module as any)._nodeModulePaths(cwd)

// Require specified modules before start-up.
;(Module as any)._preloadModules(requires)

// Prepend `ts-node` arguments to CLI for child processes.
process.execArgv.unshift(__filename, ...process.argv.slice(2, process.argv.length - args._.length))
process.argv = [process.argv[1]].concat(scriptPath || []).concat(args._.slice(1))
Expand Down
16 changes: 11 additions & 5 deletions src/index.spec.ts
Expand Up @@ -477,7 +477,7 @@ describe('ts-node', function () {
const BIN_EXEC = `"${BIN_PATH}" --project tests/tsconfig-options/tsconfig.json`

it('should override compiler options from env', function (done) {
exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, {
exec(`${BIN_EXEC} tests/tsconfig-options/log-options1.js`, {
env: {
...process.env,
TS_NODE_COMPILER_OPTIONS: '{"typeRoots": ["env-typeroots"]}'
Expand All @@ -491,33 +491,38 @@ describe('ts-node', function () {
})

it('should use options from `tsconfig.json`', function (done) {
exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, function (err, stdout) {
exec(`${BIN_EXEC} tests/tsconfig-options/log-options1.js`, function (err, stdout) {
expect(err).to.equal(null)
const { options, config } = JSON.parse(stdout)
expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots').replace(/\\/g, '/')])
expect(config.options.types).to.deep.equal(['tsconfig-tsnode-types'])
expect(options.pretty).to.equal(undefined)
expect(options.skipIgnore).to.equal(false)
expect(options.transpileOnly).to.equal(true)
expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required1.js')])
return done()
})
})

it('should have flags override `tsconfig.json`', function (done) {
exec(`${BIN_EXEC} --skip-ignore --compiler-options "{\\"types\\":[\\"flags-types\\"]}" tests/tsconfig-options/log-options.js`, function (err, stdout) {
it('should have flags override / merge with `tsconfig.json`', function (done) {
exec(`${BIN_EXEC} --skip-ignore --compiler-options "{\\"types\\":[\\"flags-types\\"]}" --require ./tests/tsconfig-options/required2.js tests/tsconfig-options/log-options2.js`, function (err, stdout) {
expect(err).to.equal(null)
const { options, config } = JSON.parse(stdout)
expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots').replace(/\\/g, '/')])
expect(config.options.types).to.deep.equal(['flags-types'])
expect(options.pretty).to.equal(undefined)
expect(options.skipIgnore).to.equal(true)
expect(options.transpileOnly).to.equal(true)
expect(options.require).to.deep.equal([
join(__dirname, '../tests/tsconfig-options/required1.js'),
'./tests/tsconfig-options/required2.js'
])
return done()
})
})

it('should have `tsconfig.json` override environment', function (done) {
exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, {
exec(`${BIN_EXEC} tests/tsconfig-options/log-options1.js`, {
env: {
...process.env,
TS_NODE_PRETTY: 'true',
Expand All @@ -531,6 +536,7 @@ describe('ts-node', function () {
expect(options.pretty).to.equal(true)
expect(options.skipIgnore).to.equal(false)
expect(options.transpileOnly).to.equal(true)
expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required1.js')])
return done()
})
})
Expand Down
38 changes: 36 additions & 2 deletions src/index.ts
Expand Up @@ -5,6 +5,7 @@ import { BaseError } from 'make-error'
import * as util from 'util'
import { fileURLToPath } from 'url'
import * as _ts from 'typescript'
import * as Module from 'module'

/**
* Does this version of node obey the package.json "type" field
Expand Down Expand Up @@ -196,6 +197,15 @@ export interface CreateOptions {
* Ignore TypeScript warnings by diagnostic code.
*/
ignoreDiagnostics?: Array<number | string>
/**
* Modules to require, like node's `--require` flag.
*
* If specified in tsconfig.json, the modules will be resolved relative to the tsconfig.json file.
*
* If specified programmatically, each input string should be pre-resolved to an absolute path for
* best results.
*/
require?: Array<string>
readFile?: (path: string) => string | undefined
fileExists?: (path: string) => boolean
transformers?: _ts.CustomTransformers | ((p: _ts.Program) => _ts.CustomTransformers)
Expand Down Expand Up @@ -229,7 +239,7 @@ export interface TsConfigOptions extends Omit<RegisterOptions,
| 'skipProject'
| 'project'
| 'dir'
> { }
> {}

/**
* Like `Object.assign`, but ignores `undefined` properties.
Expand Down Expand Up @@ -383,6 +393,9 @@ export function register (opts: RegisterOptions = {}): Register {
// Register the extensions.
registerExtensions(service.options.preferTsExts, extensions, service, originalJsHandler)

// Require specified modules before start-up.
;(Module as any)._preloadModules(service.options.require)

return service
}

Expand All @@ -409,7 +422,11 @@ export function create (rawOptions: CreateOptions = {}): Register {

// Read config file and merge new options between env and CLI options.
const { config, options: tsconfigOptions } = readConfig(cwd, ts, rawOptions)
const options = assign<CreateOptions>({}, DEFAULTS, tsconfigOptions || {}, rawOptions)
const options = assign<RegisterOptions>({}, DEFAULTS, tsconfigOptions || {}, rawOptions)
options.require = [
...tsconfigOptions.require || [],
...rawOptions.require || []
]

// If `compiler` option changed based on tsconfig, re-load the compiler.
if (options.compiler !== compilerName) {
Expand Down Expand Up @@ -1002,6 +1019,14 @@ function readConfig (
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames
}, basePath, undefined, configFileName))

if (tsconfigOptions.require) {
// Modules are found relative to the tsconfig file, not the `dir` option
const tsconfigRelativeRequire = createRequire(configFileName!)
tsconfigOptions.require = tsconfigOptions.require.map((path: string) => {
return tsconfigRelativeRequire.resolve(path)
})
}

return { config: fixedConfig, options: tsconfigOptions }
}

Expand Down Expand Up @@ -1062,3 +1087,12 @@ function getTokenAtPosition (ts: typeof _ts, sourceFile: _ts.SourceFile, positio
return current
}
}

let nodeCreateRequire: (path: string) => NodeRequire
function createRequire (filename: string) {
if (!nodeCreateRequire) {
// tslint:disable-next-line
nodeCreateRequire = Module.createRequire || Module.createRequireFromPath || require('../dist-raw/node-createrequire').createRequireFromPath
}
return nodeCreateRequire(filename)
}
@@ -1,4 +1,5 @@
const assert = require('assert')
assert(process.required1)
const register = process[Symbol.for('ts-node.register.instance')]
console.log(JSON.stringify({
options: register.options,
Expand Down
3 changes: 3 additions & 0 deletions tests/tsconfig-options/log-options2.js
@@ -0,0 +1,3 @@
const assert = require('assert')
require('./log-options1')
assert(process.required2)
1 change: 1 addition & 0 deletions tests/tsconfig-options/required1.js
@@ -0,0 +1 @@
process.required1 = true
2 changes: 2 additions & 0 deletions tests/tsconfig-options/required2.js
@@ -0,0 +1,2 @@
require('assert')(process.required1)
process.required2 = true
1 change: 1 addition & 0 deletions tests/tsconfig-options/tsconfig.json
Expand Up @@ -5,6 +5,7 @@
"types": ["tsconfig-tsnode-types"]
},
"transpileOnly": true,
"require": ["./required1"],
"skipIgnore": false
},
"compilerOptions": {
Expand Down

0 comments on commit 436e3a8

Please sign in to comment.