Easily, quickly and reliably run a Node.js script from the CLI.
npx xnr any-file.{ts,tsx,cts,mts,js,jsx,cjs,mjs}
- converts local dependencies — will transpile anything needed to run the file
- zero config — it should just work - but if it doesn't work, leave an issue!
- fast — skips TypeScript type checking
- light — <4MB including dependencies = faster CI
- tolerant — makes as few assumptions as possible
- supports tsconfig paths if provided
- equally, doesn't need tsconfig
- plays nice with rogue npm dependencies that expect one of
require
orimport
- just performant* JavaScript — no Rust or Golang!
* uses
sucrase
to convert to TypeScript to JavaScript, and then performs fast AST manipulations to make the interop work - no tsc/babel/esbuild/swc here
runner | single TS file | big TS project | install time | install size | compat #1 | compat #2 | compat #3 | compat #4 |
---|---|---|---|---|---|---|---|---|
xnr | 0.14 sec |
0.56 sec |
4.82 sec |
3.9 MB |
✅ | ✅ | ✅ | ✅ |
esr | 0.08 sec |
0.12 sec |
7.71 sec |
10.6 MB |
✅ | ✅ | ✅ | ❌ |
swc-node | 0.22 sec |
0.38 sec |
37.88 sec |
95.1 MB |
✅ | ✅ | ❌ | ❌ |
ts-node | 0.65 sec |
1.60 sec |
16.10 sec |
45.2 MB |
✅ | ❌ | ❌ | ❌ |
Methodology
const run = (date: Date): void => { console.log( [ date.getFullYear(), (date.getMonth() + 1).toString().padStart(2, "0"), date.getDate().toString().padStart(2, "0"), ].join("-") ); }; run(new Date(3000, 0, 1));Measured by running a simple script that imports the
date-fn
(TypeScript) > source files directly.// repo:date-fns/src/index.ts import { format } from "(date-fns-source)"; // where `(date-fn-source)` => // #1 `./src` // #2 `./src/index.ts` // #3 `./src/index.js` // #4 `./src` with `"type": "module"` in package.json // support for each import path varies by runner const run = (): void => { console.log(format(new Date(3000, 0, 1), "yyyy-MM-dd")); }; run();echo "xnr:" start_timer && node ./node_modules/.bin/xnr ./file.ts && print_timer echo "ts-node:" start_timer && node ./node_modules/.bin/ts-node ./file.ts && print_timer echo "esr:" start_timer && node ./node_modules/.bin/esr ./file.ts && print_timer echo "swc-node:" start_timer && node -r @swc-node/register ./file.ts && print_timer2023-05-12, 3 run avg, MacBook Pro w/ M1 Pro
installed required dependencies with npm into an empty dir, then:
rm package.json && rm package-lock.json && du -sk .
tested with my (slow 😢) 1.5MB/s Wi-Fi download speed (no cache, 3 run avg)
Optional: install it
npm install --save-dev xnr
Run it from the CLI
const typedFn = (str: string) => console.log(str);
typedFn("hello world");
> npx xnr your-file.ts
hello world
Or build now, run later
# requires xnr to be installed
> npx xnrb your-file.ts your-build-dir
your-build-dir/your-file.cjs
> node your-build-dir/your-file.cjs
hello world
your-build-dir
contains all the needed local dependencies (transpiled) to run the file natively
// Convert an input code string to a node-friendly esm code string
export declare const transform: (
inputCode: string,
filePath?: string | undefined
) => Promise<string>;
// Convert source code from an entry file into a directory of node-friendly esm code
export declare const build: (
entryFilePath: string,
outputDirectory?: string | undefined
) => Promise<string | undefined>;
// Runs a file, no questions asked (auto-transpiling it and its dependencies as required)
export declare const run: (
entryFilePath: string,
args?: string[],
outputDirectory?: string | undefined
) => Promise<number>; // status code
Please leave bug reports if something doesn't work as expected! 😄
Apache-2.0