diff --git a/docs/generated/packages/react/generators/application.json b/docs/generated/packages/react/generators/application.json
index 5889181603..ca2d6d8128 100644
--- a/docs/generated/packages/react/generators/application.json
+++ b/docs/generated/packages/react/generators/application.json
@@ -95,7 +95,7 @@
"bundler": {
"description": "The bundler to use.",
"type": "string",
- "enum": ["vite", "webpack", "rspack", "rsbuild"],
+ "enum": ["vite", "rsbuild", "rspack", "webpack"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "vite",
"x-priority": "important"
diff --git a/docs/generated/packages/workspace/generators/preset.json b/docs/generated/packages/workspace/generators/preset.json
index 89a5aedc41..9fa202b6f0 100644
--- a/docs/generated/packages/workspace/generators/preset.json
+++ b/docs/generated/packages/workspace/generators/preset.json
@@ -69,7 +69,7 @@
"bundler": {
"description": "The bundler to use for building the application.",
"type": "string",
- "enum": ["webpack", "vite", "rspack", "esbuild"],
+ "enum": ["vite", "rspack", "rsbuild", "esbuild", "webpack"],
"default": "vite"
},
"docker": {
diff --git a/e2e/next/src/__snapshots__/next.test.ts.snap b/e2e/next/src/__snapshots__/next.test.ts.snap
index 2149098288..370c433755 100644
--- a/e2e/next/src/__snapshots__/next.test.ts.snap
+++ b/e2e/next/src/__snapshots__/next.test.ts.snap
@@ -5,7 +5,7 @@ exports[`Next.js Applications next-env.d.ts should remain the same after a build
///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
"
`;
@@ -14,6 +14,6 @@ exports[`Next.js Applications next-env.d.ts should remain the same after a build
///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
+// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
"
`;
diff --git a/e2e/vite/src/vite-legacy.test.ts b/e2e/vite/src/vite-legacy.test.ts
index cc0117dd52..9eb5213a39 100644
--- a/e2e/vite/src/vite-legacy.test.ts
+++ b/e2e/vite/src/vite-legacy.test.ts
@@ -241,14 +241,14 @@ export default App;
const results = runCLI(`build ${app} --buildLibsFromSource=true`);
expect(results).toContain('Successfully ran target build for project');
// this should be more modules than build from dist
- expect(results).toContain('40 modules transformed');
+ expect(results).toContain('43 modules transformed');
});
it('should build app from libs dist', () => {
const results = runCLI(`build ${app} --buildLibsFromSource=false`);
expect(results).toContain('Successfully ran target build for project');
// this should be less modules than building from source
- expect(results).toContain('38 modules transformed');
+ expect(results).toContain('41 modules transformed');
});
it('should build app from libs without package.json in lib', () => {
diff --git a/packages/next/package.json b/packages/next/package.json
index cb5a5f39ce..df6b4b16e2 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -37,7 +37,7 @@
"dependencies": {
"@nx/devkit": "file:../devkit",
"@babel/plugin-proposal-decorators": "^7.22.7",
- "@svgr/webpack": "^8.0.1",
+ "@svgr/webpack": "^8.1.0",
"copy-webpack-plugin": "^10.2.4",
"file-loader": "^6.2.0",
"ignore": "^5.0.4",
diff --git a/packages/next/src/generators/application/application.ts b/packages/next/src/generators/application/application.ts
index 160f9eb85f..a99c7f889e 100644
--- a/packages/next/src/generators/application/application.ts
+++ b/packages/next/src/generators/application/application.ts
@@ -8,11 +8,8 @@ import {
} from '@nx/devkit';
import { initGenerator as jsInitGenerator } from '@nx/js';
import { setupTailwindGenerator } from '@nx/react';
-import {
- testingLibraryReactVersion,
- typesReactDomVersion,
- typesReactVersion,
-} from '@nx/react/src/utils/versions';
+import { testingLibraryReactVersion } from '@nx/react/src/utils/versions';
+import { getReactDependenciesVersionsToInstall } from '@nx/react/src/utils/version-utils';
import { normalizeOptions } from './lib/normalize-options';
import { Schema } from './schema';
@@ -104,9 +101,10 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
}
if (!options.skipPackageJson) {
+ const reactVersions = await getReactDependenciesVersionsToInstall(host);
const devDependencies: Record = {
- '@types/react': typesReactVersion,
- '@types/react-dom': typesReactDomVersion,
+ '@types/react': reactVersions['@types/react'],
+ '@types/react-dom': reactVersions['@types/react-dom'],
};
if (options.unitTestRunner && options.unitTestRunner !== 'none') {
diff --git a/packages/next/src/generators/application/files/common/next-env.d.ts__tmpl__ b/packages/next/src/generators/application/files/common/next-env.d.ts__tmpl__
index d8b0ee36a6..8c2499693c 100644
--- a/packages/next/src/generators/application/files/common/next-env.d.ts__tmpl__
+++ b/packages/next/src/generators/application/files/common/next-env.d.ts__tmpl__
@@ -2,4 +2,4 @@
///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/<%- appDirType %>/building-your-application/configuring/typescript for more information.
+// see https://nextjs.org/docs/<%- appDirType %>/api-reference/config/typescript for more information.
diff --git a/packages/next/src/generators/application/lib/add-e2e.ts b/packages/next/src/generators/application/lib/add-e2e.ts
index 6d30779272..97af47fe5b 100644
--- a/packages/next/src/generators/application/lib/add-e2e.ts
+++ b/packages/next/src/generators/application/lib/add-e2e.ts
@@ -224,12 +224,12 @@ async function getNextE2EWebServerInfo(
{
plugin: '@nx/next/plugin',
serveTargetName: 'devTargetName',
- serveStaticTargetName: 'serveStaticTargetName',
+ serveStaticTargetName: 'startTargetName',
configFilePath,
},
{
defaultServeTargetName: defaultServeTarget,
- defaultServeStaticTargetName: 'serve-static',
+ defaultServeStaticTargetName: 'start',
defaultE2EWebServerAddress: `http://127.0.0.1:${e2ePort}`,
defaultE2ECiBaseUrl: `http://localhost:${e2ePort}`,
defaultE2EPort: e2ePort,
diff --git a/packages/next/src/generators/init/init.ts b/packages/next/src/generators/init/init.ts
index 8401b29e4d..7ea4872677 100644
--- a/packages/next/src/generators/init/init.ts
+++ b/packages/next/src/generators/init/init.ts
@@ -8,23 +8,33 @@ import {
createProjectGraphAsync,
} from '@nx/devkit';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
-import { reactDomVersion, reactVersion } from '@nx/react/src/utils/versions';
+import {
+ getReactDependenciesVersionsToInstall,
+ isReact18,
+} from '@nx/react/src/utils/version-utils';
import { addGitIgnoreEntry } from '../../utils/add-gitignore-entry';
-import { nextVersion, nxVersion } from '../../utils/versions';
+import { nxVersion } from '../../utils/versions';
+import { getNextDependenciesVersionsToInstall } from '../../utils/version-utils';
import type { InitSchema } from './schema';
-function updateDependencies(host: Tree, schema: InitSchema) {
+async function updateDependencies(host: Tree, schema: InitSchema) {
const tasks: GeneratorCallback[] = [];
tasks.push(removeDependenciesFromPackageJson(host, ['@nx/next'], []));
+ const versions = await getNextDependenciesVersionsToInstall(
+ host,
+ await isReact18(host)
+ );
+ const reactVersions = await getReactDependenciesVersionsToInstall(host);
+
tasks.push(
addDependenciesToPackageJson(
host,
{
- next: nextVersion,
- react: reactVersion,
- 'react-dom': reactDomVersion,
+ next: versions.next,
+ react: reactVersions.react,
+ 'react-dom': reactVersions['react-dom'],
},
{
'@nx/next': nxVersion,
@@ -86,7 +96,7 @@ export async function nextInitGeneratorInternal(
let installTask: GeneratorCallback = () => {};
if (!schema.skipPackageJson) {
- installTask = updateDependencies(host, schema);
+ installTask = await updateDependencies(host, schema);
}
return installTask;
diff --git a/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap b/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap
index 9fa42c5386..45f62c3d20 100644
--- a/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap
+++ b/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap
@@ -45,12 +45,12 @@ exports[`@nx/next/plugin integrated projects should create nodes 1`] = `
},
},
"my-serve-static": {
- "executor": "@nx/web:file-server",
+ "command": "next start",
+ "dependsOn": [
+ "my-build",
+ ],
"options": {
- "buildTarget": "my-build",
- "port": 3000,
- "spa": false,
- "staticFilePath": "{projectRoot}/out",
+ "cwd": "my-app",
},
},
"my-start": {
@@ -121,12 +121,12 @@ exports[`@nx/next/plugin root projects should create nodes 1`] = `
},
},
"serve-static": {
- "executor": "@nx/web:file-server",
+ "command": "next start",
+ "dependsOn": [
+ "build",
+ ],
"options": {
- "buildTarget": "build",
- "port": 3000,
- "spa": false,
- "staticFilePath": "{projectRoot}/out",
+ "cwd": ".",
},
},
"start": {
diff --git a/packages/next/src/plugins/plugin.ts b/packages/next/src/plugins/plugin.ts
index 1f4111909d..5c817dcbdd 100644
--- a/packages/next/src/plugins/plugin.ts
+++ b/packages/next/src/plugins/plugin.ts
@@ -28,6 +28,9 @@ export interface NextPluginOptions {
buildTargetName?: string;
devTargetName?: string;
startTargetName?: string;
+ /**
+ * @deprecated Use `startTargetName` instead.
+ */
serveStaticTargetName?: string;
buildDepsTargetName?: string;
watchDepsTargetName?: string;
@@ -172,9 +175,11 @@ async function buildNextTargets(
targets[options.devTargetName] = getDevTargetConfig(projectRoot);
- targets[options.startTargetName] = getStartTargetConfig(options, projectRoot);
+ const startTarget = getStartTargetConfig(options, projectRoot);
- targets[options.serveStaticTargetName] = getStaticServeTargetConfig(options);
+ targets[options.startTargetName] = startTarget;
+
+ targets[options.serveStaticTargetName] = startTarget;
addBuildAndWatchDepsTargets(
context.workspaceRoot,
diff --git a/packages/next/src/utils/version-utils.ts b/packages/next/src/utils/version-utils.ts
new file mode 100644
index 0000000000..24472a9120
--- /dev/null
+++ b/packages/next/src/utils/version-utils.ts
@@ -0,0 +1,55 @@
+import { type Tree, readJson, createProjectGraphAsync } from '@nx/devkit';
+import { clean, coerce, major } from 'semver';
+import { nextVersion, next14Version } from './versions';
+
+type NextDependenciesVersions = {
+ next: string;
+};
+
+export async function getNextDependenciesVersionsToInstall(
+ tree: Tree,
+ isReact18 = false
+): Promise {
+ if (await isNext14(tree)) {
+ return {
+ next: next14Version,
+ };
+ } else {
+ return {
+ next: nextVersion,
+ };
+ }
+}
+
+export async function isNext14(tree: Tree) {
+ let installedNextVersion = await getInstalledNextVersionFromGraph();
+ if (!installedNextVersion) {
+ installedNextVersion = getInstalledNextVersion(tree);
+ }
+ return major(installedNextVersion) === 14;
+}
+
+export function getInstalledNextVersion(tree: Tree): string {
+ const pkgJson = readJson(tree, 'package.json');
+ const installedNextVersion =
+ pkgJson.dependencies && pkgJson.dependencies['next'];
+
+ if (
+ !installedNextVersion ||
+ installedNextVersion === 'latest' ||
+ installedNextVersion === 'next'
+ ) {
+ return clean(nextVersion) ?? coerce(nextVersion).version;
+ }
+
+ return clean(installedNextVersion) ?? coerce(installedNextVersion).version;
+}
+
+export async function getInstalledNextVersionFromGraph() {
+ const graph = await createProjectGraphAsync();
+ const nextDep = graph.externalNodes?.['npm:next'];
+ if (!nextDep) {
+ return undefined;
+ }
+ return clean(nextDep.data.version) ?? coerce(nextDep.data.version).version;
+}
diff --git a/packages/next/src/utils/versions.ts b/packages/next/src/utils/versions.ts
index 6b05ebf344..1398b38034 100644
--- a/packages/next/src/utils/versions.ts
+++ b/packages/next/src/utils/versions.ts
@@ -1,6 +1,7 @@
export const nxVersion = require('../../package.json').version;
-export const nextVersion = '14.2.16';
+export const nextVersion = '~15.1.4';
+export const next14Version = '~14.2.16';
export const eslintConfigNextVersion = '14.2.16';
export const sassVersion = '1.62.1';
export const lessLoader = '11.1.0';
diff --git a/packages/react/migrations.json b/packages/react/migrations.json
index 94f5a3acd2..bf08d839bf 100644
--- a/packages/react/migrations.json
+++ b/packages/react/migrations.json
@@ -197,6 +197,15 @@
"alwaysAddToPackageJson": false
}
}
+ },
+ "20.3.0": {
+ "version": "20.3.0-beta.0",
+ "packages": {
+ "@testing-library/react": {
+ "version": "16.1.0",
+ "alwaysAddToPackageJson": false
+ }
+ }
}
}
}
diff --git a/packages/react/package.json b/packages/react/package.json
index e5d7f0a334..0f8d11a87b 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -44,7 +44,8 @@
"@nx/web": "file:../web",
"@nx/module-federation": "file:../module-federation",
"express": "^4.19.2",
- "http-proxy-middleware": "^3.0.3"
+ "http-proxy-middleware": "^3.0.3",
+ "semver": "^7.6.3"
},
"publishConfig": {
"access": "public"
diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts
index aaa13e44e0..33d60bc838 100644
--- a/packages/react/src/generators/application/application.spec.ts
+++ b/packages/react/src/generators/application/application.spec.ts
@@ -1,9 +1,8 @@
-import 'nx/src/internal-testing-utils/mock-project-graph';
-
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import {
getPackageManagerCommand,
getProjects,
+ ProjectGraph,
readJson,
readNxJson,
Tree,
@@ -21,6 +20,17 @@ const { load } = require('@zkochan/js-yaml');
// which is v9 while we are testing for the new v10 version
jest.mock('@nx/cypress/src/utils/cypress-version');
+let projectGraph: ProjectGraph;
+jest.mock('@nx/devkit', () => {
+ const original = jest.requireActual('@nx/devkit');
+ return {
+ ...original,
+ createProjectGraphAsync: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve(projectGraph)),
+ };
+});
+
const packageCmd = getPackageManagerCommand().exec;
describe('app', () => {
@@ -41,6 +51,7 @@ describe('app', () => {
beforeEach(() => {
mockedInstalledCypressVersion.mockReturnValue(10);
appTree = createTreeWithEmptyWorkspace();
+ projectGraph = { dependencies: {}, nodes: {}, externalNodes: {} };
});
describe('not nested', () => {
@@ -1569,4 +1580,55 @@ describe('app', () => {
}
);
});
+
+ describe('react 19 support', () => {
+ beforeEach(() => {
+ projectGraph = { dependencies: {}, nodes: {}, externalNodes: {} };
+ });
+
+ it('should add react 19 dependencies when react version is not found', async () => {
+ projectGraph.externalNodes['npm:react'] = undefined;
+ const tree = createTreeWithEmptyWorkspace();
+
+ await applicationGenerator(tree, {
+ ...schema,
+ directory: 'my-dir/my-app',
+ });
+
+ const packageJson = readJson(tree, 'package.json');
+ expect(packageJson.dependencies['react']).toMatchInlineSnapshot(
+ `"19.0.0"`
+ );
+ expect(packageJson.dependencies['react-dom']).toMatchInlineSnapshot(
+ `"19.0.0"`
+ );
+ });
+
+ it('should add react 18 dependencies when react version is already 18', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+
+ projectGraph.externalNodes['npm:react'] = {
+ type: 'npm',
+ name: 'npm:react',
+ data: {
+ version: '18.3.1',
+ packageName: 'react',
+ hash: 'sha512-4+0/v9+l9/3+3/2+2/1+1/0',
+ },
+ };
+
+ await applicationGenerator(tree, {
+ ...schema,
+ directory: 'my-dir/my-app',
+ });
+
+ const packageJson = readJson(tree, 'package.json');
+ expect(packageJson.dependencies['react']).toMatchInlineSnapshot(
+ `"18.3.1"`
+ );
+ expect(packageJson.dependencies['react-dom']).toMatchInlineSnapshot(
+ `"18.3.1"`
+ );
+ });
+ });
});
diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts
index 176e18fd51..694aeb9b73 100644
--- a/packages/react/src/generators/application/application.ts
+++ b/packages/react/src/generators/application/application.ts
@@ -153,7 +153,7 @@ export async function applicationGeneratorInternal(
// Handle tsconfig.spec.json for jest or vitest
updateSpecConfig(tree, options);
- const stylePreprocessorTask = installCommonDependencies(tree, options);
+ const stylePreprocessorTask = await installCommonDependencies(tree, options);
tasks.push(stylePreprocessorTask);
const styledTask = addStyledModuleDependencies(tree, options);
tasks.push(styledTask);
diff --git a/packages/react/src/generators/application/lib/install-common-dependencies.ts b/packages/react/src/generators/application/lib/install-common-dependencies.ts
index 3f5bd9fa03..f9d12056fb 100644
--- a/packages/react/src/generators/application/lib/install-common-dependencies.ts
+++ b/packages/react/src/generators/application/lib/install-common-dependencies.ts
@@ -6,14 +6,16 @@ import {
sassVersion,
swcLoaderVersion,
testingLibraryReactVersion,
+ testingLibraryDomVersion,
tsLibVersion,
typesNodeVersion,
typesReactDomVersion,
typesReactVersion,
} from '../../../utils/versions';
import { NormalizedSchema } from '../schema';
+import { getReactDependenciesVersionsToInstall } from '../../../utils/version-utils';
-export function installCommonDependencies(
+export async function installCommonDependencies(
host: Tree,
options: NormalizedSchema
) {
@@ -21,11 +23,13 @@ export function installCommonDependencies(
return () => {};
}
+ const reactDeps = await getReactDependenciesVersionsToInstall(host);
+
const dependencies: Record = {};
const devDependencies: Record = {
'@types/node': typesNodeVersion,
- '@types/react': typesReactVersion,
- '@types/react-dom': typesReactDomVersion,
+ '@types/react': reactDeps['@types/react'],
+ '@types/react-dom': reactDeps['@types/react-dom'],
};
if (options.bundler !== 'vite') {
@@ -58,6 +62,7 @@ export function installCommonDependencies(
if (options.unitTestRunner && options.unitTestRunner !== 'none') {
devDependencies['@testing-library/react'] = testingLibraryReactVersion;
+ devDependencies['@testing-library/dom'] = testingLibraryDomVersion;
}
return addDependenciesToPackageJson(host, {}, devDependencies);
diff --git a/packages/react/src/generators/application/schema.json b/packages/react/src/generators/application/schema.json
index 7fa75f1588..d384322b53 100644
--- a/packages/react/src/generators/application/schema.json
+++ b/packages/react/src/generators/application/schema.json
@@ -101,7 +101,7 @@
"bundler": {
"description": "The bundler to use.",
"type": "string",
- "enum": ["vite", "webpack", "rspack", "rsbuild"],
+ "enum": ["vite", "rsbuild", "rspack", "webpack"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "vite",
"x-priority": "important"
diff --git a/packages/react/src/generators/host/host.rspack.spec.ts b/packages/react/src/generators/host/host.rspack.spec.ts
index 0330a9b3ed..2d3cb39bd4 100644
--- a/packages/react/src/generators/host/host.rspack.spec.ts
+++ b/packages/react/src/generators/host/host.rspack.spec.ts
@@ -9,6 +9,10 @@ jest.mock('@nx/devkit', () => {
const original = jest.requireActual('@nx/devkit');
return {
...original,
+ createProjectGraphAsync: jest.fn().mockResolvedValue({
+ dependencies: {},
+ nodes: {},
+ }),
readCachedProjectGraph: jest.fn().mockImplementation(
(): ProjectGraph => ({
dependencies: {},
diff --git a/packages/react/src/generators/host/host.webpack.spec.ts b/packages/react/src/generators/host/host.webpack.spec.ts
index 7f4a836377..820b644e88 100644
--- a/packages/react/src/generators/host/host.webpack.spec.ts
+++ b/packages/react/src/generators/host/host.webpack.spec.ts
@@ -9,6 +9,10 @@ jest.mock('@nx/devkit', () => {
const original = jest.requireActual('@nx/devkit');
return {
...original,
+ createProjectGraphAsync: jest.fn().mockResolvedValue({
+ dependencies: {},
+ nodes: {},
+ }),
readCachedProjectGraph: jest.fn().mockImplementation(
(): ProjectGraph => ({
dependencies: {},
diff --git a/packages/react/src/generators/init/init.ts b/packages/react/src/generators/init/init.ts
index 105e863d62..ac267d2fd3 100755
--- a/packages/react/src/generators/init/init.ts
+++ b/packages/react/src/generators/init/init.ts
@@ -6,21 +6,22 @@ import {
type GeneratorCallback,
type Tree,
} from '@nx/devkit';
-import { nxVersion, reactDomVersion, reactVersion } from '../../utils/versions';
+import { nxVersion } from '../../utils/versions';
import { InitSchema } from './schema';
+import { getReactDependenciesVersionsToInstall } from '../../utils/version-utils';
-export async function reactInitGenerator(host: Tree, schema: InitSchema) {
+export async function reactInitGenerator(tree: Tree, schema: InitSchema) {
const tasks: GeneratorCallback[] = [];
if (!schema.skipPackageJson) {
- tasks.push(removeDependenciesFromPackageJson(host, ['@nx/react'], []));
-
+ tasks.push(removeDependenciesFromPackageJson(tree, ['@nx/react'], []));
+ const reactDeps = await getReactDependenciesVersionsToInstall(tree);
tasks.push(
addDependenciesToPackageJson(
- host,
+ tree,
{
- react: reactVersion,
- 'react-dom': reactDomVersion,
+ react: reactDeps.react,
+ 'react-dom': reactDeps['react-dom'],
},
{
'@nx/react': nxVersion,
@@ -32,7 +33,7 @@ export async function reactInitGenerator(host: Tree, schema: InitSchema) {
}
if (!schema.skipFormat) {
- await formatFiles(host);
+ await formatFiles(tree);
}
return runTasksInSerial(...tasks);
diff --git a/packages/react/src/generators/setup-ssr/setup-ssr.spec.ts b/packages/react/src/generators/setup-ssr/setup-ssr.spec.ts
index b400220264..196f1f4287 100644
--- a/packages/react/src/generators/setup-ssr/setup-ssr.spec.ts
+++ b/packages/react/src/generators/setup-ssr/setup-ssr.spec.ts
@@ -5,6 +5,7 @@ import {
ProjectGraph,
readCachedProjectGraph,
readJson,
+ readNxJson,
Tree,
} from '@nx/devkit';
import applicationGenerator from '../application/application';
@@ -12,82 +13,93 @@ import setupSsrGenerator from './setup-ssr';
import { Linter } from '@nx/eslint';
jest.mock('@nx/devkit', () => {
+ const myAppData = {
+ root: 'my-app',
+ sourceRoot: 'my-app/src',
+ targets: {
+ build: {
+ executor: '@nx/webpack:webpack',
+ outputs: ['{options.outputPath}'],
+ defaultConfiguration: 'production',
+ options: {
+ compiler: 'babel',
+ outputPath: 'dist/app/my-app',
+ index: 'my-app/src/index.html',
+ baseHref: '/',
+ main: `my-app/src/main.tsx`,
+ tsConfig: 'my-app/tsconfig.app.json',
+ assets: ['my-app/src/favicon.ico', 'src/assets'],
+ styles: [`my-app/src/styles.css`],
+ scripts: [],
+ webpackConfig: 'my-app/webpack.config.js',
+ },
+ configurations: {
+ development: {
+ extractLicenses: false,
+ optimization: false,
+ sourceMap: true,
+ vendorChunk: true,
+ },
+ production: {
+ fileReplacements: [
+ {
+ replace: `my-app/src/environments/environment.ts`,
+ with: `my-app/src/environments/environment.prod.ts`,
+ },
+ ],
+ optimization: true,
+ outputHashing: 'all',
+ sourceMap: false,
+ namedChunks: false,
+ extractLicenses: true,
+ vendorChunk: false,
+ },
+ },
+ },
+ serve: {
+ executor: '@nx/webpack:dev-server',
+ defaultConfiguration: 'development',
+ options: {
+ buildTarget: `my-app:build`,
+ hmr: true,
+ },
+ configurations: {
+ development: {
+ buildTarget: `my-app:build:development`,
+ },
+ production: {
+ buildTarget: `my-app:build:production`,
+ hmr: false,
+ },
+ },
+ },
+ },
+ };
+ const pg: ProjectGraph = {
+ dependencies: {},
+ nodes: {
+ 'my-app': {
+ name: 'my-app',
+ type: 'app',
+ data: { ...myAppData },
+ },
+ },
+ };
const original = jest.requireActual('@nx/devkit');
return {
...original,
- readCachedProjectGraph: jest.fn().mockImplementation(
- (): ProjectGraph => ({
- dependencies: {},
- nodes: {
- 'my-app': {
- name: 'my-app',
- type: 'app',
- data: {
- root: 'my-app',
- sourceRoot: 'my-app/src',
- targets: {
- build: {
- executor: '@nx/webpack:webpack',
- outputs: ['{options.outputPath}'],
- defaultConfiguration: 'production',
- options: {
- compiler: 'babel',
- outputPath: 'dist/app/my-app',
- index: 'my-app/src/index.html',
- baseHref: '/',
- main: `my-app/src/main.tsx`,
- tsConfig: 'my-app/tsconfig.app.json',
- assets: ['my-app/src/favicon.ico', 'src/assets'],
- styles: [`my-app/src/styles.css`],
- scripts: [],
- webpackConfig: 'my-app/webpack.config.js',
- },
- configurations: {
- development: {
- extractLicenses: false,
- optimization: false,
- sourceMap: true,
- vendorChunk: true,
- },
- production: {
- fileReplacements: [
- {
- replace: `my-app/src/environments/environment.ts`,
- with: `my-app/src/environments/environment.prod.ts`,
- },
- ],
- optimization: true,
- outputHashing: 'all',
- sourceMap: false,
- namedChunks: false,
- extractLicenses: true,
- vendorChunk: false,
- },
- },
- },
- serve: {
- executor: '@nx/webpack:dev-server',
- defaultConfiguration: 'development',
- options: {
- buildTarget: `my-app:build`,
- hmr: true,
- },
- configurations: {
- development: {
- buildTarget: `my-app:build:development`,
- },
- production: {
- buildTarget: `my-app:build:production`,
- hmr: false,
- },
- },
- },
- },
- },
- },
- },
- })
- ),
+ createProjectGraphAsync: jest.fn().mockResolvedValue(pg),
+ readCachedProjectGraph: jest
+ .fn()
+ .mockImplementation((): ProjectGraph => pg),
+ readProjectConfiguration: jest
+ .fn()
+ .mockImplementation((tree, projectName) => {
+ if (projectName === 'my-app') {
+ return { ...myAppData };
+ }
+ return original.readProjectConfiguration(tree, projectName);
+ }),
};
});
@@ -106,12 +118,12 @@ describe('setupSsrGenerator', () => {
afterEach(() => {
process.env.NX_ADD_PLUGINS = originalEnv;
jest.clearAllMocks();
+ jest.resetModules();
});
- beforeEach(() => {
+ it('should add SSR files', async () => {
tree = createTreeWithEmptyWorkspace();
-
- applicationGenerator(tree, {
+ await applicationGenerator(tree, {
directory: appName,
style: 'css',
linter: Linter.None,
@@ -119,24 +131,14 @@ describe('setupSsrGenerator', () => {
e2eTestRunner: 'none',
skipFormat: true,
});
- });
- it('should add SSR files', async () => {
- await setupSsrGenerator(tree, {
- project: appName,
- });
-
- expect(tree.exists(`${appName}/server.ts`)).toBeTruthy();
- expect(tree.exists(`${appName}/tsconfig.server.json`)).toBeTruthy();
- });
-
- it('should support adding additional include files', async () => {
await setupSsrGenerator(tree, {
project: appName,
extraInclude: ['src/remote.d.ts'],
});
expect(tree.exists(`${appName}/server.ts`)).toBeTruthy();
+ expect(tree.exists(`${appName}/tsconfig.server.json`)).toBeTruthy();
expect(readJson(tree, `${appName}/tsconfig.server.json`)).toMatchObject({
include: ['src/remote.d.ts', 'src/main.server.tsx', 'server.ts'],
});
diff --git a/packages/react/src/generators/storybook-configuration/configuration.spec.ts b/packages/react/src/generators/storybook-configuration/configuration.spec.ts
index 144bd4d8af..474435061e 100644
--- a/packages/react/src/generators/storybook-configuration/configuration.spec.ts
+++ b/packages/react/src/generators/storybook-configuration/configuration.spec.ts
@@ -7,8 +7,8 @@ import libraryGenerator from '../library/library';
import storybookConfigurationGenerator from './configuration';
// nested code imports graph from the repo, which might have innacurate graph version
-jest.mock('nx/src/project-graph/project-graph', () => ({
- ...jest.requireActual('nx/src/project-graph/project-graph'),
+jest.mock('@nx/devkit', () => ({
+ ...jest.requireActual('@nx/devkit'),
createProjectGraphAsync: jest
.fn()
.mockImplementation(async () => ({ nodes: {}, dependencies: {} })),
diff --git a/packages/react/src/utils/version-utils.spec.ts b/packages/react/src/utils/version-utils.spec.ts
new file mode 100644
index 0000000000..f1ba612107
--- /dev/null
+++ b/packages/react/src/utils/version-utils.spec.ts
@@ -0,0 +1,115 @@
+import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
+import { getReactDependenciesVersionsToInstall } from './version-utils';
+import { type ProjectGraph } from '@nx/devkit';
+import {
+ reactDomV18Version,
+ reactDomVersion,
+ reactIsV18Version,
+ reactIsVersion,
+ reactV18Version,
+ reactVersion,
+ typesReactDomV18Version,
+ typesReactDomVersion,
+ typesReactIsV18Version,
+ typesReactIsVersion,
+ typesReactV18Version,
+ typesReactVersion,
+} from './versions';
+
+let projectGraph: ProjectGraph;
+
+jest.mock('@nx/devkit', () => {
+ const original = jest.requireActual('@nx/devkit');
+ return {
+ ...original,
+ createProjectGraphAsync: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve(projectGraph)),
+ };
+});
+
+describe('getReactDependenciesVersionsToInstall', () => {
+ beforeEach(() => {
+ projectGraph = {
+ dependencies: {},
+ nodes: {},
+ externalNodes: {},
+ };
+ });
+
+ it('should return the correct versions of react and react-dom when react 18 is installed', async () => {
+ // ARRANGE
+ projectGraph.externalNodes['npm:react'] = {
+ type: 'npm',
+ name: 'npm:react',
+ data: {
+ version: '18.3.1',
+ packageName: 'react',
+ hash: 'sha512-4+0/v9+l9/3+3/2+2/1+1/0',
+ },
+ };
+
+ // ACT
+ const reactDependencies = await getReactDependenciesVersionsToInstall(
+ createTreeWithEmptyWorkspace()
+ );
+
+ // ASSERT
+ expect(reactDependencies).toEqual({
+ react: reactV18Version,
+ 'react-dom': reactDomV18Version,
+ 'react-is': reactIsV18Version,
+ '@types/react': typesReactV18Version,
+ '@types/react-dom': typesReactDomV18Version,
+ '@types/react-is': typesReactIsV18Version,
+ });
+ });
+
+ it('should return the correct versions of react and react-dom when react 19 is installed', async () => {
+ // ARRANGE
+ projectGraph.externalNodes['npm:react'] = {
+ type: 'npm',
+ name: 'npm:react',
+ data: {
+ version: '19.0.0',
+ packageName: 'react',
+ hash: 'sha512-4+0/v9+l9/3+3/2+2/1+1/0',
+ },
+ };
+
+ // ACT
+ const reactDependencies = await getReactDependenciesVersionsToInstall(
+ createTreeWithEmptyWorkspace()
+ );
+
+ // ASSERT
+ expect(reactDependencies).toEqual({
+ react: reactVersion,
+ 'react-dom': reactDomVersion,
+ 'react-is': reactIsVersion,
+ '@types/react': typesReactVersion,
+ '@types/react-dom': typesReactDomVersion,
+ '@types/react-is': typesReactIsVersion,
+ });
+ });
+
+ it('should return the correct versions of react and react-dom when react is not installed', async () => {
+ // ARRANGE
+ projectGraph.externalNodes['npm:react'] = undefined;
+
+ // ACT
+ const reactDependencies = await getReactDependenciesVersionsToInstall(
+ createTreeWithEmptyWorkspace()
+ );
+
+ // ASSERT
+ expect(reactDependencies).toEqual({
+ react: reactVersion,
+ 'react-dom': reactDomVersion,
+ 'react-is': reactIsVersion,
+ '@types/react': typesReactVersion,
+ '@types/react-dom': typesReactDomVersion,
+ '@types/react-is': typesReactIsVersion,
+ });
+ });
+});
diff --git a/packages/react/src/utils/version-utils.ts b/packages/react/src/utils/version-utils.ts
new file mode 100644
index 0000000000..b45875a705
--- /dev/null
+++ b/packages/react/src/utils/version-utils.ts
@@ -0,0 +1,82 @@
+import { type Tree, readJson, createProjectGraphAsync } from '@nx/devkit';
+import { clean, coerce, major } from 'semver';
+import {
+ reactDomV18Version,
+ reactIsV18Version,
+ reactV18Version,
+ reactVersion,
+ typesReactDomV18Version,
+ typesReactIsV18Version,
+ typesReactV18Version,
+ reactDomVersion,
+ reactIsVersion,
+ typesReactVersion,
+ typesReactDomVersion,
+ typesReactIsVersion,
+} from './versions';
+
+type ReactDependenciesVersions = {
+ react: string;
+ 'react-dom': string;
+ 'react-is': string;
+ '@types/react': string;
+ '@types/react-dom': string;
+ '@types/react-is': string;
+};
+
+export async function getReactDependenciesVersionsToInstall(
+ tree: Tree
+): Promise {
+ if (await isReact18(tree)) {
+ return {
+ react: reactV18Version,
+ 'react-dom': reactDomV18Version,
+ 'react-is': reactIsV18Version,
+ '@types/react': typesReactV18Version,
+ '@types/react-dom': typesReactDomV18Version,
+ '@types/react-is': typesReactIsV18Version,
+ };
+ } else {
+ return {
+ react: reactVersion,
+ 'react-dom': reactDomVersion,
+ 'react-is': reactIsVersion,
+ '@types/react': typesReactVersion,
+ '@types/react-dom': typesReactDomVersion,
+ '@types/react-is': typesReactIsVersion,
+ };
+ }
+}
+
+export async function isReact18(tree: Tree) {
+ let installedReactVersion = await getInstalledReactVersionFromGraph();
+ if (!installedReactVersion) {
+ installedReactVersion = getInstalledReactVersion(tree);
+ }
+ return major(installedReactVersion) === 18;
+}
+
+export function getInstalledReactVersion(tree: Tree): string {
+ const pkgJson = readJson(tree, 'package.json');
+ const installedReactVersion =
+ pkgJson.dependencies && pkgJson.dependencies['react'];
+
+ if (
+ !installedReactVersion ||
+ installedReactVersion === 'latest' ||
+ installedReactVersion === 'next'
+ ) {
+ return clean(reactVersion) ?? coerce(reactVersion).version;
+ }
+
+ return clean(installedReactVersion) ?? coerce(installedReactVersion).version;
+}
+
+export async function getInstalledReactVersionFromGraph() {
+ const graph = await createProjectGraphAsync();
+ const reactDep = graph.externalNodes?.['npm:react'];
+ if (!reactDep) {
+ return undefined;
+ }
+ return clean(reactDep.data.version) ?? coerce(reactDep.data.version).version;
+}
diff --git a/packages/react/src/utils/versions.ts b/packages/react/src/utils/versions.ts
index 5de406ee21..aa4eb487cd 100755
--- a/packages/react/src/utils/versions.ts
+++ b/packages/react/src/utils/versions.ts
@@ -1,13 +1,19 @@
export const nxVersion = require('../../package.json').version;
-export const reactVersion = '18.3.1';
-export const reactDomVersion = '18.3.1';
-export const reactIsVersion = '18.3.1';
+export const reactVersion = '19.0.0';
+export const reactV18Version = '18.3.1';
+export const reactDomVersion = '19.0.0';
+export const reactDomV18Version = '18.3.1';
+export const reactIsVersion = '19.0.0';
+export const reactIsV18Version = '18.3.1';
export const swcLoaderVersion = '0.1.15';
export const babelLoaderVersion = '^9.1.2';
-export const typesReactVersion = '18.3.1';
-export const typesReactDomVersion = '18.3.0';
-export const typesReactIsVersion = '18.3.0';
+export const typesReactV18Version = '18.3.1';
+export const typesReactVersion = '19.0.0';
+export const typesReactDomV18Version = '18.3.0';
+export const typesReactDomVersion = '19.0.0';
+export const typesReactIsV18Version = '18.3.0';
+export const typesReactIsVersion = '19.0.0';
export const reactViteVersion = '^4.2.0';
export const typesNodeVersion = '18.16.9';
@@ -27,7 +33,8 @@ export const styledJsxVersion = '5.1.2';
export const reactRouterDomVersion = '6.11.2';
-export const testingLibraryReactVersion = '15.0.6';
+export const testingLibraryReactVersion = '16.1.0';
+export const testingLibraryDomVersion = '10.4.0';
export const reduxjsToolkitVersion = '1.9.3';
export const reactReduxVersion = '8.0.5';
diff --git a/packages/rspack/src/utils/versions.ts b/packages/rspack/src/utils/versions.ts
index fae45debec..d8675268cb 100644
--- a/packages/rspack/src/utils/versions.ts
+++ b/packages/rspack/src/utils/versions.ts
@@ -6,11 +6,7 @@ export const rspackPluginMinifyVersion = '^0.7.5';
export const rspackPluginReactRefreshVersion = '^1.0.0';
export const lessLoaderVersion = '~11.1.3';
-export const reactVersion = '~18.2.0';
export const reactRefreshVersion = '~0.14.0';
-export const reactDomVersion = '~18.2.0';
-export const typesReactVersion = '~18.0.28';
-export const typesReactDomVersion = '~18.0.10';
export const nestjsCommonVersion = '~9.0.0';
export const nestjsCoreVersion = '~9.0.0';
diff --git a/packages/workspace/src/generators/preset/schema.d.ts b/packages/workspace/src/generators/preset/schema.d.ts
index b2a9c23e08..6587a027d7 100644
--- a/packages/workspace/src/generators/preset/schema.d.ts
+++ b/packages/workspace/src/generators/preset/schema.d.ts
@@ -11,7 +11,7 @@ export interface Schema {
standaloneConfig?: boolean;
framework?: string;
packageManager?: PackageManager;
- bundler?: 'vite' | 'webpack' | 'rspack' | 'esbuild';
+ bundler?: 'vite' | 'rsbuild' | 'webpack' | 'rspack' | 'esbuild';
docker?: boolean;
nextAppDir?: boolean;
nextSrcDir?: boolean;
diff --git a/packages/workspace/src/generators/preset/schema.json b/packages/workspace/src/generators/preset/schema.json
index e3fdb0fae0..91137092a9 100644
--- a/packages/workspace/src/generators/preset/schema.json
+++ b/packages/workspace/src/generators/preset/schema.json
@@ -72,7 +72,7 @@
"bundler": {
"description": "The bundler to use for building the application.",
"type": "string",
- "enum": ["webpack", "vite", "rspack", "esbuild"],
+ "enum": ["vite", "rspack", "rsbuild", "esbuild", "webpack"],
"default": "vite"
},
"docker": {