Skip to content

Commit

Permalink
feat: support bun and tsx for development
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Feb 21, 2024
1 parent fa66417 commit 17ad3e6
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 52 deletions.
134 changes: 89 additions & 45 deletions src/config/ts-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,50 @@ import {readTSConfig} from '../util/read-tsconfig'
import {isProd} from '../util/util'
import {Debug} from './util'
// eslint-disable-next-line new-cap
const debug = Debug('ts-node')
const debug = Debug('ts-path')

export const TS_CONFIGS: Record<string, TSConfig | undefined> = {}
const REGISTERED = new Set<string>()

function determineRuntime(): 'node' | 'bun' | 'ts-node' | 'tsx' {
/**
* Examples:
* #!/usr/bin/env bun
* bun bin/run.js
* bun bin/dev.js
*/
if (process.execPath.split(sep).includes('bun')) return 'bun'
/**
* Examples:
* #!/usr/bin/env node
* #!/usr/bin/env node --loader ts-node/esm --experimental-specifier-resolution=node --no-warnings
* node bin/run.js
* node bin/dev.js
*/
if (process.execArgv.length === 0) return 'node'
/**
* Examples:
* #!/usr/bin/env ts-node
* #!/usr/bin/env node_modules/.bin/ts-node
* ts-node bin/run.js
* ts-node bin/dev.js
*/
if (process.execArgv[0] === '--require' && process.execArgv[1].split(sep).includes('ts-node')) return 'ts-node'
if (process.execArgv[0].split(sep).includes('ts-node')) return 'ts-node'
/**
* Examples:
* #!/usr/bin/env tsx
* #!/usr/bin/env node_modules/.bin/tsx
* tsx bin/run.js
* tsx bin/dev.js
*/
if (process.execArgv[0] === '--require' && process.execArgv[1].split(sep).includes('tsx')) return 'tsx'

return 'node'
}

const RUN_TIME = determineRuntime()

