Skip to content

Commit

Permalink
feat(react): add support for React 19 for new Workspaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Coly010 committed Jan 6, 2025
1 parent cb4a93e commit 5164a7b
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 28 deletions.
9 changes: 9 additions & 0 deletions packages/react/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@
"alwaysAddToPackageJson": false
}
}
},
"20.3.0": {
"version": "20.3.0-beta.0",
"packages": {
"@testing-library/react": {
"version": "16.1.0",
"alwaysAddToPackageJson": false
}
}
}
}
}
3 changes: 2 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
66 changes: 64 additions & 2 deletions packages/react/src/generators/application/application.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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', () => {
Expand All @@ -41,6 +51,7 @@ describe('app', () => {
beforeEach(() => {
mockedInstalledCypressVersion.mockReturnValue(10);
appTree = createTreeWithEmptyWorkspace();
projectGraph = { dependencies: {}, nodes: {}, externalNodes: {} };
});

describe('not nested', () => {
Expand Down Expand Up @@ -1555,4 +1566,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"`
);
});
});
});
2 changes: 1 addition & 1 deletion packages/react/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@ import {
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
) {
if (options.skipPackageJson) {
return () => {};
}

const reactDeps = await getReactDependenciesVersionsToInstall(host);

const dependencies: Record<string, string> = {};
const devDependencies: Record<string, string> = {
'@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') {
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/generators/host/host.rspack.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/generators/host/host.webpack.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand Down
17 changes: 9 additions & 8 deletions packages/react/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -32,7 +33,7 @@ export async function reactInitGenerator(host: Tree, schema: InitSchema) {
}

if (!schema.skipFormat) {
await formatFiles(host);
await formatFiles(tree);
}

return runTasksInSerial(...tasks);
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/generators/setup-ssr/setup-ssr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>('nx/src/project-graph/project-graph'),
jest.mock('@nx/devkit', () => ({
...jest.requireActual<any>('@nx/devkit'),
createProjectGraphAsync: jest
.fn()
.mockImplementation(async () => ({ nodes: {}, dependencies: {} })),
Expand Down
115 changes: 115 additions & 0 deletions packages/react/src/utils/version-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
});
});
});
Loading

0 comments on commit 5164a7b

Please sign in to comment.