Skip to content

Commit 1bc470d

Browse files
Add moduleType option to override module type on certain files. (#1371)
* Add moduleType option to override module type on certain files. Also refactor resolverFunctions into their own file; should break this into 2x PRs later. * lint fix * add test * remove unnecessary exports from ts-internals; mark exports as @internal * add docs * strip optionBasePaths from showConfig output * proper path normalization to support windows * lint-fix * use es2015 instead of es2020 in test tsconfig to support ts2.7 * add missing path normalization when calling classifyModule * Test coverage: moduleType overrides during ts-node loader usage (#1376) * Test coverage: add test case to confirm that moduleType overrides are applied for ts-node in loader mode * Ensure that a default export exists for the esm-exception module * Re-order tsconfig.json glob rules, and use implicit globbing * lint fixup: apply prettier * Add 'experimental-specifier-resolution' flag to NPM options in ESM module override test * Ensure that a default export exists for the cjs-subdir module * Revert "Ensure that a default export exists for the cjs-subdir module" This reverts commit c64cf92. * Revert "Add 'experimental-specifier-resolution' flag to NPM options in ESM module override test" This reverts commit 1093df8. * Specify tsconfig project using TS_NODE_PROJECT environment variable * Use full file paths in preference to directory-default module imports This avoids ERR_UNSUPPORTED_DIR_IMPORT, without having to provide the 'experimental-specifier-resolution' flag to ts-node * Update index.ts * Update index.ts * Update tsconfig.json * Extract execModuleTypeOverride function * Add expected failure cases for Node 12.15, 14.13 Co-authored-by: Andrew Bradley <[email protected]> * Update tests * fix * fix * fix for TS2.7 * fix * fix * reword * update docs * address todos * fix Co-authored-by: James Addison <[email protected]>
1 parent 4e7fcb7 commit 1bc470d

28 files changed

+980
-223
lines changed

dist-raw/node-cjs-loader-utils.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,28 @@
55
const path = require('path');
66
const packageJsonReader = require('./node-package-json-reader');
77
const {JSONParse} = require('./node-primordials');
8+
const {normalizeSlashes} = require('../dist/util');
89

910
module.exports.assertScriptCanLoadAsCJSImpl = assertScriptCanLoadAsCJSImpl;
1011

11-
// copied from Module._extensions['.js']
12-
// https://github.com/nodejs/node/blob/v15.3.0/lib/internal/modules/cjs/loader.js#L1113-L1120
13-
function assertScriptCanLoadAsCJSImpl(filename) {
12+
/**
13+
* copied from Module._extensions['.js']
14+
* https://github.com/nodejs/node/blob/v15.3.0/lib/internal/modules/cjs/loader.js#L1113-L1120
15+
* @param {import('../src/index').Service} service
16+
* @param {NodeJS.Module} module
17+
* @param {string} filename
18+
*/
19+
function assertScriptCanLoadAsCJSImpl(service, module, filename) {
1420
const pkg = readPackageScope(filename);
21+
22+
// ts-node modification: allow our configuration to override
23+
const tsNodeClassification = service.moduleTypeClassifier.classifyModule(normalizeSlashes(filename));
24+
if(tsNodeClassification.moduleType === 'cjs') return;
25+
1526
// Function require shouldn't be used in ES modules.
16-
if (pkg && pkg.data && pkg.data.type === 'module') {
27+
if (tsNodeClassification.moduleType === 'esm' || (pkg && pkg.data && pkg.data.type === 'module')) {
1728
const parentPath = module.parent && module.parent.filename;
18-
const packageJsonPath = path.resolve(pkg.path, 'package.json');
29+
const packageJsonPath = pkg ? path.resolve(pkg.path, 'package.json') : null;
1930
throw createErrRequireEsm(filename, parentPath, packageJsonPath);
2031
}
2132
}

src/bin.ts

+1
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ export function main(
296296
const json = {
297297
['ts-node']: {
298298
...service.options,
299+
optionBasePaths: undefined,
299300
experimentalEsmLoader: undefined,
300301
compilerOptions: undefined,
301302
project: service.configFilePath ?? service.options.project,

src/configuration.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { resolve, dirname } from 'path';
22
import type * as _ts from 'typescript';
3-
import { CreateOptions, DEFAULTS, TSCommon, TsConfigOptions } from './index';
3+
import {
4+
CreateOptions,
5+
DEFAULTS,
6+
OptionBasePaths,
7+
TSCommon,
8+
TsConfigOptions,
9+
} from './index';
410
import type { TSInternal } from './ts-compiler-types';
511
import { createTsInternals } from './ts-internals';
612
import { getDefaultTsconfigJsonForNodeVersion } from './tsconfigs';
@@ -70,6 +76,7 @@ export function readConfig(
7076
* this function.
7177
*/
7278
tsNodeOptionsFromTsconfig: TsConfigOptions;
79+
optionBasePaths: OptionBasePaths;
7380
} {
7481
// Ordered [a, b, c] where config a extends b extends c
7582
const configChain: Array<{
@@ -110,6 +117,7 @@ export function readConfig(
110117
configFilePath,
111118
config: { errors: [result.error], fileNames: [], options: {} },
112119
tsNodeOptionsFromTsconfig: {},
120+
optionBasePaths: {},
113121
};
114122
}
115123

@@ -140,6 +148,7 @@ export function readConfig(
140148
configFilePath,
141149
config: { errors, fileNames: [], options: {} },
142150
tsNodeOptionsFromTsconfig: {},
151+
optionBasePaths: {},
143152
};
144153
}
145154
if (resolvedExtendedConfigPath == null) break;
@@ -152,6 +161,7 @@ export function readConfig(
152161

153162
// Merge and fix ts-node options that come from tsconfig.json(s)
154163
const tsNodeOptionsFromTsconfig: TsConfigOptions = {};
164+
const optionBasePaths: OptionBasePaths = {};
155165
for (let i = configChain.length - 1; i >= 0; i--) {
156166
const { config, basePath, configPath } = configChain[i];
157167
const options = filterRecognizedTsConfigTsNodeOptions(config['ts-node'])
@@ -169,6 +179,11 @@ export function readConfig(
169179
options.scopeDir = resolve(basePath, options.scopeDir!);
170180
}
171181

182+
// Downstream code uses the basePath; we do not do that here.
183+
if (options.moduleTypes) {
184+
optionBasePaths.moduleTypes = basePath;
185+
}
186+
172187
assign(tsNodeOptionsFromTsconfig, options);
173188
}
174189

@@ -222,7 +237,12 @@ export function readConfig(
222237
)
223238
);
224239

225-
return { configFilePath, config: fixedConfig, tsNodeOptionsFromTsconfig };
240+
return {
241+
configFilePath,
242+
config: fixedConfig,
243+
tsNodeOptionsFromTsconfig,
244+
optionBasePaths,
245+
};
226246
}
227247

228248
/**
@@ -251,6 +271,7 @@ function filterRecognizedTsConfigTsNodeOptions(
251271
transpiler,
252272
scope,
253273
scopeDir,
274+
moduleTypes,
254275
...unrecognized
255276
} = jsonObject as TsConfigOptions;
256277
const filteredTsConfigOptions = {
@@ -271,6 +292,7 @@ function filterRecognizedTsConfigTsNodeOptions(
271292
transpiler,
272293
scope,
273294
scopeDir,
295+
moduleTypes,
274296
};
275297
// Use the typechecker to make sure this implementation has the correct set of properties
276298
const catchExtraneousProps: keyof TsConfigOptions = (null as any) as keyof typeof filteredTsConfigOptions;

src/esm.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from 'url';
99
import { extname } from 'path';
1010
import * as assert from 'assert';
11+
import { normalizeSlashes } from './util';
1112
const {
1213
createResolve,
1314
} = require('../dist-raw/node-esm-resolve-implementation');
@@ -96,11 +97,27 @@ export function registerAndCreateEsmHooks(opts?: RegisterOptions) {
9697

9798
// If file has .ts, .tsx, or .jsx extension, then ask node how it would treat this file if it were .js
9899
const ext = extname(nativePath);
100+
let nodeSays: { format: Format };
99101
if (ext !== '.js' && !tsNodeInstance.ignored(nativePath)) {
100-
return defer(formatUrl(pathToFileURL(nativePath + '.js')));
102+
nodeSays = await defer(formatUrl(pathToFileURL(nativePath + '.js')));
103+
} else {
104+
nodeSays = await defer();
101105
}
102-
103-
return defer();
106+
// For files compiled by ts-node that node believes are either CJS or ESM, check if we should override that classification
107+
if (
108+
!tsNodeInstance.ignored(nativePath) &&
109+
(nodeSays.format === 'commonjs' || nodeSays.format === 'module')
110+
) {
111+
const { moduleType } = tsNodeInstance.moduleTypeClassifier.classifyModule(
112+
normalizeSlashes(nativePath)
113+
);
114+
if (moduleType === 'cjs') {
115+
return { format: 'commonjs' };
116+
} else if (moduleType === 'esm') {
117+
return { format: 'module' };
118+
}
119+
}
120+
return nodeSays;
104121
}
105122

106123
async function transformSource(

0 commit comments

Comments
 (0)