What are you trying to achieve?
Bun is a supported runtime for CodeceptJS 4 — the 4.0 release post says under Native ESM: "Your project needs "type": "module" and NodeJS 20 or Bun."
Run a TypeScript suite with Bun as the runtime (bunx --bun codeceptjs run).
Bun transpiles TypeScript natively, so no tsx / ts-node loader is involved. I expected to be able to leave require out of the config and not install tsx at all.
What do you get instead?
require: ['tsx/esm'] is mandatory even under Bun. Without it the run aborts with process.exit(1) and the "TypeScript Test Files Detected but No Loader Configured" banner — although the tests run perfectly fine once the check is bypassed.
The cause is checkTypeScriptLoader() in lib/utils/loaderCheck.js (called from validateTypeScriptSetup() in lib/codecept.js). It only string-matches the require array against a hardcoded list of Node loaders; there is no check of whether the runtime already handles TypeScript:
https://github.com/codeceptjs/CodeceptJS/blob/main/lib/utils/loaderCheck.js#L12-L22
So under Bun the require entry exists purely to satisfy that string match, and tsx sits in devDependencies and is imported on every run only to do nothing. Bun's module.register() is a no-op stub, so tsx's resolve/load hooks are never invoked. Verified with a hooks module that logs on every call:
// hooks.mjs
export function resolve(spec, ctx, next) { console.error('HOOK resolve:', spec); return next(spec, ctx) }
export function load(url, ctx, next) { console.error('HOOK load:', url); return next(url, ctx) }
// reg.mjs
import module from 'node:module'
module.register('./hooks.mjs', import.meta.url)
await import('./target.ts') // contains `enum E { A = 1 }`
bun --bun reg.mjs → no HOOK line at all, and the enum evaluates fine (Bun transpiled it itself).
node --experimental-strip-types reg.mjs → HOOK lines fire, i.e. registration works on Node.
Minimal reproduction
package.json { "name": "bun-tsx-repro", "type": "module", "private": true }
codecept.conf.ts (below)
basic_test.ts (below)
// codecept.conf.ts
export const config = {
tests: './*_test.ts',
helpers: {},
name: 'bun-tsx-repro',
}
// basic_test.ts
enum Color { Red = 'red' }
Feature('bun repro')
Scenario('plain TS scenario with an enum', () => {
if (Color.Red !== 'red') throw new Error('enum broken')
})
bun add --dev codeceptjs@4.1.0 # note: tsx is NOT installed
bunx --bun codeceptjs run
Result — exit 1, the loader banner. With checkTypeScriptLoader() patched to return true under Bun and nothing else changed (still no require, still no tsx in node_modules):
CodeceptJS v4.1.0 #StandWithUkraine
bun repro --
✔ plain TS scenario with an enum in 1ms
OK | 1 passed // 4ms
I also confirmed this on a real 14-suite Playwright project (~1900 tests): removing the require line, deleting tsx from node_modules entirely (import('tsx/esm') → Cannot find package 'tsx') and applying only the one-line patch, a full browser test passed unchanged — page objects, enums and extensionless relative imports included. Restoring the stock loaderCheck.js alone brings the banner back.
Suggested fix
export function checkTypeScriptLoader(requiredModules = []) {
// Bun transpiles TypeScript natively; no loader is needed
if (process.versions.bun) return true
// Check if a loader is configured in the require array
return (
requiredModules.includes('tsx/esm') ||
// ...
)
}
This is deliberately Bun-specific and not "skip the check when the runtime can do TypeScript". Node cannot replace tsx here: its native type stripping rejects enums (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX) and does not resolve extensionless relative imports.
Details
- CodeceptJS version: 4.1.0 (
main is unchanged in this area)
- Runtime: Bun 1.4.2 —
bunx --bun codeceptjs
- NodeJS Version: n/a, Bun is the runtime (Node 24.18.0 only used for the hooks comparison above)
- Operating System: Ubuntu 24.04
- Configuration file: see the minimal reproduction above
Created by AI agent (Claude Code)
What are you trying to achieve?
Bun is a supported runtime for CodeceptJS 4 — the 4.0 release post says under Native ESM: "Your project needs
"type": "module"and NodeJS 20 or Bun."Run a TypeScript suite with Bun as the runtime (
bunx --bun codeceptjs run).Bun transpiles TypeScript natively, so no
tsx/ts-nodeloader is involved. I expected to be able to leaverequireout of the config and not installtsxat all.What do you get instead?
require: ['tsx/esm']is mandatory even under Bun. Without it the run aborts withprocess.exit(1)and the "TypeScript Test Files Detected but No Loader Configured" banner — although the tests run perfectly fine once the check is bypassed.The cause is
checkTypeScriptLoader()inlib/utils/loaderCheck.js(called fromvalidateTypeScriptSetup()inlib/codecept.js). It only string-matches therequirearray against a hardcoded list of Node loaders; there is no check of whether the runtime already handles TypeScript:https://github.com/codeceptjs/CodeceptJS/blob/main/lib/utils/loaderCheck.js#L12-L22
So under Bun the
requireentry exists purely to satisfy that string match, andtsxsits indevDependenciesand is imported on every run only to do nothing. Bun'smodule.register()is a no-op stub, so tsx'sresolve/loadhooks are never invoked. Verified with a hooks module that logs on every call:bun --bun reg.mjs→ noHOOKline at all, and the enum evaluates fine (Bun transpiled it itself).node --experimental-strip-types reg.mjs→HOOKlines fire, i.e. registration works on Node.Minimal reproduction
bun add --dev codeceptjs@4.1.0 # note: tsx is NOT installed bunx --bun codeceptjs runResult — exit 1, the loader banner. With
checkTypeScriptLoader()patched to returntrueunder Bun and nothing else changed (still norequire, still notsxinnode_modules):I also confirmed this on a real 14-suite Playwright project (~1900 tests): removing the
requireline, deletingtsxfromnode_modulesentirely (import('tsx/esm')→Cannot find package 'tsx') and applying only the one-line patch, a full browser test passed unchanged — page objects, enums and extensionless relative imports included. Restoring the stockloaderCheck.jsalone brings the banner back.Suggested fix
This is deliberately Bun-specific and not "skip the check when the runtime can do TypeScript". Node cannot replace
tsxhere: its native type stripping rejects enums (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX) and does not resolve extensionless relative imports.Details
mainis unchanged in this area)bunx --bun codeceptjsCreated by AI agent (Claude Code)