Skip to content

Commit

Permalink
Add moduleType option to override module type on certain files. (#1371)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
cspotcode and jayaddison authored Jul 9, 2021
1 parent 4e7fcb7 commit 1bc470d
Show file tree
Hide file tree
Showing 28 changed files with 980 additions and 223 deletions.
21 changes: 16 additions & 5 deletions dist-raw/node-cjs-loader-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,28 @@
const path = require('path');
const packageJsonReader = require('./node-package-json-reader');
const {JSONParse} = require('./node-primordials');
const {normalizeSlashes} = require('../dist/util');

module.exports.assertScriptCanLoadAsCJSImpl = assertScriptCanLoadAsCJSImpl;

// copied from Module._extensions['.js']
// https://github.com/nodejs/node/blob/v15.3.0/lib/internal/modules/cjs/loader.js#L1113-L1120
function assertScriptCanLoadAsCJSImpl(filename) {
/**
* copied from Module._extensions['.js']
* https://github.com/nodejs/node/blob/v15.3.0/lib/internal/modules/cjs/loader.js#L1113-L1120
* @param {import('../src/index').Service} service
* @param {NodeJS.Module} module
* @param {string} filename
*/
function assertScriptCanLoadAsCJSImpl(service, module, filename) {
const pkg = readPackageScope(filename);

// ts-node modification: allow our configuration to override
const tsNodeClassification = service.moduleTypeClassifier.classifyModule(normalizeSlashes(filename));
if(tsNodeClassification.moduleType === 'cjs') return;

// Function require shouldn't be used in ES modules.
if (pkg && pkg.data && pkg.data.type === 'module') {
if (tsNodeClassification.moduleType === 'esm' || (pkg && pkg.data && pkg.data.type === 'module')) {
const parentPath = module.parent && module.parent.filename;
const packageJsonPath = path.resolve(pkg.path, 'package.json');
const packageJsonPath = pkg ? path.resolve(pkg.path, 'package.json') : null;
throw createErrRequireEsm(filename, parentPath, packageJsonPath);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export function main(
const json = {
['ts-node']: {
...service.options,
optionBasePaths: undefined,
experimentalEsmLoader: undefined,
compilerOptions: undefined,
project: service.configFilePath ?? service.options.project,
Expand Down
26 changes: 24 additions & 2 deletions src/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { resolve, dirname } from 'path';
import type * as _ts from 'typescript';
import { CreateOptions, DEFAULTS, TSCommon, TsConfigOptions } from './index';
import {
CreateOptions,
DEFAULTS,
OptionBasePaths,
TSCommon,
TsConfigOptions,
} from './index';
import type { TSInternal } from './ts-compiler-types';
import { createTsInternals } from './ts-internals';
import { getDefaultTsconfigJsonForNodeVersion } from './tsconfigs';
Expand Down Expand Up @@ -70,6 +76,7 @@ export function readConfig(
* this function.
*/
tsNodeOptionsFromTsconfig: TsConfigOptions;
optionBasePaths: OptionBasePaths;
} {
// Ordered [a, b, c] where config a extends b extends c
const configChain: Array<{
Expand Down Expand Up @@ -110,6 +117,7 @@ export function readConfig(
configFilePath,
config: { errors: [result.error], fileNames: [], options: {} },
tsNodeOptionsFromTsconfig: {},
optionBasePaths: {},
};
}

Expand Down Expand Up @@ -140,6 +148,7 @@ export function readConfig(
configFilePath,
config: { errors, fileNames: [], options: {} },
tsNodeOptionsFromTsconfig: {},
optionBasePaths: {},
};
}
if (resolvedExtendedConfigPath == null) break;
Expand All @@ -152,6 +161,7 @@ export function readConfig(

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

// Downstream code uses the basePath; we do not do that here.
if (options.moduleTypes) {
optionBasePaths.moduleTypes = basePath;
}

assign(tsNodeOptionsFromTsconfig, options);
}

Expand Down Expand Up @@ -222,7 +237,12 @@ export function readConfig(
)
);

return { configFilePath, config: fixedConfig, tsNodeOptionsFromTsconfig };
return {
configFilePath,
config: fixedConfig,
tsNodeOptionsFromTsconfig,
optionBasePaths,
};
}

/**
Expand Down Expand Up @@ -251,6 +271,7 @@ function filterRecognizedTsConfigTsNodeOptions(
transpiler,
scope,
scopeDir,
moduleTypes,
...unrecognized
} = jsonObject as TsConfigOptions;
const filteredTsConfigOptions = {
Expand All @@ -271,6 +292,7 @@ function filterRecognizedTsConfigTsNodeOptions(
transpiler,
scope,
scopeDir,
moduleTypes,
};
// 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;
Expand Down
23 changes: 20 additions & 3 deletions src/esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from 'url';
import { extname } from 'path';
import * as assert from 'assert';
import { normalizeSlashes } from './util';
const {
createResolve,
} = require('../dist-raw/node-esm-resolve-implementation');
Expand Down Expand Up @@ -96,11 +97,27 @@ export function registerAndCreateEsmHooks(opts?: RegisterOptions) {

// If file has .ts, .tsx, or .jsx extension, then ask node how it would treat this file if it were .js
const ext = extname(nativePath);
let nodeSays: { format: Format };
if (ext !== '.js' && !tsNodeInstance.ignored(nativePath)) {
return defer(formatUrl(pathToFileURL(nativePath + '.js')));
nodeSays = await defer(formatUrl(pathToFileURL(nativePath + '.js')));
} else {
nodeSays = await defer();
}

return defer();
// For files compiled by ts-node that node believes are either CJS or ESM, check if we should override that classification
if (
!tsNodeInstance.ignored(nativePath) &&
(nodeSays.format === 'commonjs' || nodeSays.format === 'module')
) {
const { moduleType } = tsNodeInstance.moduleTypeClassifier.classifyModule(
normalizeSlashes(nativePath)
);
if (moduleType === 'cjs') {
return { format: 'commonjs' };
} else if (moduleType === 'esm') {
return { format: 'module' };
}
}
return nodeSays;
}

async function transformSource(
Expand Down
Loading

0 comments on commit 1bc470d

Please sign in to comment.