Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react): Add react-router to create-nx-workspace and react app generator #30316

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 33 additions & 34 deletions docs/generated/cli/create-nx-workspace.md

Large diffs are not rendered by default.

67 changes: 33 additions & 34 deletions docs/generated/packages/nx/documents/create-nx-workspace.md

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion docs/generated/packages/react/generators/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@
"routing": {
"type": "boolean",
"description": "Generate application with routes.",
"x-prompt": "Would you like to add React Router to this application?",
"x-prompt": "Would you like to add routing to this application?",
"default": false
},
"useReactRouter": {
"description": "Use React Router for routing.",
"type": "boolean",
"default": false
},
"skipFormat": {
Expand Down
5 changes: 5 additions & 0 deletions docs/generated/packages/workspace/generators/new.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"type": "boolean",
"default": true
},
"useReactRouter": {
"description": "Use React Router for routing.",
"type": "boolean",
"default": false
},
"standaloneApi": {
"description": "Use Standalone Components if generating an Angular application.",
"type": "boolean",
Expand Down
5 changes: 5 additions & 0 deletions docs/generated/packages/workspace/generators/preset.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"type": "boolean",
"default": true
},
"useReactRouter": {
"description": "Use React Router for routing.",
"type": "boolean",
"default": false
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",
Expand Down
68 changes: 68 additions & 0 deletions e2e/react/src/react-router.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
checkFilesExist,
cleanupProject,
ensureCypressInstallation,
newProject,
readFile,
runCLI,
uniq,
} from '@nx/e2e/utils';

describe('React Router Applications', () => {
beforeAll(() => {
newProject({ packages: ['@nx/react'] });
ensureCypressInstallation();
});

afterAll(() => cleanupProject());

it('should generate a react-router application', async () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --no-interactive`
);

const packageJson = JSON.parse(readFile('package.json'));
expect(packageJson.dependencies['react-router']).toBeDefined();
expect(packageJson.dependencies['@react-router/node']).toBeDefined();
expect(packageJson.dependencies['@react-router/serve']).toBeDefined();
expect(packageJson.dependencies['isbot']).toBeDefined();

checkFilesExist(`${appName}/app/app.tsx`);
checkFilesExist(`${appName}/app/entry.client.tsx`);
checkFilesExist(`${appName}/app/entry.server.tsx`);
checkFilesExist(`${appName}/app/routes.tsx`);
checkFilesExist(`${appName}/react-router.config.ts`);
checkFilesExist(`${appName}/vite.config.ts`);
});

it('should be able to build a react-router application', async () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --no-interactive`
);

const buildResult = runCLI(`build ${appName}`);
expect(buildResult).toContain('Successfully ran target build');
});

it('should be able to lint a react-router application', async () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --linter=eslint --no-interactive`
);

const buildResult = runCLI(`lint ${appName}`);
expect(buildResult).toContain('Successfully ran target lint');
});

it('should be able to test a react-router application', async () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --unit-test-runner=vitest --no-interactive`
);

