diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 4bd5898c..3d2a5417 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -48,7 +48,7 @@ jobs:
matrix:
os: [ubuntu, windows]
# Don't forget to add all new flavors to this list!
- flavor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+ flavor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
include:
# Node 12.15
# TODO Add comments about why we test 12.15; I think git blame says it's because of an ESM behavioral change that happened at 12.16
@@ -119,8 +119,15 @@ jobs:
typescript: next
typescriptFlag: next
downgradeNpm: true
- # Node nightly
+ # Node 17
- flavor: 12
+ node: 17
+ nodeFlag: 17
+ typescript: latest
+ typescriptFlag: latest
+ downgradeNpm: true
+ # Node nightly
+ - flavor: 13
node: nightly
nodeFlag: nightly
typescript: latest
@@ -154,7 +161,7 @@ jobs:
$version = (Invoke-WebRequest https://nodejs.org/download/nightly/index.json | ConvertFrom-json)[0].version
$url = "https://nodejs.org/download/nightly/$version/win-x64/node.exe"
$targetPath = (Get-Command node.exe).Source
- Invoke-WebRequest -Uri $url -OutFile $targetPath
+ Invoke-WebRequest -Uri $url -OutFile $targetPath -UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::Chrome)
node --version
# lint, build, test
# Downgrade from npm 7 to 6 because 7 still seems buggy to me
diff --git a/.prettierignore b/.prettierignore
index e0158cb5..492a816c 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -11,3 +11,4 @@ tests/throw error.ts
tests/throw error react tsx.tsx
tests/esm/throw error.ts
tests/legacy-source-map-support-interop/index.ts
+tests/main-realpath/symlink/symlink.tsx
diff --git a/README.md b/README.md
index d5519856..90fb9f54 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ You can build the readme with this command:
cd website && yarn build-readme
-->
-# 
+# [](https://typestrong.org/ts-node)
[](https://npmjs.org/package/ts-node)
[](https://npmjs.org/package/ts-node)
@@ -60,13 +60,15 @@ The latest documentation can also be found on our website: *Environment:* `TS_NODE_PROJECT`
-* `--skip-project` Skip project config resolution and loading
*Default:* `false`
*Environment:* `TS_NODE_SKIP_PROJECT`
-* `-c, --cwd-mode` Resolve config relative to the current directory instead of the directory of the entrypoint script
-* `-O, --compiler-options [opts]` JSON object to merge with compiler options
*Environment:* `TS_NODE_COMPILER_OPTIONS`
-* `--show-config` Print resolved `tsconfig.json`, including `ts-node` options, and exit
+* `--skipProject` Skip project config resolution and loading
*Default:* `false`
*Environment:* `TS_NODE_SKIP_PROJECT`
+* `-c, --cwdMode` Resolve config relative to the current directory instead of the directory of the entrypoint script
+* `-O, --compilerOptions [opts]` JSON object to merge with compiler options
*Environment:* `TS_NODE_COMPILER_OPTIONS`
+* `--showConfig` Print resolved `tsconfig.json`, including `ts-node` options, and exit
## Typechecking
-* `-T, --transpile-only` Use TypeScript's faster `transpileModule`
*Default:* `false`
*Environment:* `TS_NODE_TRANSPILE_ONLY`
-* `--type-check` Opposite of `--transpile-only`
*Default:* `true`
*Environment:* `TS_NODE_TYPE_CHECK`
-* `-H, --compiler-host` Use TypeScript's compiler host API
*Default:* `false`
*Environment:* `TS_NODE_COMPILER_HOST`
+* `-T, --transpileOnly` Use TypeScript's faster `transpileModule`
*Default:* `false`
*Environment:* `TS_NODE_TRANSPILE_ONLY`
+* `--typeCheck` Opposite of `--transpileOnly`
*Default:* `true`
*Environment:* `TS_NODE_TYPE_CHECK`
+* `-H, --compilerHost` Use TypeScript's compiler host API
*Default:* `false`
*Environment:* `TS_NODE_COMPILER_HOST`
* `--files` Load `files`, `include` and `exclude` from `tsconfig.json` on startup
*Default:* `false`
*Environment:* `TS_NODE_FILES`
-* `-D, --ignore-diagnostics [code]` Ignore TypeScript warnings by diagnostic code
*Environment:* `TS_NODE_IGNORE_DIAGNOSTICS`
+* `-D, --ignoreDiagnostics [code]` Ignore TypeScript warnings by diagnostic code
*Environment:* `TS_NODE_IGNORE_DIAGNOSTICS`
## Transpilation
* `-I, --ignore [pattern]` Override the path patterns to skip compilation
*Default:* `/node_modules/`
*Environment:* `TS_NODE_IGNORE`
-* `--skip-ignore` Skip ignore checks
*Default:* `false`
*Environment:* `TS_NODE_SKIP_IGNORE`
+* `--skipIgnore` Skip ignore checks
*Default:* `false`
*Environment:* `TS_NODE_SKIP_IGNORE`
* `-C, --compiler [name]` Specify a custom TypeScript compiler
*Default:* `typescript`
*Environment:* `TS_NODE_COMPILER`
+* `--swc` Transpile with [swc](#swc). Implies `--transpileOnly`
*Default:* `false`
* `--transpiler [name]` Specify a third-party, non-typechecking transpiler
-* `--prefer-ts-exts` Re-order file extensions so that TypeScript imports are preferred
*Default:* `false`
*Environment:* `TS_NODE_PREFER_TS_EXTS`
+* `--preferTsExts` Re-order file extensions so that TypeScript imports are preferred
*Default:* `false`
*Environment:* `TS_NODE_PREFER_TS_EXTS`
## Diagnostics
-* `--log-error` Logs TypeScript errors to stderr instead of throwing exceptions
*Default:* `false`
*Environment:* `TS_NODE_LOG_ERROR`
+* `--logError` Logs TypeScript errors to stderr instead of throwing exceptions
*Default:* `false`
*Environment:* `TS_NODE_LOG_ERROR`
* `--pretty` Use pretty diagnostic formatter
*Default:* `false`
*Environment:* `TS_NODE_PRETTY`
* `TS_NODE_DEBUG` Enable debug logging
@@ -321,9 +341,10 @@ node --trace-deprecation --abort-on-uncaught-exception -r ts-node/register ./ind
* `--emit` Emit output files into `.ts-node` directory
*Default:* `false`
*Environment:* `TS_NODE_EMIT`
* `--scope` Scope compiler to files within `scopeDir`. Anything outside this directory is ignored.
\*Default: `false`
*Environment:* `TS_NODE_SCOPE`
* `--scopeDir` Directory within which compiler is limited when `scope` is enabled.
*Default:* First of: `tsconfig.json` "rootDir" if specified, directory containing `tsconfig.json`, or cwd if no `tsconfig.json` is loaded.
*Environment:* `TS_NODE_SCOPE_DIR`
-* `moduleType` Override the module type of certain files, ignoring the `package.json` `"type"` field. See [Module type overrides](#module-type-overrides) for details.
*Default:* obeys `package.json` `"type"` and `tsconfig.json` `"module"`
*Can only be specified via `tsconfig.json` or API.*
+* `moduleTypes` Override the module type of certain files, ignoring the `package.json` `"type"` field. See [Module type overrides](#module-type-overrides) for details.
*Default:* obeys `package.json` `"type"` and `tsconfig.json` `"module"`
*Can only be specified via `tsconfig.json` or API.*
* `TS_NODE_HISTORY` Path to history file for REPL
*Default:* `~/.ts_node_repl_history`
-* `--no-experimental-repl-await` Disable top-level await in REPL. Equivalent to node's [`--no-experimental-repl-await`](https://nodejs.org/api/cli.html#cli_no_experimental_repl_await)
*Default:* Enabled if TypeScript version is 3.8 or higher and target is ES2018 or higher.
*Environment:* `TS_NODE_EXPERIMENTAL_REPL_AWAIT` set `false` to disable
+* `--noExperimentalReplAwait` Disable top-level await in REPL. Equivalent to node's [`--no-experimental-repl-await`](https://nodejs.org/api/cli.html#cli_no_experimental_repl_await)
*Default:* Enabled if TypeScript version is 3.8 or higher and target is ES2018 or higher.
*Environment:* `TS_NODE_EXPERIMENTAL_REPL_AWAIT` set `false` to disable
+* `experimentalResolverFeatures` Enable experimental features that re-map imports and require calls to support: `baseUrl`, `paths`, `rootDirs`, `.js` to `.ts` file extension mappings, `outDir` to `rootDir` mappings for composite projects and monorepos. For details, see [#1514](https://github.com/TypeStrong/ts-node/issues/1514)
*Default:* `false`
*Can only be specified via `tsconfig.json` or API.*
## API
@@ -403,7 +424,7 @@ You must set [`"type": "module"`](https://nodejs.org/api/packages.html#packages_
## Understanding configuration
ts-node uses sensible default configurations to reduce boilerplate while still respecting `tsconfig.json` if you
-have one. If you are unsure which configuration is used, you can log it with `ts-node --show-config`. This is similar to
+have one. If you are unsure which configuration is used, you can log it with `ts-node --showConfig`. This is similar to
`tsc --showConfig` but includes `"ts-node"` options as well.
ts-node also respects your locally-installed `typescript` version, but global installations fallback to the globally-installed
@@ -415,7 +436,7 @@ ts-node v10.0.0
node v16.1.0
compiler v4.2.2
-$ ts-node --show-config
+$ ts-node --showConfig
{
"compilerOptions": {
"target": "es6",
@@ -490,7 +511,7 @@ These tricks will make ts-node faster.
It is often better to use `tsc --noEmit` to typecheck once before your tests run or as a lint step. In these cases, ts-node can skip typechecking.
* Enable [`transpileOnly`](#options) to skip typechecking
-* Use our [`swc` integration](#bundled-swc-integration)
+* Use our [`swc` integration](#swc)
* This is by far the fastest option
## With typechecking
@@ -519,12 +540,20 @@ Vanilla `node` loads `.js` by reading code from disk and executing it. Our hook
### Skipping `node_modules`
-By default, **TypeScript Node** avoids compiling files in `/node_modules/` for three reasons:
+By default, ts-node avoids compiling files in `/node_modules/` for three reasons:
1. Modules should always be published in a format node.js can consume
2. Transpiling the entire dependency tree will make your project slower
3. Differing behaviours between TypeScript and node.js (e.g. ES2015 modules) can result in a project that works until you decide to support a feature natively from node.js
+If you need to import uncompiled TypeScript in `node_modules`, use [`--skipIgnore`](#transpilation) or [`TS_NODE_SKIP_IGNORE`](#transpilation) to bypass this restriction.
+
+### Skipping pre-compiled TypeScript
+
+If a compiled JavaScript file with the same name as a TypeScript file already exists, the TypeScript file will be ignored. ts-node will import the pre-compiled JavaScript.
+
+To force ts-node to import the TypeScript source, not the precompiled JavaScript, use [`--preferTsExts`](#transpilation).
+
## paths and baseUrl
You can use ts-node together with [tsconfig-paths](https://www.npmjs.com/package/tsconfig-paths) to load modules according to the `paths` section in `tsconfig.json`.
@@ -628,7 +657,7 @@ For example, to use `ttypescript` and `ts-transformer-keys`, add this to your `t
}
```
-## Third-party transpilers
+## Transpilers
In transpile-only mode, we skip typechecking to speed up execution time. You can go a step further and use a
third-party transpiler to transform TypeScript into JavaScript even faster. You will still benefit from
@@ -641,12 +670,11 @@ boilerplate.
> For our purposes, a compiler implements TypeScript's API and can perform typechecking.
> A third-party transpiler does not. Both transform TypeScript into JavaScript.
-### Bundled `swc` integration
+### swc
-We have bundled an experimental `swc` integration.
+swc support is built-in via the `--swc` flag or `"swc": true` tsconfig option.
-[`swc`](https://swc.rs) is a TypeScript-compatible transpiler implemented in Rust. This makes it an order of magnitude faster
-than `transpileModule`.
+[`swc`](https://swc.rs) is a TypeScript-compatible transpiler implemented in Rust. This makes it an order of magnitude faster than vanilla `transpileOnly`.
To use it, first install `@swc/core` or `@swc/wasm`. If using `importHelpers`, also install `@swc/helpers`. If `target` is less than "es2015" and using either `async`/`await` or generator functions, also install `regenerator-runtime`.
@@ -659,20 +687,38 @@ Then add the following to your `tsconfig.json`.
```jsonc title="tsconfig.json"
{
"ts-node": {
- "transpileOnly": true,
- "transpiler": "ts-node/transpilers/swc-experimental"
+ "swc": true
}
}
```
> `swc` uses `@swc/helpers` instead of `tslib`. If you have enabled `importHelpers`, you must also install `@swc/helpers`.
+### Third-party transpilers
+
+The `transpiler` option allows using third-party transpiler integrations with ts-node. `transpiler` must be given the
+name of a module which can be `require()`d. The built-in `swc` integration is exposed as `ts-node/transpilers/swc`.
+
+For example, to use a hypothetical "speedy-ts-compiler", first install it into your project: `npm install speedy-ts-compiler`
+
+Then add the following to your tsconfig:
+
+```jsonc title="tsconfig.json"
+{
+ "ts-node": {
+ "transpileOnly": true,
+ "transpiler": "speedy-ts-compiler"
+ }
+}
+```
+
### Writing your own integration
To write your own transpiler integration, check our [API docs](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html).
-Integrations are `require()`d, so they can be published to npm. The module must export a `create` function matching the
-[`TranspilerModule`](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html) interface.
+Integrations are `require()`d by ts-node, so they can be published to npm for convenience. The module must export a `create` function described by our
+[`TranspilerModule`](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html) interface. `create` is invoked by ts-node
+at startup to create the transpiler. The transpiler is used repeatedly to transform TypeScript into JavaScript.
## Module type overrides
diff --git a/api-extractor/ts-node.api.md b/api-extractor/ts-node.api.md
index 6c6e8ee6..67f04f2b 100644
--- a/api-extractor/ts-node.api.md
+++ b/api-extractor/ts-node.api.md
@@ -31,7 +31,7 @@ export interface CreateOptions {
ignore?: string[];
ignoreDiagnostics?: Array;
logError?: boolean;
- moduleTypes?: Record;
+ moduleTypes?: ModuleTypes;
pretty?: boolean;
project?: string;
projectSearchDir?: string;
@@ -43,10 +43,12 @@ export interface CreateOptions {
scopeDir?: string;
skipIgnore?: boolean;
skipProject?: boolean;
+ swc?: boolean;
// (undocumented)
transformers?: _ts.CustomTransformers | ((p: _ts.Program) => _ts.CustomTransformers);
transpileOnly?: boolean;
transpiler?: string | [string, object];
+ tsTrace?: (str: string) => void;
typeCheck?: boolean;
}
@@ -72,12 +74,15 @@ export interface CreateReplOptions {
// @public (undocumented)
export interface CreateTranspilerOptions {
// (undocumented)
- service: Pick;
+ service: Pick>;
}
// @public
export type EvalAwarePartialHost = Pick;
+// @public (undocumented)
+export type ModuleTypes = Record;
+
// @public (undocumented)
export interface NodeLoaderHooksAPI1 {
// (undocumented)
@@ -118,12 +123,22 @@ export namespace NodeLoaderHooksAPI2 {
// (undocumented)
export type LoadHook = (url: string, context: {
format: NodeLoaderHooksFormat | null | undefined;
+ importAssertions?: NodeImportAssertions;
}, defaultLoad: NodeLoaderHooksAPI2['load']) => Promise<{
format: NodeLoaderHooksFormat;
source: string | Buffer | undefined;
}>;
// (undocumented)
+ export interface NodeImportAssertions {
+ // (undocumented)
+ type?: 'json';
+ }
+ // (undocumented)
+ export type NodeImportConditions = unknown;
+ // (undocumented)
export type ResolveHook = (specifier: string, context: {
+ conditions?: NodeImportConditions;
+ importAssertions?: NodeImportAssertions;
parentURL: string;
}, defaultResolve: ResolveHook) => Promise<{
url: string;
@@ -147,6 +162,7 @@ export const REGISTER_INSTANCE: unique symbol;
// @public
export interface RegisterOptions extends CreateOptions {
+ experimentalResolverFeatures?: boolean;
preferTsExts?: boolean;
}
@@ -276,7 +292,7 @@ export interface TSCommon {
}
// @public
-export interface TsConfigOptions extends Omit {
+export interface TsConfigOptions extends Omit {
}
// @public
diff --git a/ava.config.js b/ava.config.js
index 9cf877b8..6181565d 100644
--- a/ava.config.js
+++ b/ava.config.js
@@ -3,6 +3,12 @@ export default {
failWithoutAssertions: false,
environmentVariables: {
ts_node_install_lock: `id-${Math.floor(Math.random() * 10e9)}`,
+ // Force jest expect() errors to generate colorized strings, makes output more readable.
+ // Delete the env var within ava processes via `require` option below.
+ // This avoids passing it to spawned processes under test, which would negatively affect
+ // their behavior.
+ FORCE_COLOR: '3',
},
+ require: ['./src/test/remove-env-var-force-color.js'],
timeout: '300s',
};
diff --git a/dist-raw/node-errors.js b/dist-raw/node-errors.js
index f7c6edcf..73255a7d 100644
--- a/dist-raw/node-errors.js
+++ b/dist-raw/node-errors.js
@@ -12,6 +12,7 @@ exports.codes = {
ERR_PACKAGE_PATH_NOT_EXPORTED: createErrorCtor(joinArgs('ERR_PACKAGE_PATH_NOT_EXPORTED')),
ERR_UNSUPPORTED_DIR_IMPORT: createErrorCtor(joinArgs('ERR_UNSUPPORTED_DIR_IMPORT')),
ERR_UNSUPPORTED_ESM_URL_SCHEME: createErrorCtor(joinArgs('ERR_UNSUPPORTED_ESM_URL_SCHEME')),
+ ERR_UNKNOWN_FILE_EXTENSION: createErrorCtor(joinArgs('ERR_UNKNOWN_FILE_EXTENSION')),
}
function joinArgs(name) {
diff --git a/dist-raw/node-options.js b/dist-raw/node-options.js
index 0602a476..22722755 100644
--- a/dist-raw/node-options.js
+++ b/dist-raw/node-options.js
@@ -34,7 +34,9 @@ function parseArgv(argv) {
'--es-module-specifier-resolution': '--experimental-specifier-resolution',
'--experimental-policy': String,
'--conditions': [String],
- '--pending-deprecation': Boolean
+ '--pending-deprecation': Boolean,
+ '--experimental-json-modules': Boolean,
+ '--experimental-wasm-modules': Boolean,
}, {
argv,
permissive: true
diff --git a/esm.mjs b/esm.mjs
index 4d404070..0843cf34 100644
--- a/esm.mjs
+++ b/esm.mjs
@@ -4,9 +4,5 @@ const require = createRequire(fileURLToPath(import.meta.url));
/** @type {import('./dist/esm')} */
const esm = require('./dist/esm');
-export const {
- resolve,
- load,
- getFormat,
- transformSource,
-} = esm.registerAndCreateEsmHooks();
+export const { resolve, load, getFormat, transformSource } =
+ esm.registerAndCreateEsmHooks();
diff --git a/esm/transpile-only.mjs b/esm/transpile-only.mjs
index 07b2c7ae..2e154da0 100644
--- a/esm/transpile-only.mjs
+++ b/esm/transpile-only.mjs
@@ -4,9 +4,5 @@ const require = createRequire(fileURLToPath(import.meta.url));
/** @type {import('../dist/esm')} */
const esm = require('../dist/esm');
-export const {
- resolve,
- load,
- getFormat,
- transformSource,
-} = esm.registerAndCreateEsmHooks({ transpileOnly: true });
+export const { resolve, load, getFormat, transformSource } =
+ esm.registerAndCreateEsmHooks({ transpileOnly: true });
diff --git a/package-lock.json b/package-lock.json
index db9178e3..632b7c96 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "ts-node",
- "version": "10.4.0",
+ "version": "10.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -510,27 +510,18 @@
}
},
"@napi-rs/triples": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.2.tgz",
- "integrity": "sha512-EL3SiX43m9poFSnhDx4d4fn9SSaqyO2rHsCNhETi9bWPmjXK3uPJ0QpPFtx39FEdHcz1vJmsiW41kqc0AgvtzQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz",
+ "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==",
"dev": true
},
"@node-rs/helper": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.1.0.tgz",
- "integrity": "sha512-r43YnnrY5JNzDuXJdW3sBJrKzvejvFmFWbiItUEoBJsaPzOIWFMhXB7i5j4c9EMXcFfxveF4l7hT+rLmwtjrVQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz",
+ "integrity": "sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg==",
"dev": true,
"requires": {
- "@napi-rs/triples": "^1.0.2",
- "tslib": "^2.1.0"
- },
- "dependencies": {
- "tslib": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
- "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
- "dev": true
- }
+ "@napi-rs/triples": "^1.0.3"
}
},
"@nodelib/fs.scandir": {
@@ -709,83 +700,107 @@
"dev": true
},
"@swc/core": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.58.tgz",
- "integrity": "sha512-u/vaon34x4ISDDdgZLaxacPB4Ly8SqqmFkBKPp2VtUDbD12VqKzb6EoLDC3A5EULFQDgIdIMHuVlBB+mc8dq0w==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.106.tgz",
+ "integrity": "sha512-9uw8gqU+lsk7KROAcSNhsrnBgNiC5H4MIaps5LlnnEevJmKu/o1ws22tXc2qjJg+F4/V1ynUbh8E0rYlmo1XGw==",
"dev": true,
"requires": {
"@node-rs/helper": "^1.0.0",
- "@swc/core-android-arm64": "^1.2.58",
- "@swc/core-darwin-arm64": "^1.2.58",
- "@swc/core-darwin-x64": "^1.2.58",
- "@swc/core-linux-arm-gnueabihf": "^1.2.58",
- "@swc/core-linux-arm64-gnu": "^1.2.58",
- "@swc/core-linux-x64-gnu": "^1.2.58",
- "@swc/core-linux-x64-musl": "^1.2.58",
- "@swc/core-win32-ia32-msvc": "^1.2.58",
- "@swc/core-win32-x64-msvc": "^1.2.58"
+ "@swc/core-android-arm64": "^1.2.106",
+ "@swc/core-darwin-arm64": "^1.2.106",
+ "@swc/core-darwin-x64": "^1.2.106",
+ "@swc/core-freebsd-x64": "^1.2.106",
+ "@swc/core-linux-arm-gnueabihf": "^1.2.106",
+ "@swc/core-linux-arm64-gnu": "^1.2.106",
+ "@swc/core-linux-arm64-musl": "^1.2.106",
+ "@swc/core-linux-x64-gnu": "^1.2.106",
+ "@swc/core-linux-x64-musl": "^1.2.106",
+ "@swc/core-win32-arm64-msvc": "^1.2.106",
+ "@swc/core-win32-ia32-msvc": "^1.2.106",
+ "@swc/core-win32-x64-msvc": "^1.2.106"
}
},
"@swc/core-android-arm64": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.58.tgz",
- "integrity": "sha512-eSNNt/KiAbseOZ/lbaHnXClWOOeEPBRJBjxIBDX6U4oXaHLBCwgwU+qhWziVV4Lq6gX0zqcw6JY7Pxz9r2Pxzw==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.106.tgz",
+ "integrity": "sha512-F5T6kP3yV9S0/oXyco305QaAyE6rLNsNSdR0QI4CtACwKadiPwTOptwNIDCiTNLNgWlWLQmIRkPpxg+G4doT6Q==",
"dev": true,
"optional": true
},
"@swc/core-darwin-arm64": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.58.tgz",
- "integrity": "sha512-PHZm9kYi4KjWgac86fhr1238elI7M1K8Zh634eDCJCZbU7LHWUWOyeTpT9G8dxOuAUTOUZDaCHNW/+63N5XWPA==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.106.tgz",
+ "integrity": "sha512-bgKzzYLFnc+mv2mDS/DLwzBvx5DCC9ZCKYY46b4dAnBfasr+SMHj+v/WI84HtilbjLBMUfYZ2hgYKls3CebIIQ==",
"dev": true,
"optional": true
},
"@swc/core-darwin-x64": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.2.58.tgz",
- "integrity": "sha512-jKJNNxBbt/ckp49QUP28P+YEGDS3baruCBRbVkgJQY5Nj5GKw5kay6prVf6ajhoegmtjLr+1p3By7S5XOgIc8g==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.2.106.tgz",
+ "integrity": "sha512-I5Uhit5RqbXaMIV2+v9jL+MIQeR3lT1DqVwzxZs1bTARclAheFZQpTmg+h6QmichjCiUT74SXQb6Apc/vqYKog==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-freebsd-x64": {
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.106.tgz",
+ "integrity": "sha512-ZSK3vgzbA2Pkpw2LgHlAkUdx4okIpdXXTbLXuc5jkZMw1KhRWpeQaDlwbrN7XVynAYjkj2qgGQ7wv1tD43vQig==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm-gnueabihf": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.58.tgz",
- "integrity": "sha512-dnZcurOjTEr2IkSdWakyoVlE6ay3QQSSTv/9IsBH3eI7CI2+W8m9AtQ+KyN5BKPBSK5NjswF59xA3gocbsUpng==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.106.tgz",
+ "integrity": "sha512-WZh6XV8cQ9Fh3IQNX9z87Tv68+sLtfnT51ghMQxceRhfvc5gIaYW+PCppezDDdlPJnWXhybGWNPAl5SHppWb2g==",
"dev": true,
"optional": true
},
"@swc/core-linux-arm64-gnu": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.58.tgz",
- "integrity": "sha512-lSOd73EqFLx0I0f9UJq2wbwjQc+tbXbLznJp89tEZeLOljuMJkF3O22l2Nv6Vet6NBPbTQYiKy6ouibFvOqMag==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.106.tgz",
+ "integrity": "sha512-OSI9VUWPsRrCbUlRQ4KdYqdwV63VYBC5ahSNq+72DXhtRwVbLSFuF7MNsnXgUSMHidxbc0No3/bPPamshqHdsQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-linux-arm64-musl": {
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.106.tgz",
+ "integrity": "sha512-de8AAUOP8D2/tZIpQ399xw+pGGKlR1+l5Jmy4lW7ixarEI4xKkBSF4bS9eXtC1jckmenzrLPiK/5sSbQSf6BWQ==",
"dev": true,
"optional": true
},
"@swc/core-linux-x64-gnu": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.58.tgz",
- "integrity": "sha512-bDU2LiURs4MKXWNNUKxVU1KKCO6lp1ILszkLPYuRAHbbQCtoQUe5JCbFlCqniFOxZOm2NBtZF4a+z5bGFpb0QA==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.106.tgz",
+ "integrity": "sha512-QzFC7+lBSuVBmX5tS2pdM+74voiJcGgIMJ+x9pcjUu3GkDl3ow6WC6ta2WUzlgGopCGNp6IdZaFemKRzjLr3lw==",
"dev": true,
"optional": true
},
"@swc/core-linux-x64-musl": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.58.tgz",
- "integrity": "sha512-wdF8nHrlMI4PUL13PQFo4BMfCr9HL3kNWftiA8i+mJhfp8Z2xfyarOnVkeXmYmQYGPoqSDCsskui6n5PvBLePw==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.106.tgz",
+ "integrity": "sha512-QZ1gFqNiCJefkNMihbmYc7nr5stERyjoQpWgAIN6dzrgMUzRHXHGDRl/p1qsXW2VKos+okSdLwPFEmRT94H+1A==",
+ "dev": true,
+ "optional": true
+ },
+ "@swc/core-win32-arm64-msvc": {
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.106.tgz",
+ "integrity": "sha512-MbuQwk+s43bfBNnAZTKnoQlfo4UPSOsy6t9F15yU4P3rVUuFtcxdZg6CpDnUqNPbojILXujp8z4SSigRYh5cgg==",
"dev": true,
"optional": true
},
"@swc/core-win32-ia32-msvc": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.58.tgz",
- "integrity": "sha512-Qrox0Kz3KQSYnMwAH55DYXzOG+L0PPYQHaQnJCh5rywKDUx2n/Ar5zKkVkEhRf0ehPgKajt0h2BYHsTpqNA9/w==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.106.tgz",
+ "integrity": "sha512-BFxWpcPxsG2LLQZ+8K8ma45rbTckjpPbnvOOhybQ0hEhLgoVzMVPp3RIUGmC+RMZe6DkGSaEQf/Rjn2cbMdQhw==",
"dev": true,
"optional": true
},
"@swc/core-win32-x64-msvc": {
- "version": "1.2.58",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.58.tgz",
- "integrity": "sha512-HPmxovhC7DbNcXLJe5nUmo+4o6Ea2d7oFdli3IvTgDri0IynQaRlfVWIuNnZmEsN7Gl1kW7PUK5WZXPUosMn8A==",
+ "version": "1.2.106",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.106.tgz",
+ "integrity": "sha512-Emn5akqApGXzPsA7ntSXEohL0AH0WjQMHy6mT3MS9Yil42yTJ96dJGf68ejKVptxwg7Iz798mT+J9r1JbAFBgg==",
"dev": true,
"optional": true
},
@@ -3131,15 +3146,6 @@
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
- "lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "requires": {
- "yallist": "^3.0.2"
- }
- },
"lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
@@ -3178,9 +3184,9 @@
}
},
"marked": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.4.tgz",
- "integrity": "sha512-jBo8AOayNaEcvBhNobg6/BLhdsK3NvnKWJg33MAAPbvTWiG4QBn9gpW1+7RssrKu4K1dKlN+0goVQwV41xEfOA==",
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
+ "integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
"dev": true
},
"matcher": {
@@ -3595,15 +3601,6 @@
"mimic-fn": "^2.1.0"
}
},
- "onigasm": {
- "version": "2.2.5",
- "resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.5.tgz",
- "integrity": "sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==",
- "dev": true,
- "requires": {
- "lru-cache": "^5.1.1"
- }
- },
"ora": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz",
@@ -3876,9 +3873,9 @@
"dev": true
},
"prettier": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
- "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
+ "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
"dev": true
},
"pretty-format": {
@@ -4271,13 +4268,13 @@
"dev": true
},
"shiki": {
- "version": "0.9.11",
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.11.tgz",
- "integrity": "sha512-tjruNTLFhU0hruCPoJP0y+B9LKOmcqUhTpxn7pcJB3fa+04gFChuEmxmrUfOJ7ZO6Jd+HwMnDHgY3lv3Tqonuw==",
+ "version": "0.9.15",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.15.tgz",
+ "integrity": "sha512-/Y0z9IzhJ8nD9nbceORCqu6NgT9X6I8Fk8c3SICHI5NbZRLdZYFaB233gwct9sU0vvSypyaL/qaKvzyQGJBZSw==",
"dev": true,
"requires": {
"jsonc-parser": "^3.0.0",
- "onigasm": "^2.2.5",
+ "vscode-oniguruma": "^1.6.1",
"vscode-textmate": "5.2.0"
}
},
@@ -4709,16 +4706,16 @@
}
},
"typedoc": {
- "version": "0.22.4",
- "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.4.tgz",
- "integrity": "sha512-M/a8NnPxq3/iZNNVjzFCK5gu4m//HTJIPbSS0JQVbkHJPP9wyepR12agylWTSqeVZe0xsbidVtO26+PP7iD/jw==",
+ "version": "0.22.10",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.10.tgz",
+ "integrity": "sha512-hQYZ4WtoMZ61wDC6w10kxA42+jclWngdmztNZsDvIz7BMJg7F2xnT+uYsUa7OluyKossdFj9E9Ye4QOZKTy8SA==",
"dev": true,
"requires": {
- "glob": "^7.1.7",
+ "glob": "^7.2.0",
"lunr": "^2.3.9",
- "marked": "^3.0.4",
+ "marked": "^3.0.8",
"minimatch": "^3.0.4",
- "shiki": "^0.9.11"
+ "shiki": "^0.9.12"
},
"dependencies": {
"glob": {
@@ -4738,9 +4735,9 @@
}
},
"typescript": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
- "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
+ "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
"dev": true
},
"typescript-json-schema": {
@@ -4885,6 +4882,11 @@
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
},
+ "v8-compile-cache-lib": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
+ "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA=="
+ },
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -4901,6 +4903,12 @@
"integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA==",
"dev": true
},
+ "vscode-oniguruma": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz",
+ "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==",
+ "dev": true
+ },
"vscode-textmate": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
@@ -5038,12 +5046,6 @@
"integrity": "sha1-le+U+F7MgdAHwmThkKEg8KPIVms=",
"dev": true
},
- "yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true
- },
"yargs": {
"version": "17.2.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz",
diff --git a/package.json b/package.json
index 060a78c4..5e6323f4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ts-node",
- "version": "10.4.0",
+ "version": "10.5.0",
"description": "TypeScript execution environment and REPL for node.js, with source map support",
"main": "dist/index.js",
"exports": {
@@ -23,6 +23,7 @@
"./esm.mjs": "./esm.mjs",
"./esm/transpile-only": "./esm/transpile-only.mjs",
"./esm/transpile-only.mjs": "./esm/transpile-only.mjs",
+ "./transpilers/swc": "./transpilers/swc.js",
"./transpilers/swc-experimental": "./transpilers/swc-experimental.js",
"./node10/tsconfig.json": "./node10/tsconfig.json",
"./node12/tsconfig.json": "./node12/tsconfig.json",
@@ -125,15 +126,15 @@
"lodash": "^4.17.15",
"ntypescript": "^1.201507091536.1",
"nyc": "^15.0.1",
- "prettier": "^2.2.1",
+ "prettier": "^2.5.1",
"proper-lockfile": "^4.1.2",
"proxyquire": "^2.0.0",
"react": "^16.14.0",
"rimraf": "^3.0.0",
"semver": "^7.1.3",
"throat": "^6.0.1",
- "typedoc": "^0.22.4",
- "typescript": "4.4.3",
+ "typedoc": "^0.22.10",
+ "typescript": "4.5.2",
"typescript-json-schema": "^0.51.0",
"util.promisify": "^1.0.1"
},
@@ -163,6 +164,7 @@
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.0",
"yn": "3.1.1"
},
"prettier": {
diff --git a/scripts/create-merged-schema.ts b/scripts/create-merged-schema.ts
index 4016fd33..c380deee 100755
--- a/scripts/create-merged-schema.ts
+++ b/scripts/create-merged-schema.ts
@@ -15,30 +15,53 @@ async function main() {
const schemastoreSchema = await getSchemastoreSchema();
/** ts-node schema auto-generated from ts-node source code */
- const typescriptNodeSchema = require('../tsconfig.schema.json');
+ const originalTsNodeSchema = require('../tsconfig.schema.json');
+ // Apply this prefix to the names of all ts-node-generated definitions
+ const tsnodeDefinitionPrefix = 'tsNode';
+ let tsNodeSchema: any = JSON.parse(
+ JSON.stringify(originalTsNodeSchema).replace(
+ /#\/definitions\//g,
+ `#/definitions/${tsnodeDefinitionPrefix}`
+ )
+ );
+ tsNodeSchema.definitions = Object.fromEntries(
+ Object.entries(tsNodeSchema.definitions).map(([key, value]) => [
+ `${tsnodeDefinitionPrefix}${key}`,
+ value,
+ ])
+ );
+ // console.dir(tsNodeSchema, {
+ // depth: Infinity
+ // });
/** Patch ts-node stuff into the schemastore definition. */
const mergedSchema = {
...schemastoreSchema,
definitions: {
- ...schemastoreSchema.definitions,
+ ...Object.fromEntries(
+ Object.entries(schemastoreSchema.definitions).filter(
+ ([key]) => !key.startsWith(tsnodeDefinitionPrefix)
+ )
+ ),
+ ...tsNodeSchema.definitions,
+ tsNodeTsConfigOptions: undefined,
+ tsNodeTsConfigSchema: undefined,
tsNodeDefinition: {
properties: {
'ts-node': {
- ...typescriptNodeSchema.definitions.TsConfigOptions,
+ ...tsNodeSchema.definitions.tsNodeTsConfigOptions,
description:
- typescriptNodeSchema.definitions.TsConfigSchema.properties[
+ tsNodeSchema.definitions.tsNodeTsConfigSchema.properties[
'ts-node'
].description,
properties: {
- ...typescriptNodeSchema.definitions.TsConfigOptions.properties,
+ ...tsNodeSchema.definitions.tsNodeTsConfigOptions.properties,
compilerOptions: {
- ...typescriptNodeSchema.definitions.TsConfigOptions.properties
+ ...tsNodeSchema.definitions.tsNodeTsConfigOptions.properties
.compilerOptions,
allOf: [
{
- $ref:
- '#/definitions/compilerOptionsDefinition/properties/compilerOptions',
+ $ref: '#/definitions/compilerOptionsDefinition/properties/compilerOptions',
},
],
},
@@ -47,14 +70,16 @@ async function main() {
},
},
},
- allOf: [
- // Splice into the allOf array at a spot that looks good. Does not affect
- // behavior of the schema, but looks nicer if we want to submit as a PR to schemastore.
- ...schemastoreSchema.allOf.slice(0, 4),
- { $ref: '#/definitions/tsNodeDefinition' },
- ...schemastoreSchema.allOf.slice(4),
- ],
};
+ // Splice into the allOf array at a spot that looks good. Does not affect
+ // behavior of the schema, but looks nicer if we want to submit as a PR to schemastore.
+ mergedSchema.allOf = mergedSchema.allOf.filter(
+ (item: any) => !item.$ref?.includes('tsNode')
+ );
+ mergedSchema.allOf.splice(mergedSchema.allOf.length - 1, 0, {
+ $ref: '#/definitions/tsNodeDefinition',
+ });
+
writeFileSync(
resolve(__dirname, '../tsconfig.schemastore-schema.json'),
JSON.stringify(mergedSchema, null, 2)
@@ -62,9 +87,7 @@ async function main() {
}
export async function getSchemastoreSchema() {
- const {
- data: schemastoreSchema,
- } = await axios.get(
+ const { data: schemastoreSchema } = await axios.get(
'https://schemastore.azurewebsites.net/schemas/json/tsconfig.json',
{ responseType: 'json' }
);
diff --git a/src/bin-cwd.ts b/src/bin-cwd.ts
index bd2f4483..3a669765 100644
--- a/src/bin-cwd.ts
+++ b/src/bin-cwd.ts
@@ -2,4 +2,4 @@
import { main } from './bin';
-main(undefined, { '--cwd-mode': true });
+main(undefined, { '--cwdMode': true });
diff --git a/src/bin-script-deprecated.ts b/src/bin-script-deprecated.ts
index 07112d85..a4dcdb91 100644
--- a/src/bin-script-deprecated.ts
+++ b/src/bin-script-deprecated.ts
@@ -7,4 +7,4 @@ console.warn(
'Please use ts-node-script instead'
);
-main(undefined, { '--script-mode': true });
+main(undefined, { '--scriptMode': true });
diff --git a/src/bin-script.ts b/src/bin-script.ts
index 78f9ab32..7c28aa1f 100644
--- a/src/bin-script.ts
+++ b/src/bin-script.ts
@@ -2,4 +2,4 @@
import { main } from './bin';
-main(undefined, { '--script-mode': true });
+main(undefined, { '--scriptMode': true });
diff --git a/src/bin-transpile.ts b/src/bin-transpile.ts
index 8eb81814..4dce851c 100644
--- a/src/bin-transpile.ts
+++ b/src/bin-transpile.ts
@@ -2,4 +2,4 @@
import { main } from './bin';
-main(undefined, { '--transpile-only': true });
+main(undefined, { '--transpileOnly': true });
diff --git a/src/bin.ts b/src/bin.ts
index 95c7f932..0194f3a3 100644
--- a/src/bin.ts
+++ b/src/bin.ts
@@ -1,6 +1,6 @@
#!/usr/bin/env node
-import { join, resolve, dirname, parse as parsePath } from 'path';
+import { join, resolve, dirname, parse as parsePath, relative } from 'path';
import { inspect } from 'util';
import Module = require('module');
import arg = require('arg');
@@ -17,7 +17,7 @@ import {
STDIN_NAME,
REPL_FILENAME,
} from './repl';
-import { VERSION, TSError, register } from './index';
+import { VERSION, TSError, register, versionGteLt } from './index';
import type { TSInternal } from './ts-compiler-types';
import { addBuiltinLibsToObject } from '../dist-raw/node-cjs-helpers';
@@ -28,6 +28,20 @@ export function main(
argv: string[] = process.argv.slice(2),
entrypointArgs: Record = {}
) {
+ // HACK: technically, this function is not marked @internal so it's possible
+ // that libraries in the wild are doing `require('ts-node/dist/bin').main({'--transpile-only': true})`
+ // We can mark this function @internal in next major release.
+ // For now, rewrite args to avoid a breaking change.
+ entrypointArgs = { ...entrypointArgs };
+ for (const key of Object.keys(entrypointArgs)) {
+ entrypointArgs[
+ key.replace(
+ /([a-z])-([a-z])/g,
+ (_$0, $1, $2: string) => `${$1}${$2.toUpperCase()}`
+ )
+ ] = entrypointArgs[key];
+ }
+
const args = {
...entrypointArgs,
...arg(
@@ -40,32 +54,33 @@ export function main(
// CLI options.
'--help': Boolean,
- '--cwd-mode': Boolean,
- '--script-mode': Boolean,
+ '--cwdMode': Boolean,
+ '--scriptMode': Boolean,
'--version': arg.COUNT,
- '--show-config': Boolean,
+ '--showConfig': Boolean,
// Project options.
'--cwd': String,
'--files': Boolean,
'--compiler': String,
- '--compiler-options': parse,
+ '--compilerOptions': parse,
'--project': String,
- '--ignore-diagnostics': [String],
+ '--ignoreDiagnostics': [String],
'--ignore': [String],
- '--transpile-only': Boolean,
+ '--transpileOnly': Boolean,
'--transpiler': String,
- '--type-check': Boolean,
- '--compiler-host': Boolean,
+ '--swc': Boolean,
+ '--typeCheck': Boolean,
+ '--compilerHost': Boolean,
'--pretty': Boolean,
- '--skip-project': Boolean,
- '--skip-ignore': Boolean,
- '--prefer-ts-exts': Boolean,
- '--log-error': Boolean,
+ '--skipProject': Boolean,
+ '--skipIgnore': Boolean,
+ '--preferTsExts': Boolean,
+ '--logError': Boolean,
'--emit': Boolean,
'--scope': Boolean,
- '--scope-dir': String,
- '--no-experimental-repl-await': Boolean,
+ '--scopeDir': String,
+ '--noExperimentalReplAwait': Boolean,
// Aliases.
'-e': '--eval',
@@ -75,16 +90,30 @@ export function main(
'-h': '--help',
'-s': '--script-mode',
'-v': '--version',
- '-T': '--transpile-only',
- '-H': '--compiler-host',
+ '-T': '--transpileOnly',
+ '-H': '--compilerHost',
'-I': '--ignore',
'-P': '--project',
'-C': '--compiler',
- '-D': '--ignore-diagnostics',
- '-O': '--compiler-options',
+ '-D': '--ignoreDiagnostics',
+ '-O': '--compilerOptions',
'--dir': '--cwd',
- '--showConfig': '--show-config',
- '--scopeDir': '--scope-dir',
+
+ // Support both tsc-style camelCase and node-style hypen-case for *all* flags
+ '--cwd-mode': '--cwdMode',
+ '--script-mode': '--scriptMode',
+ '--show-config': '--showConfig',
+ '--compiler-options': '--compilerOptions',
+ '--ignore-diagnostics': '--ignoreDiagnostics',
+ '--transpile-only': '--transpileOnly',
+ '--type-check': '--typeCheck',
+ '--compiler-host': '--compilerHost',
+ '--skip-project': '--skipProject',
+ '--skip-ignore': '--skipIgnore',
+ '--prefer-ts-exts': '--preferTsExts',
+ '--log-error': '--logError',
+ '--scope-dir': '--scopeDir',
+ '--no-experimental-repl-await': '--noExperimentalReplAwait',
},
{
argv,
@@ -99,72 +128,74 @@ export function main(
const {
'--cwd': cwdArg,
'--help': help = false,
- '--script-mode': scriptMode,
- '--cwd-mode': cwdMode,
+ '--scriptMode': scriptMode,
+ '--cwdMode': cwdMode,
'--version': version = 0,
- '--show-config': showConfig,
+ '--showConfig': showConfig,
'--require': argsRequire = [],
'--eval': code = undefined,
'--print': print = false,
'--interactive': interactive = false,
'--files': files,
'--compiler': compiler,
- '--compiler-options': compilerOptions,
+ '--compilerOptions': compilerOptions,
'--project': project,
- '--ignore-diagnostics': ignoreDiagnostics,
+ '--ignoreDiagnostics': ignoreDiagnostics,
'--ignore': ignore,
- '--transpile-only': transpileOnly,
- '--type-check': typeCheck,
+ '--transpileOnly': transpileOnly,
+ '--typeCheck': typeCheck,
'--transpiler': transpiler,
- '--compiler-host': compilerHost,
+ '--swc': swc,
+ '--compilerHost': compilerHost,
'--pretty': pretty,
- '--skip-project': skipProject,
- '--skip-ignore': skipIgnore,
- '--prefer-ts-exts': preferTsExts,
- '--log-error': logError,
+ '--skipProject': skipProject,
+ '--skipIgnore': skipIgnore,
+ '--preferTsExts': preferTsExts,
+ '--logError': logError,
'--emit': emit,
'--scope': scope = undefined,
- '--scope-dir': scopeDir = undefined,
- '--no-experimental-repl-await': noExperimentalReplAwait,
+ '--scopeDir': scopeDir = undefined,
+ '--noExperimentalReplAwait': noExperimentalReplAwait,
} = args;
if (help) {
console.log(`
- Usage: ts-node [options] [ -e script | script.ts ] [arguments]
-
- Options:
-
- -e, --eval [code] Evaluate code
- -p, --print Print result of \`--eval\`
- -r, --require [path] Require a node module before execution
- -i, --interactive Opens the REPL even if stdin does not appear to be a terminal
-
- -h, --help Print CLI usage
- -v, --version Print module version information
- --cwd-mode Use current directory instead of for config resolution
- --show-config Print resolved configuration and exit
-
- -T, --transpile-only Use TypeScript's faster \`transpileModule\` or a third-party transpiler
- -H, --compiler-host Use TypeScript's compiler host API
- -I, --ignore [pattern] Override the path patterns to skip compilation
- -P, --project [path] Path to TypeScript JSON project file
- -C, --compiler [name] Specify a custom TypeScript compiler
- --transpiler [name] Specify a third-party, non-typechecking transpiler
- -D, --ignore-diagnostics [code] Ignore TypeScript warnings by diagnostic code
- -O, --compiler-options [opts] JSON object to merge with compiler options
-
- --cwd Behave as if invoked within this working directory.
- --files Load \`files\`, \`include\` and \`exclude\` from \`tsconfig.json\` on startup
- --pretty Use pretty diagnostic formatter (usually enabled by default)
- --skip-project Skip reading \`tsconfig.json\`
- --skip-ignore Skip \`--ignore\` checks
- --emit Emit output files into \`.ts-node\` directory
- --scope Scope compiler to files within \`scopeDir\`. Anything outside this directory is ignored.
- --scope-dir Directory for \`--scope\`
- --prefer-ts-exts Prefer importing TypeScript files over JavaScript files
- --log-error Logs TypeScript errors to stderr instead of throwing exceptions
- --no-experimental-repl-await Disable top-level await in REPL. Equivalent to node's --no-experimental-repl-await
- `);
+Usage: ts-node [options] [ -e script | script.ts ] [arguments]
+
+Options:
+
+ -e, --eval [code] Evaluate code
+ -p, --print Print result of \`--eval\`
+ -r, --require [path] Require a node module before execution
+ -i, --interactive Opens the REPL even if stdin does not appear to be a terminal
+
+ -h, --help Print CLI usage
+ -v, --version Print module version information
+ --cwdMode Use current directory instead of for config resolution
+ --showConfig Print resolved configuration and exit
+
+ -T, --transpileOnly Use TypeScript's faster \`transpileModule\` or a third-party transpiler
+ --swc Use the swc transpiler
+ -H, --compilerHost Use TypeScript's compiler host API
+ -I, --ignore [pattern] Override the path patterns to skip compilation
+ -P, --project [path] Path to TypeScript JSON project file
+ -C, --compiler [name] Specify a custom TypeScript compiler
+ --transpiler [name] Specify a third-party, non-typechecking transpiler
+ -D, --ignoreDiagnostics [code] Ignore TypeScript warnings by diagnostic code
+ -O, --compilerOptions [opts] JSON object to merge with compiler options
+
+ --cwd Behave as if invoked within this working directory.
+ --files Load \`files\`, \`include\` and \`exclude\` from \`tsconfig.json\` on startup
+ --pretty Use pretty diagnostic formatter (usually enabled by default)
+ --skipProject Skip reading \`tsconfig.json\`
+ --skipIgnore Skip \`--ignore\` checks
+ --emit Emit output files into \`.ts-node\` directory
+ --scope Scope compiler to files within \`scopeDir\`. Anything outside this directory is ignored.
+ --scopeDir Directory for \`--scope\`
+ --preferTsExts Prefer importing TypeScript files over JavaScript files
+ --logError Logs TypeScript errors to stderr instead of throwing exceptions
+ --noExperimentalReplAwait Disable top-level await in REPL. Equivalent to node's --no-experimental-repl-await
+`);
process.exit(0);
}
@@ -256,6 +287,7 @@ export function main(
experimentalReplAwait: noExperimentalReplAwait ? false : undefined,
typeCheck,
transpiler,
+ swc,
compilerHost,
ignore,
preferTsExts,
@@ -280,24 +312,50 @@ export function main(
stdinStuff?.repl.setService(service);
// Output project information.
- if (version >= 2) {
+ if (version === 2) {
console.log(`ts-node v${VERSION}`);
console.log(`node ${process.version}`);
console.log(`compiler v${service.ts.version}`);
process.exit(0);
}
+ if (version >= 3) {
+ console.log(`ts-node v${VERSION} ${dirname(__dirname)}`);
+ console.log(`node ${process.version}`);
+ console.log(
+ `compiler v${service.ts.version} ${service.compilerPath ?? ''}`
+ );
+ process.exit(0);
+ }
if (showConfig) {
- const ts = (service.ts as any) as TSInternal;
+ const ts = service.ts as any as TSInternal;
if (typeof ts.convertToTSConfig !== 'function') {
console.error(
- 'Error: --show-config requires a typescript versions >=3.2 that support --showConfig'
+ 'Error: --showConfig requires a typescript versions >=3.2 that support --showConfig'
);
process.exit(1);
}
+ let moduleTypes = undefined;
+ if (service.options.moduleTypes) {
+ // Assumption: this codepath requires CLI invocation, so moduleTypes must have come from a tsconfig, not API.
+ const showRelativeTo = dirname(service.configFilePath!);
+ moduleTypes = {} as Record;
+ for (const [key, value] of Object.entries(service.options.moduleTypes)) {
+ moduleTypes[
+ relative(
+ showRelativeTo,
+ resolve(service.options.optionBasePaths?.moduleTypes!, key)
+ )
+ ] = value;
+ }
+ }
const json = {
['ts-node']: {
...service.options,
+ require: service.options.require?.length
+ ? service.options.require
+ : undefined,
+ moduleTypes,
optionBasePaths: undefined,
compilerOptions: undefined,
project: service.configFilePath ?? service.options.project,
@@ -423,12 +481,11 @@ let guaranteedNonexistentDirectorySuffix = 0;
* https://stackoverflow.com/questions/59865584/how-to-invalidate-cached-require-resolve-results
*/
function requireResolveNonCached(absoluteModuleSpecifier: string) {
- // node 10 and 11 fallback: The trick below triggers a node 10 & 11 bug
- // On those node versions, pollute the require cache instead. This is a deliberate
- // ts-node limitation that will *rarely* manifest, and will not matter once node 10
- // is end-of-life'd on 2021-04-30
- const isSupportedNodeVersion =
- parseInt(process.versions.node.split('.')[0], 10) >= 12;
+ // node <= 12.1.x fallback: The trick below triggers a node bug on old versions.
+ // On these old versions, pollute the require cache instead. This is a deliberate
+ // ts-node limitation that will *rarely* manifest, and will not matter once node 12
+ // is end-of-life'd on 2022-04-30
+ const isSupportedNodeVersion = versionGteLt(process.versions.node, '12.2.0');
if (!isSupportedNodeVersion) return require.resolve(absoluteModuleSpecifier);
const { dir, base } = parsePath(absoluteModuleSpecifier);
diff --git a/src/cjs-resolve-filename-hook.ts b/src/cjs-resolve-filename-hook.ts
new file mode 100644
index 00000000..9c6f66b0
--- /dev/null
+++ b/src/cjs-resolve-filename-hook.ts
@@ -0,0 +1,59 @@
+import type Module = require('module');
+import type { Service } from '.';
+
+/** @internal */
+export type ModuleConstructorWithInternals = typeof Module & {
+ _resolveFilename(
+ request: string,
+ parent?: Module,
+ isMain?: boolean,
+ options?: ModuleResolveFilenameOptions,
+ ...rest: any[]
+ ): string;
+ _preloadModules(requests?: string[]): void;
+};
+
+interface ModuleResolveFilenameOptions {
+ paths?: Array;
+}
+
+/**
+ * @internal
+ */
+export function installCommonjsResolveHookIfNecessary(tsNodeService: Service) {
+ const Module = require('module') as ModuleConstructorWithInternals;
+ const originalResolveFilename = Module._resolveFilename;
+ const shouldInstallHook = tsNodeService.options.experimentalResolverFeatures;
+ if (shouldInstallHook) {
+ Module._resolveFilename = _resolveFilename;
+ }
+ function _resolveFilename(
+ this: any,
+ request: string,
+ parent?: Module,
+ isMain?: boolean,
+ options?: ModuleResolveFilenameOptions,
+ ...rest: any[]
+ ): string {
+ if (!tsNodeService.enabled())
+ return originalResolveFilename.call(
+ this,
+ request,
+ parent,
+ isMain,
+ options,
+ ...rest
+ );
+
+ // This is a stub to support other pull requests that will be merged in the near future
+ // Right now, it does nothing.
+ return originalResolveFilename.call(
+ this,
+ request,
+ parent,
+ isMain,
+ options,
+ ...rest
+ );
+ }
+}
diff --git a/src/configuration.ts b/src/configuration.ts
index a970b49c..ff38ddd4 100644
--- a/src/configuration.ts
+++ b/src/configuration.ts
@@ -10,7 +10,7 @@ import {
import type { TSInternal } from './ts-compiler-types';
import { createTsInternals } from './ts-internals';
import { getDefaultTsconfigJsonForNodeVersion } from './tsconfigs';
-import { assign, createRequire, trace } from './util';
+import { assign, createProjectLocalResolveHelper } from './util';
/**
* TypeScript compiler option values required by `ts-node` which cannot be overridden.
@@ -94,6 +94,7 @@ export function readConfig(
readFile = ts.sys.readFile,
skipProject = DEFAULTS.skipProject,
project = DEFAULTS.project,
+ tsTrace = DEFAULTS.tsTrace,
} = rawApiOptions;
// Read project configuration when available.
@@ -137,11 +138,11 @@ export function readConfig(
readDirectory: ts.sys.readDirectory,
readFile,
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
- trace,
+ trace: tsTrace,
},
bp,
errors,
- ((ts as unknown) as TSInternal).createCompilerDiagnostic
+ (ts as unknown as TSInternal).createCompilerDiagnostic
);
if (errors.length) {
return {
@@ -164,15 +165,18 @@ export function readConfig(
const optionBasePaths: OptionBasePaths = {};
for (let i = configChain.length - 1; i >= 0; i--) {
const { config, basePath, configPath } = configChain[i];
- const options = filterRecognizedTsConfigTsNodeOptions(config['ts-node'])
- .recognized;
+ const options = filterRecognizedTsConfigTsNodeOptions(
+ config['ts-node']
+ ).recognized;
// Some options are relative to the config file, so must be converted to absolute paths here
if (options.require) {
// Modules are found relative to the tsconfig file, not the `dir` option
- const tsconfigRelativeRequire = createRequire(configPath);
+ const tsconfigRelativeResolver = createProjectLocalResolveHelper(
+ dirname(configPath)
+ );
options.require = options.require.map((path: string) =>
- tsconfigRelativeRequire.resolve(path)
+ tsconfigRelativeResolver(path, false)
);
}
if (options.scopeDir) {
@@ -183,6 +187,12 @@ export function readConfig(
if (options.moduleTypes) {
optionBasePaths.moduleTypes = basePath;
}
+ if (options.transpiler != null) {
+ optionBasePaths.transpiler = basePath;
+ }
+ if (options.compiler != null) {
+ optionBasePaths.compiler = basePath;
+ }
assign(tsNodeOptionsFromTsconfig, options);
}
@@ -249,9 +259,7 @@ export function readConfig(
* Given the raw "ts-node" sub-object from a tsconfig, return an object with only the properties
* recognized by "ts-node"
*/
-function filterRecognizedTsConfigTsNodeOptions(
- jsonObject: any
-): {
+function filterRecognizedTsConfigTsNodeOptions(jsonObject: any): {
recognized: TsConfigOptions;
unrecognized: any;
} {
@@ -276,6 +284,8 @@ function filterRecognizedTsConfigTsNodeOptions(
scopeDir,
moduleTypes,
experimentalReplAwait,
+ swc,
+ experimentalResolverFeatures,
...unrecognized
} = jsonObject as TsConfigOptions;
const filteredTsConfigOptions = {
@@ -298,9 +308,13 @@ function filterRecognizedTsConfigTsNodeOptions(
scope,
scopeDir,
moduleTypes,
+ swc,
+ experimentalResolverFeatures,
};
// Use the typechecker to make sure this implementation has the correct set of properties
- const catchExtraneousProps: keyof TsConfigOptions = (null as any) as keyof typeof filteredTsConfigOptions;
- const catchMissingProps: keyof typeof filteredTsConfigOptions = (null as any) as keyof TsConfigOptions;
+ const catchExtraneousProps: keyof TsConfigOptions =
+ null as any as keyof typeof filteredTsConfigOptions;
+ const catchMissingProps: keyof typeof filteredTsConfigOptions =
+ null as any as keyof TsConfigOptions;
return { recognized: filteredTsConfigOptions, unrecognized };
}
diff --git a/src/esm.ts b/src/esm.ts
index c83fd22c..b27be4a0 100644
--- a/src/esm.ts
+++ b/src/esm.ts
@@ -62,17 +62,28 @@ export interface NodeLoaderHooksAPI2 {
export namespace NodeLoaderHooksAPI2 {
export type ResolveHook = (
specifier: string,
- context: { parentURL: string },
+ context: {
+ conditions?: NodeImportConditions;
+ importAssertions?: NodeImportAssertions;
+ parentURL: string;
+ },
defaultResolve: ResolveHook
) => Promise<{ url: string }>;
export type LoadHook = (
url: string,
- context: { format: NodeLoaderHooksFormat | null | undefined },
+ context: {
+ format: NodeLoaderHooksFormat | null | undefined;
+ importAssertions?: NodeImportAssertions;
+ },
defaultLoad: NodeLoaderHooksAPI2['load']
) => Promise<{
format: NodeLoaderHooksFormat;
source: string | Buffer | undefined;
}>;
+ export type NodeImportConditions = unknown;
+ export interface NodeImportAssertions {
+ type?: 'json';
+ }
}
export type NodeLoaderHooksFormat =
@@ -159,7 +170,10 @@ export function createEsmHooks(tsNodeService: Service) {
// `load` from new loader hook API (See description at the top of this file)
async function load(
url: string,
- context: { format: NodeLoaderHooksFormat | null | undefined },
+ context: {
+ format: NodeLoaderHooksFormat | null | undefined;
+ importAssertions?: NodeLoaderHooksAPI2.NodeImportAssertions;
+ },
defaultLoad: typeof load
): Promise<{
format: NodeLoaderHooksFormat;
@@ -176,7 +190,10 @@ export function createEsmHooks(tsNodeService: Service) {
// Call the new defaultLoad() to get the source
const { source: rawSource } = await defaultLoad(
url,
- { format },
+ {
+ ...context,
+ format,
+ },
defaultLoad
);
diff --git a/src/index.ts b/src/index.ts
index 977922ea..0ffeea3f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,9 +10,13 @@ import type * as _ts from 'typescript';
import type { Transpiler, TranspilerFactory } from './transpilers/types';
import {
assign,
+ attemptRequireWithV8CompileCache,
cachedLookup,
+ createProjectLocalResolveHelper,
+ getBasePathForProjectLocalDependencyResolution,
normalizeSlashes,
parse,
+ ProjectLocalResolveHelper,
split,
yn,
} from './util';
@@ -24,6 +28,10 @@ import {
} from './module-type-classifier';
import { createResolverFunctions } from './resolver-functions';
import type { createEsmHooks as createEsmHooksFn } from './esm';
+import {
+ installCommonjsResolveHookIfNecessary,
+ ModuleConstructorWithInternals,
+} from './cjs-resolve-filename-hook';
export { TSCommon };
export {
@@ -256,6 +264,14 @@ export interface CreateOptions {
* Specify a custom transpiler for use with transpileOnly
*/
transpiler?: string | [string, object];
+ /**
+ * Transpile with swc instead of the TypeScript compiler, and skip typechecking.
+ *
+ * Equivalent to setting both `transpileOnly: true` and `transpiler: 'ts-node/transpilers/swc'`
+ *
+ * For complete instructions: https://typestrong.org/ts-node/docs/transpilers
+ */
+ swc?: boolean;
/**
* Paths which should not be compiled.
*
@@ -338,7 +354,7 @@ export interface CreateOptions {
* `package` overrides either of the above to default behavior, which obeys package.json "type" and
* tsconfig.json "module" options.
*/
- moduleTypes?: Record;
+ moduleTypes?: ModuleTypes;
/**
* @internal
* Set by our configuration loader whenever a config file contains options that
@@ -347,11 +363,21 @@ export interface CreateOptions {
* the configuration loader, so it is *not* necessary for their source to be set here.
*/
optionBasePaths?: OptionBasePaths;
+ /**
+ * A function to collect trace messages from the TypeScript compiler, for example when `traceResolution` is enabled.
+ *
+ * @default console.log
+ */
+ tsTrace?: (str: string) => void;
}
+export type ModuleTypes = Record;
+
/** @internal */
export interface OptionBasePaths {
moduleTypes?: string;
+ transpiler?: string;
+ compiler?: string;
}
/**
@@ -366,6 +392,15 @@ export interface RegisterOptions extends CreateOptions {
* @default false
*/
preferTsExts?: boolean;
+
+ /**
+ * Enable experimental features that re-map imports and require calls to support:
+ * `baseUrl`, `paths`, `rootDirs`, `.js` to `.ts` file extension mappings,
+ * `outDir` to `rootDir` mappings for composite projects and monorepos.
+ *
+ * For details, see https://github.com/TypeStrong/ts-node/issues/1514
+ */
+ experimentalResolverFeatures?: boolean;
}
/**
@@ -383,6 +418,7 @@ export interface TsConfigOptions
| 'cwd'
| 'projectSearchDir'
| 'optionBasePaths'
+ | 'tsTrace'
> {}
/**
@@ -418,6 +454,7 @@ export const DEFAULTS: RegisterOptions = {
compilerHost: yn(env.TS_NODE_COMPILER_HOST),
logError: yn(env.TS_NODE_LOG_ERROR),
experimentalReplAwait: yn(env.TS_NODE_EXPERIMENTAL_REPL_AWAIT) ?? undefined,
+ tsTrace: console.log.bind(console),
};
/**
@@ -425,9 +462,15 @@ export const DEFAULTS: RegisterOptions = {
*/
export class TSError extends BaseError {
name = 'TSError';
+ diagnosticText!: string;
- constructor(public diagnosticText: string, public diagnosticCodes: number[]) {
+ constructor(diagnosticText: string, public diagnosticCodes: number[]) {
super(`⨯ Unable to compile TypeScript:\n${diagnosticText}`);
+ Object.defineProperty(this, 'diagnosticText', {
+ configurable: true,
+ writable: true,
+ value: diagnosticText,
+ });
}
/**
@@ -447,6 +490,8 @@ export interface Service {
/** @internal */
[TS_NODE_SERVICE_BRAND]: true;
ts: TSCommon;
+ /** @internal */
+ compilerPath: string;
config: _ts.ParsedCommandLine;
options: RegisterOptions;
enabled(enabled?: boolean): boolean;
@@ -465,6 +510,10 @@ export interface Service {
installSourceMapSupport(): void;
/** @internal */
enableExperimentalEsmLoaderInterop(): void;
+ /** @internal */
+ transpileOnly: boolean;
+ /** @internal */
+ projectLocalResolveHelper: ProjectLocalResolveHelper;
}
/**
@@ -529,8 +578,12 @@ export function register(
originalJsHandler
);
+ installCommonjsResolveHookIfNecessary(service);
+
// Require specified modules before start-up.
- (Module as any)._preloadModules(service.options.require);
+ (Module as ModuleConstructorWithInternals)._preloadModules(
+ service.options.require
+ );
return service;
}
@@ -549,26 +602,27 @@ export function create(rawOptions: CreateOptions = {}): Service {
* be changed by the tsconfig, so we have to do this twice.
*/
function loadCompiler(name: string | undefined, relativeToPath: string) {
- const compiler = require.resolve(name || 'typescript', {
- paths: [relativeToPath, __dirname],
- });
- const ts: typeof _ts = require(compiler);
- return { compiler, ts };
+ const projectLocalResolveHelper =
+ createProjectLocalResolveHelper(relativeToPath);
+ const compiler = projectLocalResolveHelper(name || 'typescript', true);
+ const ts: typeof _ts = attemptRequireWithV8CompileCache(require, compiler);
+ return { compiler, ts, projectLocalResolveHelper };
}
// Compute minimum options to read the config file.
- let { compiler, ts } = loadCompiler(
+ let { compiler, ts, projectLocalResolveHelper } = loadCompiler(
compilerName,
- rawOptions.projectSearchDir ?? rawOptions.project ?? cwd
+ getBasePathForProjectLocalDependencyResolution(
+ undefined,
+ rawOptions.projectSearchDir,
+ rawOptions.project,
+ cwd
+ )
);
// Read config file and merge new options between env and CLI options.
- const {
- configFilePath,
- config,
- tsNodeOptionsFromTsconfig,
- optionBasePaths,
- } = readConfig(cwd, ts, rawOptions);
+ const { configFilePath, config, tsNodeOptionsFromTsconfig, optionBasePaths } =
+ readConfig(cwd, ts, rawOptions);
const options = assign(
{},
DEFAULTS,
@@ -581,6 +635,21 @@ export function create(rawOptions: CreateOptions = {}): Service {
...(rawOptions.require || []),
];
+ // Re-load the compiler in case it has changed.
+ // Compiler is loaded relative to tsconfig.json, so tsconfig discovery may cause us to load a
+ // different compiler than we did above, even if the name has not changed.
+ if (configFilePath) {
+ ({ compiler, ts, projectLocalResolveHelper } = loadCompiler(
+ options.compiler,
+ getBasePathForProjectLocalDependencyResolution(
+ configFilePath,
+ rawOptions.projectSearchDir,
+ rawOptions.project,
+ cwd
+ )
+ ));
+ }
+
// Experimental REPL await is not compatible targets lower than ES2018
const targetSupportsTla = config.options.target! >= ts.ScriptTarget.ES2018;
if (options.experimentalReplAwait === true && !targetSupportsTla) {
@@ -601,18 +670,33 @@ export function create(rawOptions: CreateOptions = {}): Service {
tsVersionSupportsTla &&
targetSupportsTla;
- // Re-load the compiler in case it has changed.
- // Compiler is loaded relative to tsconfig.json, so tsconfig discovery may cause us to load a
- // different compiler than we did above, even if the name has not changed.
- if (configFilePath) {
- ({ compiler, ts } = loadCompiler(options.compiler, configFilePath));
+ // swc implies two other options
+ // typeCheck option was implemented specifically to allow overriding tsconfig transpileOnly from the command-line
+ // So we should allow using typeCheck to override swc
+ if (options.swc && !options.typeCheck) {
+ if (options.transpileOnly === false) {
+ throw new Error(
+ "Cannot enable 'swc' option with 'transpileOnly: false'. 'swc' implies 'transpileOnly'."
+ );
+ }
+ if (options.transpiler) {
+ throw new Error(
+ "Cannot specify both 'swc' and 'transpiler' options. 'swc' uses the built-in swc transpiler."
+ );
+ }
}
const readFile = options.readFile || ts.sys.readFile;
const fileExists = options.fileExists || ts.sys.fileExists;
// typeCheck can override transpileOnly, useful for CLI flag to override config file
const transpileOnly =
- options.transpileOnly === true && options.typeCheck !== true;
+ (options.transpileOnly === true || options.swc === true) &&
+ options.typeCheck !== true;
+ const transpiler = options.transpiler
+ ? options.transpiler
+ : options.swc
+ ? require.resolve('./transpilers/swc.js')
+ : undefined;
const transformers = options.transformers || undefined;
const diagnosticFilters: Array = [
{
@@ -668,25 +752,19 @@ export function create(rawOptions: CreateOptions = {}): Service {
);
}
let customTranspiler: Transpiler | undefined = undefined;
- if (options.transpiler) {
+ if (transpiler) {
if (!transpileOnly)
throw new Error(
'Custom transpiler can only be used when transpileOnly is enabled.'
);
const transpilerName =
- typeof options.transpiler === 'string'
- ? options.transpiler
- : options.transpiler[0];
+ typeof transpiler === 'string' ? transpiler : transpiler[0];
const transpilerOptions =
- typeof options.transpiler === 'string' ? {} : options.transpiler[1] ?? {};
- // TODO mimic fixed resolution logic from loadCompiler main
- // TODO refactor into a more generic "resolve dep relative to project" helper
- const transpilerPath = require.resolve(transpilerName, {
- paths: [cwd, __dirname],
- });
+ typeof transpiler === 'string' ? {} : transpiler[1] ?? {};
+ const transpilerPath = projectLocalResolveHelper(transpilerName, true);
const transpilerFactory: TranspilerFactory = require(transpilerPath).create;
customTranspiler = transpilerFactory({
- service: { options, config },
+ service: { options, config, projectLocalResolveHelper },
...transpilerOptions,
});
}
@@ -783,9 +861,9 @@ export function create(rawOptions: CreateOptions = {}): Service {
_position: number
) => TypeInfo;
- const getCanonicalFileName = ((ts as unknown) as TSInternal).createGetCanonicalFileName(
- ts.sys.useCaseSensitiveFileNames
- );
+ const getCanonicalFileName = (
+ ts as unknown as TSInternal
+ ).createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames);
const moduleTypeClassifier = createModuleTypeClassifier({
basePath: options.optionBasePaths?.moduleTypes,
@@ -857,6 +935,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
getCompilationSettings: () => config.options,
getDefaultLibFileName: () => ts.getDefaultLibFilePath(config.options),
getCustomTransformers: getCustomTransformers,
+ trace: options.tsTrace,
};
const {
resolveModuleNames,
@@ -865,16 +944,18 @@ export function create(rawOptions: CreateOptions = {}): Service {
isFileKnownToBeInternal,
markBucketOfFilenameInternal,
} = createResolverFunctions({
- serviceHost,
+ host: serviceHost,
getCanonicalFileName,
ts,
cwd,
config,
- configFilePath,
+ projectLocalResolveHelper,
});
serviceHost.resolveModuleNames = resolveModuleNames;
- serviceHost.getResolvedModuleWithFailedLookupLocationsFromCache = getResolvedModuleWithFailedLookupLocationsFromCache;
- serviceHost.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives;
+ serviceHost.getResolvedModuleWithFailedLookupLocationsFromCache =
+ getResolvedModuleWithFailedLookupLocationsFromCache;
+ serviceHost.resolveTypeReferenceDirectives =
+ resolveTypeReferenceDirectives;
const registry = ts.createDocumentRegistry(
ts.sys.useCaseSensitiveFileNames,
@@ -941,7 +1022,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
if (diagnosticList.length) reportTSError(diagnosticList);
if (output.emitSkipped) {
- throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`);
+ return [undefined, undefined, true];
}
// Throw an error when requiring `.d.ts` files.
@@ -954,7 +1035,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
);
}
- return [output.outputFiles[1].text, output.outputFiles[0].text];
+ return [output.outputFiles[1].text, output.outputFiles[0].text, false];
};
getTypeInfo = (code: string, fileName: string, position: number) => {
@@ -1010,18 +1091,19 @@ export function create(rawOptions: CreateOptions = {}): Service {
),
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
};
+ host.trace = options.tsTrace;
const {
resolveModuleNames,
resolveTypeReferenceDirectives,
isFileKnownToBeInternal,
markBucketOfFilenameInternal,
} = createResolverFunctions({
- serviceHost: host,
+ host,
cwd,
- configFilePath,
config,
ts,
getCanonicalFileName,
+ projectLocalResolveHelper,
});
host.resolveModuleNames = resolveModuleNames;
host.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives;
@@ -1031,7 +1113,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
? ts.createIncrementalProgram({
rootNames: Array.from(rootFileNames),
options: config.options,
- host: host,
+ host,
configFileParsingDiagnostics: config.errors,
projectReferences: config.projectReferences,
})
@@ -1083,7 +1165,8 @@ export function create(rawOptions: CreateOptions = {}): Service {
};
getOutput = (code: string, fileName: string) => {
- const output: [string, string] = ['', ''];
+ let outText = '';
+ let outMap = '';
updateMemoryCache(code, fileName);
@@ -1103,9 +1186,9 @@ export function create(rawOptions: CreateOptions = {}): Service {
sourceFile,
(path, file, writeByteOrderMark) => {
if (path.endsWith('.map')) {
- output[1] = file;
+ outMap = file;
} else {
- output[0] = file;
+ outText = file;
}
if (options.emit) sys.writeFile(path, file, writeByteOrderMark);
@@ -1116,11 +1199,11 @@ export function create(rawOptions: CreateOptions = {}): Service {
);
if (result.emitSkipped) {
- throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`);
+ return [undefined, undefined, true];
}
// Throw an error when requiring files that cannot be compiled.
- if (output[0] === '') {
+ if (outText === '') {
if (program.isSourceFileFromExternalLibrary(sourceFile)) {
throw new TypeError(
`Unable to compile file from external library: ${relative(
@@ -1138,7 +1221,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
);
}
- return output;
+ return [outText, outMap, false];
};
getTypeInfo = (code: string, fileName: string, position: number) => {
@@ -1215,7 +1298,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
);
if (diagnosticList.length) reportTSError(diagnosticList);
- return [result.outputText, result.sourceMapText as string];
+ return [result.outputText, result.sourceMapText as string, false];
};
}
@@ -1232,25 +1315,27 @@ export function create(rawOptions: CreateOptions = {}): Service {
: createTranspileOnlyGetOutputFunction(
ts.ModuleKind.ES2020 || ts.ModuleKind.ES2015
);
+ const getOutputTranspileOnly = createTranspileOnlyGetOutputFunction();
// Create a simple TypeScript compiler proxy.
function compile(code: string, fileName: string, lineOffset = 0) {
const normalizedFileName = normalizeSlashes(fileName);
- const classification = moduleTypeClassifier.classifyModule(
- normalizedFileName
- );
+ const classification =
+ moduleTypeClassifier.classifyModule(normalizedFileName);
// Must always call normal getOutput to throw typechecking errors
- let [value, sourceMap] = getOutput(code, normalizedFileName);
+ let [value, sourceMap, emitSkipped] = getOutput(code, normalizedFileName);
// If module classification contradicts the above, call the relevant transpiler
if (classification.moduleType === 'cjs' && getOutputForceCommonJS) {
[value, sourceMap] = getOutputForceCommonJS(code, normalizedFileName);
} else if (classification.moduleType === 'esm' && getOutputForceESM) {
[value, sourceMap] = getOutputForceESM(code, normalizedFileName);
+ } else if (emitSkipped) {
+ [value, sourceMap] = getOutputTranspileOnly(code, normalizedFileName);
}
const output = updateOutput(
- value,
+ value!,
normalizedFileName,
- sourceMap,
+ sourceMap!,
getExtension
);
outputCache.set(normalizedFileName, { content: output });
@@ -1285,6 +1370,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
return {
[TS_NODE_SERVICE_BRAND]: true,
ts,
+ compilerPath: compiler,
config,
compile,
getTypeInfo,
@@ -1297,6 +1383,8 @@ export function create(rawOptions: CreateOptions = {}): Service {
addDiagnosticFilter,
installSourceMapSupport,
enableExperimentalEsmLoaderInterop,
+ transpileOnly,
+ projectLocalResolveHelper,
};
}
@@ -1378,7 +1466,7 @@ function registerExtension(
/**
* Internal source output.
*/
-type SourceOutput = [string, string];
+type SourceOutput = [string, string, false] | [undefined, undefined, true];
/**
* Update the output remapping the source map.
diff --git a/src/repl.ts b/src/repl.ts
index 0ef51017..41776e12 100644
--- a/src/repl.ts
+++ b/src/repl.ts
@@ -330,6 +330,11 @@ export function createRepl(options: CreateReplOptions = {}) {
}
}
+ // In case the typescript compiler hasn't compiled anything yet,
+ // make it run though compilation at least one time before
+ // the REPL starts for a snappier user experience on startup.
+ service?.compile('', state.path);
+
const repl = nodeReplStart({
prompt: '> ',
input: replService.stdin,
@@ -363,16 +368,20 @@ export function createRepl(options: CreateReplOptions = {}) {
// those starting with _
// those containing /
// those that already exist as globals
- // Intentionally suppress type errors in case @types/node does not declare any of them.
- state.input += `// @ts-ignore\n${builtinModules
- .filter(
- (name) =>
- !name.startsWith('_') &&
- !name.includes('/') &&
- !['console', 'module', 'process'].includes(name)
- )
- .map((name) => `declare import ${name} = require('${name}')`)
- .join(';')}\n`;
+ // Intentionally suppress type errors in case @types/node does not declare any of them, and because
+ // `declare import` is technically invalid syntax.
+ // Avoid this when in transpileOnly, because third-party transpilers may not handle `declare import`.
+ if (!service?.transpileOnly) {
+ state.input += `// @ts-ignore\n${builtinModules
+ .filter(
+ (name) =>
+ !name.startsWith('_') &&
+ !name.includes('/') &&
+ !['console', 'module', 'process'].includes(name)
+ )
+ .map((name) => `declare import ${name} = require('${name}')`)
+ .join(';')}\n`;
+ }
}
reset();
@@ -475,6 +484,8 @@ export function createEvalAwarePartialHost(
return { readFile, fileExists };
}
+const sourcemapCommentRe = /\/\/# ?sourceMappingURL=\S+[\s\r\n]*$/;
+
type AppendCompileAndEvalInputResult =
| { containsTopLevelAwait: true; valuePromise: Promise }
| { containsTopLevelAwait: false; value: any };
@@ -520,8 +531,23 @@ function appendCompileAndEvalInput(options: {
output = adjustUseStrict(output);
+ // Note: REPL does not respect sourcemaps!
+ // To properly do that, we'd need to prefix the code we eval -- which comes
+ // from `diffLines` -- with newlines so that it's at the proper line numbers.
+ // Then we'd need to ensure each bit of eval-ed code, if there are multiples,
+ // has the sourcemap appended to it.
+ // We might also need to integrate with our sourcemap hooks' cache; I'm not sure.
+ const outputWithoutSourcemapComment = output.replace(sourcemapCommentRe, '');
+ const oldOutputWithoutSourcemapComment = state.output.replace(
+ sourcemapCommentRe,
+ ''
+ );
+
// Use `diff` to check for new JavaScript to execute.
- const changes = diffLines(state.output, output);
+ const changes = diffLines(
+ oldOutputWithoutSourcemapComment,
+ outputWithoutSourcemapComment
+ );
if (isCompletion) {
undo();
diff --git a/src/resolver-functions.ts b/src/resolver-functions.ts
index e7652815..f032bf0a 100644
--- a/src/resolver-functions.ts
+++ b/src/resolver-functions.ts
@@ -1,5 +1,6 @@
import { resolve } from 'path';
import type * as _ts from 'typescript';
+import type { ProjectLocalResolveHelper } from './util';
/**
* @internal
@@ -7,19 +8,19 @@ import type * as _ts from 'typescript';
*/
export function createResolverFunctions(kwargs: {
ts: typeof _ts;
- serviceHost: _ts.ModuleResolutionHost;
+ host: _ts.ModuleResolutionHost;
cwd: string;
getCanonicalFileName: (filename: string) => string;
config: _ts.ParsedCommandLine;
- configFilePath: string | undefined;
+ projectLocalResolveHelper: ProjectLocalResolveHelper;
}) {
const {
- serviceHost,
+ host,
ts,
config,
cwd,
getCanonicalFileName,
- configFilePath,
+ projectLocalResolveHelper,
} = kwargs;
const moduleResolutionCache = ts.createModuleResolutionCache(
cwd,
@@ -93,7 +94,7 @@ export function createResolverFunctions(kwargs: {
moduleName,
containingFile,
config.options,
- serviceHost,
+ host,
moduleResolutionCache,
redirectedReference
);
@@ -105,69 +106,69 @@ export function createResolverFunctions(kwargs: {
};
// language service never calls this, but TS docs recommend that we implement it
- const getResolvedModuleWithFailedLookupLocationsFromCache: _ts.LanguageServiceHost['getResolvedModuleWithFailedLookupLocationsFromCache'] = (
- moduleName,
- containingFile
- ): _ts.ResolvedModuleWithFailedLookupLocations | undefined => {
- const ret = ts.resolveModuleNameFromCache(
+ const getResolvedModuleWithFailedLookupLocationsFromCache: _ts.LanguageServiceHost['getResolvedModuleWithFailedLookupLocationsFromCache'] =
+ (
moduleName,
- containingFile,
- moduleResolutionCache
- );
- if (ret && ret.resolvedModule) {
- fixupResolvedModule(ret.resolvedModule);
- }
- return ret;
- };
-
- const resolveTypeReferenceDirectives: _ts.LanguageServiceHost['resolveTypeReferenceDirectives'] = (
- typeDirectiveNames: string[],
- containingFile: string,
- redirectedReference: _ts.ResolvedProjectReference | undefined,
- options: _ts.CompilerOptions
- ): (_ts.ResolvedTypeReferenceDirective | undefined)[] => {
- // Note: seems to be called with empty typeDirectiveNames array for all files.
- return typeDirectiveNames.map((typeDirectiveName) => {
- let { resolvedTypeReferenceDirective } = ts.resolveTypeReferenceDirective(
- typeDirectiveName,
+ containingFile
+ ): _ts.ResolvedModuleWithFailedLookupLocations | undefined => {
+ const ret = ts.resolveModuleNameFromCache(
+ moduleName,
containingFile,
- config.options,
- serviceHost,
- redirectedReference
+ moduleResolutionCache
);
- if (typeDirectiveName === 'node' && !resolvedTypeReferenceDirective) {
- // Resolve @types/node relative to project first, then __dirname (copy logic from elsewhere / refactor into reusable function)
- let typesNodePackageJsonPath: string | undefined;
- try {
- typesNodePackageJsonPath = require.resolve(
- '@types/node/package.json',
- {
- paths: [configFilePath ?? cwd, __dirname],
- }
- );
- } catch {} // gracefully do nothing when @types/node is not installed for any reason
- if (typesNodePackageJsonPath) {
- const typeRoots = [resolve(typesNodePackageJsonPath, '../..')];
- ({
- resolvedTypeReferenceDirective,
- } = ts.resolveTypeReferenceDirective(
+ if (ret && ret.resolvedModule) {
+ fixupResolvedModule(ret.resolvedModule);
+ }
+ return ret;
+ };
+
+ const resolveTypeReferenceDirectives: _ts.LanguageServiceHost['resolveTypeReferenceDirectives'] =
+ (
+ typeDirectiveNames: string[],
+ containingFile: string,
+ redirectedReference: _ts.ResolvedProjectReference | undefined,
+ options: _ts.CompilerOptions
+ ): (_ts.ResolvedTypeReferenceDirective | undefined)[] => {
+ // Note: seems to be called with empty typeDirectiveNames array for all files.
+ return typeDirectiveNames.map((typeDirectiveName) => {
+ let { resolvedTypeReferenceDirective } =
+ ts.resolveTypeReferenceDirective(
typeDirectiveName,
containingFile,
- {
- ...config.options,
- typeRoots,
- },
- serviceHost,
+ config.options,
+ host,
redirectedReference
- ));
+ );
+ if (typeDirectiveName === 'node' && !resolvedTypeReferenceDirective) {
+ // Resolve @types/node relative to project first, then __dirname (copy logic from elsewhere / refactor into reusable function)
+ let typesNodePackageJsonPath: string | undefined;
+ try {
+ typesNodePackageJsonPath = projectLocalResolveHelper(
+ '@types/node/package.json',
+ true
+ );
+ } catch {} // gracefully do nothing when @types/node is not installed for any reason
+ if (typesNodePackageJsonPath) {
+ const typeRoots = [resolve(typesNodePackageJsonPath, '../..')];
+ ({ resolvedTypeReferenceDirective } =
+ ts.resolveTypeReferenceDirective(
+ typeDirectiveName,
+ containingFile,
+ {
+ ...config.options,
+ typeRoots,
+ },
+ host,
+ redirectedReference
+ ));
+ }
}
- }
- if (resolvedTypeReferenceDirective) {
- fixupResolvedModule(resolvedTypeReferenceDirective);
- }
- return resolvedTypeReferenceDirective;
- });
- };
+ if (resolvedTypeReferenceDirective) {
+ fixupResolvedModule(resolvedTypeReferenceDirective);
+ }
+ return resolvedTypeReferenceDirective;
+ });
+ };
return {
resolveModuleNames,
diff --git a/src/test/esm-loader.spec.ts b/src/test/esm-loader.spec.ts
index 6c3c1c51..0de0482c 100644
--- a/src/test/esm-loader.spec.ts
+++ b/src/test/esm-loader.spec.ts
@@ -5,17 +5,19 @@
import { context } from './testlib';
import semver = require('semver');
import {
+ CMD_ESM_LOADER_WITHOUT_PROJECT,
contextTsNodeUnderTest,
EXPERIMENTAL_MODULES_FLAG,
resetNodeEnvironment,
TEST_DIR,
} from './helpers';
import { createExec } from './exec-helpers';
-import { join } from 'path';
+import { join, resolve } from 'path';
import * as expect from 'expect';
import type { NodeLoaderHooksAPI2 } from '../';
const nodeUsesNewHooksApi = semver.gte(process.version, '16.12.0');
+const nodeSupportsImportAssertions = semver.gte(process.version, '17.1.0');
const test = context(contextTsNodeUnderTest);
@@ -71,3 +73,55 @@ test.suite('hooks', (_test) => {
});
}
});
+
+if (nodeSupportsImportAssertions) {
+ test.suite('Supports import assertions', (test) => {
+ test('Can import JSON using the appropriate flag and assertion', async (t) => {
+ const { err, stdout } = await exec(
+ `${CMD_ESM_LOADER_WITHOUT_PROJECT} --experimental-json-modules ./importJson.ts`,
+ {
+ cwd: resolve(TEST_DIR, 'esm-import-assertions'),
+ }
+ );
+ expect(err).toBe(null);
+ expect(stdout.trim()).toBe(
+ 'A fuchsia car has 2 seats and the doors are open.\nDone!'
+ );
+ });
+ });
+
+ test.suite("Catch unexpected changes to node's loader context", (test) => {
+ /*
+ * This does not test ts-node.
+ * Rather, it is meant to alert us to potentially breaking changes in node's
+ * loader API. If node starts returning more or less properties on `context`
+ * objects, we want to know, because it may indicate that our loader code
+ * should be updated to accomodate the new properties, either by proxying them,
+ * modifying them, or suppressing them.
+ */
+ test('Ensure context passed to loader by node has only expected properties', async (t) => {
+ const { stdout, stderr } = await exec(
+ `node --loader ./esm-loader-context/loader.mjs --experimental-json-modules ./esm-loader-context/index.mjs`
+ );
+ const rows = stdout.split('\n').filter((v) => v[0] === '{');
+ expect(rows.length).toBe(14);
+ rows.forEach((row) => {
+ const json = JSON.parse(row) as {
+ resolveContextKeys?: string[];
+ loadContextKeys?: string;
+ };
+ if (json.resolveContextKeys) {
+ expect(json.resolveContextKeys).toEqual([
+ 'conditions',
+ 'importAssertions',
+ 'parentURL',
+ ]);
+ } else if (json.loadContextKeys) {
+ expect(json.loadContextKeys).toEqual(['format', 'importAssertions']);
+ } else {
+ throw new Error('Unexpected stdout in test.');
+ }
+ });
+ });
+ });
+}
diff --git a/src/test/exec-helpers.ts b/src/test/exec-helpers.ts
index 87a79787..fc70f0e3 100644
--- a/src/test/exec-helpers.ts
+++ b/src/test/exec-helpers.ts
@@ -1,6 +1,6 @@
import type { ChildProcess, ExecException, ExecOptions } from 'child_process';
import { exec as childProcessExec } from 'child_process';
-import * as expect from 'expect';
+import { expect } from './testlib';
export type ExecReturn = Promise & { child: ChildProcess };
export interface ExecResult {
diff --git a/src/test/helpers.ts b/src/test/helpers.ts
index 245e0a92..83d45d3f 100644
--- a/src/test/helpers.ts
+++ b/src/test/helpers.ts
@@ -14,7 +14,6 @@ import type * as tsNodeTypes from '../index';
import type _createRequire from 'create-require';
import { has, once } from 'lodash';
import semver = require('semver');
-import * as expect from 'expect';
const createRequire: typeof _createRequire = require('create-require');
export { tsNodeTypes };
diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts
index 730d1a3b..8eef6bc0 100644
--- a/src/test/index.spec.ts
+++ b/src/test/index.spec.ts
@@ -70,6 +70,7 @@ test.suite('ts-node', (test) => {
testsDirRequire.resolve('ts-node/esm/transpile-only');
testsDirRequire.resolve('ts-node/esm/transpile-only.mjs');
+ testsDirRequire.resolve('ts-node/transpilers/swc');
testsDirRequire.resolve('ts-node/transpilers/swc-experimental');
testsDirRequire.resolve('ts-node/node10/tsconfig.json');
@@ -78,6 +79,14 @@ test.suite('ts-node', (test) => {
testsDirRequire.resolve('ts-node/node16/tsconfig.json');
});
+ test('should not load typescript outside of loadConfig', async () => {
+ const { err, stdout } = await exec(
+ `node -e "require('ts-node'); console.dir(Object.keys(require.cache).filter(k => k.includes('node_modules/typescript')).length)"`
+ );
+ expect(err).toBe(null);
+ expect(stdout).toBe('0\n');
+ });
+
test.suite('cli', (test) => {
test('should execute cli', async () => {
const { err, stdout } = await exec(
@@ -304,20 +313,50 @@ test.suite('ts-node', (test) => {
expect(err.message).toMatch('error TS1003: Identifier expected');
});
- test('should support third-party transpilers via --transpiler', async () => {
- const { err, stdout } = await exec(
- `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} --transpiler ts-node/transpilers/swc-experimental transpile-only-swc`
- );
- expect(err).toBe(null);
- expect(stdout).toMatch('Hello World!');
- });
+ for (const flavor of [
+ '--transpiler ts-node/transpilers/swc transpile-only-swc',
+ '--transpiler ts-node/transpilers/swc-experimental transpile-only-swc',
+ '--swc transpile-only-swc',
+ 'transpile-only-swc-via-tsconfig',
+ 'transpile-only-swc-shorthand-via-tsconfig',
+ ]) {
+ test(`should support swc and third-party transpilers: ${flavor}`, async () => {
+ const { err, stdout } = await exec(
+ `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ${flavor}`,
+ {
+ env: {
+ ...process.env,
+ NODE_OPTIONS: `${
+ process.env.NODE_OPTIONS || ''
+ } --require ${require.resolve('../../tests/spy-swc-transpiler')}`,
+ },
+ }
+ );
+ expect(err).toBe(null);
+ expect(stdout).toMatch(
+ 'Hello World! swc transpiler invocation count: 1\n'
+ );
+ });
+ }
- test('should support third-party transpilers via tsconfig', async () => {
- const { err, stdout } = await exec(
- `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} transpile-only-swc-via-tsconfig`
- );
- expect(err).toBe(null);
- expect(stdout).toMatch('Hello World!');
+ test.suite('should support `traceResolution` compiler option', (test) => {
+ test('prints traces before running code when enabled', async () => {
+ const { err, stdout } = await exec(
+ `${BIN_PATH} --compiler-options="{ \\"traceResolution\\": true }" -e "console.log('ok')"`
+ );
+ expect(err).toBeNull();
+ expect(stdout).toContain('======== Resolving module');
+ expect(stdout.endsWith('ok\n')).toBe(true);
+ });
+
+ test('does NOT print traces when not enabled', async () => {
+ const { err, stdout } = await exec(
+ `${BIN_PATH} -e "console.log('ok')"`
+ );
+ expect(err).toBeNull();
+ expect(stdout).not.toContain('======== Resolving module');
+ expect(stdout.endsWith('ok\n')).toBe(true);
+ });
});
if (semver.gte(process.version, '12.16.0')) {
@@ -858,7 +897,7 @@ test.suite('ts-node', (test) => {
});
if (semver.gte(ts.version, '3.2.0')) {
- test('--show-config should log resolved configuration', async (t) => {
+ test('--showConfig should log resolved configuration', async (t) => {
function native(path: string) {
return path.replace(/\/|\\/g, pathSep);
}
@@ -877,7 +916,6 @@ test.suite('ts-node', (test) => {
cwd: native(`${ROOT_DIR}/tests`),
projectSearchDir: native(`${ROOT_DIR}/tests`),
project: native(`${ROOT_DIR}/tests/tsconfig.json`),
- require: [],
},
compilerOptions: {
target: 'es6',
@@ -907,7 +945,7 @@ test.suite('ts-node', (test) => {
`${CMD_TS_NODE_WITH_PROJECT_FLAG} --showConfig`
);
expect(err).not.toBe(null);
- expect(stderr).toMatch('Error: --show-config requires');
+ expect(stderr).toMatch('Error: --showConfig requires');
});
}
@@ -1055,10 +1093,7 @@ test.suite('ts-node', (test) => {
test.suite('supports experimental-specifier-resolution=node', (test) => {
test('via --experimental-specifier-resolution', async () => {
- const {
- err,
- stdout,
- } = await exec(
+ const { err, stdout } = await exec(
`${CMD_ESM_LOADER_WITHOUT_PROJECT} --experimental-specifier-resolution=node index.ts`,
{ cwd: join(TEST_DIR, './esm-node-resolver') }
);
@@ -1066,10 +1101,7 @@ test.suite('ts-node', (test) => {
expect(stdout).toBe('foo bar baz biff libfoo\n');
});
test('via --es-module-specifier-resolution alias', async () => {
- const {
- err,
- stdout,
- } = await exec(
+ const { err, stdout } = await exec(
`${CMD_ESM_LOADER_WITHOUT_PROJECT} ${EXPERIMENTAL_MODULES_FLAG} --es-module-specifier-resolution=node index.ts`,
{ cwd: join(TEST_DIR, './esm-node-resolver') }
);
@@ -1115,7 +1147,7 @@ test.suite('ts-node', (test) => {
expect(err).not.toBe(null);
// expect error from node's default resolver
expect(stderr).toMatch(
- /Error \[ERR_UNSUPPORTED_ESM_URL_SCHEME\]:.*(?:\n.*){0,1}\n *at defaultResolve/
+ /Error \[ERR_UNSUPPORTED_ESM_URL_SCHEME\]:.*(?:\n.*){0,2}\n *at defaultResolve/
);
});
@@ -1224,3 +1256,14 @@ test.suite('ts-node', (test) => {
}
});
});
+
+test('Falls back to transpileOnly when ts compiler returns emitSkipped', async () => {
+ const { err, stdout } = await exec(
+ `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} --project tsconfig.json ./outside-rootDir/foo.js`,
+ {
+ cwd: join(TEST_DIR, 'emit-skipped-fallback'),
+ }
+ );
+ expect(err).toBe(null);
+ expect(stdout).toBe('foo\n');
+});
diff --git a/src/test/register.spec.ts b/src/test/register.spec.ts
index db04b284..97769283 100644
--- a/src/test/register.spec.ts
+++ b/src/test/register.spec.ts
@@ -12,7 +12,8 @@ import * as exp from 'expect';
import { join, resolve } from 'path';
import proxyquire = require('proxyquire');
-const SOURCE_MAP_REGEXP = /\/\/# sourceMappingURL=data:application\/json;charset=utf\-8;base64,[\w\+]+=*$/;
+const SOURCE_MAP_REGEXP =
+ /\/\/# sourceMappingURL=data:application\/json;charset=utf\-8;base64,[\w\+]+=*$/;
const createOptions: tsNodeTypes.CreateOptions = {
project: PROJECT,
diff --git a/src/test/remove-env-var-force-color.js b/src/test/remove-env-var-force-color.js
new file mode 100644
index 00000000..ebbb349b
--- /dev/null
+++ b/src/test/remove-env-var-force-color.js
@@ -0,0 +1 @@
+delete process.env.FORCE_COLOR;
diff --git a/src/test/repl/helpers.ts b/src/test/repl/helpers.ts
index cc4edfcf..a085a266 100644
--- a/src/test/repl/helpers.ts
+++ b/src/test/repl/helpers.ts
@@ -39,12 +39,13 @@ export async function contextReplHelpers(
stderr,
...createReplOpts,
});
- const service = (registerHooks
- ? tsNodeUnderTest.register
- : tsNodeUnderTest.create)({
+ const service = (
+ registerHooks ? tsNodeUnderTest.register : tsNodeUnderTest.create
+ )({
...replService.evalAwarePartialHost,
project: `${TEST_DIR}/tsconfig.json`,
...createServiceOpts,
+ tsTrace: replService.console.log.bind(replService.console),
});
replService.setService(service);
t.teardown(async () => {
diff --git a/src/test/repl/node-repl-tla.ts b/src/test/repl/node-repl-tla.ts
index 61623792..5c4962e7 100644
--- a/src/test/repl/node-repl-tla.ts
+++ b/src/test/repl/node-repl-tla.ts
@@ -39,9 +39,11 @@ export async function upstreamTopLevelAwaitTests({
},
});
replService.setService(service);
- (replService.stdout as NodeJS.WritableStream & {
- isTTY: boolean;
- }).isTTY = true;
+ (
+ replService.stdout as NodeJS.WritableStream & {
+ isTTY: boolean;
+ }
+ ).isTTY = true;
const replServer = replService.startInternal({
prompt: PROMPT,
terminal: true,
diff --git a/src/test/repl/repl-environment.spec.ts b/src/test/repl/repl-environment.spec.ts
index 9071688c..f54d2030 100644
--- a/src/test/repl/repl-environment.spec.ts
+++ b/src/test/repl/repl-environment.spec.ts
@@ -3,8 +3,7 @@
* globals, __filename, builtin module accessors.
*/
-import { test as _test } from '../testlib';
-import * as expect from 'expect';
+import { test as _test, expect } from '../testlib';
import * as promisify from 'util.promisify';
import * as getStream from 'get-stream';
import {
@@ -45,53 +44,50 @@ test.suite(
const globalInRepl = global as GlobalInRepl;
const programmaticTest = test.macro(
(
- {
- evalCodeBefore,
- stdinCode,
- waitFor,
- }: {
- evalCodeBefore: string | null;
- stdinCode: string;
- waitFor?: () => boolean;
- },
- assertions: (stdout: string) => Promise | void
- ) => async (t) => {
- delete globalInRepl.testReport;
- delete globalInRepl.replReport;
- delete globalInRepl.stdinReport;
- delete globalInRepl.evalReport;
- delete globalInRepl.module;
- delete globalInRepl.exports;
- delete globalInRepl.fs;
- delete globalInRepl.__filename;
- delete globalInRepl.__dirname;
- const {
- stdin,
- stderr,
- stdout,
- replService,
- } = t.context.createReplViaApi({ registerHooks: true });
- if (typeof evalCodeBefore === 'string') {
- replService.evalCode(evalCodeBefore);
+ {
+ evalCodeBefore,
+ stdinCode,
+ waitFor,
+ }: {
+ evalCodeBefore: string | null;
+ stdinCode: string;
+ waitFor?: () => boolean;
+ },
+ assertions: (stdout: string) => Promise | void
+ ) =>
+ async (t) => {
+ delete globalInRepl.testReport;
+ delete globalInRepl.replReport;
+ delete globalInRepl.stdinReport;
+ delete globalInRepl.evalReport;
+ delete globalInRepl.module;
+ delete globalInRepl.exports;
+ delete globalInRepl.fs;
+ delete globalInRepl.__filename;
+ delete globalInRepl.__dirname;
+ const { stdin, stderr, stdout, replService } =
+ t.context.createReplViaApi({ registerHooks: true });
+ if (typeof evalCodeBefore === 'string') {
+ replService.evalCode(evalCodeBefore);
+ }
+ replService.start();
+ stdin.write(stdinCode);
+ stdin.end();
+ let done = false;
+ await Promise.race([
+ promisify(setTimeout)(20e3),
+ (async () => {
+ while (!done && !waitFor?.()) {
+ await promisify(setTimeout)(1e3);
+ }
+ })(),
+ ]);
+ done = true;
+ stdout.end();
+ stderr.end();
+ expect(await getStream(stderr)).toBe('');
+ await assertions(await getStream(stdout));
}
- replService.start();
- stdin.write(stdinCode);
- stdin.end();
- let done = false;
- await Promise.race([
- promisify(setTimeout)(20e3),
- (async () => {
- while (!done && !waitFor?.()) {
- await promisify(setTimeout)(1e3);
- }
- })(),
- ]);
- done = true;
- stdout.end();
- stderr.end();
- expect(await getStream(stderr)).toBe('');
- await assertions(await getStream(stdout));
- }
);
const declareGlobals = `declare var replReport: any, stdinReport: any, evalReport: any, restReport: any, global: any, __filename: any, __dirname: any, module: any, exports: any;`;
diff --git a/src/test/repl/repl.spec.ts b/src/test/repl/repl.spec.ts
index 2ef41d15..3a106a38 100644
--- a/src/test/repl/repl.spec.ts
+++ b/src/test/repl/repl.spec.ts
@@ -1,15 +1,16 @@
+import { _test, expect } from '../testlib';
import { ts } from '../helpers';
import semver = require('semver');
-import * as expect from 'expect';
import {
CMD_TS_NODE_WITH_PROJECT_FLAG,
contextTsNodeUnderTest,
+ getStream,
TEST_DIR,
} from '../helpers';
import { createExec, createExecTester } from '../exec-helpers';
import { upstreamTopLevelAwaitTests } from './node-repl-tla';
-import { _test } from '../testlib';
import { contextReplHelpers } from './helpers';
+import { promisify } from 'util';
const test = _test.context(contextTsNodeUnderTest).context(contextReplHelpers);
@@ -30,6 +31,16 @@ test('should run REPL when --interactive passed and stdin is not a TTY', async (
expect(stdout).toBe('> 123\n' + 'undefined\n' + '> ');
});
+test('should echo a value when using the swc transpiler', async () => {
+ const execPromise = exec(
+ `${CMD_TS_NODE_WITH_PROJECT_FLAG} --interactive --transpiler ts-node/transpilers/swc-experimental`
+ );
+ execPromise.child.stdin!.end('400\n401\n');
+ const { err, stdout } = await execPromise;
+ expect(err).toBe(null);
+ expect(stdout).toBe('> 400\n> 401\n> ');
+});
+
test('REPL has command to get type information', async () => {
const execPromise = exec(`${CMD_TS_NODE_WITH_PROJECT_FLAG} --interactive`);
execPromise.child.stdin!.end('\nconst a = 123\n.type a');
@@ -44,16 +55,20 @@ test('REPL has command to get type information', async () => {
test.serial('REPL can be configured on `start`', async (t) => {
const prompt = '#> ';
- const { stdout, stderr } = await t.context.executeInRepl('const x = 3', {
- registerHooks: true,
- startInternalOptions: {
- prompt,
- ignoreUndefined: true,
- },
- });
+ const { stdout, stderr } = await t.context.executeInRepl(
+ `const x = 3\n'done'`,
+ {
+ waitPattern: "'done'",
+ registerHooks: true,
+ startInternalOptions: {
+ prompt,
+ ignoreUndefined: true,
+ },
+ }
+ );
expect(stderr).toBe('');
- expect(stdout).toBe(`${prompt}${prompt}`);
+ expect(stdout).toBe(`${prompt}${prompt}'done'\n`);
});
// Serial because it's timing-sensitive
@@ -412,29 +427,95 @@ test.suite(
}
);
-test.serial('REPL declares types for node built-ins within REPL', async (t) => {
- const { stdout, stderr } = await t.context.executeInRepl(
- `util.promisify(setTimeout)("should not be a string" as string)
- type Duplex = stream.Duplex
- const s = stream
- 'done'`,
- {
- registerHooks: true,
- waitPattern: `done`,
- startInternalOptions: {
- useGlobal: false,
- },
+test.suite('REPL works with traceResolution', (test) => {
+ test.serial(
+ 'startup traces should print before the prompt appears when traceResolution is enabled',
+ async (t) => {
+ const repl = t.context.createReplViaApi({
+ registerHooks: false as true,
+ createServiceOpts: {
+ compilerOptions: {
+ traceResolution: true,
+ },
+ },
+ });
+
+ repl.replService.start();
+
+ repl.stdin.end();
+
+ await promisify(setTimeout)(3e3);
+
+ repl.stdout.end();
+ const stdout = await getStream(repl.stdout);
+
+ expect(stdout).toContain('======== Resolving module');
+ expect(stdout.endsWith('> ')).toBe(true);
}
);
- // Assert that we receive a typechecking error about improperly using
- // `util.promisify` but *not* an error about the absence of `util`
- expect(stderr).not.toMatch("Cannot find name 'util'");
- expect(stderr).toMatch(
- "Argument of type 'string' is not assignable to parameter of type 'number'"
+ test.serial(
+ 'traces should NOT appear when traceResolution is not enabled',
+ async (t) => {
+ const { stdout, stderr } = await t.context.executeInRepl('1', {
+ registerHooks: true,
+ startInternalOptions: { useGlobal: false },
+ waitPattern: '1\n>',
+ });
+ expect(stderr).toBe('');
+ expect(stdout).not.toContain('======== Resolving module');
+ }
);
- // Assert that both types and values can be used without error
- expect(stderr).not.toMatch("Cannot find namespace 'stream'");
- expect(stderr).not.toMatch("Cannot find name 'stream'");
- expect(stdout).toMatch(`done`);
+});
+
+test.suite('REPL declares types for node built-ins within REPL', (test) => {
+ test.runSerially();
+ test('enabled when typechecking', async (t) => {
+ const { stdout, stderr } = await t.context.executeInRepl(
+ `util.promisify(setTimeout)("should not be a string" as string)
+ type Duplex = stream.Duplex
+ const s = stream
+ 'done'`,
+ {
+ registerHooks: true,
+ waitPattern: `done`,
+ startInternalOptions: {
+ useGlobal: false,
+ },
+ }
+ );
+
+ // Assert that we receive a typechecking error about improperly using
+ // `util.promisify` but *not* an error about the absence of `util`
+ expect(stderr).not.toMatch("Cannot find name 'util'");
+ expect(stderr).toMatch(
+ "Argument of type 'string' is not assignable to parameter of type 'number'"
+ );
+ // Assert that both types and values can be used without error
+ expect(stderr).not.toMatch("Cannot find namespace 'stream'");
+ expect(stderr).not.toMatch("Cannot find name 'stream'");
+ expect(stdout).toMatch(`done`);
+ });
+
+ test('disabled in transpile-only mode, to avoid breaking third-party SWC transpiler which rejects `declare import` syntax', async (t) => {
+ const { stdout, stderr } = await t.context.executeInRepl(
+ `type Duplex = stream.Duplex
+ const s = stream
+ 'done'`,
+ {
+ createServiceOpts: {
+ swc: true,
+ },
+ registerHooks: true,
+ waitPattern: `done`,
+ startInternalOptions: {
+ useGlobal: false,
+ },
+ }
+ );
+
+ // Assert that we do not get errors about `declare import` syntax from swc
+ expect(stdout).toBe("> undefined\n> undefined\n> 'done'\n");
+ expect(stderr).toBe('');
+ });
});
diff --git a/src/test/testlib.ts b/src/test/testlib.ts
index ce97a07a..4ce806dd 100644
--- a/src/test/testlib.ts
+++ b/src/test/testlib.ts
@@ -11,7 +11,9 @@ import avaTest, {
} from 'ava';
import * as assert from 'assert';
import throat from 'throat';
-export { ExecutionContext };
+import * as expect from 'expect';
+
+export { ExecutionContext, expect };
// NOTE: this limits concurrency within a single process, but AVA launches
// each .spec file in its own process, so actual concurrency is higher.
@@ -20,12 +22,12 @@ const concurrencyLimiter = throat(16);
function once(func: T): T {
let run = false;
let ret: any = undefined;
- return (function (...args: any[]) {
+ return function (...args: any[]) {
if (run) return ret;
run = true;
ret = func(...args);
return ret;
- } as any) as T;
+ } as any as T;
}
export const test = createTestInterface({
diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts
index 6955bc90..fedc6a3a 100644
--- a/src/transpilers/swc.ts
+++ b/src/transpilers/swc.ts
@@ -15,23 +15,26 @@ export interface SwcTranspilerOptions extends CreateTranspilerOptions {
export function create(createOptions: SwcTranspilerOptions): Transpiler {
const {
swc,
- service: { config },
+ service: { config, projectLocalResolveHelper },
} = createOptions;
// Load swc compiler
let swcInstance: typeof swcWasm;
if (typeof swc === 'string') {
- swcInstance = require(swc) as typeof swcWasm;
+ swcInstance = require(projectLocalResolveHelper(
+ swc,
+ true
+ )) as typeof swcWasm;
} else if (swc == null) {
let swcResolved;
try {
- swcResolved = require.resolve('@swc/core');
+ swcResolved = projectLocalResolveHelper('@swc/core', true);
} catch (e) {
try {
- swcResolved = require.resolve('@swc/wasm');
+ swcResolved = projectLocalResolveHelper('@swc/wasm', true);
} catch (e) {
throw new Error(
- 'swc compiler requires either @swc/core or @swc/wasm to be installed as dependencies'
+ 'swc compiler requires either @swc/core or @swc/wasm to be installed as a dependency. See https://typestrong.org/ts-node/docs/transpilers'
);
}
}
@@ -52,6 +55,9 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
module,
jsxFactory,
jsxFragmentFactory,
+ strict,
+ alwaysStrict,
+ noImplicitUseStrict,
} = compilerOptions;
const nonTsxOptions = createSwcOptions(false);
const tsxOptions = createSwcOptions(true);
@@ -71,6 +77,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
}
swcTarget = swcTargets[swcTargetIndex];
const keepClassNames = target! >= /* ts.ScriptTarget.ES2016 */ 3;
+ // swc only supports these 4x module options
const moduleType =
module === ModuleKind.CommonJS
? 'commonjs'
@@ -78,7 +85,23 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
? 'amd'
: module === ModuleKind.UMD
? 'umd'
- : undefined;
+ : 'es6';
+ // In swc:
+ // strictMode means `"use strict"` is *always* emitted for non-ES module, *never* for ES module where it is assumed it can be omitted.
+ // (this assumption is invalid, but that's the way swc behaves)
+ // tsc is a bit more complex:
+ // alwaysStrict will force emitting it always unless `import`/`export` syntax is emitted which implies it per the JS spec.
+ // if not alwaysStrict, will emit implicitly whenever module target is non-ES *and* transformed module syntax is emitted.
+ // For node, best option is to assume that all scripts are modules (commonjs or esm) and thus should get tsc's implicit strict behavior.
+
+ // Always set strictMode, *unless* alwaysStrict is disabled and noImplicitUseStrict is enabled
+ const strictMode =
+ // if `alwaysStrict` is disabled, remembering that `strict` defaults `alwaysStrict` to true
+ (alwaysStrict === false || (alwaysStrict !== true && strict !== true)) &&
+ // if noImplicitUseStrict is enabled
+ noImplicitUseStrict === true
+ ? false
+ : true;
return {
sourceMaps: sourceMap,
// isModule: true,
@@ -86,6 +109,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
? ({
noInterop: !esModuleInterop,
type: moduleType,
+ strictMode,
} as swcTypes.ModuleConfig)
: undefined,
swcrc: false,
@@ -143,7 +167,8 @@ targetMapping.set(/* ts.ScriptTarget.ES2018 */ 5, 'es2018');
targetMapping.set(/* ts.ScriptTarget.ES2019 */ 6, 'es2019');
targetMapping.set(/* ts.ScriptTarget.ES2020 */ 7, 'es2020');
targetMapping.set(/* ts.ScriptTarget.ES2021 */ 8, 'es2021');
-targetMapping.set(/* ts.ScriptTarget.ESNext */ 99, 'es2021');
+targetMapping.set(/* ts.ScriptTarget.ES2022 */ 9, 'es2022');
+targetMapping.set(/* ts.ScriptTarget.ESNext */ 99, 'es2022');
type SwcTarget = typeof swcTargets[number];
/**
@@ -160,6 +185,7 @@ const swcTargets = [
'es2019',
'es2020',
'es2021',
+ 'es2022',
] as const;
const ModuleKind = {
diff --git a/src/transpilers/types.ts b/src/transpilers/types.ts
index 3c1e7afc..ab524cbd 100644
--- a/src/transpilers/types.ts
+++ b/src/transpilers/types.ts
@@ -16,7 +16,11 @@ export type TranspilerFactory = (
) => Transpiler;
export interface CreateTranspilerOptions {
// TODO this is confusing because its only a partial Service. Rename?
- service: Pick;
+ // Careful: must avoid stripInternal breakage by guarding with Extract<>
+ service: Pick<
+ Service,
+ Extract<'config' | 'options' | 'projectLocalResolveHelper', keyof Service>
+ >;
}
export interface Transpiler {
// TODOs
diff --git a/src/tsconfigs.ts b/src/tsconfigs.ts
index 74a92744..cc104fd7 100644
--- a/src/tsconfigs.ts
+++ b/src/tsconfigs.ts
@@ -7,7 +7,7 @@ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
* @internal
*/
export function getDefaultTsconfigJsonForNodeVersion(ts: TSCommon): any {
- const tsInternal = (ts as any) as TSInternal;
+ const tsInternal = ts as any as TSInternal;
if (nodeMajor >= 16) {
const config = require('@tsconfig/node16/tsconfig.json');
if (configCompatible(config)) return config;
diff --git a/src/util.ts b/src/util.ts
index 23e19bba..adc5eaca 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -4,6 +4,7 @@ import {
} from 'module';
import type _createRequire from 'create-require';
import * as ynModule from 'yn';
+import { dirname } from 'path';
/** @internal */
export const createRequire =
@@ -92,7 +93,66 @@ export function cachedLookup(fn: (arg: T) => R): (arg: T) => R {
}
/**
- * We do not support ts's `trace` option yet. In the meantime, rather than omit
- * `trace` options in hosts, I am using this placeholder.
+ * @internal
+ * Require something with v8-compile-cache, which should make subsequent requires faster.
+ * Do lots of error-handling so that, worst case, we require without the cache, and users are not blocked.
+ */
+export function attemptRequireWithV8CompileCache(
+ requireFn: typeof require,
+ specifier: string
+) {
+ try {
+ const v8CC = (
+ require('v8-compile-cache-lib') as typeof import('v8-compile-cache-lib')
+ ).install();
+ try {
+ return requireFn(specifier);
+ } finally {
+ v8CC?.uninstall();
+ }
+ } catch (e) {
+ return requireFn(specifier);
+ }
+}
+
+/**
+ * Helper to discover dependencies relative to a user's project, optionally
+ * falling back to relative to ts-node. This supports global installations of
+ * ts-node, for example where someone does `#!/usr/bin/env -S ts-node --swc` and
+ * we need to fallback to a global install of @swc/core
+ * @internal
*/
-export function trace(s: string): void {}
+export function createProjectLocalResolveHelper(localDirectory: string) {
+ return function projectLocalResolveHelper(
+ specifier: string,
+ fallbackToTsNodeRelative: boolean
+ ) {
+ return require.resolve(specifier, {
+ paths: fallbackToTsNodeRelative
+ ? [localDirectory, __dirname]
+ : [localDirectory],
+ });
+ };
+}
+/** @internal */
+export type ProjectLocalResolveHelper = ReturnType<
+ typeof createProjectLocalResolveHelper
+>;
+
+/**
+ * Used as a reminder of all the factors we must consider when finding project-local dependencies and when a config file
+ * on disk may or may not exist.
+ * @internal
+ */
+export function getBasePathForProjectLocalDependencyResolution(
+ configFilePath: string | undefined,
+ projectSearchDirOption: string | undefined,
+ projectOption: string | undefined,
+ cwdOption: string
+) {
+ if (configFilePath != null) return dirname(configFilePath);
+ return projectSearchDirOption ?? projectOption ?? cwdOption;
+ // TODO technically breaks if projectOption is path to a file, not a directory,
+ // and we attempt to resolve relative specifiers. By the time we resolve relative specifiers,
+ // should have configFilePath, so not reach this codepath.
+}
diff --git a/tests/emit-skipped-fallback/outside-rootDir/foo.js b/tests/emit-skipped-fallback/outside-rootDir/foo.js
new file mode 100644
index 00000000..ad12b2b9
--- /dev/null
+++ b/tests/emit-skipped-fallback/outside-rootDir/foo.js
@@ -0,0 +1,15 @@
+// This file causes TS to return emitSkipped because it's outside of rootDir and
+// it's .js. I assume this happens because the emit path is the same as the
+// input path, and perhaps also because the file is classified "external"
+
+const decorator = () => {};
+
+class Foo {
+ // Using a decorator to prove this .js file is getting compiled
+ @decorator
+ method() {
+ return 'foo';
+ }
+}
+
+console.log(new Foo().method());
diff --git a/tests/emit-skipped-fallback/tsconfig.json b/tests/emit-skipped-fallback/tsconfig.json
new file mode 100644
index 00000000..aae99723
--- /dev/null
+++ b/tests/emit-skipped-fallback/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "allowJs": true,
+ "experimentalDecorators": true,
+ "rootDir": "rootDir"
+ }
+}
diff --git a/tests/esm-custom-loader/loader.mjs b/tests/esm-custom-loader/loader.mjs
index bf82e766..1e3113ac 100755
--- a/tests/esm-custom-loader/loader.mjs
+++ b/tests/esm-custom-loader/loader.mjs
@@ -11,6 +11,5 @@ const tsNodeInstance = register({
},
});
-export const { resolve, getFormat, transformSource, load } = createEsmHooks(
- tsNodeInstance
-);
+export const { resolve, getFormat, transformSource, load } =
+ createEsmHooks(tsNodeInstance);
diff --git a/tests/esm-import-assertions/car.json b/tests/esm-import-assertions/car.json
new file mode 100644
index 00000000..a63dfe91
--- /dev/null
+++ b/tests/esm-import-assertions/car.json
@@ -0,0 +1,5 @@
+{
+ "color": "fuchsia",
+ "doors": "open",
+ "seats": 2
+}
diff --git a/tests/esm-import-assertions/importJson.ts b/tests/esm-import-assertions/importJson.ts
new file mode 100644
index 00000000..b4446aa7
--- /dev/null
+++ b/tests/esm-import-assertions/importJson.ts
@@ -0,0 +1,28 @@
+import carData from './car.json' assert { type: 'json' };
+
+if (carData.color !== 'fuchsia') throw new Error('failed to import json');
+
+const { default: dynamicCarData } = await import('./car.json', {
+ assert: { type: 'json' },
+});
+
+if (dynamicCarData.doors !== 'open')
+ throw new Error('failed to dynamically import json');
+
+console.log(
+ `A ${carData.color} car has ${carData.seats} seats and the doors are ${dynamicCarData.doors}.`
+);
+
+// Test that omitting the assertion causes node to throw an error
+await import('./car.json').then(
+ () => {
+ throw new Error('should have thrown');
+ },
+ (error: any) => {
+ if (error.code !== 'ERR_IMPORT_ASSERTION_TYPE_MISSING') {
+ throw error;
+ }
+ /* error is expected */
+ }
+);
+console.log('Done!');
diff --git a/tests/esm-import-assertions/package.json b/tests/esm-import-assertions/package.json
new file mode 100644
index 00000000..3dbc1ca5
--- /dev/null
+++ b/tests/esm-import-assertions/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/tests/esm-import-assertions/tsconfig.json b/tests/esm-import-assertions/tsconfig.json
new file mode 100644
index 00000000..d626b927
--- /dev/null
+++ b/tests/esm-import-assertions/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "target": "ESNext",
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true
+ }
+}
diff --git a/tests/esm-loader-context/index.mjs b/tests/esm-loader-context/index.mjs
new file mode 100644
index 00000000..3def3324
--- /dev/null
+++ b/tests/esm-loader-context/index.mjs
@@ -0,0 +1,7 @@
+import * as moduleA from './moduleA.mjs';
+import * as moduleB from './moduleB.mjs' assert { foo: 'bar' };
+import * as jsonModule from './jsonModuleA.json' assert { type: 'json' };
+
+await import('./moduleC.mjs');
+await import('./moduleD.mjs', { foo: 'bar' });
+await import('./jsonModuleB.json', { assert: { type: 'json' } });
diff --git a/tests/esm-loader-context/jsonModuleA.json b/tests/esm-loader-context/jsonModuleA.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/tests/esm-loader-context/jsonModuleA.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/esm-loader-context/jsonModuleB.json b/tests/esm-loader-context/jsonModuleB.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/tests/esm-loader-context/jsonModuleB.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/esm-loader-context/loader.mjs b/tests/esm-loader-context/loader.mjs
new file mode 100644
index 00000000..d500b5a7
--- /dev/null
+++ b/tests/esm-loader-context/loader.mjs
@@ -0,0 +1,8 @@
+export function resolve(specifier, context, defaultResolve) {
+ console.log(JSON.stringify({ resolveContextKeys: Object.keys(context) }));
+ return defaultResolve(specifier, context);
+}
+export function load(url, context, defaultLoad) {
+ console.log(JSON.stringify({ loadContextKeys: Object.keys(context) }));
+ return defaultLoad(url, context);
+}
diff --git a/tests/esm-loader-context/moduleA.mjs b/tests/esm-loader-context/moduleA.mjs
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/esm-loader-context/moduleB.mjs b/tests/esm-loader-context/moduleB.mjs
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/esm-loader-context/moduleC.mjs b/tests/esm-loader-context/moduleC.mjs
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/esm-loader-context/moduleD.mjs b/tests/esm-loader-context/moduleD.mjs
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/spy-swc-transpiler.js b/tests/spy-swc-transpiler.js
new file mode 100644
index 00000000..99e77acc
--- /dev/null
+++ b/tests/spy-swc-transpiler.js
@@ -0,0 +1,16 @@
+// Spy on the swc transpiler so that tests can prove it was used rather than
+// TypeScript's `transpileModule`.
+const swcTranspiler = require('ts-node/transpilers/swc');
+
+global.swcTranspilerCalls = 0;
+
+const wrappedCreate = swcTranspiler.create;
+swcTranspiler.create = function (...args) {
+ const transpiler = wrappedCreate(...args);
+ const wrappedTranspile = transpiler.transpile;
+ transpiler.transpile = function (...args) {
+ global.swcTranspilerCalls++;
+ return wrappedTranspile.call(this, ...args);
+ };
+ return transpiler;
+};
diff --git a/tests/transpile-only-swc-shorthand-via-tsconfig/index.ts b/tests/transpile-only-swc-shorthand-via-tsconfig/index.ts
new file mode 100644
index 00000000..909fde69
--- /dev/null
+++ b/tests/transpile-only-swc-shorthand-via-tsconfig/index.ts
@@ -0,0 +1,13 @@
+// Test for #1343
+const Decorator = function () {};
+@Decorator
+class World {}
+
+// intentional type errors to check transpile-only ESM loader skips type checking
+parseInt(1101, 2);
+const x: number = `Hello ${World.name}! swc transpiler invocation count: ${global.swcTranspilerCalls}`;
+console.log(x);
+
+// test module type emit
+import { readFileSync } from 'fs';
+readFileSync;
diff --git a/tests/transpile-only-swc-shorthand-via-tsconfig/tsconfig.json b/tests/transpile-only-swc-shorthand-via-tsconfig/tsconfig.json
new file mode 100644
index 00000000..9dba8805
--- /dev/null
+++ b/tests/transpile-only-swc-shorthand-via-tsconfig/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "ts-node": {
+ "swc": true
+ },
+ "compilerOptions": {
+ "target": "ES2018",
+ "module": "CommonJS",
+ "allowJs": true,
+ "jsx": "react",
+ "experimentalDecorators": true
+ }
+}
diff --git a/tests/transpile-only-swc-via-tsconfig/index.ts b/tests/transpile-only-swc-via-tsconfig/index.ts
index b7dfa09c..909fde69 100644
--- a/tests/transpile-only-swc-via-tsconfig/index.ts
+++ b/tests/transpile-only-swc-via-tsconfig/index.ts
@@ -5,7 +5,7 @@ class World {}
// intentional type errors to check transpile-only ESM loader skips type checking
parseInt(1101, 2);
-const x: number = `Hello ${World.name}!`;
+const x: number = `Hello ${World.name}! swc transpiler invocation count: ${global.swcTranspilerCalls}`;
console.log(x);
// test module type emit
diff --git a/tests/transpile-only-swc-via-tsconfig/tsconfig.json b/tests/transpile-only-swc-via-tsconfig/tsconfig.json
index 5097d63a..863b6c4f 100644
--- a/tests/transpile-only-swc-via-tsconfig/tsconfig.json
+++ b/tests/transpile-only-swc-via-tsconfig/tsconfig.json
@@ -1,7 +1,7 @@
{
"ts-node": {
"transpileOnly": true,
- "transpiler": "ts-node/transpilers/swc-experimental"
+ "transpiler": "ts-node/transpilers/swc"
},
"compilerOptions": {
"target": "ES2018",
diff --git a/tests/transpile-only-swc/index.ts b/tests/transpile-only-swc/index.ts
index b7dfa09c..909fde69 100644
--- a/tests/transpile-only-swc/index.ts
+++ b/tests/transpile-only-swc/index.ts
@@ -5,7 +5,7 @@ class World {}
// intentional type errors to check transpile-only ESM loader skips type checking
parseInt(1101, 2);
-const x: number = `Hello ${World.name}!`;
+const x: number = `Hello ${World.name}! swc transpiler invocation count: ${global.swcTranspilerCalls}`;
console.log(x);
// test module type emit
diff --git a/transpilers/swc.js b/transpilers/swc.js
new file mode 100644
index 00000000..7cf79b13
--- /dev/null
+++ b/transpilers/swc.js
@@ -0,0 +1 @@
+module.exports = require('../dist/transpilers/swc')
diff --git a/tsconfig.json b/tsconfig.json
index bf59b8f8..158695d2 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,8 +1,9 @@
{
"$schema": "./tsconfig.schemastore-schema.json",
"compilerOptions": {
- "target": "es2015",
- "lib": ["es2015", "dom"],
+ // `target` and `lib` match @tsconfig/bases for node12, since that's the oldest node LTS, so it's the oldest node we support
+ "target": "es2019",
+ "lib": ["es2019", "es2020.promise", "es2020.bigint", "es2020.string", "dom"],
"rootDir": "src",
"outDir": "dist",
"module": "commonjs",
diff --git a/website/docs/configuration.md b/website/docs/configuration.md
index 5a1d43be..130a69eb 100644
--- a/website/docs/configuration.md
+++ b/website/docs/configuration.md
@@ -19,9 +19,9 @@ Hello, Ronald!
ts-node automatically finds and loads `tsconfig.json`. Most ts-node options can be specified in a `"ts-node"` object using their programmatic, camelCase names. We recommend this because it works even when you cannot pass CLI flags, such as `node --require ts-node/register` and when using shebangs.
-Use `--skip-project` to skip loading the `tsconfig.json`. Use `--project` to explicitly specify the path to a `tsconfig.json`.
+Use `--skipProject` to skip loading the `tsconfig.json`. Use `--project` to explicitly specify the path to a `tsconfig.json`.
-When searching, it is resolved using [the same search behavior as `tsc`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). By default, this search is performed relative to the entrypoint script. In `--cwd-mode` or if no entrypoint is specified -- for example when using the REPL -- the search is performed relative to `--cwd` / `process.cwd()`.
+When searching, it is resolved using [the same search behavior as `tsc`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). By default, this search is performed relative to the entrypoint script. In `--cwdMode` or if no entrypoint is specified -- for example when using the REPL -- the search is performed relative to `--cwd` / `process.cwd()`.
You can use this sample configuration as a starting point:
@@ -74,7 +74,7 @@ With the latest `node` and `typescript`, this is [`@tsconfig/node16`](https://gi
Older versions of `typescript` are incompatible with `@tsconfig/node16`. In those cases we will use an older default configuration.
-When in doubt, `ts-node --show-config` will log the configuration being used, and `ts-node -vv` will log `node` and `typescript` versions.
+When in doubt, `ts-node --showConfig` will log the configuration being used, and `ts-node -vv` will log `node` and `typescript` versions.
## `node` flags
diff --git a/website/docs/how-it-works.md b/website/docs/how-it-works.md
index 923c0592..f75f32c3 100644
--- a/website/docs/how-it-works.md
+++ b/website/docs/how-it-works.md
@@ -16,9 +16,16 @@ Vanilla `node` loads `.js` by reading code from disk and executing it. Our hook
## Skipping `node_modules`
-By default, **TypeScript Node** avoids compiling files in `/node_modules/` for three reasons:
+By default, ts-node avoids compiling files in `/node_modules/` for three reasons:
1. Modules should always be published in a format node.js can consume
2. Transpiling the entire dependency tree will make your project slower
3. Differing behaviours between TypeScript and node.js (e.g. ES2015 modules) can result in a project that works until you decide to support a feature natively from node.js
+If you need to import uncompiled TypeScript in `node_modules`, use [`--skipIgnore`](./options#transpilation) or [`TS_NODE_SKIP_IGNORE`](./options#transpilation) to bypass this restriction.
+
+## Skipping pre-compiled TypeScript
+
+If a compiled JavaScript file with the same name as a TypeScript file already exists, the TypeScript file will be ignored. ts-node will import the pre-compiled JavaScript.
+
+To force ts-node to import the TypeScript source, not the precompiled JavaScript, use [`--preferTsExts`](./options#transpilation).
diff --git a/website/docs/options.md b/website/docs/options.md
index 9139ea85..aadf36da 100644
--- a/website/docs/options.md
+++ b/website/docs/options.md
@@ -4,6 +4,8 @@ title: Options
`ts-node` supports `--print` (`-p`), `--eval` (`-e`), `--require` (`-r`) and `--interactive` (`-i`) similar to the [node.js CLI options](https://nodejs.org/api/cli.html).
+All command-line flags support both `--camelCase` and `--hyphen-case`.
+
_Environment variables, where available, are in `ALL_CAPS`_
## Shell
@@ -17,30 +19,31 @@ _Environment variables, where available, are in `ALL_CAPS`_
## TSConfig
- `-P, --project [path]` Path to TypeScript JSON project file
*Environment:* `TS_NODE_PROJECT`
-- `--skip-project` Skip project config resolution and loading
*Default:* `false`
*Environment:* `TS_NODE_SKIP_PROJECT`
-- `-c, --cwd-mode` Resolve config relative to the current directory instead of the directory of the entrypoint script
-- `-O, --compiler-options [opts]` JSON object to merge with compiler options
*Environment:* `TS_NODE_COMPILER_OPTIONS`
-- `--show-config` Print resolved `tsconfig.json`, including `ts-node` options, and exit
+- `--skipProject` Skip project config resolution and loading
*Default:* `false`
*Environment:* `TS_NODE_SKIP_PROJECT`
+- `-c, --cwdMode` Resolve config relative to the current directory instead of the directory of the entrypoint script
+- `-O, --compilerOptions [opts]` JSON object to merge with compiler options
*Environment:* `TS_NODE_COMPILER_OPTIONS`
+- `--showConfig` Print resolved `tsconfig.json`, including `ts-node` options, and exit
## Typechecking
-- `-T, --transpile-only` Use TypeScript's faster `transpileModule`
*Default:* `false`
*Environment:* `TS_NODE_TRANSPILE_ONLY`
-- `--type-check` Opposite of `--transpile-only`
*Default:* `true`
*Environment:* `TS_NODE_TYPE_CHECK`
-- `-H, --compiler-host` Use TypeScript's compiler host API
*Default:* `false`
*Environment:* `TS_NODE_COMPILER_HOST`
+- `-T, --transpileOnly` Use TypeScript's faster `transpileModule`
*Default:* `false`
*Environment:* `TS_NODE_TRANSPILE_ONLY`
+- `--typeCheck` Opposite of `--transpileOnly`
*Default:* `true`
*Environment:* `TS_NODE_TYPE_CHECK`
+- `-H, --compilerHost` Use TypeScript's compiler host API
*Default:* `false`
*Environment:* `TS_NODE_COMPILER_HOST`
- `--files` Load `files`, `include` and `exclude` from `tsconfig.json` on startup
*Default:* `false`
*Environment:* `TS_NODE_FILES`
-- `-D, --ignore-diagnostics [code]` Ignore TypeScript warnings by diagnostic code
*Environment:* `TS_NODE_IGNORE_DIAGNOSTICS`
+- `-D, --ignoreDiagnostics [code]` Ignore TypeScript warnings by diagnostic code
*Environment:* `TS_NODE_IGNORE_DIAGNOSTICS`
## Transpilation
- `-I, --ignore [pattern]` Override the path patterns to skip compilation
*Default:* `/node_modules/`
*Environment:* `TS_NODE_IGNORE`
-- `--skip-ignore` Skip ignore checks
*Default:* `false`
*Environment:* `TS_NODE_SKIP_IGNORE`
+- `--skipIgnore` Skip ignore checks
*Default:* `false`
*Environment:* `TS_NODE_SKIP_IGNORE`
- `-C, --compiler [name]` Specify a custom TypeScript compiler
*Default:* `typescript`
*Environment:* `TS_NODE_COMPILER`
+- `--swc` Transpile with [swc](./transpilers.md#swc). Implies `--transpileOnly`
*Default:* `false`
- `--transpiler [name]` Specify a third-party, non-typechecking transpiler
-- `--prefer-ts-exts` Re-order file extensions so that TypeScript imports are preferred
*Default:* `false`
*Environment:* `TS_NODE_PREFER_TS_EXTS`
+- `--preferTsExts` Re-order file extensions so that TypeScript imports are preferred
*Default:* `false`
*Environment:* `TS_NODE_PREFER_TS_EXTS`
## Diagnostics
-- `--log-error` Logs TypeScript errors to stderr instead of throwing exceptions
*Default:* `false`
*Environment:* `TS_NODE_LOG_ERROR`
+- `--logError` Logs TypeScript errors to stderr instead of throwing exceptions
*Default:* `false`
*Environment:* `TS_NODE_LOG_ERROR`
- `--pretty` Use pretty diagnostic formatter
*Default:* `false`
*Environment:* `TS_NODE_PRETTY`
- `TS_NODE_DEBUG` Enable debug logging
@@ -51,9 +54,10 @@ _Environment variables, where available, are in `ALL_CAPS`_
- `--emit` Emit output files into `.ts-node` directory
*Default:* `false`
*Environment:* `TS_NODE_EMIT`
- `--scope` Scope compiler to files within `scopeDir`. Anything outside this directory is ignored.
*Default: `false`
*Environment:* `TS_NODE_SCOPE`
- `--scopeDir` Directory within which compiler is limited when `scope` is enabled.
*Default:* First of: `tsconfig.json` "rootDir" if specified, directory containing `tsconfig.json`, or cwd if no `tsconfig.json` is loaded.
*Environment:* `TS_NODE_SCOPE_DIR`
-- `moduleType` Override the module type of certain files, ignoring the `package.json` `"type"` field. See [Module type overrides](./module-type-overrides.md) for details.
*Default:* obeys `package.json` `"type"` and `tsconfig.json` `"module"`
*Can only be specified via `tsconfig.json` or API.*
+- `moduleTypes` Override the module type of certain files, ignoring the `package.json` `"type"` field. See [Module type overrides](./module-type-overrides.md) for details.
*Default:* obeys `package.json` `"type"` and `tsconfig.json` `"module"`
*Can only be specified via `tsconfig.json` or API.*
- `TS_NODE_HISTORY` Path to history file for REPL
*Default:* `~/.ts_node_repl_history`
-- `--no-experimental-repl-await` Disable top-level await in REPL. Equivalent to node's [`--no-experimental-repl-await`](https://nodejs.org/api/cli.html#cli_no_experimental_repl_await)
*Default:* Enabled if TypeScript version is 3.8 or higher and target is ES2018 or higher.
*Environment:* `TS_NODE_EXPERIMENTAL_REPL_AWAIT` set `false` to disable
+- `--noExperimentalReplAwait` Disable top-level await in REPL. Equivalent to node's [`--no-experimental-repl-await`](https://nodejs.org/api/cli.html#cli_no_experimental_repl_await)
*Default:* Enabled if TypeScript version is 3.8 or higher and target is ES2018 or higher.
*Environment:* `TS_NODE_EXPERIMENTAL_REPL_AWAIT` set `false` to disable
+- `experimentalResolverFeatures` Enable experimental features that re-map imports and require calls to support: `baseUrl`, `paths`, `rootDirs`, `.js` to `.ts` file extension mappings, `outDir` to `rootDir` mappings for composite projects and monorepos. For details, see [#1514](https://github.com/TypeStrong/ts-node/issues/1514)
*Default:* `false`
*Can only be specified via `tsconfig.json` or API.*
## API
diff --git a/website/docs/performance.md b/website/docs/performance.md
index a875bd2e..69b2c6cb 100644
--- a/website/docs/performance.md
+++ b/website/docs/performance.md
@@ -9,7 +9,7 @@ These tricks will make ts-node faster.
It is often better to use `tsc --noEmit` to typecheck once before your tests run or as a lint step. In these cases, ts-node can skip typechecking.
* Enable [`transpileOnly`](./options.md) to skip typechecking
-* Use our [`swc` integration](./transpilers.md#bundled-swc-integration)
+* Use our [`swc` integration](./transpilers.md#swc)
* This is by far the fastest option
## With typechecking
diff --git a/website/docs/transpilers.md b/website/docs/transpilers.md
index eb80fd11..6d6f3709 100644
--- a/website/docs/transpilers.md
+++ b/website/docs/transpilers.md
@@ -1,5 +1,5 @@
---
-title: Third-party transpilers
+title: Transpilers
---
In transpile-only mode, we skip typechecking to speed up execution time. You can go a step further and use a
@@ -13,12 +13,11 @@ boilerplate.
> For our purposes, a compiler implements TypeScript's API and can perform typechecking.
> A third-party transpiler does not. Both transform TypeScript into JavaScript.
-## Bundled `swc` integration
+## swc
-We have bundled an experimental `swc` integration.
+swc support is built-in via the `--swc` flag or `"swc": true` tsconfig option.
-[`swc`](https://swc.rs) is a TypeScript-compatible transpiler implemented in Rust. This makes it an order of magnitude faster
-than `transpileModule`.
+[`swc`](https://swc.rs) is a TypeScript-compatible transpiler implemented in Rust. This makes it an order of magnitude faster than vanilla `transpileOnly`.
To use it, first install `@swc/core` or `@swc/wasm`. If using `importHelpers`, also install `@swc/helpers`. If `target` is less than "es2015" and using either `async`/`await` or generator functions, also install `regenerator-runtime`.
@@ -31,17 +30,35 @@ Then add the following to your `tsconfig.json`.
```json title="tsconfig.json"
{
"ts-node": {
- "transpileOnly": true,
- "transpiler": "ts-node/transpilers/swc-experimental"
+ "swc": true
}
}
```
> `swc` uses `@swc/helpers` instead of `tslib`. If you have enabled `importHelpers`, you must also install `@swc/helpers`.
+## Third-party transpilers
+
+The `transpiler` option allows using third-party transpiler integrations with ts-node. `transpiler` must be given the
+name of a module which can be `require()`d. The built-in `swc` integration is exposed as `ts-node/transpilers/swc`.
+
+For example, to use a hypothetical "speedy-ts-compiler", first install it into your project: `npm install speedy-ts-compiler`
+
+Then add the following to your tsconfig:
+
+```json title="tsconfig.json"
+{
+ "ts-node": {
+ "transpileOnly": true,
+ "transpiler": "speedy-ts-compiler"
+ }
+}
+```
+
## Writing your own integration
To write your own transpiler integration, check our [API docs](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html).
-Integrations are `require()`d, so they can be published to npm. The module must export a `create` function matching the
-[`TranspilerModule`](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html) interface.
+Integrations are `require()`d by ts-node, so they can be published to npm for convenience. The module must export a `create` function described by our
+[`TranspilerModule`](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html) interface. `create` is invoked by ts-node
+at startup to create the transpiler. The transpiler is used repeatedly to transform TypeScript into JavaScript.
diff --git a/website/docs/troubleshooting.md b/website/docs/troubleshooting.md
index 2ee75225..2e2125c1 100644
--- a/website/docs/troubleshooting.md
+++ b/website/docs/troubleshooting.md
@@ -5,7 +5,7 @@ title: Troubleshooting
## Understanding configuration
ts-node uses sensible default configurations to reduce boilerplate while still respecting `tsconfig.json` if you
-have one. If you are unsure which configuration is used, you can log it with `ts-node --show-config`. This is similar to
+have one. If you are unsure which configuration is used, you can log it with `ts-node --showConfig`. This is similar to
`tsc --showConfig` but includes `"ts-node"` options as well.
ts-node also respects your locally-installed `typescript` version, but global installations fallback to the globally-installed
@@ -17,7 +17,7 @@ ts-node v10.0.0
node v16.1.0
compiler v4.2.2
-$ ts-node --show-config
+$ ts-node --showConfig
{
"compilerOptions": {
"target": "es6",
diff --git a/website/docs/usage.md b/website/docs/usage.md
index ecd6721b..d988e606 100644
--- a/website/docs/usage.md
+++ b/website/docs/usage.md
@@ -20,10 +20,10 @@ ts-node -p -e '"Hello, world!"'
# Pipe scripts to execute with TypeScript.
echo 'console.log("Hello, world!")' | ts-node
-# Equivalent to ts-node --transpile-only
+# Equivalent to ts-node --transpileOnly
ts-node-transpile-only script.ts
-# Equivalent to ts-node --cwd-mode
+# Equivalent to ts-node --cwdMode
ts-node-cwd script.ts
```
@@ -35,14 +35,27 @@ ts-node-cwd script.ts
console.log("Hello, world!")
```
-Passing CLI arguments via shebang is allowed on Mac but not Linux. For example, the following will fail on Linux:
+Passing options via shebang requires the [`env -S` flag](https://manpages.debian.org/bullseye/coreutils/env.1.en.html#S), which is available on recent versions of `env`. ([compatibility](https://github.com/TypeStrong/ts-node/pull/1448#issuecomment-913895766))
+```typescript
+#!/usr/bin/env -S ts-node --files
+// This shebang works on Mac and Linux with newer versions of env
+// Technically, Mac allows omitting `-S`, but Linux requires it
```
-#!/usr/bin/env ts-node --files
-// This shebang is not portable. It only works on Mac
+
+To write scripts with maximum portability, [specify all options in your `tsconfig.json`](./configuration#via-tsconfigjson-recommended) and omit them from the shebang.
+
+```typescript
+#!/usr/bin/env ts-node
+// This shebang works everywhere
```
-Instead, specify all ts-node options in your `tsconfig.json`.
+To test your version of `env` for compatibility:
+
+```shell
+# Note that these unusual quotes are necessary
+/usr/bin/env --debug '-S echo foo bar'
+```
## Programmatic
diff --git a/website/readme-sources/prefix.md b/website/readme-sources/prefix.md
index a24738ff..f797087f 100644
--- a/website/readme-sources/prefix.md
+++ b/website/readme-sources/prefix.md
@@ -14,7 +14,7 @@ You can build the readme with this command:
cd website && yarn build-readme
-->
-# 
+# [](https://typestrong.org/ts-node)
[](https://npmjs.org/package/ts-node)
[](https://npmjs.org/package/ts-node)