@nx/react 20.5.0 → 20.6.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +6 -6
  2. package/router-plugin.d.ts +1 -0
  3. package/router-plugin.js +5 -0
  4. package/src/generators/application/application.js +48 -9
  5. package/src/generators/application/files/react-router-ssr/common/app/app-nav.tsx__tmpl__ +15 -0
  6. package/src/generators/application/files/react-router-ssr/common/app/entry.client.tsx__tmpl__ +18 -0
  7. package/src/generators/application/files/react-router-ssr/common/app/entry.server.tsx__tmpl__ +74 -0
  8. package/src/generators/application/files/react-router-ssr/common/app/root.tsx__tmpl__ +51 -0
  9. package/src/generators/application/files/react-router-ssr/common/app/routes/about.tsx__tmpl__ +7 -0
  10. package/src/generators/application/files/react-router-ssr/common/app/routes.tsx__tmpl__ +6 -0
  11. package/src/generators/application/files/react-router-ssr/common/public/favicon.ico +0 -0
  12. package/src/generators/application/files/react-router-ssr/common/react-router.config.ts__tmpl__ +5 -0
  13. package/src/generators/application/files/react-router-ssr/common/tests/routes/_index.spec.tsx__tmpl__ +16 -0
  14. package/src/generators/application/files/react-router-ssr/common/tsconfig.app.json__tmpl__ +23 -0
  15. package/src/generators/application/files/react-router-ssr/common/tsconfig.json__tmpl__ +27 -0
  16. package/src/generators/application/files/react-router-ssr/non-root/.gitignore__tmpl__ +5 -0
  17. package/src/generators/application/files/react-router-ssr/non-root/package.json__tmpl__ +24 -0
  18. package/src/generators/application/files/react-router-ssr/nx-welcome/claimed/app/nx-welcome.tsx__tmpl__ +866 -0
  19. package/src/generators/application/files/react-router-ssr/nx-welcome/not-configured/app/nx-welcome.tsx__tmpl__ +866 -0
  20. package/src/generators/application/files/react-router-ssr/nx-welcome/unclaimed/app/nx-welcome.tsx__tmpl__ +864 -0
  21. package/src/generators/application/files/react-router-ssr/ts-solution/package.json__tmpl__ +24 -0
  22. package/src/generators/application/files/react-router-ssr/ts-solution/tsconfig.app.json__tmpl__ +39 -0
  23. package/src/generators/application/lib/add-e2e.js +29 -20
  24. package/src/generators/application/lib/add-linting.d.ts +1 -0
  25. package/src/generators/application/lib/add-linting.js +38 -0
  26. package/src/generators/application/lib/add-project.js +20 -16
  27. package/src/generators/application/lib/add-routing.js +1 -1
  28. package/src/generators/application/lib/bundlers/add-vite.js +15 -6
  29. package/src/generators/application/lib/create-application-files.js +40 -3
  30. package/src/generators/application/lib/install-common-dependencies.js +13 -2
  31. package/src/generators/application/lib/normalize-options.js +4 -0
  32. package/src/generators/application/schema.d.ts +2 -1
  33. package/src/generators/application/schema.json +10 -1
  34. package/src/generators/host/host.js +2 -2
  35. package/src/generators/init/init.js +23 -0
  36. package/src/generators/init/schema.d.ts +2 -0
  37. package/src/generators/library/lib/create-files.js +2 -1
  38. package/src/generators/library/lib/normalize-options.js +1 -0
  39. package/src/generators/library/library.js +14 -11
  40. package/src/generators/library/schema.d.ts +1 -0
  41. package/src/generators/library/schema.json +4 -0
  42. package/src/generators/remote/remote.js +2 -2
  43. package/src/plugins/router-plugin.d.ts +10 -0
  44. package/src/plugins/router-plugin.js +219 -0
  45. package/src/utils/ast-utils.d.ts +1 -1
  46. package/src/utils/ast-utils.js +2 -2
  47. package/src/utils/versions.d.ts +3 -1
  48. package/src/utils/versions.js +6 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/react",
3
- "version": "20.5.0",
3
+ "version": "20.6.0-beta.1",
4
4
  "private": false,
5
5
  "description": "The React plugin for Nx contains executors and generators for managing React applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Jest, Vitest, Playwright, Cypress, and Storybook.\n\n- Generators for applications, libraries, components, hooks, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
