fastify-autoload: Node 20 auto-loader not able to transpile TS to JS when using ts-node/esm

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.10.0

Plugin version

No response

Node.js version

20.1.0

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

13.3.1

Description

I think this is a Node issue, but only @fastify/autoloader is affected by it, so I am opening an issue with you good folks as well in case this is really a Fastify issue instead or if it is a Node issue but you are able to find a workaround!

  fastify.register(fastifyAutoload, {
    dir: join(__dirname, 'routes'),
    forceESM: true,
  });

This piece of code works in Node 19 when using node --loader=ts-node/esm (the route files are in TypeScript) but not on Node 20.

I have come up with a repro here: https://github.com/TomasHubelbauer/node-esm-loader-repro

Steps to Reproduce

Follow my repro repo: https://github.com/TomasHubelbauer/node-esm-loader-repro

Steps copied here for posterity.

Node 20:

  1. nvm install 20 to install Node 20
  2. node --version to ensure Node version (I get 20.1.0)
  3. npm install to install dependencies
  4. npm run test to run the health.test.ts script

Notice the test fails and Fastify’s autoload is seemingly not aware of the --loader option and attempts to load routes/health.ts without TypeScript to JavaScript conversion via ts-node/esm.

npm run test

> repro@0.0.0 test
> node --loader=ts-node/esm --experimental-specifier-resolution=node --test health.test.ts

ℹ (node:95105) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
ℹ (Use `node --trace-warnings ...` to show where the warning was created)
✖ should be alive (32.750836ms)
  Error: "@fastify/autoload cannot import plugin at '/routes/health.ts'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app."
      at findPlugins (/node_modules/@fastify/autoload/index.js:224:15)
      at async autoload (/node-esm-loader-repro/node_modules/@fastify/autoload/index.js:35:22)

ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2208.335483

✖ failing tests:

✖ should be alive (32.750836ms)
  Error: "@fastify/autoload cannot import plugin at 'routes/health.ts'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app."
      at findPlugins (/node_modules/@fastify/autoload/index.js:224:15)
      at async autoload (/node_modules/@fastify/autoload/index.js:35:22)

Node 19:

  1. nvm install 19 to install Node 20
  2. node --version to ensure Node version (I get 19.9.0)
  3. npm install to install dependencies
  4. npm run test to run the health.test.ts script

Notice the test passes and Fastify’s autoload is inherit the --loader option and uses the ts-node/esm loader successfully to auto-load routes/health.ts.

npm run test

> repro@0.0.0 test
> node --loader=ts-node/esm --experimental-specifier-resolution=node --test health.test.ts

ℹ (node:95453) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
ℹ (Use `node --trace-warnings ...` to show where the warning was created)
✔ should be alive (472.711395ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2679.742161

Expected Behavior

Works the same way in both 19 and 20.

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Comments: 23 (13 by maintainers)

Most upvoted comments

But you could do it “manually” by setting e.g. the TS_NODE_DEV to a truthy value.

 "scripts": {
    "test": "TS_NODE_DEV=1 node --loader=ts-node/esm --experimental-specifier-resolution=node --test health.test.ts"
  },

Maybe we should add a environment variable, like FASTIFY_AUTOLOAD_TS to generally override it

@Uzlopak 's tip work. In my case I’m using tsx.

"dev": "TS_NODE_DEV=1 node --no-warnings=ExperimentalWarning --watch --loader=tsx src/server.ts",

Testing the autoload forceEsm tests with node:test likewise fails. It feels like a node issue, but I’m out of my depth. I’ll just import the routes manually in my tests.

~/sandbox/autoload-min[main] npm run test

> autoload-min@1.0.0 test
> FASITY_AUTOLOAD_TYPESCRIPT=1 node --import=ts-node/esm --test index.ts

TypeError: Unknown file extension ".ts" for /Users/rbulkley/sandbox/autoload-min/index.ts
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:160:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:203:36)
    at defaultLoad (node:internal/modules/esm/load:143:22)
    at async ModuleLoader.load (node:internal/modules/esm/loader:409:7)
    at async ModuleLoader.moduleProvider (node:internal/modules/esm/loader:291:45)
    at async link (node:internal/modules/esm/module_job:76:21) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

I’ve added a tsconfig to the min repo, which still produces the above result. Interestingly, with the tsconfig included and running tsx (without esm enforcement) I get the expected result. With esm enforced in tsx I get other errors. However, ts-node/esm is as shown above.

A few subsequent inspect tests make it appear as if the _preload_modules are not being set. I don’t know enough about that env variable to know where I could be going wrong.

the original issue is still unresolved

The APIs for loaders changed in v20 and it broke ts-node, mostly due to https://github.com/nodejs/node/issues/47880.

Once the dust settle and there is an API for us to use, we’ll need to make adaptation here.

Works the same way in both 19 and 20.

I would just hold this issue and only fix it when --loader becomes stable. As far as I know, there are numbers of issue about the experimental loader. The error is mostly cause by https://github.com/nodejs/node/pull/44710

Moved to the autoload module.