Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@libsql/client can't be used from CommonJS modules using TypeScript #182

Open
daniel-chambers opened this issue Feb 27, 2024 · 2 comments
Open
Labels
bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed

Comments

@daniel-chambers
Copy link

@libsql/client appears intended to be able to support both ESM and CJS modules, but unfortunately, it doesn't appear to work properly with the TypeScript compiler.

If one creates a simple NodeJS (v18) package like so:

package.json
{
  "name": "nodejs-test",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "author": "",
  "license": "ISC",
  "scripts": {
    "build": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "@libsql/client": "^0.5.2"
  },
  "devDependencies": {
    "@tsconfig/node18": "^18.2.2",
    "typescript": "^5.3.3"
  }
}

with a fairly basic tsconfig:

tsconfig.json
{
  "extends": "@tsconfig/node18",
  "compilerOptions": {
    "outDir": "dist",
    "resolveJsonModule": true,
    "noUncheckedIndexedAccess": true,
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "stripInternal": true,
  },
  "include": [
    "src/**/*",
    "test/**/*"
  ]
}

and then import @libsql/client in src/index.ts

src/index.ts
import {} from "@libsql/client";

You will get the following error:

src/index.ts:1:16 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@libsql/client")' call instead.
To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field "type": "module" to '/home/daniel/temp/nodejs-test/package.json'.

This appears to be caused by @libsql/client sharing the .d.ts files across both the lib-esm and lib-cjs folders.

If you ask the TypeScript compiler to explain its module resolution (tsc --traceResolution), you get this:

tsc --traceResolution
======== Resolving module '@libsql/client' from '/home/daniel/temp/nodejs-test/src/index.ts'. ========
Explicitly specified module resolution kind: 'Node16'.
Resolving in CJS mode with conditions 'require', 'types', 'node'.  <-------------------------------------------- Good
File '/home/daniel/temp/nodejs-test/src/package.json' does not exist according to earlier cached lookups.
File '/home/daniel/temp/nodejs-test/package.json' exists according to earlier cached lookups.
Loading module '@libsql/client' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
Directory '/home/daniel/temp/nodejs-test/src/node_modules' does not exist, skipping all lookups in it.
Scoped package detected, looking in 'libsql__client'
Found 'package.json' at '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/package.json'.
Entering conditional exports.
Matched 'exports' condition 'types'. <-------------------------------------------------------- Hrm...
Using 'exports' subpath '.' with target './lib-esm/node.d.ts'.
File '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-esm/node.d.ts' exists - use it as a name resolution result.
Resolved under condition 'types'.
Exiting conditional exports.
Resolving real path for '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-esm/node.d.ts', result '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-esm/node.d.ts'.
======== Module name '@libsql/client' was successfully resolved to '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-esm/node.d.ts' with Package ID '@libsql/client/lib-esm/node.d.ts@0.5.2'. ========
File '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-esm/package.json' does not exist.
File '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/package.json' exists according to earlier cached lookups.
======== Resolving module '@libsql/core/api' from '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-esm/node.d.ts'. ========
Explicitly specified module resolution kind: 'Node16'.
Resolving in ESM mode with conditions 'import', 'types', 'node'. <--------------------------------------------- Bad

We can see the module resolution is using exports/./types to find the lib-esm/node.d.ts, which appears to be flipping the compiler into ESM mode (maybe it can see the ESM .js files next to the .d.ts files).

However, if we copy all the .d.ts files into the lib-cjs folder in the package (so that they are duplicated), and remove all the types properties under exports (so the TS compiler will search for .d.ts files next to the .js files instead), we get the correct behaviour and the error goes away. Here's what the module resolution trace output shows for that:

tsc --traceResolution
======== Resolving module '@libsql/client' from '/home/daniel/temp/nodejs-test/src/index.ts'. ========
Explicitly specified module resolution kind: 'Node16'.
Resolving in CJS mode with conditions 'require', 'types', 'node'. <--------------------------------------------- Good
File '/home/daniel/temp/nodejs-test/src/package.json' does not exist according to earlier cached lookups.
File '/home/daniel/temp/nodejs-test/package.json' exists according to earlier cached lookups.
Loading module '@libsql/client' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
Directory '/home/daniel/temp/nodejs-test/src/node_modules' does not exist, skipping all lookups in it.
Scoped package detected, looking in 'libsql__client'
Found 'package.json' at '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/package.json'.
Entering conditional exports.
Saw non-matching condition 'import'.
Matched 'exports' condition 'require'. <--------------------------------------------------- Excellent
Using 'exports' subpath '.' with target './lib-cjs/node.js'.
File name '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.js' has a '.js' extension - stripping it.
File '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.ts' does not exist.
File '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.tsx' does not exist.
File '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts' exists - use it as a name resolution result. <---------------------- Yep
Resolved under condition 'require'.
Exiting conditional exports.
Resolving real path for '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts', result '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts'.
======== Module name '@libsql/client' was successfully resolved to '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts' with Package ID '@libsql/client/lib-cjs/node.d.ts@0.5.2'. ========
Found 'package.json' at '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/package.json'.
======== Resolving module '@libsql/core/api' from '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts'. ========
Explicitly specified module resolution kind: 'Node16'.
Resolving in CJS mode with conditions 'require', 'types', 'node'. <---------------------------------------- Seems good!

