knex / knex

A query builder for PostgreSQL, MySQL, CockroachDB, SQL Server, SQLite3 and Oracle, designed to be flexible, portable, and fun to use.
https://knexjs.org/
MIT License
19.2k stars 2.12k forks source link

Named export 'knex' not found. #5277

Open HonzaKopecky opened 2 years ago

HonzaKopecky commented 2 years ago

Environment

Knex version: 2.2.0 Database + version: MySQL 8.0.27-0ubuntu0.20.04.1 - (Ubuntu), connector mysql v2.18.1 ts-node: 10.9.1 node: 14.20.0 (tried also with v16) OS: WSL2 (Ubuntu)

@lorefnon

Bug

I am trying to run my server application using ts-node package with enabled support of ES Modules as described in the ts-node documentation. It's still an experimental functionality but all other dependencies work well so far. The problem I am experiencing is that when I use ts-node to run my script, I am getting an error that the named export knex is not defined since knex is a CommonJS package altough I am using the TypeScript syntax you are recommending in the documentation.

When I run the code below (this is truncated content of the server/start.ts script) using npm run server, I run into this error:

import { knex } from 'knex';
         ^^^^
SyntaxError: Named export 'knex' not found. The requested module 'knex' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'knex';
const { knex } = pkg;

import argv from 'argv';
import path from 'path';
import { knex, Knex } from 'knex';

export class Starter {
    private dbConnection : Knex;

    constructor(private script: string) {
        this.initDbConnection();
    }

    private initDbConnection() {
        const knexConfig : Knex.Config = {
          client: 'mysql',
          connection: {
            host: '',
            user: '',
            password: '',
            database: ''
          }
        }
        this.dbConnection = knex(knexConfig);
    }
}

argv.option({
    name: 'script',
    short: 's',
    type: 'string',
    description: `Start this script.`,
});

const script = argv.run().options.script;

new Starter(`${path.dirname(import.meta.url)}/${script}.ts`);

My package.json is as follows:

