Skip to content

Commit c1696b2

Browse files
Copilotkobenguyent
andcommitted
Complete fix for TypeScript ESM import issue - recommend tsx over ts-node/esm
Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com>
1 parent 87c0766 commit c1696b2

File tree

9 files changed

+53
-69
lines changed

9 files changed

+53
-69
lines changed

docs/typescript.md

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,33 @@ Scenario('successful login', ({ I }) => {
9292
- 🚀 **Works with Mocha:** Uses CommonJS hooks that Mocha understands
9393
-**Complete:** Handles all TypeScript features (enums, decorators, etc.)
9494

95-
### Using ts-node/esm (Alternative)
95+
### Using ts-node/esm (Not Recommended)
9696

97-
If you prefer ts-node:
97+
> ⚠️ **Note:** `ts-node/esm` has significant limitations with module resolution and doesn't work well with modern ESM TypeScript projects. **We strongly recommend using `tsx` instead.** The information below is provided for reference only.
98+
99+
`ts-node/esm` has several issues:
100+
- Doesn't support `"type": "module"` in package.json
101+
- Doesn't resolve extensionless imports or `.js` imports to `.ts` files
102+
- Requires explicit `.ts` extensions in imports, which isn't standard TypeScript practice
103+
- Less reliable than `tsx` for ESM scenarios
104+
105+
**If you still want to use ts-node/esm:**
98106

99-
**Installation:**
100107
```bash
101108
npm install --save-dev ts-node
102109
```
103110

104-
**Configuration:**
105111
```typescript
106112
// codecept.conf.ts
107113
export const config = {
108114
tests: './**/*_test.ts',
109-
require: ['ts-node/esm'], // ← Use ts-node ESM loader
115+
require: ['ts-node/esm'],
110116
helpers: { /* ... */ }
111117
}
112118
```
113119

114-
**Required tsconfig.json:**
115120
```json
121+
// tsconfig.json
116122
{
117123
"compilerOptions": {
118124
"module": "ESNext",
@@ -126,33 +132,12 @@ export const config = {
126132
}
127133
```
128134

129-
**Required package.json:**
130-
```json
131-
{
132-
"name": "your-project",
133-
"version": "1.0.0"
134-
// ⚠️ DO NOT include "type": "module" when using ts-node/esm
135-
// ts-node/esm works with CommonJS-style loading
136-
}
137-
```
138-
139-
**⚠️ Important Notes:**
140-
141-
1. **Do not use `"type": "module"`** in your package.json when using `ts-node/esm`. The ts-node/esm loader uses CommonJS-style module loading internally and is incompatible with `"type": "module"`.
142-
143-
2. **Use `.js` extensions in imports**: When writing TypeScript code for ESM, you must use `.js` extensions in your imports, even when importing TypeScript files:
144-
145-
```typescript
146-
// ❌ Wrong - will cause "Cannot find module" error
147-
import loginPage from "./pages/Login"
148-
149-
// ✅ Correct - use .js extension
150-
import loginPage from "./pages/Login.js"
151-
```
152-
153-
TypeScript will automatically resolve `.js` imports to `.ts` files during type-checking. This is the standard approach for ESM + TypeScript projects as documented in the [TypeScript handbook](https://www.typescriptlang.org/docs/handbook/modules/theory.html#typescript-imitates-the-hosts-module-resolution-but-with-types).
135+
**Critical Limitations:**
136+
- ❌ Cannot use `"type": "module"` in package.json
137+
- ❌ Import statements must match the actual file (no automatic resolution)
138+
- ❌ Module resolution doesn't work like standard TypeScript/Node.js ESM
154139

155-
3. **For `"type": "module"` projects, use `tsx` instead**: If you need `"type": "module"` in your package.json, use `tsx/cjs` which is designed to work in both CommonJS and ESM contexts.
140+
**Recommendation:** Use `tsx/cjs` instead for a better experience.
156141

157142
### Full TypeScript Features in Tests
158143

lib/utils/loaderCheck.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,18 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
6565
✅ Complete: Handles all TypeScript features
6666
6767
┌─────────────────────────────────────────────────────────────────────────────┐
68-
│ Option 2: ts-node/esm (Alternative - Established, Requires Config)
68+
│ Option 2: ts-node/esm (Not Recommended - Has Module Resolution Issues)
6969
└─────────────────────────────────────────────────────────────────────────────┘
7070
71+
⚠️ ts-node/esm has significant limitations and is not recommended:
72+
- Doesn't work with "type": "module" in package.json
73+
- Module resolution doesn't work like standard TypeScript ESM
74+
- Import statements must use explicit file paths
75+
76+
We strongly recommend using tsx/cjs instead.
77+
78+
If you still want to use ts-node/esm:
79+
7180
Installation:
7281
npm install --save-dev ts-node
7382
@@ -88,18 +97,7 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
8897
}
8998
}
9099
91-
3. In package.json, DO NOT include "type": "module"
92-
ts-node/esm is incompatible with "type": "module"
93-
94-
If you need "type": "module", use tsx/cjs instead.
95-
96-
⚠️ Important: When using ESM with TypeScript, use .js extensions in imports:
97-
98-
❌ Wrong: import loginPage from "./pages/Login"
99-
✅ Correct: import loginPage from "./pages/Login.js"
100-
101-
TypeScript will resolve .js imports to .ts files during type-checking.
102-
This is the recommended approach for ESM + TypeScript projects.
100+
3. Do NOT use "type": "module" in package.json
103101
104102
📚 Documentation: https://codecept.io/typescript
105103

test/data/typescript-esm-imports/package.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

test/data/typescript-esm-imports/typescript_esm_test.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

test/data/typescript-esm-imports/codecept.conf.ts renamed to test/data/typescript-tsx-esm/codecept.conf.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ export const config: CodeceptJS.MainConfig = {
66
require: "../helper.js"
77
}
88
},
9-
name: "typescript-esm-imports-test",
10-
require: ["ts-node/esm"]
9+
name: "typescript-tsx-esm-test",
10+
require: ["tsx/cjs"]
1111
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "typescript-tsx-esm",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"devDependencies": {
6+
"tsx": "^4.20.6"
7+
}
8+
}
File renamed without changes.

test/data/typescript-esm-imports/tsconfig.json renamed to test/data/typescript-tsx-esm/tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"target": "es2022",
44
"lib": ["es2022", "DOM"],
55
"esModuleInterop": true,
6-
"module": "nodenext",
7-
"moduleResolution": "nodenext",
6+
"module": "esnext",
7+
"moduleResolution": "node",
88
"strictNullChecks": false,
99
"types": ["codeceptjs", "node"],
1010
"declaration": true,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// With tsx, you can import without extension - tsx handles resolution
2+
import loginPage from "./pages/Login";
3+
4+
Feature("TypeScript tsx ESM with type:module");
5+
6+
Scenario("Import page object without extension using tsx", () => {
7+
loginPage.login("testuser");
8+
});
9+
10+
Scenario("Page object methods work correctly", () => {
11+
loginPage.login("admin");
12+
loginPage.logout();
13+
});

0 commit comments

Comments
 (0)