In newer versions of Node.js you can register your own loader and resolvers.
This is an example of automatically transpiling .ts files.
Usage:
> node --import ./ts-loader.mjs ./my-app.ts
| import { transform } from 'esbuild'; | |
| import { existsSync } from 'node:fs'; | |
| import { fileURLToPath } from 'node:url'; | |
| const isRelativeJs = /^\.{1,2}\/.+\.js$/; | |
| /** | |
| * In TypeScript you can use .js extension in import statements, even though the actual file uses .ts | |
| * @see https://nodejs.org/api/module.html#resolvespecifier-context-nextresolve | |
| * @param {string} specifier | |
| * @param {Object} context | |
| * @param {Function} nextResolve | |
| */ | |
| export async function resolve(specifier, context, nextResolve) { | |
| if (isRelativeJs.test(specifier) && !existsSync(fileURLToPath(new URL(specifier, context.parentURL)))) { | |
| return nextResolve(specifier.replace(/\.js$/, '.ts'), context); | |
| } | |
| return nextResolve(specifier, context); | |
| } | |
| /** | |
| * Transpile .ts files using esbuild. | |
| * @see https://nodejs.org/api/module.html#loadurl-context-nextload | |
| * @param {string} url | |
| * @param {Object} context | |
| * @param {Function} nextLoad | |
| */ | |
| export async function load(url, context, nextLoad) { | |
| if (!url.endsWith('.ts')) return nextLoad(url, context); | |
| const { source, format } = await nextLoad(url, { ...context, format: 'module' }); | |
| const { code } = await transform(source, { loader: 'ts' }); | |
| return { source: code, format }; | |
| } |
| import { register } from 'node:module'; | |
| register(new URL('ts-loader-hooks.mjs', import.meta.url)); |