Skip to content

Commit

Permalink
refactor: better support typescript (#372)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
climba03003 committed May 3, 2024
1 parent c1685f4 commit 9e1742c
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 30 deletions.
48 changes: 18 additions & 30 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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.`)
}

Expand Down Expand Up @@ -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.`)
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
127 changes: 127 additions & 0 deletions runtime.js
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 9e1742c

Please sign in to comment.