const buildResult = runCLI(`test ${appName}`);
expect(buildResult).toContain('Successfully ran target test');
});
});
6 changes: 6 additions & 0 deletions e2e/utils/create-project-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ export function runCreateWorkspace(
cwd = e2eCwd,
bundler,
routing,
useReactRouter,
standaloneApi,
docker,
nextAppDir,
Expand All @@ -244,6 +245,7 @@ export function runCreateWorkspace(
bundler?: 'webpack' | 'vite';
standaloneApi?: boolean;
routing?: boolean;
useReactRouter?: boolean;
docker?: boolean;
nextAppDir?: boolean;
nextSrcDir?: boolean;
Expand Down Expand Up @@ -295,6 +297,10 @@ export function runCreateWorkspace(
command += ` --routing=${routing}`;
}

if (useReactRouter !== undefined) {
command += ` --useReactRouter=${useReactRouter}`;
}

if (base) {
command += ` --defaultBase="${base}"`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ export default defineConfig(() => ({
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: ['src/test-setup.ts'],
reporters: ['default'],
coverage: {
Expand Down
84 changes: 53 additions & 31 deletions packages/create-nx-workspace/bin/create-nx-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ interface ReactArguments extends BaseArguments {
stack: 'react';
workspaceType: 'standalone' | 'integrated';
appName: string;
framework: 'none' | 'next' | 'remix';
framework: 'none' | 'next';
style: string;
bundler: 'webpack' | 'vite' | 'rspack';
nextAppDir: boolean;
nextSrcDir: boolean;
useReactRouter: boolean;
routing: boolean;
unitTestRunner: 'none' | 'jest' | 'vitest';
e2eTestRunner: 'none' | 'cypress' | 'playwright';
}
Expand Down Expand Up @@ -156,10 +158,14 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
default: true,
})
.option('routing', {
describe: chalk.dim`Add a routing setup for an Angular app.`,
describe: chalk.dim`Add a routing setup for an Angular or React app.`,
type: 'boolean',
default: true,
})
.option('useReactRouter', {
describe: chalk.dim`Generate a Server-Side Rendered (SSR) React app using React Router.`,
type: 'boolean',
})
.option('bundler', {
describe: chalk.dim`Bundler to be used to build the app.`,
type: 'string',
Expand Down Expand Up @@ -376,8 +382,6 @@ async function determineStack(
case Preset.ReactMonorepo:
case Preset.NextJs:
case Preset.NextJsStandalone:
case Preset.RemixStandalone:
case Preset.RemixMonorepo:
case Preset.ReactNative:
case Preset.Expo:
return 'react';
Expand Down Expand Up @@ -590,6 +594,8 @@ async function determineReactOptions(
let bundler: undefined | 'webpack' | 'vite' | 'rspack' = undefined;
let unitTestRunner: undefined | 'none' | 'jest' | 'vitest' = undefined;
let e2eTestRunner: undefined | 'none' | 'cypress' | 'playwright' = undefined;
let useReactRouter = false;
let routing = true;
let nextAppDir = false;
let nextSrcDir = false;
let linter: undefined | 'none' | 'eslint';
Expand All @@ -601,8 +607,7 @@ async function determineReactOptions(
preset = parsedArgs.preset;
if (
preset === Preset.ReactStandalone ||
preset === Preset.NextJsStandalone ||
preset === Preset.RemixStandalone
preset === Preset.NextJsStandalone
) {
appName = parsedArgs.appName ?? parsedArgs.name;
} else {
Expand All @@ -628,17 +633,12 @@ async function determineReactOptions(
} else {
preset = Preset.NextJs;
}
} else if (framework === 'remix') {
if (isStandalone) {
preset = Preset.RemixStandalone;
} else {
preset = Preset.RemixMonorepo;
}
} else if (framework === 'react-native') {
preset = Preset.ReactNative;
} else if (framework === 'expo') {
preset = Preset.Expo;
} else {
useReactRouter = await determineReactRouter(parsedArgs);
if (isStandalone) {
preset = Preset.ReactStandalone;
} else {
Expand All @@ -648,7 +648,7 @@ async function determineReactOptions(
}

if (preset === Preset.ReactStandalone || preset === Preset.ReactMonorepo) {
bundler = await determineReactBundler(parsedArgs);
bundler = useReactRouter ? 'vite' : await determineReactBundler(parsedArgs);
unitTestRunner = await determineUnitTestRunner(parsedArgs, {
preferVitest: bundler === 'vite',
});
Expand All @@ -660,14 +660,6 @@ async function determineReactOptions(
exclude: 'vitest',
});
e2eTestRunner = await determineE2eTestRunner(parsedArgs);
} else if (
preset === Preset.RemixMonorepo ||
preset === Preset.RemixStandalone
) {
unitTestRunner = await determineUnitTestRunner(parsedArgs, {
preferVitest: true,
});
e2eTestRunner = await determineE2eTestRunner(parsedArgs);
} else if (preset === Preset.ReactNative || preset === Preset.Expo) {
unitTestRunner = await determineUnitTestRunner(parsedArgs, {
exclude: 'vitest',
Expand Down Expand Up @@ -747,6 +739,8 @@ async function determineReactOptions(
nextSrcDir,
unitTestRunner,
e2eTestRunner,
useReactRouter,
routing,
linter,
formatter,
workspaces,
Expand Down Expand Up @@ -1220,9 +1214,9 @@ async function determineAppName(

async function determineReactFramework(
parsedArgs: yargs.Arguments<ReactArguments>
): Promise<'none' | 'nextjs' | 'remix' | 'expo' | 'react-native'> {
): Promise<'none' | 'nextjs' | 'expo' | 'react-native'> {
const reply = await enquirer.prompt<{
framework: 'none' | 'nextjs' | 'remix' | 'expo' | 'react-native';
framework: 'none' | 'nextjs' | 'expo' | 'react-native';
}>([
{
name: 'framework',
Expand All @@ -1232,23 +1226,19 @@ async function determineReactFramework(
{
name: 'none',
message: 'None',
hint: ' I only want react and react-dom',
hint: ' I only want react, react-dom or react-router',
},
{
name: 'nextjs',
message: 'Next.js [ https://nextjs.org/ ]',
},
{
name: 'remix',
message: 'Remix [ https://remix.run/ ]',
message: 'Next.js [ https://nextjs.org/ ]',
},
{
name: 'expo',
message: 'Expo [ https://expo.io/ ]',
message: 'Expo [ https://expo.io/ ]',
},
{
name: 'react-native',
message: 'React Native [ https://reactnative.dev/ ]',
message: 'React Native [ https://reactnative.dev/ ]',
},
],
initial: 0,
Expand Down Expand Up @@ -1493,3 +1483,35 @@ async function determineE2eTestRunner(
]);
return reply.e2eTestRunner;
}

async function determineReactRouter(
parsedArgs: yargs.Arguments<{
useReactRouter?: boolean;
}>
): Promise<boolean> {
if (parsedArgs.routing !== undefined && parsedArgs.routing === false)
return false;
if (parsedArgs.useReactRouter !== undefined) return parsedArgs.useReactRouter;
const reply = await enquirer.prompt<{
response: 'Yes' | 'No';
}>([
{
message:
'Would you like to use React Router for server-side rendering [https://reactrouter.com/]?',
type: 'autocomplete',
name: 'response',
skip: !parsedArgs.interactive || isCI(),
choices: [
{
name: 'Yes',
hint: 'I want to use React Router',
},
{
name: 'No',
},
],
initial: 0,
},
]);
return reply.response === 'Yes';
}
1 change: 0 additions & 1 deletion packages/create-nx-workspace/src/create-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ function getWorkspaceGlobsFromPreset(preset: string): string[] {
case Preset.Nuxt:
case Preset.ReactNative:
case Preset.ReactMonorepo:
case Preset.RemixMonorepo:
case Preset.VueMonorepo:
case Preset.WebComponents:
return ['apps/*'];
Expand Down
2 changes: 0 additions & 2 deletions packages/create-nx-workspace/src/utils/preset/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export enum Preset {
NuxtStandalone = 'nuxt-standalone',
NextJs = 'next',
NextJsStandalone = 'nextjs-standalone',
RemixMonorepo = 'remix-monorepo',
RemixStandalone = 'remix-standalone',
ReactNative = 'react-native',
Expo = 'expo',
Nest = 'nest',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export default defineConfig(() => ({
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
Expand Down Expand Up @@ -585,7 +585,7 @@ export default defineConfig(() => ({
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/myApp',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ export default defineConfig(() => ({
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
Expand Down Expand Up @@ -511,7 +511,7 @@ export default defineConfig(() => ({
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
Expand Down
Loading