Skip to content

Commit 6f66541

Browse files
authored
Nodenext polish (#1757)
* 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
1 parent c6010aa commit 6f66541

7 files changed

+57
-12
lines changed

dist-raw/node-internal-modules-cjs-loader.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function readPackageScope(checkPath) {
144144
*/
145145
function createCjsLoader(opts) {
146146
const {nodeEsmResolver, preferTsExts} = opts;
147-
const {replacementsForCjs, replacementsForJs, replacementsForMjs} = opts.extensions;
147+
const {replacementsForCjs, replacementsForJs, replacementsForMjs, replacementsForJsx} = opts.extensions;
148148
const {
149149
encodedSepRegEx,
150150
packageExportsResolve,
@@ -219,10 +219,11 @@ function statReplacementExtensions(p) {
219219
const lastDotIndex = p.lastIndexOf('.');
220220
if(lastDotIndex >= 0) {
221221
const ext = p.slice(lastDotIndex);
222-
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
222+
if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
223223
const pathnameWithoutExtension = p.slice(0, lastDotIndex);
224224
const replacementExts =
225225
ext === '.js' ? replacementsForJs
226+
: ext === '.jsx' ? replacementsForJsx
226227
: ext === '.mjs' ? replacementsForMjs
227228
: replacementsForCjs;
228229
for (let i = 0; i < replacementExts.length; i++) {
@@ -240,10 +241,11 @@ function tryReplacementExtensions(p, isMain) {
240241
const lastDotIndex = p.lastIndexOf('.');
241242
if(lastDotIndex >= 0) {
242243
const ext = p.slice(lastDotIndex);
243-
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
244+
if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
244245
const pathnameWithoutExtension = p.slice(0, lastDotIndex);
245246
const replacementExts =
246247
ext === '.js' ? replacementsForJs
248+
: ext === '.jsx' ? replacementsForJsx
247249
: ext === '.mjs' ? replacementsForMjs
248250
: replacementsForCjs;
249251
for (let i = 0; i < replacementExts.length; i++) {

dist-raw/node-internal-modules-esm-resolve.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function createResolve(opts) {
9292
// TODO receive cached fs implementations here
9393
const {preferTsExts, tsNodeExperimentalSpecifierResolution, extensions} = opts;
9494
const esrnExtensions = extensions.experimentalSpecifierResolutionAddsIfOmitted;
95-
const {legacyMainResolveAddsIfOmitted, replacementsForCjs, replacementsForJs, replacementsForMjs} = extensions;
95+
const {legacyMainResolveAddsIfOmitted, replacementsForCjs, replacementsForJs, replacementsForMjs, replacementsForJsx} = extensions;
9696
// const experimentalSpecifierResolution = tsNodeExperimentalSpecifierResolution ?? getOptionValue('--experimental-specifier-resolution');
9797
const experimentalSpecifierResolution = tsNodeExperimentalSpecifierResolution != null ? tsNodeExperimentalSpecifierResolution : getOptionValue('--experimental-specifier-resolution');
9898

@@ -310,10 +310,11 @@ function resolveReplacementExtensions(search) {
310310
const lastDotIndex = search.pathname.lastIndexOf('.');
311311
if(lastDotIndex >= 0) {
312312
const ext = search.pathname.slice(lastDotIndex);
313-
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
313+
if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
314314
const pathnameWithoutExtension = search.pathname.slice(0, lastDotIndex);
315315
const replacementExts =
316316
ext === '.js' ? replacementsForJs
317+
: ext === '.jsx' ? replacementsForJsx
317318
: ext === '.mjs' ? replacementsForMjs
318319
: replacementsForCjs;
319320
const guess = new URL(search.toString());

src/bin.ts

+13
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
createEsmHooks,
2525
createFromPreloadedConfig,
2626
DEFAULTS,
27+
ExperimentalSpecifierResolution,
2728
} from './index';
2829
import type { TSInternal } from './ts-compiler-types';
2930
import { addBuiltinLibsToObject } from '../dist-raw/node-internal-modules-cjs-helpers';
@@ -140,6 +141,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
140141
'--scope': Boolean,
141142
'--scopeDir': String,
142143
'--noExperimentalReplAwait': Boolean,
144+
'--experimentalSpecifierResolution': String,
143145

144146
// Aliases.
145147
'-e': '--eval',
@@ -173,6 +175,8 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
173175
'--log-error': '--logError',
174176
'--scope-dir': '--scopeDir',
175177
'--no-experimental-repl-await': '--noExperimentalReplAwait',
178+
'--experimental-specifier-resolution':
179+
'--experimentalSpecifierResolution',
176180
},
177181
{
178182
argv,
@@ -215,6 +219,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
215219
'--scope': scope = undefined,
216220
'--scopeDir': scopeDir = undefined,
217221
'--noExperimentalReplAwait': noExperimentalReplAwait,
222+
'--experimentalSpecifierResolution': experimentalSpecifierResolution,
218223
'--esm': esm,
219224
_: restArgs,
220225
} = args;
@@ -253,6 +258,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
253258
scope,
254259
scopeDir,
255260
noExperimentalReplAwait,
261+
experimentalSpecifierResolution,
256262
esm,
257263
};
258264
}
@@ -300,6 +306,8 @@ Options:
300306
--preferTsExts Prefer importing TypeScript files over JavaScript files
301307
--logError Logs TypeScript errors to stderr instead of throwing exceptions
302308
--noExperimentalReplAwait Disable top-level await in REPL. Equivalent to node's --no-experimental-repl-await
309+
--experimentalSpecifierResolution [node|explicit]
310+
Equivalent to node's --experimental-specifier-resolution
303311
`);
304312

305313
process.exit(0);
@@ -361,6 +369,8 @@ function phase3(payload: BootstrapState) {
361369
argsRequire,
362370
scope,
363371
scopeDir,
372+
esm,
373+
experimentalSpecifierResolution,
364374
} = payload.parseArgvResult;
365375
const { cwd, scriptPath } = payload.phase2Result!;
366376

@@ -388,6 +398,9 @@ function phase3(payload: BootstrapState) {
388398
scope,
389399
scopeDir,
390400
preferTsExts,
401+
esm,
402+
experimentalSpecifierResolution:
403+
experimentalSpecifierResolution as ExperimentalSpecifierResolution,
391404
});
392405

393406
if (preloadedConfig.options.esm) payload.shouldUseChildProcess = true;

src/file-extensions.ts

+2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export function getExtensions(
100100
const replacementsForJs = r.filter((ext) =>
101101
['.js', '.jsx', '.ts', '.tsx'].includes(ext)
102102
);
103+
const replacementsForJsx = r.filter((ext) => ['.jsx', '.tsx'].includes(ext));
103104
const replacementsForMjs = r.filter((ext) => ['.mjs', '.mts'].includes(ext));
104105
const replacementsForCjs = r.filter((ext) => ['.cjs', '.cts'].includes(ext));
105106
const replacementsForJsOrMjs = r.filter((ext) =>
@@ -143,6 +144,7 @@ export function getExtensions(
143144
legacyMainResolveAddsIfOmitted,
144145
replacementsForMjs,
145146
replacementsForCjs,
147+
replacementsForJsx,
146148
replacementsForJs,
147149
};
148150
}

src/index.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,12 @@ export interface CreateOptions {
367367
* @default false
368368
*/
369369
preferTsExts?: boolean;
370+
/**
371+
* Like node's `--experimental-specifier-resolution`, , but can also be set in your `tsconfig.json` for convenience.
372+
*
373+
* For details, see https://nodejs.org/dist/latest-v18.x/docs/api/esm.html#customizing-esm-specifier-resolution-algorithm
374+
*/
375+
experimentalSpecifierResolution?: 'node' | 'explicit';
370376
}
371377

372378
export type ModuleTypes = Record<string, ModuleTypeOverride>;
@@ -394,9 +400,6 @@ export interface RegisterOptions extends CreateOptions {
394400
* For details, see https://github.com/TypeStrong/ts-node/issues/1514
395401
*/
396402
experimentalResolver?: boolean;
397-
398-
/** @internal */
399-
experimentalSpecifierResolution?: 'node' | 'explicit';
400403
}
401404

402405
export type ExperimentalSpecifierResolution = 'node' | 'explicit';

website/docs/module-type-overrides.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
title: Module type overrides
33
---
44

5-
> 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
6-
in this section. `NodeNext`, `.mts`, and `.cts` should work well for most projects.
5+
> 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
6+
in this section. Setting `"module": "NodeNext"` and using the `.cts` file extension should work well for most projects.
77

88
When deciding how a file should be compiled and executed -- as either CommonJS or native ECMAScript module -- ts-node matches
99
`node` and `tsc` behavior. This means TypeScript files are transformed according to your `tsconfig.json` `"module"`

website/docs/options.md

+26-2
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,35 @@ Disable top-level await in REPL. Equivalent to node's [`--no-experimental-repl-
369369

370370
### experimentalResolver
371371

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

374-
*Default:* `false`<br/>
374+
* resolves `.js` to `.ts`, so that `import "./foo.js"` will execute `foo.ts`
375+
* resolves `.cjs` to `.cts`
376+
* resolves `.mjs` to `.mts`
377+
* allows including file extensions in CommonJS, for consistency with ESM where this is often mandatory
378+
379+
In the future, this hook will also support:
380+
381+
* `baseUrl`, `paths`
382+
* `rootDirs`
383+
* `outDir` to `rootDir` mappings for composite projects and monorepos
384+
385+
For details, see [#1514](https://github.com/TypeStrong/ts-node/issues/1514).
386+
387+
*Default:* `false`, but will likely be enabled by default in a future version<br/>
375388
*Can only be specified via `tsconfig.json` or API.*
376389

390+
### experimentalSpecifierResolution
391+
392+
```shell
393+
ts-node --experimentalSpecifierResolution node
394+
```
395+
396+
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.
397+
Requires `esm` to be enabled.
398+
399+
*Default:* `explicit`<br/>
400+
377401
## API Options
378402

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

0 commit comments

Comments
 (0)