6
6
  "repository": {
@@ -38,11 +38,11 @@
38
38
  "minimatch": "9.0.3",
39
39
  "picocolors": "^1.1.0",
40
40
  "tslib": "^2.3.0",
41
- "@nx/devkit": "20.5.0",
42
- "@nx/js": "20.5.0",
43
- "@nx/eslint": "20.5.0",
44
- "@nx/web": "20.5.0",
45
- "@nx/module-federation": "20.5.0",
41
+ "@nx/devkit": "20.6.0-beta.1",
42
+ "@nx/js": "20.6.0-beta.1",
43
+ "@nx/eslint": "20.6.0-beta.1",
44
+ "@nx/web": "20.6.0-beta.1",
45
+ "@nx/module-federation": "20.6.0-beta.1",
46
46
  "express": "^4.21.2",
47
47
  "http-proxy-middleware": "^3.0.3",
48
48
  "semver": "^7.6.3"
@@ -0,0 +1 @@
1
+ export { createNodesV2, ReactRouterPluginOptions, } from './src/plugins/router-plugin';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNodesV2 = void 0;
4
+ var router_plugin_1 = require("./src/plugins/router-plugin");
5
+ Object.defineProperty(exports, "createNodesV2", { enumerable: true, get: function () { return router_plugin_1.createNodesV2; } });
@@ -26,9 +26,11 @@ const add_rspack_1 = require("./lib/bundlers/add-rspack");
26
26
  const add_rsbuild_1 = require("./lib/bundlers/add-rsbuild");
27
27
  const add_vite_1 = require("./lib/bundlers/add-vite");
28
28
  const sort_fields_1 = require("@nx/js/src/utils/package-json/sort-fields");
29
+ const prompt_1 = require("@nx/devkit/src/generators/prompt");
29
30
  async function applicationGenerator(tree, schema) {
30
31
  return await applicationGeneratorInternal(tree, {
31
32
  addPlugin: false,
33
+ useProjectJson: true,
32
34
  ...schema,
33
35
  });
34
36
  }
@@ -44,11 +46,25 @@ async function applicationGeneratorInternal(tree, schema) {
44
46
  });
45
47
  tasks.push(jsInitTask);
46
48
  const options = await (0, normalize_options_1.normalizeOptions)(tree, schema);
47
- // If we are using the new TS solution
48
- // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
49
- if (options.isUsingTsSolutionConfig) {
50
- (0, ts_solution_setup_1.addProjectToTsSolutionWorkspace)(tree, options.appProjectRoot);
51
- }
49
+ options.useReactRouter = options.routing
50
+ ? options.useReactRouter ??
51
+ (await (0, prompt_1.promptWhenInteractive)({
52
+ name: 'response',
53
+ message: 'Would you like to use react-router for server-side rendering?',
54
+ type: 'autocomplete',
55
+ choices: [
56
+ {
57
+ name: 'Yes',
58
+ message: 'I want to use react-router [ https://reactrouter.com/start/framework/routing ]',
59
+ },
60
+ {
61
+ name: 'No',
62
+ message: 'I do not want to use react-router for server-side rendering',
63
+ },
64
+ ],
65
+ initial: 0,
66
+ }, { response: 'No' }).then((r) => r.response === 'Yes'))
67
+ : false;
52
68
  (0, show_possible_warnings_1.showPossibleWarnings)(tree, options);
