From 45c458e6774e8007fb2aecd4e96f4fc1b5f54fb3 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Fri, 26 Jul 2024 08:30:16 -0400 Subject: [PATCH] fix(react): generate valid Vite + JSX setup for React (#27130) The current `@nx/react:app` generator does not take the `--js` option into account. There are two problems: 1. `index.html` includes `main.tsx` not `main.jsx`. 2. `.js` files with JSX are invalid in Vite, and must be named `.jsx`. This PR adds a new option to the `toJS` devkit util to preserve `.jsx` rather than renaming them to `.js`. The vast majority of non-Vite React projects will use `.js` and not `.jsx` (e.g. Next.js, Expo, Remix, etc.) so we just want to apply this change to Vite only for now. In the future we could enhance React generators to support `--jsx`, for example. ## Current Behavior ## Expected Behavior ## Related Issue(s) Fixes #20810 --- docs/generated/devkit/ToJSOptions.md | 11 ++--- packages/devkit/src/generators/to-js.spec.ts | 45 +++++++++++++++++++ packages/devkit/src/generators/to-js.ts | 20 ++++++--- .../application/application.spec.ts | 13 ++++++ .../files/base-vite/index.html__tmpl__ | 2 +- .../lib/create-application-files.ts | 5 ++- 6 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 packages/devkit/src/generators/to-js.spec.ts diff --git a/docs/generated/devkit/ToJSOptions.md b/docs/generated/devkit/ToJSOptions.md index 73060f872d..69b31b379e 100644 --- a/docs/generated/devkit/ToJSOptions.md +++ b/docs/generated/devkit/ToJSOptions.md @@ -4,8 +4,9 @@ #### Type declaration -| Name | Type | -| :---------- | :------------------------------ | -| `extension` | `".js"` \| `".mjs"` \| `".cjs"` | -| `module?` | `ModuleKind` | -| `target?` | `ScriptTarget` | +| Name | Type | +| :----------- | :------------------------------ | +| `extension?` | `".js"` \| `".mjs"` \| `".cjs"` | +| `module?` | `ModuleKind` | +| `target?` | `ScriptTarget` | +| `useJsx?` | `boolean` | diff --git a/packages/devkit/src/generators/to-js.spec.ts b/packages/devkit/src/generators/to-js.spec.ts new file mode 100644 index 0000000000..e79dd8e1eb --- /dev/null +++ b/packages/devkit/src/generators/to-js.spec.ts @@ -0,0 +1,45 @@ +import { createTree } from 'nx/src/generators/testing-utils/create-tree'; +import type { Tree } from 'nx/src/generators/tree'; + +import { toJS } from './to-js'; + +describe('toJS', () => { + let tree: Tree; + beforeEach(() => { + tree = createTree(); + }); + + it('should renamed .ts and .tsx file to .js', () => { + tree.write('a.ts', '// a'); + tree.write('b.tsx', '// b'); + + toJS(tree); + + expect(tree.exists('a.ts')).toBeFalsy(); + expect(tree.exists('b.tsx')).toBeFalsy(); + expect(tree.read('a.js', 'utf-8')).toContain('// a'); + expect(tree.read('b.js', 'utf-8')).toContain('// b'); + }); + + it('should support different extensions', () => { + tree.write('a.ts', '// a'); + + toJS(tree, { + extension: '.mjs', + }); + + expect(tree.read('a.mjs', 'utf-8')).toContain('// a'); + }); + + it('should support .jsx rather than .js files (for Vite)', () => { + tree.write('a.ts', '// a'); + tree.write('b.tsx', '// b'); + + toJS(tree, { + useJsx: true, + }); + + expect(tree.read('a.js', 'utf-8')).toContain('// a'); + expect(tree.read('b.jsx', 'utf-8')).toContain('// b'); + }); +}); diff --git a/packages/devkit/src/generators/to-js.ts b/packages/devkit/src/generators/to-js.ts index cba3f61b4e..f2dd4a2427 100644 --- a/packages/devkit/src/generators/to-js.ts +++ b/packages/devkit/src/generators/to-js.ts @@ -1,12 +1,13 @@ import type { Tree } from 'nx/src/devkit-exports'; -import type { ScriptTarget, ModuleKind } from 'typescript'; +import type { ModuleKind, ScriptTarget } from 'typescript'; import { typescriptVersion } from '../utils/versions'; import { ensurePackage } from '../utils/package-json'; export type ToJSOptions = { - target?: ScriptTarget; + extension?: '.js' | '.mjs' | '.cjs'; module?: ModuleKind; - extension: '.js' | '.mjs' | '.cjs'; + target?: ScriptTarget; + useJsx?: boolean; }; /** @@ -32,10 +33,15 @@ export function toJS(tree: Tree, options?: ToJSOptions): void { module: options?.module ?? ModuleKind.ESNext, }) ); - tree.rename( - c.path, - c.path.replace(/\.tsx?$/, options?.extension ?? '.js') - ); + tree.rename(c.path, c.path.replace(/\.ts$/, options?.extension ?? '.js')); + if (options?.useJsx) { + tree.rename(c.path, c.path.replace(/\.tsx$/, '.jsx')); + } else { + tree.rename( + c.path, + c.path.replace(/\.tsx$/, options?.extension ?? '.js') + ); + } } } } diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 5429b209a7..bd72ff56d2 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -399,6 +399,19 @@ describe('app', () => { }); expect(appTree.read('my-app/vite.config.ts', 'utf-8')).toMatchSnapshot(); + expect(appTree.read('my-app/index.html', 'utf-8')).toContain('main.tsx'); + }); + + it('should setup vite if bundler is vite (--js)', async () => { + await applicationGenerator(appTree, { + ...schema, + name: 'my-app', + bundler: 'vite', + js: true, + }); + + expect(appTree.read('my-app/index.html', 'utf-8')).toContain('main.jsx'); + expect(appTree.exists('my-app/src/main.jsx')).toBeTruthy(); }); it('should setup the nx vite dev server builder if bundler is vite', async () => { diff --git a/packages/react/src/generators/application/files/base-vite/index.html__tmpl__ b/packages/react/src/generators/application/files/base-vite/index.html__tmpl__ index 6af82e6a00..5e62456103 100644 --- a/packages/react/src/generators/application/files/base-vite/index.html__tmpl__ +++ b/packages/react/src/generators/application/files/base-vite/index.html__tmpl__ @@ -11,6 +11,6 @@
- + diff --git a/packages/react/src/generators/application/lib/create-application-files.ts b/packages/react/src/generators/application/lib/create-application-files.ts index 972bb3fe7e..6205ce81fe 100644 --- a/packages/react/src/generators/application/lib/create-application-files.ts +++ b/packages/react/src/generators/application/lib/create-application-files.ts @@ -42,6 +42,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { const templateVariables = { ...names(options.name), ...options, + js: !!options.js, // Ensure this is defined in template tmpl: '', offsetFromRoot: offsetFromRoot(options.appProjectRoot), appTests, @@ -155,7 +156,9 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { ); if (options.js) { - toJS(host); + toJS(host, { + useJsx: options.bundler === 'vite', + }); } createTsConfig(