Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nodenext polish #1757

Merged
merged 8 commits into from May 20, 2022
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 @@ -101,7 +101,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 @@ -25,6 +25,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 @@ -141,6 +142,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
'--scope': Boolean,
'--scopeDir': String,
'--noExperimentalReplAwait': Boolean,
'--experimentalSpecifierResolution': String,

// Aliases.
'-e': '--eval',
Expand Down Expand Up @@ -174,6 +176,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 @@ -216,6 +220,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 @@ -254,6 +259,7 @@ function parseArgv(argv: string[], entrypointArgs: Record<string, any>) {
scope,
scopeDir,
noExperimentalReplAwait,
experimentalSpecifierResolution,
esm,
};
}
Expand Down Expand Up @@ -301,6 +307,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 @@ -362,6 +370,8 @@ function phase3(payload: BootstrapState) {
argsRequire,
scope,
scopeDir,
esm,
experimentalSpecifierResolution,
} = payload.parseArgvResult;
const { cwd, scriptPath } = payload.phase2Result!;

Expand Down Expand Up @@ -389,6 +399,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 @@ -99,6 +99,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 @@ -142,6 +143,7 @@ export function getExtensions(
legacyMainResolveAddsIfOmitted,
replacementsForMjs,
replacementsForCjs,
replacementsForJsx,
replacementsForJs,
};
}
9 changes: 6 additions & 3 deletions src/index.ts
Expand Up @@ -393,6 +393,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 @@ -420,9 +426,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.