function isErrno(error: any): error is NodeJS.ErrnoException {
return 'code' in error && error.code === 'ENOENT'
}
Expand All @@ -29,7 +68,7 @@ async function loadTSConfig(root: string): Promise<TSConfig | undefined> {
} catch (error) {
if (isErrno(error)) return

debug(`Could not parse tsconfig.json. Skipping ts-node registration for ${root}.`)
debug(`Could not parse tsconfig.json. Skipping typescript path lookup for ${root}.`)
memoizedWarn(`Could not parse tsconfig.json for ${root}. Falling back to compiled source.`)
}
}
Expand All @@ -39,21 +78,6 @@ async function registerTSNode(root: string): Promise<TSConfig | undefined> {
if (!tsconfig) return
if (REGISTERED.has(root)) return tsconfig

debug('registering ts-node at', root)
const tsNodePath = require.resolve('ts-node', {paths: [root, __dirname]})
debug('ts-node path:', tsNodePath)
let tsNode: typeof TSNode

try {
tsNode = require(tsNodePath)
} catch {
debug(`Could not find ts-node at ${tsNodePath}. Skipping ts-node registration for ${root}.`)
memoizedWarn(
`Could not find ts-node at ${tsNodePath}. Please ensure that ts-node is a devDependency. Falling back to compiled source.`,
)
return
}

const typeRoots = [join(root, 'node_modules', '@types')]

const rootDirs: string[] = []
Expand All @@ -70,31 +94,52 @@ async function registerTSNode(root: string): Promise<TSConfig | undefined> {
rootDirs.push(join(root, 'src'))
}

// Because we need to provide a modified `rootDirs` to ts-node, we need to
// remove `baseUrl` and `rootDir` from `compilerOptions` so that they
// don't conflict.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {baseUrl, rootDir, ...rest} = tsconfig.compilerOptions
const conf: TSNode.RegisterOptions = {
compilerOptions: {
...rest,
rootDirs,
typeRoots,
},
...tsconfig['ts-node'],
cwd: root,
esm: tsconfig['ts-node']?.esm ?? true,
experimentalSpecifierResolution: tsconfig['ts-node']?.experimentalSpecifierResolution ?? 'explicit',
scope: true,
scopeDir: root,
skipProject: true,
transpileOnly: true,
debug('tsconfig: %O', tsconfig)

if (RUN_TIME === 'tsx' || RUN_TIME === 'bun') {
debug(`Skipping ts-node registration for ${root} because the runtime is: ${RUN_TIME}`)
} else {
debug('registering ts-node at', root)
const tsNodePath = require.resolve('ts-node', {paths: [root, __dirname]})
debug('ts-node path:', tsNodePath)
let tsNode: typeof TSNode

try {
tsNode = require(tsNodePath)
} catch {
debug(`Could not find ts-node at ${tsNodePath}. Skipping ts-node registration for ${root}.`)
memoizedWarn(
`Could not find ts-node at ${tsNodePath}. Please ensure that ts-node is a devDependency. Falling back to compiled source.`,
)
return
}

// Because we need to provide a modified `rootDirs` to ts-node, we need to
// remove `baseUrl` and `rootDir` from `compilerOptions` so that they
// don't conflict.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {baseUrl, rootDir, ...rest} = tsconfig.compilerOptions
const conf: TSNode.RegisterOptions = {
compilerOptions: {
...rest,
rootDirs,
typeRoots,
},
...tsconfig['ts-node'],
cwd: root,
esm: tsconfig['ts-node']?.esm ?? true,
experimentalSpecifierResolution: tsconfig['ts-node']?.experimentalSpecifierResolution ?? 'explicit',
scope: true,
scopeDir: root,
skipProject: true,
transpileOnly: true,
}

debug('ts-node options: %O', conf)
tsNode.register(conf)
}

tsNode.register(conf)
REGISTERED.add(root)
debug('tsconfig: %O', tsconfig)
debug('ts-node options: %O', conf)

return tsconfig
}
Expand Down Expand Up @@ -132,8 +177,7 @@ function cannotUseTsNode(root: string, plugin: Plugin | undefined, isProduction:
if (plugin?.moduleType !== 'module' || isProduction) return false

const nodeMajor = Number.parseInt(process.version.replace('v', '').split('.')[0], 10)
const tsNodeExecIsUsed = process.execArgv[0] === '--require' && process.execArgv[1].split(sep).includes(`ts-node`)
return tsNodeExecIsUsed && nodeMajor >= 20
return RUN_TIME === 'ts-node' && nodeMajor >= 20
}

/**
Expand Down Expand Up @@ -198,21 +242,21 @@ export async function tsPath(root: string, orig: string | undefined, plugin?: Pl
// NOTE: The order of these checks matter!

if (settings.tsnodeEnabled === false) {
debug(`Skipping ts-node registration for ${root} because tsNodeEnabled is explicitly set to false`)
debug(`Skipping typescript path lookup for ${root} because tsNodeEnabled is explicitly set to false`)
return orig
}

const isProduction = isProd()

// Do not skip ts-node registration if the plugin is linked
if (settings.tsnodeEnabled === undefined && isProduction && plugin?.type !== 'link') {
debug(`Skipping ts-node registration for ${root} because NODE_ENV is NOT "test" or "development"`)
debug(`Skipping typescript path lookup for ${root} because NODE_ENV is NOT "test" or "development"`)
return orig
}

if (cannotTranspileEsm(rootPlugin, plugin, isProduction)) {
debug(
`Skipping ts-node registration for ${root} because it's an ESM module (NODE_ENV: ${process.env.NODE_ENV}, root plugin module type: ${rootPlugin?.moduleType})`,
`Skipping typescript path lookup for ${root} because it's an ESM module (NODE_ENV: ${process.env.NODE_ENV}, root plugin module type: ${rootPlugin?.moduleType})`,
)
if (plugin?.type === 'link')
memoizedWarn(
Expand All @@ -222,7 +266,7 @@ export async function tsPath(root: string, orig: string | undefined, plugin?: Pl
}

if (cannotUseTsNode(root, plugin, isProduction)) {
debug(`Skipping ts-node registration for ${root} because ts-node is run in node version ${process.version}"`)
debug(`Skipping typescript path lookup for ${root} because ts-node is run in node version ${process.version}"`)
memoizedWarn(
`ts-node executable cannot transpile ESM in Node 20. Existing compiled source will be used instead. See https://github.com/oclif/core/issues/817.`,
)
Expand Down
10 changes: 3 additions & 7 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,10 @@ export type Settings = {
*/
performanceEnabled?: boolean
/**
* Try to use ts-node to load typescript source files instead of
* javascript files.
* Try to use ts-node to load typescript source files instead of javascript files.
* Defaults to true in development and test environments (e.g. using bin/dev.js or
* NODE_ENV=development or NODE_ENV=test).
*
* NOTE: This requires registering ts-node first.
* require('ts-node').register();
*
* Environment Variable:
* NODE_ENV=development
*/
tsnodeEnabled?: boolean
}
Expand Down

0 comments on commit 17ad3e6

Please sign in to comment.