feat(react): add support for React 19 for new Workspaces (#29286)
## Current Behavior We currently have no support for React 19, generating only React 18 applications. ## Expected Behavior Add utils to determine what version of React is installed in the workspace. If React 18 is the main version of react installed, continue to generate React 18 projects. If React 19 is the main version of react installed, generate React 19 projects. If no React version is installed or can be determined, generate React 19 projects.
This commit is contained in:
parent
bc0566f4c3
commit
a468d72c7f
@ -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"
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -5,7 +5,7 @@ exports[`Next.js Applications next-env.d.ts should remain the same after a build
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// 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
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// 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.
|
||||
"
|
||||
`;
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<string, string> = {
|
||||
'@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') {
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// 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.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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,
|
||||
|
||||
55
packages/next/src/utils/version-utils.ts
Normal file
55
packages/next/src/utils/version-utils.ts
Normal file
@ -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<NextDependenciesVersions> {
|
||||
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;
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<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') {
|
||||
@ -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);
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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: {},
|
||||
|
||||
@ -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: {},
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
ProjectGraph,
|
||||
readCachedProjectGraph,
|
||||
readJson,
|
||||
readNxJson,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import applicationGenerator from '../application/application';
|
||||
@ -12,17 +13,7 @@ import setupSsrGenerator from './setup-ssr';
|
||||
import { Linter } from '@nx/eslint';
|
||||
|
||||
jest.mock('@nx/devkit', () => {
|
||||
const original = jest.requireActual('@nx/devkit');
|
||||
return {
|
||||
...original,
|
||||
readCachedProjectGraph: jest.fn().mockImplementation(
|
||||
(): ProjectGraph => ({
|
||||
dependencies: {},
|
||||
nodes: {
|
||||
'my-app': {
|
||||
name: 'my-app',
|
||||
type: 'app',
|
||||
data: {
|
||||
const myAppData = {
|
||||
root: 'my-app',
|
||||
sourceRoot: 'my-app/src',
|
||||
targets: {
|
||||
@ -83,11 +74,32 @@ jest.mock('@nx/devkit', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const pg: ProjectGraph = {
|
||||
dependencies: {},
|
||||
nodes: {
|
||||
'my-app': {
|
||||
name: 'my-app',
|
||||
type: 'app',
|
||||
data: { ...myAppData },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
};
|
||||
const original = jest.requireActual('@nx/devkit');
|
||||
return {
|
||||
...original,
|
||||
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'],
|
||||
});
|
||||
|
||||
@ -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: {} })),
|
||||
|
||||
115
packages/react/src/utils/version-utils.spec.ts
Normal file
115
packages/react/src/utils/version-utils.spec.ts
Normal file
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
82
packages/react/src/utils/version-utils.ts
Normal file
82
packages/react/src/utils/version-utils.ts
Normal file
@ -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<ReactDependenciesVersions> {
|
||||
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;
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user