From 9e1742cc8e018146b441dc1b62c7d7a20c3136a9 Mon Sep 17 00:00:00 2001 From: KaKa <23028015+climba03003@users.noreply.github.com> Date: Fri, 3 May 2024 16:17:20 +0800 Subject: [PATCH] refactor: better support typescript (#372) * refactor: better support typescript * ci: use 4.1.0 to check * fixup * refactor: use x ?? (x = y) instead of x ??= y * ci: revert ci change --- index.js | 48 ++++++++------------ runtime.js | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 runtime.js diff --git a/index.js b/index.js index 93fd8b34..d77365e9 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 } @@ -138,11 +123,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 @@ -166,7 +154,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) { @@ -184,8 +172,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.`) } @@ -237,8 +225,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.`) } @@ -271,7 +259,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) @@ -414,7 +402,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..c5ef5d65 --- /dev/null +++ b/runtime.js @@ -0,0 +1,127 @@ +'use strict' +// TODO: change x ?? (x = y) to x ??= y when next major + +// runtime cache +const cache = {} + +let processArgv +function checkProcessArgv (moduleName) { + /* istanbul ignore next - nullish needed for non Node.js runtime */ + 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 ?? (preloadModules = (process._preload_modules ?? [])) + return preloadModules.includes(moduleName) +} + +let preloadModulesString +function checkPreloadModulesString (moduleName) { + preloadModulesString ?? (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 ?? (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 ?? (cache.babelNode = checkProcessArgv('babel-node')) + return cache.babelNode + } + }, + vitest: { + get () { + cache.vitest ?? (cache.vitest = ( + checkEnvVariable('VITEST', 'true') || + checkEnvVariable('VITEST_WORKER_ID') + )) + return cache.vitest + } + }, + jest: { + get () { + cache.jest ?? (cache.jest = checkEnvVariable('JEST_WORKER_ID')) + return cache.jest + } + }, + swc: { + get () { + cache.swc ?? (cache.swc = ( + checkPreloadModules('@swc/register') || + checkPreloadModules('@swc-node/register') || + checkProcessArgv('.bin/swc-node') + )) + return cache.swc + } + }, + tsm: { + get () { + cache.tsm ?? (cache.tsm = checkPreloadModules('tsm')) + return cache.tsm + } + }, + esbuild: { + get () { + cache.esbuild ?? (cache.esbuild = checkPreloadModules('esbuild-register')) + return cache.esbuild + } + }, + tsx: { + get () { + cache.tsx ?? (cache.tsx = checkPreloadModulesString('tsx')) + return cache.tsx + } + }, + supportTypeScript: { + get () { + cache.supportTypeScript ?? (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 ?? (cache.forceESM = ( + checkProcessArgv('ts-node/esm') || + runtime.vitest || + false + )) + return cache.forceESM + } + } +}) + +module.exports = runtime +module.exports.runtime = runtime