UNPKG

@septh/ts-run

Version:

The minimalist TypeScript script runner for Node

103 lines (89 loc) 3.86 kB
import module from 'node:module'; import path from 'node:path'; import { readFileSync } from 'node:fs'; const require = module.createRequire(import.meta.url); const jsExtRx = /\.([cm])?js$/; function transpile(m, format, filePath) { // Notes: // - This function is called by the CJS loader so it must be sync. // - We lazy-load Sucrase as the CJS loader may well never be used // at all, as ESM scripts are becoming more and more frequent. // This infers a very small performance penalty when transpile() is called // for the fist time, but we'll live with it. const { transform } = require('./transform.cjs'); const source = readFileSync(filePath).toString(); const code = transform(source, format, path.basename(filePath)); return m._compile(code, filePath) } const pkgTypeCache = new Map(); function nearestPackageType(file, defaultType) { for ( let current = path.dirname(file), previous = undefined; previous !== current; previous = current, current = path.dirname(current) ) { const pkgFile = path.join(current, 'package.json'); let cached = pkgTypeCache.get(pkgFile); if (cached === undefined) { try { const data = readFileSync(pkgFile); const { type } = JSON.parse(data.toString()); cached = type === 'module' || type ==='commonjs' ? type : defaultType; } catch(err) { const { code } = err; if (code !== 'ENOENT') console.error(err); cached = null; } pkgTypeCache.set(pkgFile, cached); } if (typeof cached === 'string') return cached } return defaultType } function installCjsHooks(defaultModuleType) { const { _resolveFilename } = module; module._resolveFilename = function _resolveFilenamePatch(request, ...otherArgs) { try { // Let's try first with the .ts extension... return _resolveFilename.call(module, request.replace(jsExtRx, '.$1ts'), ...otherArgs) } catch { // Otherwise, go as-is. return _resolveFilename.call(module, request, ...otherArgs) } }; module._extensions['.ts'] = (m, filename) => transpile(m, nearestPackageType(filename, defaultModuleType), filename); module._extensions['.cts'] = (m, filename) => transpile(m, 'commonjs', filename); module._extensions['.mts'] = (m, filename) => transpile(m, 'module', filename); } const [ major, minor, patch ] = process.versions.node.split('.').map(Number); if (!(major >= 21 || (major === 20 && minor >= 6) || (major === 18 && minor >= 19))) { const { name } = module.createRequire(import.meta.url)('#package.json'); throw new Error(`Unsupported NodeJS version ${major}.${minor}.${patch}. ${name} requires Node 18.19.0+, Node 20.6.0+ or Node 21+.`) } // Enable source map support. process.setSourceMapsEnabled(true); // Determine the default module type. let defaultModuleType = 'commonjs'; const argIndex = process.execArgv.findIndex(arg => /^--(?:experimental-)?default-type/.test(arg)); if (argIndex >= 0) { const type = process.execArgv[argIndex].split('=')[1] || process.execArgv[argIndex + 1]; if (type === 'module' || type === 'commonjs') defaultModuleType = type; } // Register the esm hooks -- those are run in a worker thread. const self = import.meta.url; module.register('./esm-hooks.js', { parentURL: self, data: { self, defaultModuleType } }); // Install the cjs hooks -- those are run synchronously in the main thread. installCjsHooks(defaultModuleType);