UNPKG

@boost/module

Version:

Module resolving and loading utilities with TypeScript support.

100 lines (83 loc) 2.67 kB
/* eslint-disable no-underscore-dangle, node/no-deprecated-api */ import fs from 'node:fs'; import type Module from 'node:module'; import { interopModule } from './interopModule'; import type { ModuleLike, PathLike, TS } from './types'; import { COMPILER_OPTIONS, getTargetFromNodeVersion, isTypeScript } from './typescript'; let tsInstance: TS | null = null; function loadTypeScript() { if (!tsInstance) { try { tsInstance = require('typescript') as TS; } catch { // Ignore and check at runtime } } return tsInstance; } function transform(contents: string, filePath: string): string { const ts = loadTypeScript(); if (!ts) { throw new Error(`\`typescript\` package required for transforming file "${filePath}".`); } return ts.transpileModule(contents, { compilerOptions: { ...COMPILER_OPTIONS, module: ts.ModuleKind.CommonJS, resolveJsonModule: true, target: getTargetFromNodeVersion(ts), }, fileName: filePath, }).outputText; } function transformHandler(mod: Module, filePath: string) { mod._compile(transform(fs.readFileSync(filePath, 'utf8'), filePath), filePath); } /** * Register `.ts` and `.tsx` file extensions into Node.js's resolution algorithm. */ export function registerExtensions() { require.extensions['.ts'] = transformHandler; require.extensions['.tsx'] = transformHandler; require.extensions['.cts'] = transformHandler; require.extensions['.mts'] = transformHandler; } /** * Unregister `.ts` and `.tsx` file extensions. */ export function unregisterExtensions() { delete require.extensions['.ts']; delete require.extensions['.tsx']; delete require.extensions['.cts']; delete require.extensions['.mts']; } /** * Like `requireModule` but for loading TypeScript files ending in `ts` or `tsx`. * When imported, will transform the file using the `typescript` package, * evaluate the code in the current module context, and apply the same process * to all child imports. * * ```ts * import { requireTSModule } from '@boost/module'; * * const result = requireTSModule('../../some/module.ts'); * ``` * * > This helper rarely needs to be used directly as `requireModule` will * > call it under the hood based on the file extension. */ export function requireTSModule<D = unknown, N extends object = {}>( path: PathLike, requirer: NodeRequire = require, ): ModuleLike<D, N> { const filePath = String(path); if (!isTypeScript(filePath)) { throw new Error( `Unable to import non-TypeScript file "${filePath}", use \`requireModule\` instead.`, ); } registerExtensions(); const result = interopModule<D, N>(requirer(filePath)); unregisterExtensions(); return result; }