While this does seem like dodgy TypeScript compiler behaviour, I'd suggest just copying the .d.ts files into both lib-esm and lib-cjs and removing the types properties from the exports entries to allow the compiler to find them dynamically. That appears to solve the problem.

@daniel-chambers
Copy link
Author

daniel-chambers commented Feb 27, 2024

If you still want the types specified explicitly in the package.json, nesting the types under import/require works too:

package.json snippet
"exports": {
    ".": {
        "import": {
            "types": "./lib-esm/node.d.ts",
            "workerd": "./lib-esm/web.js",
            "deno": "./lib-esm/web.js",
            "edge-light": "./lib-esm/web.js",
            "netlify": "./lib-esm/web.js",
            "node": "./lib-esm/node.js",
            "browser": "./lib-esm/web.js",
            "default": "./lib-esm/node.js"
        },
        "require": {
          "types": "./lib-cjs/node.d.ts",
          "default": "./lib-cjs/node.js"
        }
    },
    "./node": {
        "import": {
          "types": "./lib-esm/node.d.ts",
          "default": "./lib-esm/node.js"
        },
        "require": {
          "types": "./lib-cjs/node.d.ts",
          "default": "./lib-cjs/node.js"
        }
    },
    "./http": {
        "import": {
          "types": "./lib-esm/http.d.ts",
          "default": "./lib-esm/http.js"
        },
        "require": {
          "types": "./lib-cjs/http.d.ts",
          "default": "./lib-cjs/http.js"
        }
    },
    "./ws": {
        "import": {
          "types": "./lib-esm/ws.d.ts",
          "default": "./lib-esm/ws.js"
        },
        "require": {
          "types": "./lib-cjs/ws.d.ts",
          "default": "./lib-cjs/ws.js"
        }
    },
    "./sqlite3": {
        "import": {
          "types": "./lib-esm/sqlite3.d.ts",
          "default": "./lib-esm/sqlite3.js"
        },
        "require": {
          "types": "./lib-cjs/sqlite3.d.ts",
          "default": "./lib-cjs/sqlite3.js"
        }
    },
    "./web": {
        "import": {
          "types": "./lib-esm/web.d.ts",
          "default": "./lib-esm/web.js"
        },
        "require": {
          "types": "./lib-cjs/web.d.ts",
          "default": "./lib-cjs/web.js"
        }
    }
},
tsc --traceResolution
======== Resolving module '@libsql/client' from '/home/daniel/temp/nodejs-test/src/index.ts'. ========
Explicitly specified module resolution kind: 'Node16'.
Resolving in CJS mode with conditions 'require', 'types', 'node'.
File '/home/daniel/temp/nodejs-test/src/package.json' does not exist according to earlier cached lookups.
File '/home/daniel/temp/nodejs-test/package.json' exists according to earlier cached lookups.
Loading module '@libsql/client' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
Directory '/home/daniel/temp/nodejs-test/src/node_modules' does not exist, skipping all lookups in it.
Scoped package detected, looking in 'libsql__client'
Found 'package.json' at '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/package.json'.
Entering conditional exports.
Saw non-matching condition 'import'.
Matched 'exports' condition 'require'.
Entering conditional exports.
Matched 'exports' condition 'types'.
Using 'exports' subpath '.' with target './lib-cjs/node.d.ts'.
File '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts' exists - use it as a name resolution result.
Resolved under condition 'types'.
Exiting conditional exports.
Resolved under condition 'require'.
Exiting conditional exports.
Resolving real path for '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts', result '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts'.
======== Module name '@libsql/client' was successfully resolved to '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts' with Package ID '@libsql/client/lib-cjs/node.d.ts@0.5.2'. ========
Found 'package.json' at '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/package.json'.
======== Resolving module '@libsql/core/api' from '/home/daniel/temp/nodejs-test/node_modules/@libsql/client/lib-cjs/node.d.ts'. ========
Explicitly specified module resolution kind: 'Node16'.
Resolving in CJS mode with conditions 'require', 'types', 'node'.

@penberg penberg added bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed labels Feb 27, 2024
@kzisopoulos
Copy link

kzisopoulos commented Mar 25, 2024

Hello I had the same issue , what did the trick for me was setting those in my compilerOptions:

"module": "CommonJS",
"moduleResolution": "Node",
"target": "ES2022",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants