knex: javascript knexfile can't run typescript migrations

Environment

Knex version: 0.21.1 Database + version: postgres 12 OS: Linux

Bug

  1. Description

When the knexfile has js extension, typescript migrations and seeds can’t be run. The error thrown suggests these files are not loaded with typescript loader. Changing knexfile extension to ts fixes the problem.

This is rather unexpected, as the documentation suggests that knex can run migrations of different types, no matter what the knexfile extension is.

loadExtensions: array of file extensions which knex will treat as migrations.
  1. Error message
import Knex from 'knex';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:1047:16)
    at Module._compile (internal/modules/cjs/loader.js:1097:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1153:10)
    at Module.load (internal/modules/cjs/loader.js:977:32)
    at Function.Module._load (internal/modules/cjs/loader.js:877:14)
    at Module.require (internal/modules/cjs/loader.js:1019:19)
    at require (internal/modules/cjs/helpers.js:77:18)
    at FsMigrations.getMigration (/home/przemek/DEV/sundae/sundae-collab-demo-api/node_modules/knex/lib/migrate/sources/fs-migrations.js:82:12)
    at /home/przemek/DEV/sundae/sundae-collab-demo-api/node_modules/knex/lib/migrate/Migrator.js:76:67
    at Array.some (<anonymous>)
  1. Knexfile content
require('dotenv').config();

const defaultSettings = {
  // client
  // connection
};

module.exports.development = {
  ...defaultSettings,
  migrations: {
    loadExtensions: ['.ts'],
    extension: 'ts',
  },
  seeds: {
    loadExtensions: ['.ts'],
  },
};

module.exports.test = {
  ...defaultSettings,
  migrations: {
    loadExtensions: ['.ts'],
    extension: 'ts',
  },
  seeds: {
    loadExtensions: ['.ts'],
  },
};

module.exports.production = {
  ...defaultSettings,
  migrations: {
    directory: 'build/migrations',
  },
  seeds: {
    directory: 'build/seeds',
  },
};

Feature discussion / request

  1. Explain what is your use case

I want to run Typescript migrations and seeds in development environment. In production, where Typescript and ts-node might not be present, compiled migrations and seeds should be used instead. Ideally, one js knexfile would handle both cases.

  1. Explain what kind of feature would support this

Knexfile extension should not influence migration and seed loading. When loadExtensions includes .ts, typescript migrations and seeds should be loaded and run.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 8
  • Comments: 22

Most upvoted comments

For those seeing

Failed to load external module ts-node/register
Failed to load external module typescript-node/register
Failed to load external module typescript-register
Failed to load external module typescript-require
Failed to load external module sucrase/register/ts
Failed to load external module @babel/register
Error: Cannot find module 'ts-node/register'
...
SyntaxError: Cannot use import statement outside a module

for me the fix was that I was missing ts-node from my package.json. A simple

npm install -D ts-node

was all I needed.

@abcd-ca You can use:

TS_NODE_COMPILER_OPTIONS='{ "module": "commonjs" }' knex migrate:latest

knex uses ts-node’s require hook for .ts extension and that can accept TS compiler options through TS_NODE_COMPILER_OPTIONS env variable. These options will be merged into what you have in your tsconfig.json.

@abcd-ca I resolved it below code.

const extension =
  process.env.NODE_CONFIG_ENV === 'production' || process.env.NODE_CONFIG_ENV === 'staging'
    ? 'js'
    : 'ts';

console.log('extension ============> ', extension);

  migrations: {
    tableName: 'knex_migrations',
    extension: extension,
    directory: path.join(BASE_PATH, 'migrations'),
    disableMigrationsListValidation: true,
    loadExtensions: [`.${extension}`],
  },
  seeds: {
    extension: extension,
    directory: path.join(BASE_PATH, 'seeds'),
  },

@lorefnon That gave me this alternative idea:

created a tsconfig.knexcli.json containing an override for module,

{
  "extends": "./tsconfig",
  "compilerOptions": {
    "module": "commonjs"
  }
}

Then when running my commands,

TS_NODE_PROJECT=tsconfig.knexcli.json knex migrate:latest

Just slightly cleaner because the JSON when in an npm script needs to escape the quotes and gets a bit busy looking. Thanks!

@prk3 If you specify loadExtensions: ['.js'] in the config for production env it should work as expected.

The default value of loadExtensions comes from here which will have to be overridden if you have some files matching those extensions which you don’t want to process.

Hello @prk3 I have the same problem, when run knex migrate in heroku cli

Running knex --knexfile knexfile.ts migrate:latest on ⬢ backend-ecolab... up, run.5056 (Free)
Failed to load external module ts-node/register
Failed to load external module typescript-node/register
Failed to load external module typescript-register
Failed to load external module typescript-require
Failed to load external module sucrase/register/ts
Failed to load external module @babel/register
Error: Cannot find module 'ts-node/register'
Require stack:
- /app/knexfile.ts
- /app/node_modules/knex/bin/cli.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:966:15)
    at Function.Module._load (internal/modules/cjs/loader.js:842:27)
    at Module.require (internal/modules/cjs/loader.js:1026:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (/app/knexfile.ts:1:1)
    at Module._compile (internal/modules/cjs/loader.js:1138:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Module.require (internal/modules/cjs/loader.js:1026:19)

Still having issues with this.

This is the error I am getting:

SyntaxError: Cannot use import statement outside a module

@abcd-ca’s method of

    "module": "commonjs",

in tsconfig.json worked for me as well

~I just noticed that 0.21.1 in the changelog has a bug fix that looks related which I suspect might be causing this side effect issue.~

This was incorrect, I tried with 0.21.0 and it didn’t fix it. I got the knexfile.ts to work with the import by changing "module": "esnext" to "module": "commonjs" in tsconfig.json. The problem is in my case is that I am using Next.js 9 (9.4.0) and when I run the app, Next.js changes the module setting back to esnext which is apparently a known issue. So, I think this is not a knex problem but if you don’t know that module must be commonjs then you’re in trouble – I’d recommend adding it to the docs.

I resolve with:

yarn add ts-node -D

and add in package.json scripts:

{
  "scripts": {
     ...,
     "knex": "TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\" }' npx knex"
  }
}

now use yarn knex <your command>