Skip to content

Commit

Permalink
Nodenext polish (#1757)
Browse files Browse the repository at this point in the history
* Add experimentalSpecifierResolution to CLI flags, tsconfig, public API

* Make moduleTypes page link to https://www.typescriptlang.org/docs/handbook/esm-node.html

* Allow .jsx imports to remap to .tsx, the same way .js can map to .tsx

* improve experimentalResolver docs

* again tweak moduleTypeOverrides doc

* lint-fix

* tweak `experimentalResolver` docs
  • Loading branch information
cspotcode committed May 20, 2022
1 parent c6010aa commit 6f66541
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 12 deletions.
8 changes: 5 additions & 3 deletions dist-raw/node-internal-modules-cjs-loader.js
Expand Up @@ -144,7 +144,7 @@ function readPackageScope(checkPath) {
*/
function createCjsLoader(opts) {
const {nodeEsmResolver, preferTsExts} = opts;
const {replacementsForCjs, replacementsForJs, replacementsForMjs} = opts.extensions;
const {replacementsForCjs, replacementsForJs, replacementsForMjs, replacementsForJsx} = opts.extensions;
const {
encodedSepRegEx,
packageExportsResolve,
Expand Down Expand Up @@ -219,10 +219,11 @@ function statReplacementExtensions(p) {
const lastDotIndex = p.lastIndexOf('.');
if(lastDotIndex >= 0) {
const ext = p.slice(lastDotIndex);
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
const pathnameWithoutExtension = p.slice(0, lastDotIndex);
const replacementExts =
ext === '.js' ? replacementsForJs
: ext === '.jsx' ? replacementsForJsx
: ext === '.mjs' ? replacementsForMjs
: replacementsForCjs;
for (let i = 0; i < replacementExts.length; i++) {
Expand All @@ -240,10 +241,11 @@ function tryReplacementExtensions(p, isMain) {
const lastDotIndex = p.lastIndexOf('.');
if(lastDotIndex >= 0) {
const ext = p.slice(lastDotIndex);
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
const pathnameWithoutExtension = p.slice(0, lastDotIndex);
const replacementExts =
ext === '.js' ? replacementsForJs
: ext === '.jsx' ? replacementsForJsx
: ext === '.mjs' ? replacementsForMjs
: replacementsForCjs;
for (let i = 0; i < replacementExts.length; i++) {
Expand Down
5 changes: 3 additions & 2 deletions dist-raw/node-internal-modules-esm-resolve.js
Expand Up @@ -92,7 +92,7 @@ function createResolve(opts) {
// TODO receive cached fs implementations here
const {preferTsExts, tsNodeExperimentalSpecifierResolution, extensions} = opts;
const esrnExtensions = extensions.experimentalSpecifierResolutionAddsIfOmitted;
const {legacyMainResolveAddsIfOmitted, replacementsForCjs, replacementsForJs, replacementsForMjs} = extensions;
const {legacyMainResolveAddsIfOmitted, replacementsForCjs, replacementsForJs, replacementsForMjs, replacementsForJsx} = extensions;
// const experimentalSpecifierResolution = tsNodeExperimentalSpecifierResolution ?? getOptionValue('--experimental-specifier-resolution');
const experimentalSpecifierResolution = tsNodeExperimentalSpecifierResolution != null ? tsNodeExperimentalSpecifierResolution : getOptionValue('--experimental-specifier-resolution');

Expand Down Expand Up @@ -310,10 +310,11 @@ function resolveReplacementExtensions(search) {
const lastDotIndex = search.pathname.lastIndexOf('.');
if(lastDotIndex >= 0) {
const ext = search.pathname.slice(lastDotIndex);
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
const pathnameWithoutExtension = search.pathname.slice(0, lastDotIndex);
const replacementExts =
ext === '.js' ? replacementsForJs
: ext === '.jsx' ? replacementsForJsx
: ext === '.mjs' ? replacementsForMjs
: replacementsForCjs;
const guess = new URL(search.toString());
Expand Down
13 changes: 13 additions & 0 deletions src/bin.ts
Expand Up @@ -24,6 +24,7 @@ import {
createEsmHooks,
createFromPreloadedConfig,
DEFAULTS,
ExperimentalSpecifierResolution,
} from './index';
import type { TSInternal } from './ts-compiler-types';
import { addBuiltinLibsToObject } from '../dist-raw/node-internal-modules-cjs-helpers';
Expand Down Expand Up @@ -140,6 +141,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
'--scope': Boolean,
'--scopeDir': String,
'--noExperimentalReplAwait': Boolean,
'--experimentalSpecifierResolution': String,

// Aliases.
'-e': '--eval',
Expand Down Expand Up @@ -173,6 +175,8 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
'--log-error': '--logError',
'--scope-dir': '--scopeDir',
'--no-experimental-repl-await': '--noExperimentalReplAwait',
'--experimental-specifier-resolution':
'--experimentalSpecifierResolution',
},
{
argv,
Expand Down Expand Up @@ -215,6 +219,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
'--scope': scope = undefined,
'--scopeDir': scopeDir = undefined,
'--noExperimentalReplAwait': noExperimentalReplAwait,
'--experimentalSpecifierResolution': experimentalSpecifierResolution,
'--esm': esm,
_: restArgs,
} = args;
Expand Down Expand Up @@ -253,6 +258,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
scope,
scopeDir,
noExperimentalReplAwait,
experimentalSpecifierResolution,
esm,
};
}
Expand Down Expand Up @@ -300,6 +306,8 @@ Options:
--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
--experimentalSpecifierResolution [node|explicit]
Equivalent to node's --experimental-specifier-resolution
`);

process.exit(0);
Expand Down Expand Up @@ -361,6 +369,8 @@ function phase3(payload: BootstrapState) {
argsRequire,
scope,
scopeDir,
esm,
experimentalSpecifierResolution,
} = payload.parseArgvResult;
const { cwd, scriptPath } = payload.phase2Result!;

Expand Down Expand Up @@ -388,6 +398,9 @@ function phase3(payload: BootstrapState) {
scope,
scopeDir,
preferTsExts,
esm,
experimentalSpecifierResolution:
experimentalSpecifierResolution as ExperimentalSpecifierResolution,
});

if (preloadedConfig.options.esm) payload.shouldUseChildProcess = true;
Expand Down
2 changes: 2 additions & 0 deletions src/file-extensions.ts
Expand Up @@ -100,6 +100,7 @@ export function getExtensions(
const replacementsForJs = r.filter((ext) =>
['.js', '.jsx', '.ts', '.tsx'].includes(ext)
);
const replacementsForJsx = r.filter((ext) => ['.jsx', '.tsx'].includes(ext));
const replacementsForMjs = r.filter((ext) => ['.mjs', '.mts'].includes(ext));
const replacementsForCjs = r.filter((ext) => ['.cjs', '.cts'].includes(ext));
const replacementsForJsOrMjs = r.filter((ext) =>
Expand Down Expand Up @@ -143,6 +144,7 @@ export function getExtensions(
legacyMainResolveAddsIfOmitted,
replacementsForMjs,
replacementsForCjs,
replacementsForJsx,
replacementsForJs,
};
}
9 changes: 6 additions & 3 deletions src/index.ts
Expand Up @@ -367,6 +367,12 @@ export interface CreateOptions {
* @default false
*/
preferTsExts?: boolean;
/**
* Like node's `--experimental-specifier-resolution`, , but can also be set in your `tsconfig.json` for convenience.
*
* For details, see https://nodejs.org/dist/latest-v18.x/docs/api/esm.html#customizing-esm-specifier-resolution-algorithm
*/
experimentalSpecifierResolution?: 'node' | 'explicit';
}

export type ModuleTypes = Record<string, ModuleTypeOverride>;
Expand Down Expand Up @@ -394,9 +400,6 @@ export interface RegisterOptions extends CreateOptions {
* For details, see https://github.com/TypeStrong/ts-node/issues/1514
*/
experimentalResolver?: boolean;

/** @internal */
experimentalSpecifierResolution?: 'node' | 'explicit';
}

export type ExperimentalSpecifierResolution = 'node' | 'explicit';
Expand Down
4 changes: 2 additions & 2 deletions website/docs/module-type-overrides.md
Expand Up @@ -2,8 +2,8 @@
title: Module type overrides
---

> Wherever possible, it is recommended to use TypeScript's [`NodeNext` or `Node16` mode](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-rc/#ecmascript-module-support-in-node-js) instead of the options described
in this section. `NodeNext`, `.mts`, and `.cts` should work well for most projects.
> Wherever possible, it is recommended to use TypeScript's [`NodeNext` or `Node16` mode](https://www.typescriptlang.org/docs/handbook/esm-node.html) instead of the options described
in this section. Setting `"module": "NodeNext"` and using the `.cts` file extension should work well for most projects.

When deciding how a file should be compiled and executed -- as either CommonJS or native ECMAScript module -- ts-node matches
`node` and `tsc` behavior. This means TypeScript files are transformed according to your `tsconfig.json` `"module"`
Expand Down
28 changes: 26 additions & 2 deletions website/docs/options.md
Expand Up @@ -369,11 +369,35 @@ Disable top-level await in REPL. Equivalent to node's [`--no-experimental-repl-

### experimentalResolver

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)
Enable experimental hooks that re-map imports and require calls to support:

*Default:* `false`<br/>
* resolves `.js` to `.ts`, so that `import "./foo.js"` will execute `foo.ts`
* resolves `.cjs` to `.cts`
* resolves `.mjs` to `.mts`
* allows including file extensions in CommonJS, for consistency with ESM where this is often mandatory

In the future, this hook will also support:

* `baseUrl`, `paths`
* `rootDirs`
* `outDir` to `rootDir` mappings for composite projects and monorepos

For details, see [#1514](https://github.com/TypeStrong/ts-node/issues/1514).

*Default:* `false`, but will likely be enabled by default in a future version<br/>
*Can only be specified via `tsconfig.json` or API.*

### experimentalSpecifierResolution

```shell
ts-node --experimentalSpecifierResolution node
```

Like node's [`--experimental-specifier-resolution`](https://nodejs.org/dist/latest-v18.x/docs/api/esm.html#customizing-esm-specifier-resolution-algorithm), but can also be set in your `tsconfig.json` for convenience.
Requires `esm` to be enabled.

*Default:* `explicit`<br/>

## API Options

The API includes [additional options](https://typestrong.org/ts-node/api/interfaces/RegisterOptions.html) not shown here.

0 comments on commit 6f66541

Please sign in to comment.