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(rsbuild): add react and vue support for app generation #29349

Merged
merged 14 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"bundler": {
"description": "The bundler to use.",
"type": "string",
"enum": ["vite", "webpack", "rspack"],
"enum": ["vite", "webpack", "rspack", "rsbuild"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "vite",
"x-priority": "important"
Expand Down
8 changes: 8 additions & 0 deletions docs/generated/packages/vue/generators/application.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
]
}
},
"bundler": {
"description": "The bundler to use.",
"type": "string",
"enum": ["vite", "rsbuild"],
"x-prompt": "Which bundler do you want to use to build the application?",
"default": "vite",
"x-priority": "important"
},
"routing": {
"type": "boolean",
"description": "Generate application with routes.",
Expand Down
102 changes: 102 additions & 0 deletions e2e/react/src/react-rsbuild.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
checkFilesExist,
cleanupProject,
newProject,
runCLI,
runCLIAsync,
uniq,
} from '@nx/e2e/utils';

describe('Build React applications and libraries with Rsbuild', () => {
Coly010 marked this conversation as resolved.
Show resolved Hide resolved
beforeAll(() => {
newProject({
packages: ['@nx/react'],
});
});

afterAll(() => {
cleanupProject();
});

it('should test and lint app with bundler=rsbuild', async () => {
const rsbuildApp = uniq('rsbuildapp');

runCLI(
`generate @nx/react:app apps/${rsbuildApp} --bundler=rsbuild --unitTestRunner=vitest --no-interactive --linter=eslint`
);

const appTestResults = await runCLIAsync(`test ${rsbuildApp}`);
expect(appTestResults.combinedOutput).toContain(
'Successfully ran target test'
);

const appLintResults = await runCLIAsync(`lint ${rsbuildApp}`);
expect(appLintResults.combinedOutput).toContain(
'Successfully ran target lint'
);

await runCLIAsync(`build ${rsbuildApp}`);
checkFilesExist(`apps/${rsbuildApp}/dist/index.html`);
}, 300_000);

it('should test and lint app with bundler=rsbuild', async () => {
const rsbuildApp = uniq('rsbuildapp');

runCLI(
`generate @nx/react:app apps/${rsbuildApp} --bundler=rsbuild --unitTestRunner=vitest --no-interactive --linter=eslint`
);

const appTestResults = await runCLIAsync(`test ${rsbuildApp}`);
expect(appTestResults.combinedOutput).toContain(
'Successfully ran target test'
);

const appLintResults = await runCLIAsync(`lint ${rsbuildApp}`);
expect(appLintResults.combinedOutput).toContain(
'Successfully ran target lint'
);

await runCLIAsync(`build ${rsbuildApp}`);
checkFilesExist(`apps/${rsbuildApp}/dist/index.html`);
}, 300_000);

it('should test and lint app with bundler=rsbuild and inSourceTests', async () => {
const rsbuildApp = uniq('rsbuildapp');

runCLI(
`generate @nx/react:app apps/${rsbuildApp} --bundler=rsbuild --unitTestRunner=vitest --inSourceTests --no-interactive --linter=eslint`
);
expect(() => {
checkFilesExist(`apps/${rsbuildApp}/src/app/app.spec.tsx`);
}).toThrow();

const appTestResults = await runCLIAsync(`test ${rsbuildApp}`);
expect(appTestResults.combinedOutput).toContain(
'Successfully ran target test'
);

const appLintResults = await runCLIAsync(`lint ${rsbuildApp}`);
expect(appLintResults.combinedOutput).toContain(
'Successfully ran target lint'
);

await runCLIAsync(`build ${rsbuildApp}`);
checkFilesExist(`apps/${rsbuildApp}/dist/index.html`);
}, 300_000);

it('should support bundling with Rsbuild and Jest', async () => {
const rsbuildApp = uniq('rsbuildapp');

runCLI(
`generate @nx/react:app apps/${rsbuildApp} --bundler=rsbuild --unitTestRunner=jest --no-interactive --linter=eslint`
);

const appTestResults = await runCLIAsync(`test ${rsbuildApp}`);
expect(appTestResults.combinedOutput).toContain(
'Successfully ran target test'
);

await runCLIAsync(`build ${rsbuildApp}`);
checkFilesExist(`apps/${rsbuildApp}/dist/index.html`);
}, 300_000);
});
30 changes: 29 additions & 1 deletion e2e/vue/src/vue.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { cleanupProject, newProject, runCLI, uniq } from '@nx/e2e/utils';
import {
cleanupProject,
killPorts,
newProject,
runCLI,
runE2ETests,
uniq,
} from '@nx/e2e/utils';

