diff --git a/README.md b/README.md index 485c0404..a5e899b3 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,7 @@ provider, such as `ts-node`. Otherwise, widening the acceptance extension here w ``` ## Override TypeScript detection using an environment variable +This plugin uses [native type stripping](https://nodejs.org/docs/latest-v23.x/api/typescript.html#modules-typescript) with Node 23 and later. It is possible to override the automatic detection of a TypeScript-capable runtime using the `FASTIFY_AUTOLOAD_TYPESCRIPT` environment variable. If set to a truthy value Autoload will load `.ts` files, expecting that node has a TypeScript-capable loader. diff --git a/lib/find-plugins.js b/lib/find-plugins.js index baffd7af..939f4c99 100644 --- a/lib/find-plugins.js +++ b/lib/find-plugins.js @@ -144,9 +144,16 @@ function accumulatePlugin ({ file, type, opts, pluginTree, prefix }) { } function handleTypeScriptSupport (file, language, isHook = false) { - if (language === 'typescript' && !runtime.supportTypeScript) { - throw new Error(`@fastify/autoload cannot import ${isHook ? 'hooks ' : ''}plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) - } + if ( + language === 'typescript' && + /* c8 ignore start - This cannot be reached from Node 23 native type-stripping */ + !runtime.supportTypeScript + ) { + throw new Error( + `@fastify/autoload cannot import ${isHook ? 'hooks ' : ''}plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.` + ) + } + /* c8 ignore end */ } function filterPath (path, filter) { diff --git a/lib/runtime.js b/lib/runtime.js index 0dd3cd5a..11182407 100644 --- a/lib/runtime.js +++ b/lib/runtime.js @@ -116,7 +116,8 @@ Object.defineProperties(runtime, { runtime.tsm || runtime.tsx || runtime.esbuild || - runtime.tsimp + runtime.tsimp || + runtime.nodeVersion >= 23 ) return cache.supportTypeScript } @@ -130,7 +131,13 @@ Object.defineProperties(runtime, { ) return cache.forceESM } - } + }, + nodeVersion: { + get () { + cache.nodeVersion ??= Number(process.version.split('.')[0].slice(1)) + return cache.nodeVersion + }, + }, }) module.exports = runtime diff --git a/package.json b/package.json index 60ff2270..831731ac 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "lint": "eslint", "lint:fix": "eslint --fix", - "test": "npm run typescript && npm run typescript:jest && npm run typescript:swc-node-register && npm run typescript:tsm && npm run typescript:tsx && npm run typescript:vitest && npm run typescript:esbuild && npm run unit", + "test": "npm run typescript && npm run typescript:native && npm run typescript:jest && npm run typescript:swc-node-register && npm run typescript:tsm && npm run typescript:tsx && npm run typescript:vitest && npm run typescript:esbuild && npm run unit", "typescript": "tsd", "typescript:jest": "jest", "typescript:esm": "node scripts/unit-typescript-esm.js", @@ -17,6 +17,7 @@ "typescript:tsx": "node scripts/unit-typescript-tsx.js", "typescript:tsimp": "node scripts/unit-typescript-tsimp.js", "typescript:esbuild": "node scripts/unit-typescript-esbuild.js", + "typescript:native": "node scripts/unit-typescript-native-type-stripping.js", "typescript:vitest": "vitest run", "typescript:vitest:dev": "vitest", "unit": "node scripts/unit.js", diff --git a/scripts/unit-typescript-native-type-stripping.js b/scripts/unit-typescript-native-type-stripping.js new file mode 100644 index 00000000..6e57a543 --- /dev/null +++ b/scripts/unit-typescript-native-type-stripping.js @@ -0,0 +1,30 @@ +'use strict' + +const { exec } = require('node:child_process') +const runtime = require('../lib/runtime') + +function common () { + const args = ['node', 'test/typescript-common/index.ts'] + const child = exec(args.join(' '), { + shell: true, + }) + child.stdout.pipe(process.stdout) + child.stderr.pipe(process.stderr) + child.once('close', esm) +} + +function esm () { + const args = ['node', 'test/typescript-esm/forceESM.ts'] + + const child = exec(args.join(' '), { + shell: true, + }) + + child.stdout.pipe(process.stdout) + child.stderr.pipe(process.stderr) + child.once('close', process.exit) +} + +if (runtime.nodeVersion >= 23) { + common() +} diff --git a/scripts/unit-typescript-tsx.js b/scripts/unit-typescript-tsx.js index d76db821..d14f406f 100644 --- a/scripts/unit-typescript-tsx.js +++ b/scripts/unit-typescript-tsx.js @@ -1,12 +1,11 @@ 'use strict' const { exec } = require('node:child_process') - -const version = Number(process.version.split('.')[0].slice(1)) +const runtime = require('../lib/runtime') const args = [ 'npx', - version >= 18 ? '--node-options=--import=tsx' : '', + runtime.nodeVersion >= 18 ? '--node-options=--import=tsx' : '', 'tsnd', 'test/typescript/basic.ts' ] diff --git a/test/commonjs/error.js b/test/commonjs/error.js index d961bf6c..7070003a 100644 --- a/test/commonjs/error.js +++ b/test/commonjs/error.js @@ -2,9 +2,12 @@ const t = require('tap') const fastify = require('fastify') +const runtime = require('../../lib/runtime') + +const typeStrippingEnabled = runtime.nodeVersion >= 23 t.test('independent of module support', function (t) { - t.plan(8) + t.plan(typeStrippingEnabled ? 7 : 8) const app = fastify() app.register(require('./syntax-error/app')) @@ -28,8 +31,12 @@ t.test('independent of module support', function (t) { app3.register(require('./ts-error/app')) app3.ready(function (err) { - t.type(err, Error) - t.match(err.message, /cannot import plugin.*typescript/i) + if (typeStrippingEnabled) { + t.error(err) + } else { + t.type(err, Error) + t.match(err.message, /cannot import plugin.*typescript/i) + } }) const app4 = fastify() diff --git a/test/issues/369/test.js b/test/issues/369/test.js index 025f11bc..5bc9cfa7 100644 --- a/test/issues/369/test.js +++ b/test/issues/369/test.js @@ -4,6 +4,7 @@ const { test } = require('tap') const Fastify = require('fastify') const path = require('node:path') const autoload = require('../../..') +const runtime = require('../../../lib/runtime') test('Should throw an error when trying to load invalid hooks', async (t) => { const app = Fastify() @@ -22,7 +23,11 @@ test('Should throw an error when trying to import hooks plugin using index.ts if autoHooks: true }) - await t.rejects(app.ready(), new Error(`@fastify/autoload cannot import hooks plugin at '${path.join(__dirname, 'invalid-index-type/index.ts')}'`)) + if (runtime.nodeVersion >= 23) { + t.doesNotThrow(() => app.ready()) + } else { + await t.rejects(app.ready(), new Error(`@fastify/autoload cannot import hooks plugin at '${path.join(__dirname, 'invalid-index-type/index.ts')}'`)) + } }) test('Should not accumulate plugin if doesn\'t comply to matchFilter', async (t) => { diff --git a/test/typescript-common/basic/app.ts b/test/typescript-common/basic/app.ts new file mode 100644 index 00000000..4b228bdf --- /dev/null +++ b/test/typescript-common/basic/app.ts @@ -0,0 +1,12 @@ +import type { FastifyInstance } from 'fastify' + +const { join } = require('node:path') +const fastifyAutoLoad = require('../../../index.js') + +module.exports = function (fastify: FastifyInstance, _opts: any, next: any) { + fastify.register(fastifyAutoLoad, { + dir: join(__dirname, 'foo'), + }) + + next() +} diff --git a/test/typescript-common/basic/foo/javascript.js b/test/typescript-common/basic/foo/javascript.js new file mode 100644 index 00000000..78bbcf63 --- /dev/null +++ b/test/typescript-common/basic/foo/javascript.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports = function (fastify, _opts, next) { + fastify.get('/javascript', (_request, reply) => { + reply.send({ script: 'java' }) + }) + + next() +} diff --git a/test/typescript-common/basic/foo/shouldBeIgnored.d.ts b/test/typescript-common/basic/foo/shouldBeIgnored.d.ts new file mode 100644 index 00000000..16f1a352 --- /dev/null +++ b/test/typescript-common/basic/foo/shouldBeIgnored.d.ts @@ -0,0 +1,3 @@ +// See https://github.com/fastify/fastify-autoload/issues/65 +declare const definitionFilesAreIncluded: never +export default definitionFilesAreIncluded diff --git a/test/typescript-common/basic/foo/typescript.ts b/test/typescript-common/basic/foo/typescript.ts new file mode 100644 index 00000000..97cc2c3c --- /dev/null +++ b/test/typescript-common/basic/foo/typescript.ts @@ -0,0 +1,7 @@ +module.exports = function (fastify: any, _opts, next) { + fastify.get('/typescript', (_request, reply) => { + reply.send({ script: 'type' }) + }) + + next() +} diff --git a/test/typescript-common/index.ts b/test/typescript-common/index.ts new file mode 100644 index 00000000..33c81b7d --- /dev/null +++ b/test/typescript-common/index.ts @@ -0,0 +1,40 @@ +'use script' + +const t = require('tap') +const fastify = require('fastify') + +const basicApp = require('./basic/app.ts') + +t.plan(5) + +const app = fastify() + +app.register(basicApp) + +app.ready(async function (err) { + t.error(err) + + await app + .inject({ + url: '/javascript', + }) + .then(function (res: any) { + t.equal(res.statusCode, 200) + t.same(JSON.parse(res.payload), { script: 'java' }) + }) + .catch((err) => { + t.error(err) + }) + + await app + .inject({ + url: '/typescript', + }) + .then(function (res: any) { + t.equal(res.statusCode, 200) + t.same(JSON.parse(res.payload), { script: 'type' }) + }) + .catch((err) => { + t.error(err) + }) +}) diff --git a/test/typescript-esm/app/index.ts b/test/typescript-esm/app/index.ts index fe08fa2a..dfe4f07a 100644 --- a/test/typescript-esm/app/index.ts +++ b/test/typescript-esm/app/index.ts @@ -1,4 +1,4 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' +import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' export default function (fastify: FastifyInstance, _: object, next: (err?: Error) => void): void { fastify.get('/installed', (_: FastifyRequest, reply: FastifyReply): void => { diff --git a/test/typescript-esm/forceESM.ts b/test/typescript-esm/forceESM.ts index 64111034..be9bb771 100644 --- a/test/typescript-esm/forceESM.ts +++ b/test/typescript-esm/forceESM.ts @@ -2,7 +2,7 @@ import fastify from 'fastify' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import t from 'tap' -import fastifyAutoLoad from '../../' +import fastifyAutoLoad from '../../index.js' t.plan(4)