Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: better support typescript #372

Merged
merged 5 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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
Expand All @@ -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) {
Expand All @@ -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.`)
}

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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
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
Loading