cube-js / cube-js/cube

ESM/CSJ compatibility issue with @cubejs-client/core cube client library

Open
#10,041 1 comment 2 reactions 0 assignees View on GitHub
client:core
Dominant language
Rust
Stars
20.8k
Forks
2.1k
Avg merge
1d 2h
Merged PRs (30d)
181

Description

## Describe the bug

The `@cubejs-client/core` ESM build (`dist/src/index.js`) contains extensionless relative imports (e.g., `import ResultSet from './ResultSet'`) which fail in Node.js ESM strict mode.

Node.js ESM specification requires explicit file extensions for relative imports. When running in strict ESM environments (using `tsx`, `ts-node --esm`, Vite, or native Node.js ESM), the module resolution fails because Node cannot resolve `./ResultSet` without the `.js` extension.

This affects any project using:
- `"type": "module"` in `package.json`
- TypeScript loaders in ESM mode (`tsx`, `ts-node`)
- Modern build tools with strict ESM (Vite, esbuild)
- Node.js with native ESM imports

## To Reproduce

**Steps to reproduce:**

1. Create a new Node.js ESM project:
```bash
mkdir cube-esm-test
cd cube-esm-test
npm init -y
npm install @cubejs-client/core tsx
```

2. Update `package.json`:
```json
{
"type": "module"
}
```

3. Create `test.ts`:
```typescript
import cubejs from '@cubejs-client/core';

const client = cubejs('token', {
apiUrl: 'http://localhost:4000/cubejs-api/v1'
});

const meta = await client.meta();
console.log(meta);
```

4. Run with `tsx`:
```bash
npx tsx test.ts
```

**Error:**
```
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/path/to/node_modules/@cubejs-client/core/dist/src/ResultSet' imported from /path/to/node_modules/@cubejs-client/core/dist/src/index.js
```

## Expected behavior

The ESM build should use explicit `.js` extensions for all relative imports, allowing Node.js to properly resolve modules:

```javascript
// Current (broken)
import ResultSet from './ResultSet';

// Expected (working)
import ResultSet from './ResultSet.js';
```

## Root Cause

Looking at `node_modules/@cubejs-client/core/dist/src/index.js`:
```javascript
import ResultSet from './ResultSet'; // ❌ Missing .js extension
import SqlQuery from './SqlQuery'; // ❌ Missing .js extension
import Meta from './Meta'; // ❌ Missing .js extension
// ... etc
```

The TypeScript compilation doesn't automatically add `.js` extensions to emitted ESM output, and the build process doesn't have a post-processing step to add them.

## Suggested Solutions

### Option 1: Fix TypeScript Build (Recommended)

Add a post-build step to rewrite import paths:

```bash
# After tsc compilation, add .js extensions
npx fix-esm-import-path dist/src
```

Or use a bundler that handles this automatically:
```javascript
// tsup.config.ts
export default {
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
// tsup automatically fixes import extensions
}
```

### Option 2: Expose CJS in Exports (Interim)

Update `package.json` to provide a CJS fallback:

```json
{
"exports": {
".": {
"import": "./dist/src/index.js",
"require": "./dist/cubejs-client-core.cjs.js",
"default": "./dist/src/index.js"
}
}
}
```

This allows consumers to fall back to the working CJS bundle when ESM fails.

## Workaround

Currently, users must implement a dual ESM/CJS loader:

```typescript
async function loadCubeFactory() {
try {
// Try ESM
const esm = await import("@cubejs-client/core");
return esm.default ?? esm;
} catch {
// Fallback to CJS via createRequire
const { createRequire } = await import("node:module");
const require = createRequire(import.meta.url);
const cjs = require("@cubejs-client/core");
return cjs.default ?? cjs;
}
}

const factory = await loadCubeFactory();
const client = factory('token', { apiUrl: 'http://localhost:4000/cubejs-api/v1' });
```

## Version

- `@cubejs-client/core`: 1.3.73 (latest)
- Node.js: 20.x, 22.x, 24.x (all affected)
- TypeScript: 5.x

## Additional Context

This is a common issue in the TypeScript/Node.js ESM ecosystem. Similar issues have been fixed by other libraries:
- https://github.com/nodejs/node/blob/main/doc/api/esm.md#mandatory-file-extensions
- TypeScript tracking issue: https://github.com/microsoft/TypeScript/issues/16577

The ESM specification requires explicit file extensions for relative imports to avoid ambiguity and improve resolution performance. Many modern tools (Vite, Next.js 13+, Remix, etc.) enforce this strictly.

Related discussions:
- https://nodejs.org/api/esm.html#esm_mandatory_file_extensions
- https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.