describe('Vue Plugin', () => {
let proj: string;
Expand Down Expand Up @@ -33,6 +40,27 @@ describe('Vue Plugin', () => {
// }
}, 200_000);

it('should serve application in dev mode with rsbuild', async () => {
const app = uniq('app');

runCLI(
`generate @nx/vue:app ${app} --bundler=rsbuild --unitTestRunner=vitest --e2eTestRunner=playwright`
);
let result = runCLI(`test ${app}`);
expect(result).toContain(`Successfully ran target test for project ${app}`);

result = runCLI(`build ${app}`);
expect(result).toContain(
`Successfully ran target build for project ${app}`
);

if (runE2ETests()) {
const e2eResults = runCLI(`e2e ${app}-e2e --no-watch`);
expect(e2eResults).toContain('Successfully ran target e2e');
expect(await killPorts()).toBeTruthy();
}
}, 200_000);

it('should build library', async () => {
const lib = uniq('lib');

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@
"@nx/powerpack-enterprise-cloud": "1.1.0-beta.9",
"@nx/powerpack-license": "1.1.0-beta.9",
"@nx/react": "20.3.0-beta.0",
"@nx/rsbuild": "20.3.0-beta.0",
"@nx/rspack": "20.3.0-beta.0",
"@nx/storybook": "20.3.0-beta.0",
"@nx/vite": "20.3.0-beta.0",
"@nx/web": "20.3.0-beta.0",
"@nx/webpack": "20.3.0-beta.0",
"@nx/vite": "20.3.0-beta.0",
"@phenomnomnominal/tsquery": "~5.0.1",
"@playwright/test": "^1.36.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
Expand Down
1 change: 1 addition & 0 deletions packages/react/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@nx/playwright",
"@nx/jest",
"@nx/rollup",
"@nx/rsbuild",
"@nx/storybook",
"@nx/vite",
"@nx/webpack",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,145 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`app --bundler=rsbuild should generate valid rsbuild config files for @emotion/styled 1`] = `
"import styled from '@emotion/styled';
import NxWelcome from "./nx-welcome";

const StyledApp = styled.div\`
// Your style here
\`;

export function App() {
return (
<StyledApp>
<NxWelcome title="my-app"/>
</StyledApp>
);
}

export default App;

"
`;

exports[`app --bundler=rsbuild should generate valid rsbuild config files for @emotion/styled 2`] = `
"import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rsbuild/core';

export default defineConfig({
html: {
template: './src/index.html'
},tools: { swc: { jsc: { experimental: { plugins: [['@swc/plugin-emotion', {}],], }, }, }, },
plugins: [pluginReact({
swcReactOptions: {
importSource: '@emotion/react',
}
})],
source: {
entry: {
index: './src/main.tsx'
},
tsconfigPath: './tsconfig.app.json',
},
output: {copy: [{ from: './src/favicon.ico' },{ from: './src/assets' }],
target: 'web',
distPath: {
root: 'dist',
},
}
});
"
`;

exports[`app --bundler=rsbuild should generate valid rsbuild config files for styled-components 1`] = `
"import styled from 'styled-components';
import NxWelcome from "./nx-welcome";

const StyledApp = styled.div\`
// Your style here
\`;

export function App() {
return (
<StyledApp>
<NxWelcome title="my-app"/>
</StyledApp>
);
}

export default App;

"
`;

exports[`app --bundler=rsbuild should generate valid rsbuild config files for styled-components 2`] = `
"import { pluginStyledComponents } from '@rsbuild/plugin-styled-components';
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rsbuild/core';

export default defineConfig({
html: {
template: './src/index.html'
},
plugins: [pluginReact(),pluginStyledComponents()],
source: {
entry: {
index: './src/main.tsx'
},
tsconfigPath: './tsconfig.app.json',
},
output: {copy: [{ from: './src/favicon.ico' },{ from: './src/assets' }],
target: 'web',
distPath: {
root: 'dist',
},
}
});
"
`;

exports[`app --bundler=rsbuild should generate valid rsbuild config files for styled-jsx 1`] = `
"import NxWelcome from "./nx-welcome";

export function App() {
return (
<div>
<style jsx>{\`/** your style here **/\`}</style>
<NxWelcome title="my-app"/>
</div>
);
}

export default App;


"
`;

exports[`app --bundler=rsbuild should generate valid rsbuild config files for styled-jsx 2`] = `
"import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rsbuild/core';

export default defineConfig({
html: {
template: './src/index.html'
},tools: { swc: { jsc: { experimental: { plugins: [['@swc/plugin-styled-jsx', {}],], }, }, }, },
plugins: [pluginReact()],
source: {
entry: {
index: './src/main.tsx'
},
tsconfigPath: './tsconfig.app.json',
},
output: {copy: [{ from: './src/favicon.ico' },{ from: './src/assets' }],
target: 'web',
distPath: {
root: 'dist',
},
}
});
"
`;

exports[`app --minimal should create default application without Nx welcome component 1`] = `
"// Uncomment this line to use CSS modules
// import styles from './app.module.css';
Expand Down
24 changes: 24 additions & 0 deletions packages/react/src/generators/application/application.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1442,4 +1442,28 @@ describe('app', () => {
`);
});
});

describe('--bundler=rsbuild', () => {
it.each([
{ style: 'styled-components' },
{ style: 'styled-jsx' },
{ style: '@emotion/styled' },
])(
`should generate valid rsbuild config files for $style`,
async ({ style }) => {
await applicationGenerator(appTree, {
...schema,
bundler: 'rsbuild',
style: style as any,
});

const content = appTree.read('my-app/src/app/app.tsx').toString();
expect(content).toMatchSnapshot();
const configContents = appTree
.read('my-app/rsbuild.config.ts')
.toString();
expect(configContents).toMatchSnapshot();
}
);
});
});
Loading
Loading