Skip to content

@nx/js - Migrations

For an overview of the plugin and setup instructions, see the @nx/js introduction.

The @nx/js plugin provides various migrations to help you migrate to newer versions of js projects within your Nx workspace. Below is a complete reference for all available migrations.

Version: 23.1.0-beta.8

Adds "ignoreDeprecations": "6.0" to tsconfig files whose compilerOptions (or ts-node.compilerOptions) carry a TypeScript 6 deprecated option value, set directly or inherited through extends including from a base this migration does not edit (e.g. moduleResolution node/node10/classic, baseUrl, target es5, esModuleInterop false, outFile, module amd/umd/system/none, alwaysStrict false, allowSyntheticDefaultImports false, downlevelIteration set). Also pins "strict": false, "noUncheckedSideEffectImports": false, "types": ["*"], and "esModuleInterop": false in chain-root tsconfigs (no “extends”) that lack each key, preserving pre-TS6 behavior where these defaults changed; esModuleInterop false is itself deprecated (removed in TS7) so it also receives ignoreDeprecations, deferring the interop change to the TS7 migration. Also adds ignoreDeprecations to every tsconfig.json (the exact name jest/ts-node auto-resolve when compiling a config file; ts-node injects a deprecated default target: es5 there, so the load hits a TS6 error even on a clean config), even one with no deprecated value of its own; a stale local ignoreDeprecations (e.g. 5.0) that would otherwise override the inherited flag is upgraded.

NameVersion
typescript>=6.0.0

Keep Existing Workspaces Compiling on TypeScript 6

Section titled “Keep Existing Workspaces Compiling on TypeScript 6”

TypeScript 6 turns several long-deprecated compiler options into hard errors and flips a few option defaults to stricter values. So that an existing workspace keeps compiling on TypeScript 6 without being migrated to a full TypeScript 6 setup, this migration edits the tsconfig*.json files in the workspace:

  • Adds "ignoreDeprecations": "6.0" to any compilerOptions (or ts-node.compilerOptions) block whose effective options carry a TypeScript 6 deprecated option, set directly or inherited through extends (including from a base this migration does not edit, such as a package-provided config). Examples: moduleResolution set to node/node10/classic, baseUrl, target set to es5, esModuleInterop: false, outFile, module set to amd/umd/system/none, alwaysStrict: false, allowSyntheticDefaultImports: false, or downlevelIteration. A block that already inherits an effective "6.0" and sets no local value is left alone; a stale local value such as "5.0" is upgraded to "6.0", since it would otherwise override the inherited flag and still error.
  • Pins "strict": false, "noUncheckedSideEffectImports": false, "types": ["*"], and "esModuleInterop": false on every chain-root tsconfig (one without an extends) that does not already set them. TypeScript 6 treats an absent strict as true (it was false when unset before), defaults noUncheckedSideEffectImports to true (which turns a bare side-effect import such as import './styles.css' without an ambient module declaration into an error), no longer auto-loads every @types package when types is unset the way TypeScript 5 did (the "*" wildcard restores that last default), and flips esModuleInterop from false to true (so an import * as x from '<cjs>' binds a non-callable namespace object and a call or new on that import fails at runtime). Pinning all four preserves the pre-TypeScript 6 behavior. Because esModuleInterop: false is itself a TypeScript 6 deprecated value, this pin runs before the ignoreDeprecations edit above, so the added false is silenced in the same run.
  • Adds "ignoreDeprecations": "6.0" to every tsconfig.json (the exact file name jest and ts-node auto-resolve), even one that carries no deprecated option of its own. Those loaders compile config files such as jest.config.ts, and ts-node injects a default target: es5 when the config leaves it unset. TypeScript 6 rejects es5 as a deprecated value, so the flag keeps that load working. It is inert wherever nothing is actually deprecated.

Files that use extends inherit the pinned settings from their chain root, so the pins are not repeated on them, and pure solution-style tsconfigs ("files": [] with no include) receive no pins either, though a solution-style tsconfig.json still gets the config-load flag. The migration only runs when the workspace is on TypeScript 6.

tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "bundler"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "bundler",
"strict": false,
"noUncheckedSideEffectImports": false,
"types": ["*"],
"esModuleInterop": false,
"ignoreDeprecations": "6.0"
}
}

Version: 23.1.0-rc.2

Sets an explicit rootDir on project tsconfig*.json files that lack one, pinned to the source directory TypeScript 5 inferred implicitly, so programs keep compiling and emitting the same layout under TypeScript 6, which otherwise hard-fails with TS5011 or TS6059 (for example a spec tsconfig importing another project’s source through a paths alias). The pin is written even when the inferred directory already equals the tsconfig directory, because tools like ts-jest with isolatedModules compile a program per file and re-infer a deeper directory from that subset. The value is computed by the compiler, so emit layout is unchanged. Each config is written on its own; the migration never writes to a shared extends base, so a config never inherits a value computed for a sibling. Composite projects are pinned to their own directory: rootDir already defaults there under tsc so it is a no-op for a real composite build, but ts-jest strips composite for its per-file transpile and the explicit value is needed to avoid TS5011 there.

NameVersion
typescript>=6.0.0

Before TypeScript 6, a tsconfig that did not set rootDir had it inferred as the common directory of the program’s non-declaration input files. TypeScript 6 changed that default to the tsconfig’s own directory. A program whose files resolve outside that directory (most commonly a spec or e2e tsconfig that imports another project’s source through a paths alias) now hard-fails with TS5011 or TS6059 because a file falls outside the assumed root.

For every project tsconfig*.json that does not already set rootDir (directly or through extends), this migration pins rootDir to exactly the directory TypeScript 5 would have inferred, so both compilation and emit layout stay the same under TypeScript 6. The value is computed by the TypeScript compiler itself, so it matches tsc exactly, including project-reference redirects. The pin is written even when the inferred directory already equals the tsconfig directory: inference is per-program, and a tool that compiles a subset of the config’s files — ts-jest with isolatedModules builds a program per test file — re-infers a deeper common directory from that subset and fails with TS5011. Configs that cannot hit the error are skipped: ones without an output option (outDir, outFile, sourceRoot, mapRoot, or declaration + declarationDir), and ones with no input files. The migration only runs when the workspace is on TypeScript 6.

Composite projects are pinned to their own directory ("."). Under tsc a composite rootDir already defaults there, so the pin is a no-op for a real composite build — but ts-jest strips composite for its per-file transpile, and TypeScript 6 only exempts genuinely-composite programs from the containment check, so a composite spec config compiled by ts-jest still fails with TS5011 without an explicit rootDir. Pinning the own directory (rather than a deeper file-derived value) keeps the composite build’s emit layout unchanged while fixing the ts-jest case.

Each config is updated on its own; the migration never writes rootDir to a shared extends base, so a config never inherits a value computed for a sibling. Because every config that emits is given its own explicit rootDir, none is left to inherit a value pinned on a base.

A libs/products/e2e/tsconfig.json whose specs import a shared library’s source:

libs/products/e2e/tsconfig.json
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc"
},
"include": ["src/**/*.ts"]
}
libs/products/e2e/tsconfig.json
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "../../..",
"outDir": "../../../dist/out-tsc"
},
"include": ["src/**/*.ts"]
}

Version: 23.1.0-beta.0

The following packages will be updated:

NameVersionAlways add to package.json
typescript~6.0.3Updated only

Version: 23.1.0-rc.3

The following packages will be updated:

NameVersionAlways add to package.json
@swc/cli~0.8.1Updated only

Version: 23.0.0-beta.14