{
    ...,
    "type": "module",
    "scripts": {
        "server": "ts-node -P server/tsconfig.json -r dotenv/config --experimental-specifier-resolution=node server/start.ts --script=server",
        ...
    },
    "dependencies": {
        "@date-io/core": "^1.3.11",
        "@date-io/luxon": "^1.3.11",
        "@material-ui/core": "^4.5.1",
        "@material-ui/icons": "^4.5.1",
        "@material-ui/pickers": "^3.2.7",
        "@testing-library/jest-dom": "^5.11.5",
        "@testing-library/react": "^11.1.1",
        "@testing-library/user-event": "^12.2.0",
        "@types/argv": "0.0.5",
        "@types/bcryptjs": "^2.4.2",
        "@types/body-parser": "^1.19.2",
        "@types/cors": "^2.8.12",
        "@types/date-fns": "^2.6.0",
        "@types/debounce-promise": "^3.1.1",
        "@types/express": "^4.17.13",
        "@types/history": "^4.7.2",
        "@types/jsonwebtoken": "^8.5.8",
        "@types/leaflet": "^1.5.17",
        "@types/luxon": "^1.4.1",
        "@types/md5": "^2.3.2",
        "@types/node": "^18.0.6",
        "@types/node-fetch": "^2.6.2",
        "@types/papaparse": "^5.3.2",
        "@types/query-string": "^6.1.1",
        "@types/react": "^16.9.56",
        "@types/react-autosuggest": "^10.0.0",
        "@types/react-dom": "^16.9.9",
        "@types/react-easy-chart": "^0.1.4",
        "@types/react-leaflet": "^2.5.2",
        "@types/react-router-dom": "^4.3.1",
        "@types/react-table": "^6.7.17",
        "@types/react-text-mask": "^5.4.3",
        "@types/supertest": "^2.0.7",
        "@types/uuid": "^8.3.4",
        "argv": "0.0.2",
        "bcryptjs": "^2.4.3",
        "body-parser": "^1.20.0",
        "configchecker": "^1.5.1",
        "cors": "^2.8.5",
        "csv-stringify": "^6.2.0",
        "date-fns": "^2.28.0",
        "debounce-promise": "^3.1.2",
        "dotenv": "^16.0.1",
        "emailjs": "^4.0.0",
        "everstorage": "^0.9.1",
        "express": "^4.18.1",
        "history": "^4.7.2",
        "jsonwebtoken": "^8.5.1",
        "knex": "^2.2.0",
        "leaflet": "^1.7.1",
        "luxon": "^1.5.0",
        "md5": "^2.3.0",
        "mysql": "^2.18.1",
        "node-fetch": "^3.2.9",
        "objection": "^3.0.1",
        "package-json-type": "^1.0.3",
        "papaparse": "^5.2.0",
        "pdfmk": "^0.9.0",
        "query-string": "^6.2.0",
        "react": "^17.0.1",
        "react-app-polyfill": "^0.2.0",
        "react-autosuggest": "^10.0.3",
        "react-dom": "^17.0.1",
        "react-easy-chart": "^1.0.0",
        "react-leaflet": "^2.7.0",
        "react-router-dom": "^4.3.1",
        "react-scripts": "^4.0.1",
        "react-table": "^6.8.6",
        "react-text-mask": "^5.4.3",
        "socket.io": "^4.5.1",
        "socket.io-client": "^4.5.1",
        "sql-formatter": "^8.2.0",
        "supertest": "^4.0.2",
        "ts-node": "^10.9.1",
        "typescript": "^4.7.4",
        "uuid": "^8.3.2",
        "waitasecond": "^1.9.0"
    },
    "devDependencies": {
        "@typescript-eslint/eslint-plugin": "^5.30.7",
        "@typescript-eslint/parser": "^5.30.7",
        "depcheck": "^0.8.3",
        "eslint": "^8.20.0",
        "eslint-config-prettier": "^8.5.0",
        "glob-promise": "^3.4.0",
        "natural-orderby": "^2.0.3",
        "node-ssh": "^12.0.2",
        "onchange": "^6.1.0",
        "organize-imports-cli": "^0.8.0",
        "prettier": "^1.19.1",
        "promise-ftp": "^1.3.5",
        "source-map-explorer": "^1.6.0",
        "tar": "^6.1.11"
    }

tsconfig.json and server/tsconfig.json is as follows:

{
  "compilerOptions": {
    "downlevelIteration": true,
    "baseUrl": ".",
    "outDir": "build/dist",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "sourceMap": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "moduleResolution": "node",
    "rootDir": "./",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "experimentalDecorators": true,
    "noUnusedLocals": false
  },
  "include": [
    "src"
  ]
}
{
    "extends": "./../tsconfig.json",
    "compilerOptions": {
        "target": "es6",
        "module": "ESNext",
        "lib": ["es2017", "dom"],
        "rootDir": "./../"
    },
    "ts-node": {
      "esm": true
    },
    "include": ["."],
    "exclude": ["user"]
}

Solution

I actually found a solution online in this article so it seems I am not the only one out there experiencing this issue. It took me quite some time to figure this out and this looks like a very different (and unwanted?) behaviour compared to all my other dependencies.

samhess commented 1 year ago

This works for me:

import pkg from 'knex'
const { Knex, knex } = pkg

Got it from that error message: import { Knex, knex } from 'knex' ^^^^ SyntaxError: Named export 'Knex' not found. The requested module 'knex' is a CommonJS module, which may not support all module.exports as named exports. CommonJS modules can always be imported via the default export, for example using: import pkg from 'knex'; const { Knex, knex } = pkg;

tka85 commented 1 year ago

The Node state of affairs when it comes to ESM vs CJS mix up is borderline ridiculous. And we have ended up with this unpleasant disaster of an ecosystem.

Would it make sense to have a branch of knex that has correct named exports so ESM projects can use it in peace?

HonzaKopecky commented 1 year ago

I think it would be just fine (for now) if the correct way would be documented.

tka85 commented 1 year ago

For future reference: sadly the only (not so straightforward) way of making it work in my ESM project is like:

import knex, { Knex } from 'knex';
let knex_conn: Knex;
async function connect(connection: Record<string, string>) {
    let conn = {
        client: 'pg',
        connection
    }
    knex_conn = knex(conn);
    return knex_conn;
}

Source.

mhouchin commented 10 months ago

The snippet above didn't work for me:

import knex, { Knex } from 'knex';

However, the following has worked for me by telling TS to not compile the type declaration:

import knex, { type Knex } from 'knex';
andreluiz901 commented 8 months ago

Just for furture, im adding that wen using alias this error also appears, i was using this:

import "dotenv/config";
import { knex as setupKnex, Knex } from "knex";
import { env } from "./env";

export const config: Knex.Config = {
  client: env.DATABASE_CLIENT,
  connection:
    env.DATABASE_CLIENT === "sqlite"
      ? { filename: env.DATABASE_URL }
      : env.DATABASE_URL,
  useNullAsDefault: true,
  migrations: {
    extension: "ts",
    directory: "./src/db/migrations",
  },
};

export const knex = setupKnex(config);

and my way was change to what @mhouchin suggest (thanks for you and all the other guys).

import "dotenv/config";
import knex, { type Knex } from "knex";     // changed the import
import { env } from "./env";

export const config: Knex.Config = {
  client: env.DATABASE_CLIENT,
  connection:
    env.DATABASE_CLIENT === "sqlite"
      ? { filename: env.DATABASE_URL }
      : env.DATABASE_URL,
  useNullAsDefault: true,
  migrations: {
    extension: "ts",
    directory: "./src/db/migrations",
  },
};

export const kknex = knex(config);    // changed here to kknex just for name, and call config directly

I dont know if someone is working in this at this time, but, in outher project of mine, i use the same code, but with no errors, using the alias setupKnex

import "dotenv/config";
import { knex as setupKnex, Knex } from "knex";
import { env } from "./env";

export const config: Knex.Config = {
  client: env.DATABASE_CLIENT,
  connection:
    env.DATABASE_CLIENT === "sqlite"
      ? {
          filename: env.DATABASE_URL,
        }
      : env.DATABASE_URL,
  useNullAsDefault: true,
  migrations: {
    extension: "ts",
    directory: "./src/db/migrations",
  },
};

export const knex = setupKnex(config);

Comparing the package files, the major diff is the node version i think. But, i tried to run at the LTS node 20.10 and also run good.

ACTUAL PACKAGE.JSON giving error:

{
  "name": "diet-planner-api",
  "type": "module",
  "version": "1.0.0",
  "description": "API for diet planning",
  "main": "index.js",
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "github.com:andreluiz901/diet-planner-API.git"
  },
  "author": "André Luiz",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^20.10.6",
    "@types/supertest": "^6.0.2",
    "eslint": "^8.56.0",
    "supertest": "^6.3.3",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3",
    "vitest": "^1.1.0"
  },
  "dependencies": {
    "@fastify/cookie": "^9.2.0",
    "dotenv": "^16.3.1",
    "fastify": "^4.25.2",
    "knex": "^3.1.0",
    "pg": "^8.11.3",
    "sqlite3": "^5.1.7-rc.0",
    "zod": "^3.22.4"
  }
}

