diff --git a/e2e/react/src/react.test.ts b/e2e/react/src/react.test.ts
index 8e5db024e982ea..fb10609860f25e 100644
--- a/e2e/react/src/react.test.ts
+++ b/e2e/react/src/react.test.ts
@@ -8,6 +8,7 @@ import {
listFiles,
newProject,
readFile,
+ readJson,
runCLI,
runCLIAsync,
runE2ETests,
@@ -17,147 +18,238 @@ import {
} from '@nx/e2e/utils';
import { readFileSync } from 'fs-extra';
import { join } from 'path';
+const { load, dump } = require('@zkochan/js-yaml');
describe('React Applications', () => {
let proj: string;
describe('Crystal Supported Tests', () => {
- beforeAll(() => {
- proj = newProject({ packages: ['@nx/react'] });
- ensureCypressInstallation();
+ describe('useTsSolution (PM=npm)', () => {
+ beforeAll(() => {
+ proj = newProject({
+ packages: ['@nx/react'],
+ packageManager: 'npm',
+ workspaces: true,
+ });
+ ensureCypressInstallation();
+ });
+
+ afterAll(() => cleanupProject());
+ it('None buildable libs using (workspaces = true) should be excluded from js/ts plugin', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
+
+ runCLI(
+ `generate @nx/react:app apps/${appName} --name=${appName} --useTsSolution=true --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ );
+ runCLI(
+ `generate @nx/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
+ );
+
+ const nxJson = JSON.parse(readFile('nx.json'));
+
+ const jsTypescriptPlugin = nxJson.plugins.find(
+ (plugin) => plugin.plugin === '@nx/js/typescript'
+ );
+ expect(jsTypescriptPlugin).toBeDefined();
+
+ expect(
+ jsTypescriptPlugin.exclude.includes(`${libName}/*`)
+ ).toBeTruthy();
+ }, 250_000);
+
+ it('Apps/libs using (workspaces = true) should be added to workspaces', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
+
+ runCLI(
+ `generate @nx/react:app apps/${appName} --name=${appName} --useTsSolution true --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ );
+ runCLI(
+ `generate @nx/react:lib ${libName} --bundler=vite --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
+ );
+
+ const packageJson = readJson('package.json');
+ expect(packageJson.workspaces).toContain(`apps/${appName}`);
+ expect(packageJson.workspaces).toContain(`${libName}/*`);
+ });
});
- afterAll(() => cleanupProject());
+ describe('useTsSolution (PM=pnpm)', () => {
+ beforeAll(() => {
+ proj = newProject({
+ packages: ['@nx/react'],
+ packageManager: 'pnpm',
+ workspaces: true,
+ });
+ ensureCypressInstallation();
+ });
- it('should be able to use Vite to build and test apps', async () => {
- const appName = uniq('app');
- const libName = uniq('lib');
+ afterAll(() => cleanupProject());
- runCLI(
- `generate @nx/react:app apps/${appName} --name=${appName} --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
- );
- runCLI(
- `generate @nx/react:lib libs/${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
- );
+ it('None buildable libs using (workspaces = true) should be excluded from js/ts plugin', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
- // Library generated with Vite
- checkFilesExist(`libs/${libName}/vite.config.ts`);
+ runCLI(
+ `generate @nx/react:app apps/${appName} --name=${appName} --useTsSolution=true --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ );
+ runCLI(
+ `generate @nx/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
+ );
- const mainPath = `apps/${appName}/src/main.tsx`;
- updateFile(
- mainPath,
- `
- import '@${proj}/${libName}';
- ${readFile(mainPath)}
- `
- );
+ const nxJson = JSON.parse(readFile('nx.json'));
- runCLI(`build ${appName}`);
+ const jsTypescriptPlugin = nxJson.plugins.find(
+ (plugin) => plugin.plugin === '@nx/js/typescript'
+ );
+ expect(jsTypescriptPlugin).toBeDefined();
- checkFilesExist(`dist/apps/${appName}/index.html`);
+ expect(
+ jsTypescriptPlugin.exclude.includes(`${libName}/*`)
+ ).toBeTruthy();
+ }, 250_000);
- if (runE2ETests()) {
- const e2eResults = runCLI(`e2e ${appName}-e2e`);
- expect(e2eResults).toContain('Successfully ran target e2e for project');
- expect(await killPorts()).toBeTruthy();
- }
- }, 250_000);
+ it('Apps/libs using (useTsSolution = true) should be added to workspaces', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
- it('None buildable libs using (useTsSolution = true) should be excluded from js/ts plugin', async () => {
- const appName = uniq('app');
- const libName = uniq('lib');
+ runCLI(
+ `generate @nx/react:app apps/${appName} --name=${appName} --useTsSolution=true --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ );
+ runCLI(
+ `generate @nx/react:lib ${libName} --bundler=vite --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
+ );
- runCLI(
- `generate @nx/react:app apps/${appName} --name=${appName} --useTsSolution true --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
- );
- runCLI(
- `generate @nx/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
- );
+ const pnpmWorkspaceContent = readFile('pnpm-workspace.yaml');
+ const pnmpWorkspaceYaml = load(pnpmWorkspaceContent);
+ expect(pnmpWorkspaceYaml.packages).toContain(`apps/${appName}`);
+ expect(pnmpWorkspaceYaml.packages).toContain(`${libName}/*`);
+ });
+ });
- const nxJson = JSON.parse(readFile('nx.json'));
+ describe('other Crystal tests', () => {
+ beforeAll(() => {
+ proj = newProject({ packages: ['@nx/react'] });
+ ensureCypressInstallation();
+ });
- const jsTypescriptPlugin = nxJson.plugins.find(
- (plugin) => plugin.plugin === '@nx/js/typescript'
- );
- expect(jsTypescriptPlugin).toBeDefined();
+ afterAll(() => cleanupProject());
+ it('should be able to use Vite to build and test apps', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
- expect(jsTypescriptPlugin.exclude.includes(`${libName}/*`)).toBeTruthy();
- }, 250_000);
+ runCLI(
+ `generate @nx/react:app apps/${appName} --name=${appName} --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ );
+ runCLI(
+ `generate @nx/react:lib libs/${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
+ );
- it('should be able to use Rspack to build and test apps', async () => {
- const appName = uniq('app');
- const libName = uniq('lib');
+ // Library generated with Vite
+ checkFilesExist(`libs/${libName}/vite.config.ts`);
- runCLI(
- `generate @nx/react:app ${appName} --bundler=rspack --unit-test-runner=vitest --no-interactive --skipFormat --linter=eslint`
- );
- runCLI(
- `generate @nx/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
- );
+ const mainPath = `apps/${appName}/src/main.tsx`;
+ updateFile(
+ mainPath,
+ `
+ import '@${proj}/${libName}';
+ ${readFile(mainPath)}
+ `
+ );
- // Library generated with Vite
- checkFilesExist(`${libName}/vite.config.ts`);
+ runCLI(`build ${appName}`);
- const mainPath = `${appName}/src/main.tsx`;
- updateFile(
- mainPath,
- `
+ checkFilesExist(`dist/apps/${appName}/index.html`);
+
+ if (runE2ETests()) {
+ const e2eResults = runCLI(`e2e ${appName}-e2e`);
+ expect(e2eResults).toContain(
+ 'Successfully ran target e2e for project'
+ );
+ expect(await killPorts()).toBeTruthy();
+ }
+ }, 250_000);
+
+ it('should be able to use Rspack to build and test apps', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
+
+ runCLI(
+ `generate @nx/react:app ${appName} --bundler=rspack --unit-test-runner=vitest --no-interactive --skipFormat --linter=eslint`
+ );
+ runCLI(
+ `generate @nx/react:lib ${libName} --bundler=none --no-interactive --unit-test-runner=vitest --skipFormat --linter=eslint`
+ );
+
+ // Library generated with Vite
+ checkFilesExist(`${libName}/vite.config.ts`);
+
+ const mainPath = `${appName}/src/main.tsx`;
+ updateFile(
+ mainPath,
+ `
import '@${proj}/${libName}';
${readFile(mainPath)}
`
- );
+ );
- runCLI(`build ${appName}`, { verbose: true });
+ runCLI(`build ${appName}`, { verbose: true });
- checkFilesExist(`dist/${appName}/index.html`);
+ checkFilesExist(`dist/${appName}/index.html`);
- if (runE2ETests()) {
- // TODO(Colum): investigate why webkit is failing
- const e2eResults = runCLI(`e2e ${appName}-e2e -- --project=chromium`, {
- verbose: true,
- });
- expect(e2eResults).toContain('Successfully ran target e2e for project');
- expect(await killPorts()).toBeTruthy();
- }
- }, 250_000);
+ if (runE2ETests()) {
+ // TODO(Colum): investigate why webkit is failing
+ const e2eResults = runCLI(
+ `e2e ${appName}-e2e -- --project=chromium`,
+ {
+ verbose: true,
+ }
+ );
+ expect(e2eResults).toContain(
+ 'Successfully ran target e2e for project'
+ );
+ expect(await killPorts()).toBeTruthy();
+ }
+ }, 250_000);
- it('should be able to generate a react app + lib (with CSR and SSR)', async () => {
- const appName = uniq('app');
- const libName = uniq('lib');
- const libWithNoComponents = uniq('lib');
- const logoSvg = readFileSync(join(__dirname, 'logo.svg')).toString();
- const blueSvg = ``;
- const redSvg = ``;
+ it('should be able to generate a react app + lib (with CSR and SSR)', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
+ const libWithNoComponents = uniq('lib');
+ const logoSvg = readFileSync(join(__dirname, 'logo.svg')).toString();
+ const blueSvg = ``;
+ const redSvg = ``;
- runCLI(
- `generate @nx/react:app apps/${appName} --style=css --bundler=webpack --unit-test-runner=jest --no-interactive --skipFormat --linter=eslint`
- );
- runCLI(
- `generate @nx/react:lib libs/${libName} --style=css --no-interactive --unit-test-runner=jest --skipFormat --linter=eslint`
- );
- runCLI(
- `generate @nx/react:lib libs/${libWithNoComponents} --no-interactive --no-component --unit-test-runner=jest --skipFormat --linter=eslint`
- );
+ runCLI(
+ `generate @nx/react:app apps/${appName} --style=css --bundler=webpack --unit-test-runner=jest --no-interactive --skipFormat --linter=eslint`
+ );
+ runCLI(
+ `generate @nx/react:lib libs/${libName} --style=css --no-interactive --unit-test-runner=jest --skipFormat --linter=eslint`
+ );
+ runCLI(
+ `generate @nx/react:lib libs/${libWithNoComponents} --no-interactive --no-component --unit-test-runner=jest --skipFormat --linter=eslint`
+ );
- // Libs should not include package.json by default
- checkFilesDoNotExist(`libs/${libName}/package.json`);
+ // Libs should not include package.json by default
+ checkFilesDoNotExist(`libs/${libName}/package.json`);
- const mainPath = `apps/${appName}/src/main.tsx`;
- updateFile(
- mainPath,
- `
+ const mainPath = `apps/${appName}/src/main.tsx`;
+ updateFile(
+ mainPath,
+ `
import '@${proj}/${libWithNoComponents}';
import '@${proj}/${libName}';
${readFile(mainPath)}
`
- );
+ );
- updateFile(`apps/${appName}/src/app/blue/img.svg`, blueSvg); // ensure that same filenames do not conflict
- updateFile(`apps/${appName}/src/app/red/img.svg`, redSvg); // ensure that same filenames do not conflict
- updateFile(`apps/${appName}/src/app/logo.svg`, logoSvg);
- updateFile(
- `apps/${appName}/src/app/app.tsx`,
- `
+ updateFile(`apps/${appName}/src/app/blue/img.svg`, blueSvg); // ensure that same filenames do not conflict
+ updateFile(`apps/${appName}/src/app/red/img.svg`, redSvg); // ensure that same filenames do not conflict
+ updateFile(`apps/${appName}/src/app/logo.svg`, logoSvg);
+ updateFile(
+ `apps/${appName}/src/app/app.tsx`,
+ `
import { ReactComponent as Logo } from './logo.svg';
import blue from './blue/img.svg';
import red from './red/img.svg';
@@ -178,149 +270,149 @@ describe('React Applications', () => {
export default App;
`
- );
+ );
- // Make sure global stylesheets are properly processed.
- const stylesPath = `apps/${appName}/src/styles.css`;
- updateFile(
- stylesPath,
- `
+ // Make sure global stylesheets are properly processed.
+ const stylesPath = `apps/${appName}/src/styles.css`;
+ updateFile(
+ stylesPath,
+ `
.foobar {
background-image: url('/bg.png');
}
`
- );
-
- const libTestResults = await runCLIAsync(`test ${libName}`);
- expect(libTestResults.combinedOutput).toContain(
- 'Test Suites: 1 passed, 1 total'
- );
-
- await testGeneratedApp(appName, {
- checkSourceMap: true,
- checkStyles: true,
- checkLinter: true,
- // TODO(jack): check why Playwright tests are timing out in CI
- checkE2E: false,
- });
-
- // Set up SSR and check app
- runCLI(`generate @nx/react:setup-ssr ${appName} --skipFormat`);
- checkFilesExist(`apps/${appName}/src/main.server.tsx`);
- checkFilesExist(`apps/${appName}/server.ts`);
+ );
- await testGeneratedApp(appName, {
- checkSourceMap: false,
- checkStyles: false,
- checkLinter: false,
- // TODO(jack): check why Playwright tests are timing out in CI
- checkE2E: false,
- });
- }, 500_000);
+ const libTestResults = await runCLIAsync(`test ${libName}`);
+ expect(libTestResults.combinedOutput).toContain(
+ 'Test Suites: 1 passed, 1 total'
+ );
- it('should generate app with routing', async () => {
- const appName = uniq('app');
+ await testGeneratedApp(appName, {
+ checkSourceMap: true,
+ checkStyles: true,
+ checkLinter: true,
+ // TODO(jack): check why Playwright tests are timing out in CI
+ checkE2E: false,
+ });
- runCLI(
- `generate @nx/react:app apps/${appName} --routing --bundler=webpack --no-interactive --skipFormat --linter=eslint --unitTestRunner=jest`
- );
+ // Set up SSR and check app
+ runCLI(`generate @nx/react:setup-ssr ${appName} --skipFormat`);
+ checkFilesExist(`apps/${appName}/src/main.server.tsx`);
+ checkFilesExist(`apps/${appName}/server.ts`);
+
+ await testGeneratedApp(appName, {
+ checkSourceMap: false,
+ checkStyles: false,
+ checkLinter: false,
+ // TODO(jack): check why Playwright tests are timing out in CI
+ checkE2E: false,
+ });
+ }, 500_000);
- runCLI(`build ${appName}`);
+ it('should generate app with routing', async () => {
+ const appName = uniq('app');
- checkFilesExistWithHash(`dist/apps/${appName}`, [
- `index.html`,
- `runtime.*.js`,
- `main.*.js`,
- ]);
- }, 250_000);
+ runCLI(
+ `generate @nx/react:app apps/${appName} --routing --bundler=webpack --no-interactive --skipFormat --linter=eslint --unitTestRunner=jest`
+ );
- it('should be able to add a redux slice', async () => {
- const appName = uniq('app');
- const libName = uniq('lib');
+ runCLI(`build ${appName}`);
- runCLI(
- `g @nx/react:app apps/${appName} --bundler=webpack --no-interactive --skipFormat --unitTestRunner=jest --linter=eslint`
- );
- runCLI(
- `g @nx/react:redux apps/${appName}/src/app/lemon/lemon --skipFormat`
- );
- runCLI(
- `g @nx/react:lib libs/${libName} --unit-test-runner=jest --no-interactive --skipFormat`
- );
- runCLI(
- `g @nx/react:redux libs/${libName}/src/lib/orange/orange --skipFormat`
- );
+ checkFilesExistWithHash(`dist/apps/${appName}`, [
+ `index.html`,
+ `runtime.*.js`,
+ `main.*.js`,
+ ]);
+ }, 250_000);
- let lintResults = runCLI(`lint ${appName}`);
- expect(lintResults).toContain(
- `Successfully ran target lint for project ${appName}`
- );
- const appTestResults = await runCLIAsync(`test ${appName}`);
- expect(appTestResults.combinedOutput).toContain(
- `Successfully ran target test for project ${appName}`
- );
+ it('should be able to add a redux slice', async () => {
+ const appName = uniq('app');
+ const libName = uniq('lib');
- lintResults = runCLI(`lint ${libName}`);
- expect(lintResults).toContain(
- `Successfully ran target lint for project ${libName}`
- );
- const libTestResults = await runCLIAsync(`test ${libName}`);
- expect(libTestResults.combinedOutput).toContain(
- `Successfully ran target test for project ${libName}`
- );
- }, 250_000);
+ runCLI(
+ `g @nx/react:app apps/${appName} --bundler=webpack --no-interactive --skipFormat --unitTestRunner=jest --linter=eslint`
+ );
+ runCLI(
+ `g @nx/react:redux apps/${appName}/src/app/lemon/lemon --skipFormat`
+ );
+ runCLI(
+ `g @nx/react:lib libs/${libName} --unit-test-runner=jest --no-interactive --skipFormat`
+ );
+ runCLI(
+ `g @nx/react:redux libs/${libName}/src/lib/orange/orange --skipFormat`
+ );
- it('should support generating projects with the new name and root format', () => {
- const appName = uniq('app1');
- const libName = uniq('@my-org/lib1');
+ let lintResults = runCLI(`lint ${appName}`);
+ expect(lintResults).toContain(
+ `Successfully ran target lint for project ${appName}`
+ );
+ const appTestResults = await runCLIAsync(`test ${appName}`);
+ expect(appTestResults.combinedOutput).toContain(
+ `Successfully ran target test for project ${appName}`
+ );
- runCLI(
- `generate @nx/react:app ${appName} --bundler=webpack --no-interactive --skipFormat --linter=eslint --unitTestRunner=jest`
- );
+ lintResults = runCLI(`lint ${libName}`);
+ expect(lintResults).toContain(
+ `Successfully ran target lint for project ${libName}`
+ );
+ const libTestResults = await runCLIAsync(`test ${libName}`);
+ expect(libTestResults.combinedOutput).toContain(
+ `Successfully ran target test for project ${libName}`
+ );
+ }, 250_000);
- // check files are generated without the layout directory ("apps/") and
- // using the project name as the directory when no directory is provided
- checkFilesExist(`${appName}/src/main.tsx`);
- // check build works
- expect(runCLI(`build ${appName}`)).toContain(
- `Successfully ran target build for project ${appName}`
- );
- // check tests pass
- const appTestResult = runCLI(`test ${appName}`);
- expect(appTestResult).toContain(
- `Successfully ran target test for project ${appName}`
- );
+ it('should support generating projects with the new name and root format', () => {
+ const appName = uniq('app1');
+ const libName = uniq('@my-org/lib1');
- runCLI(
- `generate @nx/react:lib ${libName} --unit-test-runner=jest --buildable --no-interactive --skipFormat --linter=eslint`
- );
+ runCLI(
+ `generate @nx/react:app ${appName} --bundler=webpack --no-interactive --skipFormat --linter=eslint --unitTestRunner=jest`
+ );
- // check files are generated without the layout directory ("libs/") and
- // using the project name as the directory when no directory is provided
- checkFilesExist(`${libName}/src/index.ts`);
- // check build works
- expect(runCLI(`build ${libName}`)).toContain(
- `Successfully ran target build for project ${libName}`
- );
- // check tests pass
- const libTestResult = runCLI(`test ${libName}`);
- expect(libTestResult).toContain(
- `Successfully ran target test for project ${libName}`
- );
- }, 500_000);
+ // check files are generated without the layout directory ("apps/") and
+ // using the project name as the directory when no directory is provided
+ checkFilesExist(`${appName}/src/main.tsx`);
+ // check build works
+ expect(runCLI(`build ${appName}`)).toContain(
+ `Successfully ran target build for project ${appName}`
+ );
+ // check tests pass
+ const appTestResult = runCLI(`test ${appName}`);
+ expect(appTestResult).toContain(
+ `Successfully ran target test for project ${appName}`
+ );
- describe('React Applications: --style option', () => {
- // TODO(crystal, @jaysoo): Investigate why this is failng
- xit('should support styled-jsx', async () => {
- const appName = uniq('app');
runCLI(
- `generate @nx/react:app ${appName} --style=styled-jsx --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ `generate @nx/react:lib ${libName} --unit-test-runner=jest --buildable --no-interactive --skipFormat --linter=eslint`
);
- // update app to use styled-jsx
- updateFile(
- `apps/${appName}/src/app/app.tsx`,
- `
+ // check files are generated without the layout directory ("libs/") and
+ // using the project name as the directory when no directory is provided
+ checkFilesExist(`${libName}/src/index.ts`);
+ // check build works
+ expect(runCLI(`build ${libName}`)).toContain(
+ `Successfully ran target build for project ${libName}`
+ );
+ // check tests pass
+ const libTestResult = runCLI(`test ${libName}`);
+ expect(libTestResult).toContain(
+ `Successfully ran target test for project ${libName}`
+ );
+ }, 500_000);
+
+ describe('React Applications: --style option', () => {
+ // TODO(crystal, @jaysoo): Investigate why this is failng
+ xit('should support styled-jsx', async () => {
+ const appName = uniq('app');
+ runCLI(
+ `generate @nx/react:app ${appName} --style=styled-jsx --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ );
+
+ // update app to use styled-jsx
+ updateFile(
+ `apps/${appName}/src/app/app.tsx`,
+ `
import NxWelcome from './nx-welcome';
export function App() {
@@ -336,13 +428,13 @@ describe('React Applications', () => {
export default App;
`
- );
+ );
- // update e2e test to check for styled-jsx change
+ // update e2e test to check for styled-jsx change
- updateFile(
- `apps/${appName}-e2e/src/e2e/app.cy.ts`,
- `
+ updateFile(
+ `apps/${appName}-e2e/src/e2e/app.cy.ts`,
+ `
describe('react-test', () => {
beforeEach(() => cy.visit('/'));
@@ -353,23 +445,23 @@ describe('React Applications', () => {
});
`
- );
- if (runE2ETests()) {
- const e2eResults = runCLI(`e2e ${appName}-e2e --verbose`);
- expect(e2eResults).toContain('All specs passed!');
- }
- }, 250_000);
-
- it('should support tailwind', async () => {
- const appName = uniq('app');
- runCLI(
- `generate @nx/react:app apps/${appName} --style=tailwind --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
- );
+ );
+ if (runE2ETests()) {
+ const e2eResults = runCLI(`e2e ${appName}-e2e --verbose`);
+ expect(e2eResults).toContain('All specs passed!');
+ }
+ }, 250_000);
+
+ it('should support tailwind', async () => {
+ const appName = uniq('app');
+ runCLI(
+ `generate @nx/react:app apps/${appName} --style=tailwind --bundler=vite --no-interactive --skipFormat --linter=eslint --unitTestRunner=vitest`
+ );
- // update app to use styled-jsx
- updateFile(
- `apps/${appName}/src/app/app.tsx`,
- `
+ // update app to use styled-jsx
+ updateFile(
+ `apps/${appName}/src/app/app.tsx`,
+ `
import NxWelcome from './nx-welcome';
export function App() {
@@ -383,37 +475,38 @@ describe('React Applications', () => {
export default App;
`
- );
+ );
- runCLI(`build ${appName}`);
- const outputAssetFiles = listFiles(`dist/apps/${appName}/assets`);
- const styleFile = outputAssetFiles.find((filename) =>
- filename.endsWith('.css')
- );
- if (!styleFile) {
- throw new Error('Could not find bundled css file');
- }
- const styleFileContents = readFile(
- `dist/apps/${appName}/assets/${styleFile}`
- );
- const isStyleFileUsingTWClasses =
- styleFileContents.includes('w-20') &&
- styleFileContents.includes('h-20');
- expect(isStyleFileUsingTWClasses).toBeTruthy();
- }, 250_000);
- });
+ runCLI(`build ${appName}`);
+ const outputAssetFiles = listFiles(`dist/apps/${appName}/assets`);
+ const styleFile = outputAssetFiles.find((filename) =>
+ filename.endsWith('.css')
+ );
+ if (!styleFile) {
+ throw new Error('Could not find bundled css file');
+ }
+ const styleFileContents = readFile(
+ `dist/apps/${appName}/assets/${styleFile}`
+ );
+ const isStyleFileUsingTWClasses =
+ styleFileContents.includes('w-20') &&
+ styleFileContents.includes('h-20');
+ expect(isStyleFileUsingTWClasses).toBeTruthy();
+ }, 250_000);
+ });
- describe('--format', () => {
- it('should be formatted on freshly created apps', async () => {
- const appName = uniq('app');
- runCLI(
- `generate @nx/react:app ${appName} --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest`
- );
+ describe('--format', () => {
+ it('should be formatted on freshly created apps', async () => {
+ const appName = uniq('app');
+ runCLI(
+ `generate @nx/react:app ${appName} --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest`
+ );
- const stdout = runCLI(`format:check --projects=${appName}`, {
- silenceError: true,
+ const stdout = runCLI(`format:check --projects=${appName}`, {
+ silenceError: true,
+ });
+ expect(stdout).toEqual('');
});
- expect(stdout).toEqual('');
});
});
});
diff --git a/e2e/utils/create-project-utils.ts b/e2e/utils/create-project-utils.ts
index 05406751870dfb..4e6361172c098d 100644
--- a/e2e/utils/create-project-utils.ts
+++ b/e2e/utils/create-project-utils.ts
@@ -76,10 +76,12 @@ export function newProject({
name = uniq('proj'),
packageManager = getSelectedPackageManager(),
packages,
+ workspaces = false,
}: {
name?: string;
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun';
readonly packages?: Array;
+ workspaces?: boolean;
} = {}): string {
const newProjectStart = performance.mark('new-project:start');
try {
@@ -95,6 +97,7 @@ export function newProject({
runCreateWorkspace(projScope, {
preset: 'apps',
packageManager,
+ workspaces,
});
const createNxWorkspaceEnd = performance.mark('create-nx-workspace:end');
createNxWorkspaceMeasure = performance.measure(
@@ -228,6 +231,7 @@ export function runCreateWorkspace(
ssr,
framework,
prefix,
+ workspaces,
}: {
preset: string;
appName?: string;
@@ -249,13 +253,17 @@ export function runCreateWorkspace(
ssr?: boolean;
framework?: string;
prefix?: string;
+ workspaces?: boolean;
}
) {
projName = name;
const pm = getPackageManagerCommand({ packageManager });
+ preset = workspaces ? 'ts' : preset;
+
let command = `${pm.createWorkspace} ${name} --preset=${preset} --nxCloud=skip --no-interactive`;
+
if (appName) {
command += ` --appName=${appName}`;
}
@@ -327,6 +335,10 @@ export function runCreateWorkspace(
command += ` --prefix=${prefix}`;
}
+ if (workspaces) {
+ command += ` --workspaces`;
+ }
+
try {
const create = execSync(`${command}${isVerbose() ? ' --verbose' : ''}`, {
cwd,
diff --git a/packages/expo/src/generators/application/application.ts b/packages/expo/src/generators/application/application.ts
index 4c971cbc324b12..8d1593245fba58 100644
--- a/packages/expo/src/generators/application/application.ts
+++ b/packages/expo/src/generators/application/application.ts
@@ -1,4 +1,5 @@
import {
+ detectPackageManager,
formatFiles,
GeneratorCallback,
joinPathFragments,
@@ -21,6 +22,7 @@ import { Schema } from './schema';
import { ensureDependencies } from '../../utils/ensure-dependencies';
import { initRootBabelConfig } from '../../utils/init-root-babel-config';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
+import { addProjectToTsSolutionWorkspace } from '@nx/react/src/utils/add-app-to-pnpm-workspace';
export async function expoApplicationGenerator(
host: Tree,
@@ -95,6 +97,12 @@ export async function expoApplicationGeneratorInternal(
: undefined
);
+ // If we are using the new TS solution
+ // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
+ if (options.useTsSolution) {
+ addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
+ }
+
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/expo/src/generators/library/library.ts b/packages/expo/src/generators/library/library.ts
index 83743bf5a6e444..68107049918dae 100644
--- a/packages/expo/src/generators/library/library.ts
+++ b/packages/expo/src/generators/library/library.ts
@@ -38,6 +38,7 @@ import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-default
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
+import { addProjectToTsSolutionWorkspace } from '@nx/react/src/utils/add-app-to-pnpm-workspace';
export async function expoLibraryGenerator(
host: Tree,
@@ -130,6 +131,10 @@ export async function expoLibraryGeneratorInternal(
: undefined
);
+ if (options.isUsingTsSolutionConfig) {
+ addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`);
+ }
+
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/next/src/generators/application/application.ts b/packages/next/src/generators/application/application.ts
index c17d968e74df5e..8436d9e95ddc1a 100644
--- a/packages/next/src/generators/application/application.ts
+++ b/packages/next/src/generators/application/application.ts
@@ -1,5 +1,6 @@
import {
addDependenciesToPackageJson,
+ detectPackageManager,
formatFiles,
GeneratorCallback,
joinPathFragments,
@@ -31,6 +32,7 @@ import { showPossibleWarnings } from './lib/show-possible-warnings';
import { tsLibVersion } from '../../utils/versions';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
+import { addProjectToTsSolutionWorkspace } from '@nx/react/src/utils/add-app-to-pnpm-workspace';
export async function applicationGenerator(host: Tree, schema: Schema) {
return await applicationGeneratorInternal(host, {
@@ -132,6 +134,12 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
options.src ? 'src' : '.'
);
+ // If we are using the new TS solution
+ // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
+ if (options.useTsSolution) {
+ addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
+ }
+
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/next/src/generators/library/lib/normalize-options.ts b/packages/next/src/generators/library/lib/normalize-options.ts
index a77b07a9623feb..cc2d85876ecc51 100644
--- a/packages/next/src/generators/library/lib/normalize-options.ts
+++ b/packages/next/src/generators/library/lib/normalize-options.ts
@@ -4,10 +4,12 @@ import {
ensureProjectName,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Schema } from '../schema';
+import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export interface NormalizedSchema extends Schema {
importPath: string;
projectRoot: string;
+ isUsingTsSolutionConfig: boolean;
}
export async function normalizeOptions(
@@ -35,5 +37,6 @@ export async function normalizeOptions(
...options,
importPath,
projectRoot,
+ isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
};
}
diff --git a/packages/next/src/generators/library/library.ts b/packages/next/src/generators/library/library.ts
index 0269a3cf36a2cf..890a047f57d43e 100644
--- a/packages/next/src/generators/library/library.ts
+++ b/packages/next/src/generators/library/library.ts
@@ -16,6 +16,7 @@ import { Schema } from './schema';
import { normalizeOptions } from './lib/normalize-options';
import { eslintConfigNextVersion, tsLibVersion } from '../../utils/versions';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
+import { addProjectToTsSolutionWorkspace } from '@nx/react/src/utils/add-app-to-pnpm-workspace';
export async function libraryGenerator(host: Tree, rawOptions: Schema) {
return await libraryGeneratorInternal(host, {
@@ -154,6 +155,10 @@ export async function libraryGeneratorInternal(host: Tree, rawOptions: Schema) {
: undefined
);
+ if (options.isUsingTsSolutionConfig) {
+ addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`);
+ }
+
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/react-native/src/generators/application/application.ts b/packages/react-native/src/generators/application/application.ts
index 5518a43a0347a2..5f7eb3870df94d 100644
--- a/packages/react-native/src/generators/application/application.ts
+++ b/packages/react-native/src/generators/application/application.ts
@@ -1,4 +1,5 @@
import {
+ detectPackageManager,
formatFiles,
GeneratorCallback,
joinPathFragments,
@@ -26,6 +27,7 @@ import { ensureDependencies } from '../../utils/ensure-dependencies';
import { syncDeps } from '../../executors/sync-deps/sync-deps.impl';
import { PackageJson } from 'nx/src/utils/package-json';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
+import { addProjectToTsSolutionWorkspace } from '@nx/react/src/utils/add-app-to-pnpm-workspace';
export async function reactNativeApplicationGenerator(
host: Tree,
@@ -143,6 +145,12 @@ export async function reactNativeApplicationGeneratorInternal(
: undefined
);
+ // If we are using the new TS solution
+ // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
+ if (options.useTsSolution) {
+ addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
+ }
+
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/react-native/src/generators/library/library.ts b/packages/react-native/src/generators/library/library.ts
index 227cd2d302431d..ec1afed43a386f 100644
--- a/packages/react-native/src/generators/library/library.ts
+++ b/packages/react-native/src/generators/library/library.ts
@@ -39,6 +39,7 @@ import {
updateTsconfigFiles,
} from '@nx/js/src/utils/typescript/ts-solution-setup';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
+import { addProjectToTsSolutionWorkspace } from '@nx/react/src/utils/add-app-to-pnpm-workspace';
export async function reactNativeLibraryGenerator(
host: Tree,
@@ -130,6 +131,10 @@ export async function reactNativeLibraryGeneratorInternal(
: undefined
);
+ if (options.isUsingTsSolutionConfig) {
+ addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`);
+ }
+
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/react/package.json b/packages/react/package.json
index e5d7f0a3348d83..9072e08551402e 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -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",
+ "@zkochan/js-yaml": "0.0.7"
},
"publishConfig": {
"access": "public"
diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts
index c9a421896e46a0..7eda8623ba5110 100644
--- a/packages/react/src/generators/application/application.ts
+++ b/packages/react/src/generators/application/application.ts
@@ -10,6 +10,7 @@ import { setDefaults } from './lib/set-defaults';
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
import {
addDependenciesToPackageJson,
+ detectPackageManager,
ensurePackage,
formatFiles,
GeneratorCallback,
@@ -41,7 +42,11 @@ import { initGenerator as jsInitGenerator } from '@nx/js';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind';
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
-import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
+import {
+ isUsingTsSolutionSetup,
+ updateTsconfigFiles,
+} from '@nx/js/src/utils/typescript/ts-solution-setup';
+import { addProjectToTsSolutionWorkspace } from '../../utils/add-app-to-pnpm-workspace';
async function addLinting(host: Tree, options: NormalizedSchema) {
const tasks: GeneratorCallback[] = [];
@@ -115,7 +120,7 @@ export async function applicationGeneratorInternal(
...schema,
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
skipFormat: true,
- addTsPlugin: schema.useTsSolution,
+ addTsPlugin: isUsingTsSolutionSetup(host),
formatter: schema.formatter,
});
tasks.push(jsInitTask);
@@ -367,6 +372,12 @@ export async function applicationGeneratorInternal(
moduleResolution: 'bundler',
});
+ // If we are using the new TS solution
+ // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
+ if (options.useTsSolution) {
+ addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
+ }
+
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/react/src/generators/library/lib/normalize-options.ts b/packages/react/src/generators/library/lib/normalize-options.ts
index 76233f3dd833ba..9110d3f1a4d7fd 100644
--- a/packages/react/src/generators/library/lib/normalize-options.ts
+++ b/packages/react/src/generators/library/lib/normalize-options.ts
@@ -13,7 +13,6 @@ import {
import { assertValidStyle } from '../../../utils/assertion';
import { NormalizedSchema, Schema } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
-import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt';
export async function normalizeOptions(
host: Tree,
diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts
index cfa3e4832c3a55..9bbf8c7ff69f46 100644
--- a/packages/react/src/generators/library/library.ts
+++ b/packages/react/src/generators/library/library.ts
@@ -33,6 +33,7 @@ import { installCommonDependencies } from './lib/install-common-dependencies';
import { setDefaults } from './lib/set-defaults';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { ensureProjectIsExcludedFromPluginRegistrations } from '@nx/js/src/utils/typescript/plugin';
+import { addProjectToTsSolutionWorkspace } from '../../utils/add-app-to-pnpm-workspace';
export async function libraryGenerator(host: Tree, schema: Schema) {
return await libraryGeneratorInternal(host, {
@@ -278,6 +279,9 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
: undefined
);
+ if (options.isUsingTsSolutionConfig) {
+ addProjectToTsSolutionWorkspace(host, `${options.projectRoot}/*`);
+ }
if (!options.skipFormat) {
await formatFiles(host);
}
diff --git a/packages/react/src/utils/add-app-to-pnpm-workspace.ts b/packages/react/src/utils/add-app-to-pnpm-workspace.ts
new file mode 100644
index 00000000000000..20ac8ddb047b14
--- /dev/null
+++ b/packages/react/src/utils/add-app-to-pnpm-workspace.ts
@@ -0,0 +1,33 @@
+import { detectPackageManager, readJson, Tree } from '@nx/devkit';
+
+export function addProjectToTsSolutionWorkspace(
+ tree: Tree,
+ projectDir: string
+) {
+ if (detectPackageManager() === 'pnpm') {
+ const { load, dump } = require('@zkochan/js-yaml');
+ if (tree.exists('pnpm-workspace.yaml')) {
+ const workspaceFile = tree.read('pnpm-workspace.yaml', 'utf-8');
+ const yamlData = load(workspaceFile);
+
+ if (!yamlData.packages) {
+ yamlData.packages = [];
+ }
+
+ if (!yamlData.packages.includes(projectDir)) {
+ yamlData.packages.push(projectDir);
+ tree.write('pnpm-workspace.yaml', dump(yamlData, { indent: 2 }));
+ }
+ }
+ } else {
+ // Update package.json
+ const packageJson = readJson(tree, 'package.json');
+ if (!packageJson.workspaces) {
+ packageJson.workspaces = [];
+ }
+ if (!packageJson.workspaces.includes(projectDir)) {
+ packageJson.workspaces.push(projectDir);
+ tree.write('package.json', JSON.stringify(packageJson, null, 2));
+ }
+ }
+}
diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts
index e66b13fd2885db..5c9f00cef63d98 100644
--- a/packages/remix/src/generators/application/application.impl.ts
+++ b/packages/remix/src/generators/application/application.impl.ts
@@ -1,5 +1,6 @@
import {
addProjectConfiguration,
+ detectPackageManager,
ensurePackage,
formatFiles,
generateFiles,
@@ -48,6 +49,7 @@ import {
isUsingTsSolutionSetup,
updateTsconfigFiles,
} from '@nx/js/src/utils/typescript/ts-solution-setup';
+import { addProjectToTsSolutionWorkspace } from '@nx/react/src/utils/add-app-to-pnpm-workspace';
export function remixApplicationGenerator(
tree: Tree,
@@ -374,6 +376,12 @@ export default {...nxPreset};
'.'
);
+ // If we are using the new TS solution
+ // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
+ if (options.useTsSolution) {
+ addProjectToTsSolutionWorkspace(tree, options.projectRoot);
+ }
+
tasks.push(() => {
logShowProjectCommand(options.projectName);
});
diff --git a/packages/workspace/src/generators/new/generate-workspace-files.spec.ts b/packages/workspace/src/generators/new/generate-workspace-files.spec.ts
index a53182eebf46f7..bbb0ce981a8b52 100644
--- a/packages/workspace/src/generators/new/generate-workspace-files.spec.ts
+++ b/packages/workspace/src/generators/new/generate-workspace-files.spec.ts
@@ -332,8 +332,8 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
const packageJson = tree.read('/proj/pnpm-workspace.yaml', 'utf-8');
expect(packageJson).toMatchInlineSnapshot(`
"packages:
- - apps/**
- - packages/**
+ - "apps/**"
+ - "packages/**"
"
`);
});
diff --git a/packages/workspace/src/generators/new/generate-workspace-files.ts b/packages/workspace/src/generators/new/generate-workspace-files.ts
index da8bb757206806..951aa263dc4756 100644
--- a/packages/workspace/src/generators/new/generate-workspace-files.ts
+++ b/packages/workspace/src/generators/new/generate-workspace-files.ts
@@ -429,7 +429,7 @@ function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) {
tree.write(
join(options.directory, 'pnpm-workspace.yaml'),
`packages:
- - ${workspaces.join('\n - ')}
+ ${workspaces.map((workspace) => `- "${workspace}"`).join('\n ')}
`
);
} else {