From 8f03da0a576fa8e4c4cdd980bff3e4f6e499bdc3 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 26 Apr 2024 15:06:23 +0800 Subject: [PATCH 1/5] refactor: better support typescript --- index.js | 48 ++++++++------------ runtime.js | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 runtime.js diff --git a/index.js b/index.js index 81260671..72612167 100644 --- a/index.js +++ b/index.js @@ -3,30 +3,15 @@ const { promises: { readdir, readFile } } = require('node:fs') const { join, relative, sep } = require('node:path') const { pathToFileURL } = require('node:url') +const runtime = require('./runtime') -const isFastifyAutoloadTypescriptOverride = !!process.env.FASTIFY_AUTOLOAD_TYPESCRIPT -const isTsNode = (Symbol.for('ts-node.register.instance') in process) || !!process.env.TS_NODE_DEV -const isBabelNode = process.execArgv.concat(process.argv).some((arg) => arg.indexOf('babel-node') >= 0) - -const isVitestEnvironment = process.env.VITEST === 'true' || process.env.VITEST_WORKER_ID !== undefined -const isJestEnvironment = process.env.JEST_WORKER_ID !== undefined -const isSWCRegister = process._preload_modules?.includes('@swc/register') -const isSWCNodeRegister = process._preload_modules?.includes('@swc-node/register') -/* istanbul ignore next - OS specific */ -const isSWCNode = typeof process.env._ === 'string' && process.env._.includes('.bin/swc-node') -const isTsm = process._preload_modules?.includes('tsm') -const isEsbuildRegister = process._preload_modules?.includes('esbuild-register') -const isTsx = process._preload_modules?.toString()?.includes('tsx') -const typescriptSupport = isFastifyAutoloadTypescriptOverride || isTsNode || isVitestEnvironment || isBabelNode || isJestEnvironment || isSWCRegister || isSWCNodeRegister || isSWCNode || isTsm || isTsx || isEsbuildRegister - -const forceESMEnvironment = isVitestEnvironment || false const routeParamPattern = /\/_/gu const routeMixedParamPattern = /__/gu const defaults = { - scriptPattern: /(?:(?:^.?|\.[^d]|[^.]d|[^.][^d])\.ts|\.js|\.cjs|\.mjs)$/iu, - indexPattern: /^index(?:\.ts|\.js|\.cjs|\.mjs)$/iu, - autoHooksPattern: /^[_.]?auto_?hooks(?:\.ts|\.js|\.cjs|\.mjs)$/iu, + scriptPattern: /(?:(?:^.?|\.[^d]|[^.]d|[^.][^d])\.ts|\.js|\.cjs|\.mjs|\.cts|\.mts)$/iu, + indexPattern: /^index(?:\.ts|\.js|\.cjs|\.mjs|\.cts|\.mts)$/iu, + autoHooksPattern: /^[_.]?auto_?hooks(?:\.ts|\.js|\.cjs|\.mjs|\.cts|\.mts)$/iu, dirNameRoutePrefix: true, encapsulate: true } @@ -132,11 +117,14 @@ async function getPackageType (cwd) { } } -const typescriptPattern = /\.ts$/iu -const modulePattern = /\.mjs$/iu -const commonjsPattern = /\.cjs$/iu +const typescriptPattern = /\.(ts|mts|cts)$/iu +const modulePattern = /\.(mjs|mts)$/iu +const commonjsPattern = /\.(cjs|cts)$/iu function getScriptType (fname, packageType) { - return (modulePattern.test(fname) ? 'module' : commonjsPattern.test(fname) ? 'commonjs' : typescriptPattern.test(fname) ? 'typescript' : packageType) || 'commonjs' + return { + language: typescriptPattern.test(fname) ? 'typescript' : 'javascript', + type: (modulePattern.test(fname) ? 'module' : commonjsPattern.test(fname) ? 'commonjs' : packageType) || 'commonjs' + } } // eslint-disable-next-line default-param-last @@ -160,7 +148,7 @@ async function findPlugins (dir, options, hookedAccumulator = {}, prefix, depth const autoHooks = list.find((dirent) => autoHooksPattern.test(dirent.name)) if (autoHooks) { const autoHooksFile = join(dir, autoHooks.name) - const autoHooksType = getScriptType(autoHooksFile, options.packageType) + const { type: autoHooksType } = getScriptType(autoHooksFile, options.packageType) // Overwrite current hooks? if (options.overwriteHooks && currentHooks.length > 0) { @@ -178,8 +166,8 @@ async function findPlugins (dir, options, hookedAccumulator = {}, prefix, depth const indexDirent = list.find((dirent) => indexPattern.test(dirent.name)) if (indexDirent) { const file = join(dir, indexDirent.name) - const type = getScriptType(file, options.packageType) - if (type === 'typescript' && !typescriptSupport) { + const { language, type } = getScriptType(file, options.packageType) + if (language === 'typescript' && !runtime.supportTypeScript) { throw new Error(`@fastify/autoload cannot import hooks plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) } @@ -231,8 +219,8 @@ async function findPlugins (dir, options, hookedAccumulator = {}, prefix, depth } if (dirent.isFile() && scriptPattern.test(dirent.name)) { - const type = getScriptType(file, options.packageType) - if (type === 'typescript' && !typescriptSupport) { + const { language, type } = getScriptType(file, options.packageType) + if (language === 'typescript' && !runtime.supportTypeScript) { throw new Error(`@fastify/autoload cannot import plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) } @@ -265,7 +253,7 @@ async function findPlugins (dir, options, hookedAccumulator = {}, prefix, depth async function loadPlugin ({ file, type, directoryPrefix, options, log }) { const { options: overrideConfig, forceESM, encapsulate } = options let content - if (forceESM || type === 'module' || forceESMEnvironment) { + if (forceESM || type === 'module' || runtime.forceESM) { content = await import(pathToFileURL(file).href) } else { content = require(file) @@ -408,7 +396,7 @@ function wrapRoutes (content) { async function loadHook (hook, options) { let hookContent - if (options.forceESM || hook.type === 'module' || forceESMEnvironment) { + if (options.forceESM || hook.type === 'module' || runtime.forceESM) { hookContent = await import(pathToFileURL(hook.file).href) } else { hookContent = require(hook.file) diff --git a/runtime.js b/runtime.js new file mode 100644 index 00000000..87e51bd8 --- /dev/null +++ b/runtime.js @@ -0,0 +1,126 @@ +'use strict' + +// runtime cache +const cache = {} + +let processArgv +function checkProcessArgv (moduleName) { + processArgv ??= (process.execArgv ?? []).concat(process.argv ?? []) + return processArgv.some((arg) => arg.indexOf(moduleName) >= 0) +} + +let preloadModules +function checkPreloadModules (moduleName) { + // since _preload_modules is internal property + // it may not exist in different runtime + preloadModules ??= (process._preload_modules ?? []) + return preloadModules.includes(moduleName) +} + +let preloadModulesString +function checkPreloadModulesString (moduleName) { + preloadModulesString ??= preloadModules.toString() + return preloadModulesString.includes(moduleName) +} + +function checkEnvVariable (name, value) { + return value + ? process.env[name] === value + : process.env[name] !== undefined +} + +const runtime = {} +// use Object.defineProperties to provide lazy load +Object.defineProperties(runtime, { + tsNode: { + get () { + cache.tsNode ??= ( + // --require tsnode/register + (Symbol.for('ts-node.register.instance') in process) || + // --loader ts-node/esm + checkProcessArgv('ts-node/esm') || + // ts-node-dev + !!process.env.TS_NODE_DEV + ) + return cache.tsNode + } + }, + babelNode: { + get () { + cache.babelNode ??= checkProcessArgv('babel-node') + return cache.babelNode + } + }, + vitest: { + get () { + cache.vitest ??= ( + checkEnvVariable('VITEST', 'true') || + checkEnvVariable('VITEST_WORKER_ID') + ) + return cache.vitest + } + }, + jest: { + get () { + cache.jest ??= checkEnvVariable('JEST_WORKER_ID') + return cache.jest + } + }, + swc: { + get () { + cache.swc ??= ( + checkPreloadModules('@swc/register') || + checkPreloadModules('@swc-node/register') || + checkProcessArgv('.bin/swc-node') + ) + return cache.swc + } + }, + tsm: { + get () { + cache.tsm ??= checkPreloadModules('tsm') + return cache.tsm + } + }, + esbuild: { + get () { + cache.esbuild ??= checkPreloadModules('esbuild-register') + return cache.esbuild + } + }, + tsx: { + get () { + cache.tsx ??= checkPreloadModulesString('tsx') + return cache.tsx + } + }, + supportTypeScript: { + get () { + cache.supportTypeScript ??= ( + checkEnvVariable('FASTIFY_AUTOLOAD_TYPESCRIPT') || + runtime.tsNode || + runtime.vitest || + runtime.babelNode || + runtime.jest || + runtime.swc || + runtime.tsm || + runtime.tsx || + runtime.esbuild + ) + return cache.supportTypeScript + } + }, + forceESM: { + get () { + cache.forceESM ??= ( + checkProcessArgv('ts-node/esm') || + runtime.vitest || + false + ) + return cache.forceESM + } + } +}) + +module.exports = runtime +module.exports.runtime = runtime From 34f2d45a26c74992d68f00e75269e025ad98450c Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 26 Apr 2024 15:09:53 +0800 Subject: [PATCH 2/5] ci: use 4.1.0 to check --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b002766..5395d92e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ on: jobs: test: - uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3 + uses: fastify/workflows/.github/workflows/plugins-ci.yml@v4.1.0 with: license-check: true lint: true From 2c02cb4297272d953fc6882f556fd009164abfbc Mon Sep 17 00:00:00 2001 From: KaKa Date: Thu, 2 May 2024 23:40:14 +0800 Subject: [PATCH 3/5] fixup --- runtime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime.js b/runtime.js index 87e51bd8..c6bfdcba 100644 --- a/runtime.js +++ b/runtime.js @@ -5,14 +5,14 @@ const cache = {} let processArgv function checkProcessArgv (moduleName) { + /* istanbul ignore next - nullish needed for non Node.js runtime */ processArgv ??= (process.execArgv ?? []).concat(process.argv ?? []) return processArgv.some((arg) => arg.indexOf(moduleName) >= 0) } let preloadModules function checkPreloadModules (moduleName) { - // since _preload_modules is internal property - // it may not exist in different runtime + /* istanbul ignore next - nullish needed for non Node.js runtime */ preloadModules ??= (process._preload_modules ?? []) return preloadModules.includes(moduleName) } From 96a3701abc9ed79ef55f48d69654fdb29bab9273 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 3 May 2024 11:23:05 +0800 Subject: [PATCH 4/5] refactor: use x ?? (x = y) instead of x ??= y --- runtime.js | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/runtime.js b/runtime.js index c6bfdcba..c5ef5d65 100644 --- a/runtime.js +++ b/runtime.js @@ -1,4 +1,5 @@ 'use strict' +// TODO: change x ?? (x = y) to x ??= y when next major // runtime cache const cache = {} @@ -6,20 +7,20 @@ const cache = {} let processArgv function checkProcessArgv (moduleName) { /* istanbul ignore next - nullish needed for non Node.js runtime */ - processArgv ??= (process.execArgv ?? []).concat(process.argv ?? []) + processArgv ?? (processArgv = (process.execArgv ?? []).concat(process.argv ?? [])) return processArgv.some((arg) => arg.indexOf(moduleName) >= 0) } let preloadModules function checkPreloadModules (moduleName) { /* istanbul ignore next - nullish needed for non Node.js runtime */ - preloadModules ??= (process._preload_modules ?? []) + preloadModules ?? (preloadModules = (process._preload_modules ?? [])) return preloadModules.includes(moduleName) } let preloadModulesString function checkPreloadModulesString (moduleName) { - preloadModulesString ??= preloadModules.toString() + preloadModulesString ?? (preloadModulesString = preloadModules.toString()) return preloadModulesString.includes(moduleName) } @@ -34,69 +35,69 @@ const runtime = {} Object.defineProperties(runtime, { tsNode: { get () { - cache.tsNode ??= ( + cache.tsNode ?? (cache.tsNode = ( // --require tsnode/register (Symbol.for('ts-node.register.instance') in process) || // --loader ts-node/esm checkProcessArgv('ts-node/esm') || // ts-node-dev !!process.env.TS_NODE_DEV - ) + )) return cache.tsNode } }, babelNode: { get () { - cache.babelNode ??= checkProcessArgv('babel-node') + cache.babelNode ?? (cache.babelNode = checkProcessArgv('babel-node')) return cache.babelNode } }, vitest: { get () { - cache.vitest ??= ( + cache.vitest ?? (cache.vitest = ( checkEnvVariable('VITEST', 'true') || checkEnvVariable('VITEST_WORKER_ID') - ) + )) return cache.vitest } }, jest: { get () { - cache.jest ??= checkEnvVariable('JEST_WORKER_ID') + cache.jest ?? (cache.jest = checkEnvVariable('JEST_WORKER_ID')) return cache.jest } }, swc: { get () { - cache.swc ??= ( + cache.swc ?? (cache.swc = ( checkPreloadModules('@swc/register') || checkPreloadModules('@swc-node/register') || checkProcessArgv('.bin/swc-node') - ) + )) return cache.swc } }, tsm: { get () { - cache.tsm ??= checkPreloadModules('tsm') + cache.tsm ?? (cache.tsm = checkPreloadModules('tsm')) return cache.tsm } }, esbuild: { get () { - cache.esbuild ??= checkPreloadModules('esbuild-register') + cache.esbuild ?? (cache.esbuild = checkPreloadModules('esbuild-register')) return cache.esbuild } }, tsx: { get () { - cache.tsx ??= checkPreloadModulesString('tsx') + cache.tsx ?? (cache.tsx = checkPreloadModulesString('tsx')) return cache.tsx } }, supportTypeScript: { get () { - cache.supportTypeScript ??= ( + cache.supportTypeScript ?? (cache.supportTypeScript = ( checkEnvVariable('FASTIFY_AUTOLOAD_TYPESCRIPT') || runtime.tsNode || runtime.vitest || @@ -106,17 +107,17 @@ Object.defineProperties(runtime, { runtime.tsm || runtime.tsx || runtime.esbuild - ) + )) return cache.supportTypeScript } }, forceESM: { get () { - cache.forceESM ??= ( + cache.forceESM ?? (cache.forceESM = ( checkProcessArgv('ts-node/esm') || runtime.vitest || false - ) + )) return cache.forceESM } } From 1f21ed5baf05e4bf7b914b03c8de59c25f6d2021 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 3 May 2024 11:23:56 +0800 Subject: [PATCH 5/5] ci: revert ci change --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5395d92e..7b002766 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ on: jobs: test: - uses: fastify/workflows/.github/workflows/plugins-ci.yml@v4.1.0 + uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3 with: license-check: true lint: true