OLD PACKAGE.JSON running with no errors:

{
  "name": "modulo-02-aulas",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "engines": {
    "node": "18"
  },
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "knex": "node --no-warnings --import tsx ./node_modules/.bin/knex --knexfile ./knexFile.ts ",
    "knex:migration": "node --no-warnings --import tsx ./node_modules/.bin/knex --knexfile ./knexFile.ts --migrations-directory ./src/db/migrations",
    "knex:m-latest": "node --no-warnings --import tsx ./node_modules/.bin/knex --knexfile ./knexFile.ts --migrations-directory ./src/db/migrations migrate:latest",
    "migrate:create": "npm run knex -- migrate:make --migrations-directory ./src/db/migrations -x ts",
    "lint": "eslint src --ext .ts --fix",
    "test": "vitest",
    "build": "tsup src --out-dir build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^20.10.5",
    "@types/supertest": "^6.0.2",
    "eslint": "^8.56.0",
    "supertest": "^6.3.3",
    "tsup": "^8.0.1",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3",
    "vitest": "^1.1.0",
    "sqlite3": "^5.1.7-rc.0"
  },
  "dependencies": {
    "@fastify/cookie": "^9.2.0",
    "dotenv": "^16.3.1",
    "fastify": "^4.25.2",
    "knex": "^3.1.0",
    "pg": "^8.11.3",
    "zod": "^3.22.4"
  }
}