@septh/ts-run
Version:
The minimalist TypeScript script runner for Node
103 lines (89 loc) • 3.86 kB
JavaScript
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);