Rewrites @nx/js/src/* subpath imports to the new @nx/js/internal entry. The ./src/* wildcard has been removed from @nx/js’s exports map; @nx/js/src/release/version-actions is preserved as a non-wildcard entry for back-compat with existing nx.json release configs. If a rewritten import resolves to a symbol that lives on the public @nx/js entry (e.g. libraryGenerator, extractTsConfigBase, resolvePathsBaseUrl), change the specifier to @nx/js.

update-23-0-0-migrate-create-nodes-v2-import

Section titled “update-23-0-0-migrate-create-nodes-v2-import”

Version: 23.0.0-beta.24

Rename imports of createNodesV2 from @nx/js/typescript to the canonical createNodes export.

Rename createNodesV2 imports to createNodes

Section titled “Rename createNodesV2 imports to createNodes”

@nx/js renamed its primary inferred-plugin export from createNodesV2 to createNodes. The createNodesV2 name is preserved as a deprecated alias for now, but new code should use createNodes.

This migration scans every .ts, .tsx, .cts, and .mts file in your workspace and rewrites named imports and re-exports of createNodesV2 from @nx/js/typescript to createNodes.

import { createNodesV2 } from '@nx/js/typescript';
import { createNodes } from '@nx/js/typescript';

Aliases are preserved (createNodesV2 as cn becomes createNodes as cn), and if a file already imports both names ({ createNodes, createNodesV2 }) the redundant binding is dropped.

Only static import/export named bindings from @nx/js/typescript are rewritten. Namespace imports, dynamic import(...), require(...) destructuring, and property access such as plugin.createNodesV2 are left untouched — they keep working through the createNodesV2 runtime alias. Update those by hand if you want to drop the deprecated name everywhere.

Version: 23.0.0-beta.17

The following packages will be updated:

NameVersionAlways add to package.json
@swc/cli~0.8.0Updated only

Version: 22.6.4

The following packages will be updated:

NameVersionAlways add to package.json
verdaccio^6.3.2Updated only

Version: 22.5.0-beta.1

The following packages will be updated:

NameVersionAlways add to package.json
@swc/core^1.15.5Updated only
@swc/helpers^0.5.18Updated only
@swc-node/register^1.11.1Updated only

Version: 22.5.0-beta.1

The following packages will be updated:

NameVersionAlways add to package.json
@swc/cli^0.7.10Updated only

Version: 22.1.0-rc.1

Removes redundant TypeScript project references from project’s tsconfig.json files when runtime tsconfig files (e.g., tsconfig.lib.json, tsconfig.app.json) exist.

Removes Redundant TypeScript Project References from tsconfig.json Files

Section titled “Removes Redundant TypeScript Project References from tsconfig.json Files”

Removes redundant TypeScript project references from tsconfig.json files when runtime tsconfig files (e.g., tsconfig.lib.json, tsconfig.app.json) exist. Previously, external project references were duplicated in both the project’s tsconfig.json and runtime tsconfig files. This migration syncs the TypeScript project references to match the project graph, ensuring that external references only appear in runtime tsconfig files when they exist.

When a project has runtime tsconfig files like tsconfig.lib.json, the migration will remove external project references from the project’s tsconfig.json file:

libs/my-lib/tsconfig.json
{
"compilerOptions": {
"composite": true,
},
"references": [
{
"path": "../other-lib",
},
],
}
libs/my-lib/tsconfig.json
{
"compilerOptions": {
"composite": true,
},
"references": [],
}

The external references remain in the runtime tsconfig file where they belong:

libs/my-lib/tsconfig.lib.json
{
"compilerOptions": {
"composite": true,
},
"references": [
{
"path": "../other-lib/tsconfig.lib.json",
},
],
}
libs/my-lib/tsconfig.lib.json
{
"compilerOptions": {
"composite": true,
},
"references": [
{
"path": "../other-lib/tsconfig.lib.json",
},
],
}

For projects without runtime tsconfig files, the project’s tsconfig.json file will continue to contain external project references:

libs/legacy-lib/tsconfig.json
{
"compilerOptions": {
"composite": true,
},
"references": [
{
"path": "../other-lib",
},
],
}
libs/legacy-lib/tsconfig.json
{
"compilerOptions": {
"composite": true,
},
"references": [
{
"path": "../other-lib",
},
],
}

Internal project references (references within the same project directory) are preserved in the project’s tsconfig.json:

libs/my-lib/tsconfig.json
{
"compilerOptions": {
"composite": true,
},
"references": [
{
"path": "./tsconfig.lib.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}
libs/my-lib/tsconfig.json
{
"compilerOptions": {
"composite": true,
},
"references": [
{
"path": "./tsconfig.lib.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}

Version: 22.0.0-beta.0

Remove the deprecated external and externalBuildTargets options from the @nx/js:swc and @nx/js:tsc executors.

Remove the external and externalBuildTargets Options from the @nx/js:swc and @nx/js:tsc Executors

Section titled “Remove the external and externalBuildTargets Options from the @nx/js:swc and @nx/js:tsc Executors”

Remove the deprecated external and externalBuildTargets options from the @nx/js:swc and @nx/js:tsc executors. These options were used for inlining dependencies, which was an experimental feature and has been deprecated for a long time. The migration only removes the options from the project configuration and target defaults. If you rely on inlining dependencies, you need to make sure they are all buildable or use a different build tool that supports bundling.

Remove external and externalBuildTargets from the @nx/js:swc or @nx/js:tsc executor options in project configuration.

libs/my-lib/project.json
{
"targets": {
"build": {
"executor": "@nx/js:swc",
"options": {
"main": "libs/my-lib/src/index.ts",
"outputPath": "dist/libs/my-lib",
"tsConfig": "libs/my-lib/tsconfig.lib.json",
"external": ["react", "react-dom"],
"externalBuildTargets": ["build"]
}
}
}
}
libs/my-lib/project.json
{
"targets": {
"build": {
"executor": "@nx/js:swc",
"options": {
"main": "libs/my-lib/src/index.ts",
"outputPath": "dist/libs/my-lib",
"tsConfig": "libs/my-lib/tsconfig.lib.json"
}
}
}
}

Remove external and externalBuildTargets from the @nx/js:swc or @nx/js:tsc executor target defaults in nx.json.

nx.json
{
"targetDefaults": {
"@nx/js:swc": {
"options": {
"main": "{projectRoot}/src/index.ts",
"outputPath": "dist/{projectRoot}",
"tsConfig": "{projectRoot}/tsconfig.lib.json",
"external": "all",
"externalBuildTargets": ["build"]
}
}
}
}
nx.json
{
"targetDefaults": {
"@nx/js:swc": {
"options": {
"main": "{projectRoot}/src/index.ts",
"outputPath": "dist/{projectRoot}",
"tsConfig": "{projectRoot}/tsconfig.lib.json"
}
}
}
}

Version: 21.5.0-beta.2

Migrate the legacy ‘development’ custom condition to a workspace-unique custom condition name.

Migrate development custom condition to unique workspace-specific name

Section titled “Migrate development custom condition to unique workspace-specific name”

Replace the TypeScript development custom condition with a unique workspace-specific name to avoid conflicts when consuming packages in other workspaces.

The migration will update the custom condition name in both tsconfig.base.json and all workspace package.json files that use the development custom condition:

tsconfig.base.json
{
"compilerOptions": {
"customConditions": ["development"]
}
}
tsconfig.base.json
{
"compilerOptions": {
"customConditions": ["@my-org/source"] // assuming the root package.json name is `@my-org/source`
}
}

The migration also updates package.json files that use the development condition in their exports field and point to TypeScript files:

libs/my-lib/package.json
{
"name": "@myorg/my-lib",
"exports": {
".": {
"development": "./src/index.ts",
"default": "./dist/index.js"
}
}
}
libs/my-lib/package.json
{
"name": "@myorg/my-lib",
"exports": {
".": {
"@my-org/source": "./src/index.ts",
"default": "./dist/index.js"
}
}
}

If the custom condition is not set to ["development"] or the package.json’s exports field doesn’t point to TypeScript files, the migration will not modify the configuration:

libs/my-lib/package.json
{
"name": "@myorg/my-lib",
"exports": {
".": {
"development": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
libs/my-lib/package.json
{
"name": "@myorg/my-lib",
"exports": {
".": {
"development": "./dist/index.js",
"default": "./dist/index.js"
}
}
}

Version: 21.5.0-beta.2

The following packages will be updated:

NameVersionAlways add to package.json
typescript~5.9.2Updated only

Version: 21.2.0-beta.0

The following packages will be updated:

NameVersionAlways add to package.json
typescript~5.8.2Updated only