53
69
  const initTask = await (0, init_1.default)(tree, {
54
70
  ...options,
@@ -83,6 +99,11 @@ async function applicationGeneratorInternal(tree, schema) {
83
99
  }
84
100
  await (0, create_application_files_1.createApplicationFiles)(tree, options);
85
101
  (0, add_project_1.addProject)(tree, options);
102
+ // If we are using the new TS solution
103
+ // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
104
+ if (options.isUsingTsSolutionConfig) {
105
+ await (0, ts_solution_setup_1.addProjectToTsSolutionWorkspace)(tree, options.appProjectRoot);
106
+ }
86
107
  if (options.style === 'tailwind') {
87
108
  const twTask = await (0, setup_tailwind_1.setupTailwindGenerator)(tree, {
88
109
  project: options.projectName,
@@ -112,16 +133,34 @@ async function applicationGeneratorInternal(tree, schema) {
112
133
  }
113
134
  // Handle tsconfig.spec.json for jest or vitest
114
135
  (0, update_jest_config_1.updateSpecConfig)(tree, options);
115
- const stylePreprocessorTask = await (0, install_common_dependencies_1.installCommonDependencies)(tree, options);
116
- tasks.push(stylePreprocessorTask);
136
+ const commonDependencyTask = await (0, install_common_dependencies_1.installCommonDependencies)(tree, options);
137
+ tasks.push(commonDependencyTask);
117
138
  const styledTask = (0, add_styled_dependencies_1.addStyledModuleDependencies)(tree, options);
118
139
  tasks.push(styledTask);
119
- const routingTask = (0, add_routing_1.addRouting)(tree, options);
120
- tasks.push(routingTask);
140
+ if (!options.useReactRouter) {
141
+ const routingTask = (0, add_routing_1.addRouting)(tree, options);
142
+ tasks.push(routingTask);
143
+ }
121
144
  (0, set_defaults_1.setDefaults)(tree, options);
122
145
  if (options.bundler === 'rspack' && options.style === 'styled-jsx') {
123
146
  (0, add_rspack_1.handleStyledJsxForRspack)(tasks, tree, options);
124
147
  }
148
+ if (options.useReactRouter) {
149
+ (0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.json'), (json) => {
150
+ const types = new Set(json.compilerOptions?.types || []);
151
+ types.add('@react-router/node');
152
+ return {
153
+ ...json,
154
+ compilerOptions: {
155
+ ...json.compilerOptions,
156
+ jsx: 'react-jsx',
157
+ moduleResolution: 'bundler',
158
+ types: Array.from(types),
159
+ },
160
+ };
161
+ });
162
+ }
163
+ // Only for the new TS solution
125
164
  (0, ts_solution_setup_1.updateTsconfigFiles)(tree, options.appProjectRoot, 'tsconfig.app.json', {
126
165
  jsx: 'react-jsx',
127
166
  module: 'esnext',
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ import { NavLink } from "react-router";
3
+
4
+ export function AppNav() {
5
+ return (
6
+ <nav>
7
+ <NavLink to="/" end>
8
+ Home
9
+ </NavLink>
10
+ <NavLink to="/about" end>
11
+ About
12
+ </NavLink>
13
+ </nav>
14
+ );
15
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * By default, React Router will handle hydrating your app on the client for you.
3
+ * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx react-router reveal` ✨
4
+ * For more information, see https://reactrouter.com/explanation/special-files#entryclienttsx
5
+ */
6
+
7
+ import { HydratedRouter } from 'react-router/dom';
8
+ import { startTransition, StrictMode } from "react";
9
+ import { hydrateRoot } from "react-dom/client";
10
+
11
+ startTransition(() => {
12
+ hydrateRoot(
13
+ document,
14
+ <StrictMode>
15
+ <HydratedRouter />
16
+ </StrictMode>
17
+ );
18
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * By default, React Router will handle generating the HTTP Response for you.
3
+ * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4
+ * For more information, see https://reactrouter.com/explanation/special-files#entryservertsx
5
+ */
6
+
7
+ import { PassThrough } from "node:stream";
8
+
9
+ import type { AppLoadContext, EntryContext } from "react-router";
10
+ import { createReadableStreamFromReadable } from "@react-router/node";
11
+ import { ServerRouter } from "react-router";
12
+ import { isbot } from "isbot";
13
+ import type { RenderToPipeableStreamOptions } from "react-dom/server";
14
+ import { renderToPipeableStream } from "react-dom/server";
15
+
16
+ export const streamTimeout = 5_000;
17
+
18
+ export default function handleRequest(
19
+ request: Request,
20
+ responseStatusCode: number,
21
+ responseHeaders: Headers,
22
+ routerContext: EntryContext,
23
+ loadContext: AppLoadContext
24
+ ) {
25
+ return new Promise((resolve, reject) => {
26
+ let shellRendered = false;
27
+ const userAgent = request.headers.get("user-agent");
28
+
29
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
30
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
31
+ const readyOption: keyof RenderToPipeableStreamOptions =
32
+ (userAgent && isbot(userAgent)) || routerContext.isSpaMode
33
+ ? "onAllReady"
34
+ : "onShellReady";
35
+
36
+ const { pipe, abort } = renderToPipeableStream(
37
+ <ServerRouter context={routerContext} url={request.url} />,
38
+ {
39
+ [readyOption]() {
40
+ shellRendered = true;
41
+ const body = new PassThrough();
42
+ const stream = createReadableStreamFromReadable(body);
43
+
44
+ responseHeaders.set("Content-Type", "text/html");
45
+
46
+ resolve(
47
+ new Response(stream, {
48
+ headers: responseHeaders,
49
+ status: responseStatusCode,
50
+ })
51
+ );
52
+
53
+ pipe(body);
54
+ },
55
+ onShellError(error: unknown) {
56
+ reject(error);
57
+ },
58
+ onError(error: unknown) {
59
+ responseStatusCode = 500;
60
+ // Log streaming rendering errors from inside the shell. Don't log
61
+ // errors encountered during initial shell rendering since they'll
62
+ // reject and get logged in handleDocumentRequest.
63
+ if (shellRendered) {
64
+ console.error(error);
65
+ }
66
+ },
67
+ }
68
+ );
69
+
70
+ // Abort the rendering stream after the `streamTimeout` so it has time to
71
+ // flush down the rejected boundaries
72
+ setTimeout(abort, streamTimeout + 1000);
73
+ });
74
+ }
@@ -0,0 +1,51 @@
1
+ import {
2
+ Links,
3
+ Meta,
4
+ Outlet,
5
+ Scripts,
6
+ ScrollRestoration,
7
+ type MetaFunction,
8
+ type LinksFunction
9
+ } from "react-router";
10
+
11
+ import { AppNav } from './app-nav'
12
+
13
+ export const meta: MetaFunction = () => ([{
14
+ title: "New Nx React Router App",
15
+ }]);
16
+
17
+ export const links: LinksFunction = () => [
18
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
19
+ {
20
+ rel: "preconnect",
21
+ href: "https://fonts.gstatic.com",
22
+ crossOrigin: "anonymous",
23
+ },
24
+ {
25
+ rel: "stylesheet",
26
+ href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
27
+ },
28
+ ];
29
+
30
+ export function Layout({ children }: { children: React.ReactNode }) {
31
+ return (
32
+ <html lang="en">
33
+ <head>
34
+ <meta charSet="utf-8" />
35
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
36
+ <Meta />
37
+ <Links />
38
+ </head>
39
+ <body>
40
+ <AppNav />
41
+ {children}
42
+ <ScrollRestoration />
43
+ <Scripts />
44
+ </body>
45
+ </html>
46
+ );
47
+ }
48
+
49
+ export default function App() {
50
+ return <Outlet />;
51
+ }
@@ -0,0 +1,7 @@
1
+ export default function AboutComponent() {
2
+ return (
3
+ <div>
4
+ <h1>About!!!</h1>
5
+ </div>
6
+ );
7
+ }
@@ -0,0 +1,6 @@
1
+ import { type RouteConfig, index, route } from "@react-router/dev/routes";
2
+
3
+ export default [
4
+ index('<%- js ? `./${fileName}.jsx` : `./${fileName}.tsx` %>'),
5
+ route('about', './routes/about.tsx')
6
+ ] satisfies RouteConfig;
@@ -0,0 +1,5 @@
1
+ import type { Config } from "@react-router/dev/config";
2
+
3
+ export default {
4
+ ssr: true,
5
+ } satisfies Config;
@@ -0,0 +1,16 @@
1
+ import { createRoutesStub } from 'react-router';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import App from '../../app/app';
4
+
5
+ test('renders loader data', async () => {
6
+ const ReactRouterStub = createRoutesStub([
7
+ {
8
+ path: '/',
9
+ Component: App,
10
+ },
11
+ ]);
12
+
13
+ render(<ReactRouterStub />);
14
+
15
+ await waitFor(() => screen.findByText('Hello there,'));
16
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": [
4
+ "app/**/*.ts",
5
+ "app/**/*.tsx",
6
+ "app/**/*.js",
7
+ "app/**/*.jsx",
8
+ "**/.server/**/*.ts",
9
+ "**/.server/**/*.tsx",
10
+ "**/.client/**/*.ts",
11
+ "**/.client/**/*.tsx"
12
+ ],
13
+ "exclude": [
14
+ "tests/**/*.spec.ts",
15
+ "tests/**/*.test.ts",
16
+ "tests/**/*.spec.tsx",
17
+ "tests/**/*.test.tsx",
18
+ "tests/**/*.spec.js",
19
+ "tests/**/*.test.js",
20
+ "tests/**/*.spec.jsx",
21
+ "tests/**/*.test.jsx"
22
+ ]
23
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "extends": "<%= offsetFromRoot %>tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
5
+ "types": ["@react-router/node", "vite/client"],
6
+ "isolatedModules": true,
7
+ "esModuleInterop": true,
8
+ "jsx": "react-jsx",
9
+ "module": "ESNext",
10
+ "moduleResolution": "Bundler",
11
+ "resolveJsonModule": true,
12
+ "target": "ES2022",
13
+ "strict": true,
14
+ "allowJs": true,
15
+ "skipLibCheck": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ // Vite takes care of building everything.
18
+ "noEmit": true
19
+ },
20
+ "include": [],
21
+ "files": [],
22
+ "references": [
23
+ {
24
+ "path": "./tsconfig.app.json"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,5 @@
1
+ .cache
2
+ build
3
+ public/build
4
+ .env
5
+ .react-router
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "<%= projectName %>",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {},
6
+ "dependencies": {
7
+ "@react-router/node": "<%= reactRouterVersion %>",
8
+ "@react-router/serve": "<%= reactRouterVersion %>",
9
+ "isbot": "<%= reactRouterIsBotVersion %>",
10
+ "react": "<%= reactVersion %>",
11
+ "react-dom": "<%= reactVersion %>",
12
+ "react-router": "<%= reactRouterVersion %>"
13
+ },
14
+ "devDependencies": {
15
+ "@react-router/dev": "<%= reactRouterVersion %>",
16
+ "@types/node": "<%= typesNodeVersion %>",
17
+ "@types/react": "<%= reactVersion %>",
18
+ "@types/react-dom": "<%= reactVersion %>"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "sideEffects": false
24
+ }