From 5dbe0403744bf8d872a60b7e9c705d9113feab24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Tue, 25 Mar 2025 12:51:02 +0100 Subject: [PATCH] fix(misc): override `customConditions` when using an incompatible module resolution (#30477) ## Current Behavior In a few different places (Crystal plugins, executors, generators, helpers) where `ts-node` compiler options are overridden and `moduleResolution` is being set to something other than `node16`, `nodenext`, or `bundler`, an error can occur if the `customConditions` compiler option is being used. ## Expected Behavior When overriding the `ts-node` compiler options and changing forcing `moduleResolution` to have a value that's incompatible with `customConditions`, the latter should be unset (set to `null`) to avoid errors. ## Related Issue(s) Fixes # --- .../convert-to-rspack.spec.ts | 160 ++++++++++++++++++ .../convert-to-rspack/lib/update-tsconfig.ts | 24 ++- packages/jest/src/executors/jest/jest.impl.ts | 7 +- packages/jest/src/plugins/plugin.spec.ts | 22 +-- packages/jest/src/plugins/plugin.ts | 16 +- packages/js/package.json | 2 - .../src/utils/typescript/tsnode-register.ts | 21 --- packages/nx/src/plugins/js/utils/register.ts | 1 + packages/rspack/src/plugins/plugin.spec.ts | 6 +- packages/rspack/src/plugins/plugin.ts | 1 + .../configuration/configuration.spec.ts | 1 + .../configuration/lib/edit-root-tsconfig.ts | 31 ++-- 12 files changed, 230 insertions(+), 62 deletions(-) delete mode 100644 packages/js/src/utils/typescript/tsnode-register.ts diff --git a/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts b/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts index 5bed973fbc..2cd5b3d94b 100644 --- a/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts +++ b/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts @@ -484,4 +484,164 @@ describe('convert-to-rspack', () => { " `); }); + + it('should configure ts-node in the tsconfig.json file', async () => { + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'app', { + root: 'apps/app', + sourceRoot: 'apps/app/src', + projectType: 'application', + targets: { + build: { + executor: '@angular-devkit/build-angular:browser', + options: { + outputPath: 'dist/apps/app', + index: 'apps/app/src/index.html', + main: 'apps/app/src/main.ts', + polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is + tsConfig: 'apps/app/tsconfig.app.json', + assets: [ + 'apps/app/src/favicon.ico', + 'apps/app/src/assets', + { input: 'apps/app/public', glob: '**/*' }, + ], + styles: ['apps/app/src/styles.scss'], + scripts: [], + }, + }, + }, + }); + writeJson(tree, 'apps/app/tsconfig.json', {}); + updateJson(tree, 'package.json', (json) => { + json.scripts ??= {}; + json.scripts.build = 'nx build'; + return json; + }); + + await convertToRspack(tree, { project: 'app', skipFormat: true }); + + expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "ts-node": { + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node10" + } + } + } + " + `); + }); + + it('should configure ts-node in the tsconfig.json file to unset "customConditions" when it is defined in the root tsconfig.json file', async () => { + const tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'tsconfig.base.json', (json) => { + json.compilerOptions ??= {}; + json.compilerOptions.customConditions = ['development']; + return json; + }); + addProjectConfiguration(tree, 'app', { + root: 'apps/app', + sourceRoot: 'apps/app/src', + projectType: 'application', + targets: { + build: { + executor: '@angular-devkit/build-angular:browser', + options: { + outputPath: 'dist/apps/app', + index: 'apps/app/src/index.html', + main: 'apps/app/src/main.ts', + polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is + tsConfig: 'apps/app/tsconfig.app.json', + assets: [ + 'apps/app/src/favicon.ico', + 'apps/app/src/assets', + { input: 'apps/app/public', glob: '**/*' }, + ], + styles: ['apps/app/src/styles.scss'], + scripts: [], + }, + }, + }, + }); + writeJson(tree, 'apps/app/tsconfig.json', {}); + updateJson(tree, 'package.json', (json) => { + json.scripts ??= {}; + json.scripts.build = 'nx build'; + return json; + }); + + await convertToRspack(tree, { project: 'app', skipFormat: true }); + + expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "ts-node": { + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node10", + "customConditions": null + } + } + } + " + `); + }); + + it('should configure ts-node in the tsconfig.json file to unset "customConditions" when it is defined in the project tsconfig.json file', async () => { + const tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'app', { + root: 'apps/app', + sourceRoot: 'apps/app/src', + projectType: 'application', + targets: { + build: { + executor: '@angular-devkit/build-angular:browser', + options: { + outputPath: 'dist/apps/app', + index: 'apps/app/src/index.html', + main: 'apps/app/src/main.ts', + polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is + tsConfig: 'apps/app/tsconfig.app.json', + assets: [ + 'apps/app/src/favicon.ico', + 'apps/app/src/assets', + { input: 'apps/app/public', glob: '**/*' }, + ], + styles: ['apps/app/src/styles.scss'], + scripts: [], + }, + }, + }, + }); + writeJson(tree, 'apps/app/tsconfig.json', { + compilerOptions: { + customConditions: ['development'], + }, + }); + updateJson(tree, 'package.json', (json) => { + json.scripts ??= {}; + json.scripts.build = 'nx build'; + return json; + }); + + await convertToRspack(tree, { project: 'app', skipFormat: true }); + + expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "compilerOptions": { + "customConditions": [ + "development" + ] + }, + "ts-node": { + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node10", + "customConditions": null + } + } + } + " + `); + }); }); diff --git a/packages/angular/src/generators/convert-to-rspack/lib/update-tsconfig.ts b/packages/angular/src/generators/convert-to-rspack/lib/update-tsconfig.ts index 3f55a325d9..de2b4e08d6 100644 --- a/packages/angular/src/generators/convert-to-rspack/lib/update-tsconfig.ts +++ b/packages/angular/src/generators/convert-to-rspack/lib/update-tsconfig.ts @@ -1,12 +1,26 @@ import { joinPathFragments, readJson, Tree, writeJson } from '@nx/devkit'; +import { getRootTsConfigFileName } from '@nx/js'; export function updateTsconfig(tree: Tree, projectRoot: string) { const tsconfigPath = joinPathFragments(projectRoot, 'tsconfig.json'); const tsconfig = readJson(tree, tsconfigPath); - tsconfig['ts-node'] = { - compilerOptions: { - module: 'CommonJS', - }, - }; + + tsconfig['ts-node'] ??= {}; + tsconfig['ts-node'].compilerOptions ??= {}; + tsconfig['ts-node'].compilerOptions.module = 'CommonJS'; + tsconfig['ts-node'].compilerOptions.moduleResolution = 'Node10'; + + if (tsconfig.compilerOptions?.customConditions) { + tsconfig['ts-node'].compilerOptions.customConditions = null; + } else { + const rootTsconfigFile = getRootTsConfigFileName(tree); + if (rootTsconfigFile) { + const rootTsconfigJson = readJson(tree, rootTsconfigFile); + if (rootTsconfigJson.compilerOptions?.customConditions) { + tsconfig['ts-node'].compilerOptions.customConditions = null; + } + } + } + writeJson(tree, tsconfigPath, tsconfig); } diff --git a/packages/jest/src/executors/jest/jest.impl.ts b/packages/jest/src/executors/jest/jest.impl.ts index e9ae181417..1688eb13a3 100644 --- a/packages/jest/src/executors/jest/jest.impl.ts +++ b/packages/jest/src/executors/jest/jest.impl.ts @@ -23,7 +23,12 @@ export async function jestExecutor( ): Promise<{ success: boolean }> { // Jest registers ts-node with module CJS https://github.com/SimenB/jest/blob/v29.6.4/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L117-L119 // We want to support of ESM via 'module':'nodenext', we need to override the resolution until Jest supports it. - process.env.TS_NODE_COMPILER_OPTIONS ??= '{"moduleResolution":"node10"}'; + const existingValue = process.env['TS_NODE_COMPILER_OPTIONS']; + process.env['TS_NODE_COMPILER_OPTIONS'] = JSON.stringify({ + ...(existingValue ? JSON.parse(existingValue) : {}), + moduleResolution: 'Node10', + customConditions: null, + }); const config = await parseJestConfig(options, context); diff --git a/packages/jest/src/plugins/plugin.spec.ts b/packages/jest/src/plugins/plugin.spec.ts index 317c4637c4..2513309547 100644 --- a/packages/jest/src/plugins/plugin.spec.ts +++ b/packages/jest/src/plugins/plugin.spec.ts @@ -98,7 +98,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -179,7 +179,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -249,7 +249,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -318,7 +318,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -398,7 +398,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -482,7 +482,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -552,7 +552,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -632,7 +632,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -702,7 +702,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -782,7 +782,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ @@ -852,7 +852,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => { "options": { "cwd": "proj", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}", }, }, "outputs": [ diff --git a/packages/jest/src/plugins/plugin.ts b/packages/jest/src/plugins/plugin.ts index 455fbdb4b9..de693672c1 100644 --- a/packages/jest/src/plugins/plugin.ts +++ b/packages/jest/src/plugins/plugin.ts @@ -253,13 +253,21 @@ async function buildJestTargets( const targets: Record = {}; const namedInputs = getNamedInputs(projectRoot, context); + const existingTsNodeCompilerOptions = process.env['TS_NODE_COMPILER_OPTIONS']; + const tsNodeCompilerOptions = JSON.stringify({ + ...(existingTsNodeCompilerOptions + ? JSON.parse(existingTsNodeCompilerOptions) + : {}), + moduleResolution: 'node10', + customConditions: null, + }); const target: TargetConfiguration = (targets[options.targetName] = { command: 'jest', options: { cwd: projectRoot, // Jest registers ts-node with module CJS https://github.com/SimenB/jest/blob/v29.6.4/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L117-L119 // We want to support of ESM via 'module':'nodenext', we need to override the resolution until Jest supports it. - env: { TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}' }, + env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions }, }, metadata: { technologies: ['jest'], @@ -341,7 +349,7 @@ async function buildJestTargets( outputs, options: { cwd: projectRoot, - env: { TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}' }, + env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions }, }, metadata: { technologies: ['jest'], @@ -481,9 +489,7 @@ async function buildJestTargets( outputs, options: { cwd: projectRoot, - env: { - TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}', - }, + env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions }, }, metadata: { technologies: ['jest'], diff --git a/packages/js/package.json b/packages/js/package.json index d7b6e616a2..5f81b60c4b 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -60,8 +60,6 @@ "semver": "^7.5.3", "source-map-support": "0.5.19", "tinyglobby": "^0.2.12", - "ts-node": "10.9.1", - "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0" }, "peerDependencies": { diff --git a/packages/js/src/utils/typescript/tsnode-register.ts b/packages/js/src/utils/typescript/tsnode-register.ts deleted file mode 100644 index a1d2ef6eb8..0000000000 --- a/packages/js/src/utils/typescript/tsnode-register.ts +++ /dev/null @@ -1,21 +0,0 @@ -export function tsNodeRegister(file: string, tsConfig?: string) { - if (!file?.endsWith('.ts')) return; - // Register TS compiler lazily - require('ts-node').register({ - project: tsConfig, - compilerOptions: { - module: 'CommonJS', - types: ['node'], - }, - }); - - if (!tsConfig) return; - - // Register paths in tsConfig - const tsconfigPaths = require('tsconfig-paths'); - const { absoluteBaseUrl: baseUrl, paths } = - tsconfigPaths.loadConfig(tsConfig); - if (baseUrl && paths) { - tsconfigPaths.register({ baseUrl, paths }); - } -} diff --git a/packages/nx/src/plugins/js/utils/register.ts b/packages/nx/src/plugins/js/utils/register.ts index f02030b397..4d5105a04e 100644 --- a/packages/nx/src/plugins/js/utils/register.ts +++ b/packages/nx/src/plugins/js/utils/register.ts @@ -245,6 +245,7 @@ export function getTranspiler( // use NodeJs module resolution until support for TS 4.x is dropped and then // we can switch to Node10 compilerOptions.moduleResolution = ts.ModuleResolutionKind.NodeJs; + compilerOptions.customConditions = null; compilerOptions.target = ts.ScriptTarget.ES2021; compilerOptions.inlineSourceMap = true; compilerOptions.skipLibCheck = true; diff --git a/packages/rspack/src/plugins/plugin.spec.ts b/packages/rspack/src/plugins/plugin.spec.ts index 302df3a418..fb00b1a736 100644 --- a/packages/rspack/src/plugins/plugin.spec.ts +++ b/packages/rspack/src/plugins/plugin.spec.ts @@ -88,7 +88,7 @@ describe('@nx/rspack', () => { ], "cwd": "my-app", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"Node10","module":"CommonJS"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"Node10","module":"CommonJS","customConditions":null}", }, }, "outputs": [], @@ -106,7 +106,7 @@ describe('@nx/rspack', () => { ], "cwd": "my-app", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"Node10","module":"CommonJS"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"Node10","module":"CommonJS","customConditions":null}", }, }, }, @@ -118,7 +118,7 @@ describe('@nx/rspack', () => { ], "cwd": "my-app", "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"Node10","module":"CommonJS"}", + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"Node10","module":"CommonJS","customConditions":null}", }, }, }, diff --git a/packages/rspack/src/plugins/plugin.ts b/packages/rspack/src/plugins/plugin.ts index 0befd32112..a907cd1b53 100644 --- a/packages/rspack/src/plugins/plugin.ts +++ b/packages/rspack/src/plugins/plugin.ts @@ -187,6 +187,7 @@ async function createRspackTargets( ...(existingValue ? JSON.parse(existingValue) : {}), module: 'CommonJS', moduleResolution: 'Node10', + customConditions: null, }); } diff --git a/packages/storybook/src/generators/configuration/configuration.spec.ts b/packages/storybook/src/generators/configuration/configuration.spec.ts index fdeb3be5cf..2b6bba1dd1 100644 --- a/packages/storybook/src/generators/configuration/configuration.spec.ts +++ b/packages/storybook/src/generators/configuration/configuration.spec.ts @@ -844,6 +844,7 @@ describe('@nx/storybook:configuration for Storybook v7', () => { "ts-node": { "compilerOptions": { "module": "commonjs", + "moduleResolution": "node10", }, }, } diff --git a/packages/storybook/src/generators/configuration/lib/edit-root-tsconfig.ts b/packages/storybook/src/generators/configuration/lib/edit-root-tsconfig.ts index ce6ae2bb27..10b9de75de 100644 --- a/packages/storybook/src/generators/configuration/lib/edit-root-tsconfig.ts +++ b/packages/storybook/src/generators/configuration/lib/edit-root-tsconfig.ts @@ -1,4 +1,5 @@ -import { updateJson, type Tree } from '@nx/devkit'; +import { readJson, updateJson, type Tree } from '@nx/devkit'; +import { getRootTsConfigFileName } from '@nx/js'; /** * This is a temporary fix for Storybook to support TypeScript configuration files. @@ -12,21 +13,23 @@ export function editRootTsConfig(tree: Tree) { } updateJson(tree, 'tsconfig.json', (json) => { - if (json['ts-node']) { - json['ts-node'] = { - ...json['ts-node'], - compilerOptions: { - ...(json['ts-node'].compilerOptions ?? {}), - module: 'commonjs', - }, - }; + json['ts-node'] ??= {}; + json['ts-node'].compilerOptions ??= {}; + json['ts-node'].compilerOptions.module = 'commonjs'; + json['ts-node'].compilerOptions.moduleResolution = 'node10'; + + if (json.compilerOptions?.customConditions) { + json['ts-node'].compilerOptions.customConditions = null; } else { - json['ts-node'] = { - compilerOptions: { - module: 'commonjs', - }, - }; + const rootTsconfigFile = getRootTsConfigFileName(tree); + if (rootTsconfigFile) { + const rootTsconfigJson = readJson(tree, rootTsconfigFile); + if (rootTsconfigJson.compilerOptions?.customConditions) { + json['ts-node'].compilerOptions.customConditions = null; + } + } } + return json; }); }