feat(rsbuild): add react and vue support for app generation (#29349)
## Current Behavior We do not have a generator that can scaffold a react or vue app using rsbuild. ## Expected Behavior Update the react application generator to support a bundler option of rsbuild Update the vue application generator to support a bundler option of rsbuild
This commit is contained in:
parent
b03c9c1e38
commit
4b586a1acc
@ -95,7 +95,7 @@
|
|||||||
"bundler": {
|
"bundler": {
|
||||||
"description": "The bundler to use.",
|
"description": "The bundler to use.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["vite", "webpack", "rspack"],
|
"enum": ["vite", "webpack", "rspack", "rsbuild"],
|
||||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||||
"default": "vite",
|
"default": "vite",
|
||||||
"x-priority": "important"
|
"x-priority": "important"
|
||||||
|
|||||||
@ -26,6 +26,11 @@
|
|||||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||||
"x-priority": "important"
|
"x-priority": "important"
|
||||||
},
|
},
|
||||||
|
"devServerPort": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The port for the dev server to listen on.",
|
||||||
|
"default": 4200
|
||||||
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Target platform for the build, same as the Rsbuild output.target config option.",
|
"description": "Target platform for the build, same as the Rsbuild output.target config option.",
|
||||||
|
|||||||
@ -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": {
|
"routing": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Generate application with routes.",
|
"description": "Generate application with routes.",
|
||||||
|
|||||||
110
e2e/react/src/react-rsbuild.test.ts
Normal file
110
e2e/react/src/react-rsbuild.test.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
checkFilesExist,
|
||||||
|
cleanupProject,
|
||||||
|
newProject,
|
||||||
|
runCLI,
|
||||||
|
runCLIAsync,
|
||||||
|
runE2ETests,
|
||||||
|
uniq,
|
||||||
|
} from '@nx/e2e/utils';
|
||||||
|
|
||||||
|
describe('Build React applications and libraries with Rsbuild', () => {
|
||||||
|
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`);
|
||||||
|
|
||||||
|
if (runE2ETests()) {
|
||||||
|
const result = runCLI(`e2e ${rsbuildApp}-e2e --verbose`);
|
||||||
|
expect(result).toContain(
|
||||||
|
`Successfully ran target e2e for project ${rsbuildApp}-e2e`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 300_000);
|
||||||
|
});
|
||||||
@ -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', () => {
|
describe('Vue Plugin', () => {
|
||||||
let proj: string;
|
let proj: string;
|
||||||
@ -33,6 +40,29 @@ describe('Vue Plugin', () => {
|
|||||||
// }
|
// }
|
||||||
}, 200_000);
|
}, 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}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: enable this when tests are passing again.
|
||||||
|
// Colum confirmed locally that the generated config and the playwright tests are working.
|
||||||
|
// 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 () => {
|
it('should build library', async () => {
|
||||||
const lib = uniq('lib');
|
const lib = uniq('lib');
|
||||||
|
|
||||||
|
|||||||
@ -83,11 +83,12 @@
|
|||||||
"@nx/powerpack-enterprise-cloud": "1.1.0-beta.9",
|
"@nx/powerpack-enterprise-cloud": "1.1.0-beta.9",
|
||||||
"@nx/powerpack-license": "1.1.0-beta.9",
|
"@nx/powerpack-license": "1.1.0-beta.9",
|
||||||
"@nx/react": "20.3.0-beta.0",
|
"@nx/react": "20.3.0-beta.0",
|
||||||
|
"@nx/rsbuild": "20.3.0-beta.0",
|
||||||
"@nx/rspack": "20.3.0-beta.0",
|
"@nx/rspack": "20.3.0-beta.0",
|
||||||
"@nx/storybook": "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/web": "20.3.0-beta.0",
|
||||||
"@nx/webpack": "20.3.0-beta.0",
|
"@nx/webpack": "20.3.0-beta.0",
|
||||||
|
"@nx/vite": "20.3.0-beta.0",
|
||||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||||
"@playwright/test": "^1.36.1",
|
"@playwright/test": "^1.36.1",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||||
|
|||||||
@ -58,6 +58,7 @@
|
|||||||
"@nx/playwright",
|
"@nx/playwright",
|
||||||
"@nx/jest",
|
"@nx/jest",
|
||||||
"@nx/rollup",
|
"@nx/rollup",
|
||||||
|
"@nx/rsbuild",
|
||||||
"@nx/storybook",
|
"@nx/storybook",
|
||||||
"@nx/vite",
|
"@nx/vite",
|
||||||
"@nx/webpack",
|
"@nx/webpack",
|
||||||
|
|||||||
@ -1,5 +1,192 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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',
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200
|
||||||
|
},
|
||||||
|
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`] = `
|
exports[`app --minimal should create default application without Nx welcome component 1`] = `
|
||||||
"// Uncomment this line to use CSS modules
|
"// Uncomment this line to use CSS modules
|
||||||
// import styles from './app.module.css';
|
// import styles from './app.module.css';
|
||||||
|
|||||||
@ -1454,4 +1454,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();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,19 @@
|
|||||||
import { extraEslintDependencies } from '../../utils/lint';
|
import {
|
||||||
import { NormalizedSchema, Schema } from './schema';
|
formatFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
joinPathFragments,
|
||||||
|
readNxJson,
|
||||||
|
runTasksInSerial,
|
||||||
|
Tree,
|
||||||
|
updateNxJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||||
|
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||||
|
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||||
|
import { extractTsConfigBase } from '../../utils/create-ts-config';
|
||||||
|
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
|
||||||
|
import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind';
|
||||||
|
import reactInitGenerator from '../init/init';
|
||||||
import { createApplicationFiles } from './lib/create-application-files';
|
import { createApplicationFiles } from './lib/create-application-files';
|
||||||
import { updateSpecConfig } from './lib/update-jest-config';
|
import { updateSpecConfig } from './lib/update-jest-config';
|
||||||
import { normalizeOptions } from './lib/normalize-options';
|
import { normalizeOptions } from './lib/normalize-options';
|
||||||
@ -7,111 +21,43 @@ import { addProject } from './lib/add-project';
|
|||||||
import { addJest } from './lib/add-jest';
|
import { addJest } from './lib/add-jest';
|
||||||
import { addRouting } from './lib/add-routing';
|
import { addRouting } from './lib/add-routing';
|
||||||
import { setDefaults } from './lib/set-defaults';
|
import { setDefaults } from './lib/set-defaults';
|
||||||
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
|
import { addLinting } from './lib/add-linting';
|
||||||
import {
|
|
||||||
addDependenciesToPackageJson,
|
|
||||||
ensurePackage,
|
|
||||||
formatFiles,
|
|
||||||
GeneratorCallback,
|
|
||||||
joinPathFragments,
|
|
||||||
logger,
|
|
||||||
readNxJson,
|
|
||||||
runTasksInSerial,
|
|
||||||
stripIndents,
|
|
||||||
Tree,
|
|
||||||
updateNxJson,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import reactInitGenerator from '../init/init';
|
|
||||||
import { Linter, lintProjectGenerator } from '@nx/eslint';
|
|
||||||
import { babelLoaderVersion, nxVersion } from '../../utils/versions';
|
|
||||||
import { maybeJs } from '../../utils/maybe-js';
|
|
||||||
import { installCommonDependencies } from './lib/install-common-dependencies';
|
|
||||||
import { extractTsConfigBase } from '../../utils/create-ts-config';
|
|
||||||
import { addSwcDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies';
|
|
||||||
import * as pc from 'picocolors';
|
|
||||||
import { showPossibleWarnings } from './lib/show-possible-warnings';
|
|
||||||
import { addE2e } from './lib/add-e2e';
|
import { addE2e } from './lib/add-e2e';
|
||||||
|
import { showPossibleWarnings } from './lib/show-possible-warnings';
|
||||||
|
import { installCommonDependencies } from './lib/install-common-dependencies';
|
||||||
|
import { initWebpack } from './lib/bundlers/add-webpack';
|
||||||
import {
|
import {
|
||||||
addExtendsToLintConfig,
|
handleStyledJsxForRspack,
|
||||||
addOverrideToLintConfig,
|
initRspack,
|
||||||
addPredefinedConfigToFlatLintConfig,
|
setupRspackConfiguration,
|
||||||
isEslintConfigSupported,
|
} from './lib/bundlers/add-rspack';
|
||||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
import {
|
||||||
import { initGenerator as jsInitGenerator } from '@nx/js';
|
initRsbuild,
|
||||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
setupRsbuildConfiguration,
|
||||||
import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind';
|
} from './lib/bundlers/add-rsbuild';
|
||||||
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
import {
|
||||||
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
setupViteConfiguration,
|
||||||
|
setupVitestConfiguration,
|
||||||
async function addLinting(host: Tree, options: NormalizedSchema) {
|
} from './lib/bundlers/add-vite';
|
||||||
const tasks: GeneratorCallback[] = [];
|
import { Schema } from './schema';
|
||||||
if (options.linter === Linter.EsLint) {
|
|
||||||
const lintTask = await lintProjectGenerator(host, {
|
|
||||||
linter: options.linter,
|
|
||||||
project: options.projectName,
|
|
||||||
tsConfigPaths: [
|
|
||||||
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
|
||||||
],
|
|
||||||
unitTestRunner: options.unitTestRunner,
|
|
||||||
skipFormat: true,
|
|
||||||
rootProject: options.rootProject,
|
|
||||||
skipPackageJson: options.skipPackageJson,
|
|
||||||
addPlugin: options.addPlugin,
|
|
||||||
});
|
|
||||||
tasks.push(lintTask);
|
|
||||||
|
|
||||||
if (isEslintConfigSupported(host)) {
|
|
||||||
if (useFlatConfig(host)) {
|
|
||||||
addPredefinedConfigToFlatLintConfig(
|
|
||||||
host,
|
|
||||||
options.appProjectRoot,
|
|
||||||
'flat/react'
|
|
||||||
);
|
|
||||||
// Add an empty rules object to users know how to add/override rules
|
|
||||||
addOverrideToLintConfig(host, options.appProjectRoot, {
|
|
||||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
|
||||||
rules: {},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const addExtendsTask = addExtendsToLintConfig(
|
|
||||||
host,
|
|
||||||
options.appProjectRoot,
|
|
||||||
{ name: 'plugin:@nx/react', needCompatFixup: true }
|
|
||||||
);
|
|
||||||
tasks.push(addExtendsTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.skipPackageJson) {
|
|
||||||
const installTask = addDependenciesToPackageJson(
|
|
||||||
host,
|
|
||||||
extraEslintDependencies.dependencies,
|
|
||||||
extraEslintDependencies.devDependencies
|
|
||||||
);
|
|
||||||
const addSwcTask = addSwcDependencies(host);
|
|
||||||
tasks.push(installTask, addSwcTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return runTasksInSerial(...tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function applicationGenerator(
|
export async function applicationGenerator(
|
||||||
host: Tree,
|
tree: Tree,
|
||||||
schema: Schema
|
schema: Schema
|
||||||
): Promise<GeneratorCallback> {
|
): Promise<GeneratorCallback> {
|
||||||
return await applicationGeneratorInternal(host, {
|
return await applicationGeneratorInternal(tree, {
|
||||||
addPlugin: false,
|
addPlugin: false,
|
||||||
...schema,
|
...schema,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function applicationGeneratorInternal(
|
export async function applicationGeneratorInternal(
|
||||||
host: Tree,
|
tree: Tree,
|
||||||
schema: Schema
|
schema: Schema
|
||||||
): Promise<GeneratorCallback> {
|
): Promise<GeneratorCallback> {
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
|
|
||||||
const jsInitTask = await jsInitGenerator(host, {
|
const jsInitTask = await jsInitGenerator(tree, {
|
||||||
...schema,
|
...schema,
|
||||||
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
|
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
@ -120,17 +66,17 @@ export async function applicationGeneratorInternal(
|
|||||||
});
|
});
|
||||||
tasks.push(jsInitTask);
|
tasks.push(jsInitTask);
|
||||||
|
|
||||||
const options = await normalizeOptions(host, schema);
|
const options = await normalizeOptions(tree, schema);
|
||||||
showPossibleWarnings(host, options);
|
showPossibleWarnings(tree, options);
|
||||||
|
|
||||||
const initTask = await reactInitGenerator(host, {
|
const initTask = await reactInitGenerator(tree, {
|
||||||
...options,
|
...options,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
});
|
});
|
||||||
tasks.push(initTask);
|
tasks.push(initTask);
|
||||||
|
|
||||||
if (!options.addPlugin) {
|
if (!options.addPlugin) {
|
||||||
const nxJson = readNxJson(host);
|
const nxJson = readNxJson(tree);
|
||||||
nxJson.targetDefaults ??= {};
|
nxJson.targetDefaults ??= {};
|
||||||
if (!Object.keys(nxJson.targetDefaults).includes('build')) {
|
if (!Object.keys(nxJson.targetDefaults).includes('build')) {
|
||||||
nxJson.targetDefaults.build = {
|
nxJson.targetDefaults.build = {
|
||||||
@ -140,159 +86,48 @@ export async function applicationGeneratorInternal(
|
|||||||
} else if (!nxJson.targetDefaults.build.dependsOn) {
|
} else if (!nxJson.targetDefaults.build.dependsOn) {
|
||||||
nxJson.targetDefaults.build.dependsOn = ['^build'];
|
nxJson.targetDefaults.build.dependsOn = ['^build'];
|
||||||
}
|
}
|
||||||
updateNxJson(host, nxJson);
|
updateNxJson(tree, nxJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.bundler === 'webpack') {
|
if (options.bundler === 'webpack') {
|
||||||
const { webpackInitGenerator } = ensurePackage<
|
await initWebpack(tree, options, tasks);
|
||||||
typeof import('@nx/webpack')
|
|
||||||
>('@nx/webpack', nxVersion);
|
|
||||||
const webpackInitTask = await webpackInitGenerator(host, {
|
|
||||||
skipPackageJson: options.skipPackageJson,
|
|
||||||
skipFormat: true,
|
|
||||||
addPlugin: options.addPlugin,
|
|
||||||
});
|
|
||||||
tasks.push(webpackInitTask);
|
|
||||||
if (!options.skipPackageJson) {
|
|
||||||
const { ensureDependencies } = await import(
|
|
||||||
'@nx/webpack/src/utils/ensure-dependencies'
|
|
||||||
);
|
|
||||||
tasks.push(ensureDependencies(host, { uiFramework: 'react' }));
|
|
||||||
}
|
|
||||||
} else if (options.bundler === 'rspack') {
|
} else if (options.bundler === 'rspack') {
|
||||||
const { rspackInitGenerator } = ensurePackage('@nx/rspack', nxVersion);
|
await initRspack(tree, options, tasks);
|
||||||
const rspackInitTask = await rspackInitGenerator(host, {
|
} else if (options.bundler === 'rsbuild') {
|
||||||
...options,
|
await initRsbuild(tree, options, tasks);
|
||||||
addPlugin: false,
|
|
||||||
skipFormat: true,
|
|
||||||
});
|
|
||||||
tasks.push(rspackInitTask);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.rootProject) {
|
if (!options.rootProject) {
|
||||||
extractTsConfigBase(host);
|
extractTsConfigBase(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
await createApplicationFiles(host, options);
|
await createApplicationFiles(tree, options);
|
||||||
addProject(host, options);
|
addProject(tree, options);
|
||||||
|
|
||||||
if (options.style === 'tailwind') {
|
if (options.style === 'tailwind') {
|
||||||
const twTask = await setupTailwindGenerator(host, {
|
const twTask = await setupTailwindGenerator(tree, {
|
||||||
project: options.projectName,
|
project: options.projectName,
|
||||||
});
|
});
|
||||||
tasks.push(twTask);
|
tasks.push(twTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.bundler === 'vite') {
|
if (options.bundler === 'vite') {
|
||||||
const { createOrEditViteConfig, viteConfigurationGenerator } =
|
await setupViteConfiguration(tree, options, tasks);
|
||||||
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
|
|
||||||
// We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development.
|
|
||||||
// See: https://vitejs.dev/guide/env-and-mode.html
|
|
||||||
if (
|
|
||||||
host.exists(joinPathFragments(options.appProjectRoot, 'src/environments'))
|
|
||||||
) {
|
|
||||||
host.delete(
|
|
||||||
joinPathFragments(options.appProjectRoot, 'src/environments')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const viteTask = await viteConfigurationGenerator(host, {
|
|
||||||
uiFramework: 'react',
|
|
||||||
project: options.projectName,
|
|
||||||
newProject: true,
|
|
||||||
includeVitest: options.unitTestRunner === 'vitest',
|
|
||||||
inSourceTests: options.inSourceTests,
|
|
||||||
compiler: options.compiler,
|
|
||||||
skipFormat: true,
|
|
||||||
addPlugin: options.addPlugin,
|
|
||||||
projectType: 'application',
|
|
||||||
});
|
|
||||||
tasks.push(viteTask);
|
|
||||||
createOrEditViteConfig(
|
|
||||||
host,
|
|
||||||
{
|
|
||||||
project: options.projectName,
|
|
||||||
includeLib: false,
|
|
||||||
includeVitest: options.unitTestRunner === 'vitest',
|
|
||||||
inSourceTests: options.inSourceTests,
|
|
||||||
rollupOptionsExternal: [
|
|
||||||
"'react'",
|
|
||||||
"'react-dom'",
|
|
||||||
"'react/jsx-runtime'",
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
options.compiler === 'swc'
|
|
||||||
? `import react from '@vitejs/plugin-react-swc'`
|
|
||||||
: `import react from '@vitejs/plugin-react'`,
|
|
||||||
],
|
|
||||||
plugins: ['react()'],
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
} else if (options.bundler === 'rspack') {
|
} else if (options.bundler === 'rspack') {
|
||||||
const { configurationGenerator } = ensurePackage('@nx/rspack', nxVersion);
|
await setupRspackConfiguration(tree, options, tasks);
|
||||||
const rspackTask = await configurationGenerator(host, {
|
} else if (options.bundler === 'rsbuild') {
|
||||||
project: options.projectName,
|
await setupRsbuildConfiguration(tree, options, tasks);
|
||||||
main: joinPathFragments(
|
|
||||||
options.appProjectRoot,
|
|
||||||
maybeJs(
|
|
||||||
{
|
|
||||||
js: options.js,
|
|
||||||
useJsx: true,
|
|
||||||
},
|
|
||||||
`src/main.tsx`
|
|
||||||
)
|
|
||||||
),
|
|
||||||
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
|
||||||
target: 'web',
|
|
||||||
newProject: true,
|
|
||||||
framework: 'react',
|
|
||||||
});
|
|
||||||
tasks.push(rspackTask);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
||||||
const { createOrEditViteConfig, vitestGenerator } = ensurePackage<
|
await setupVitestConfiguration(tree, options, tasks);
|
||||||
typeof import('@nx/vite')
|
|
||||||
>('@nx/vite', nxVersion);
|
|
||||||
|
|
||||||
const vitestTask = await vitestGenerator(host, {
|
|
||||||
uiFramework: 'react',
|
|
||||||
coverageProvider: 'v8',
|
|
||||||
project: options.projectName,
|
|
||||||
inSourceTests: options.inSourceTests,
|
|
||||||
skipFormat: true,
|
|
||||||
addPlugin: options.addPlugin,
|
|
||||||
});
|
|
||||||
tasks.push(vitestTask);
|
|
||||||
createOrEditViteConfig(
|
|
||||||
host,
|
|
||||||
{
|
|
||||||
project: options.projectName,
|
|
||||||
includeLib: false,
|
|
||||||
includeVitest: true,
|
|
||||||
inSourceTests: options.inSourceTests,
|
|
||||||
rollupOptionsExternal: [
|
|
||||||
"'react'",
|
|
||||||
"'react-dom'",
|
|
||||||
"'react/jsx-runtime'",
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
options.compiler === 'swc'
|
|
||||||
? `import react from '@vitejs/plugin-react-swc'`
|
|
||||||
: `import react from '@vitejs/plugin-react'`,
|
|
||||||
],
|
|
||||||
plugins: ['react()'],
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(options.bundler === 'vite' || options.unitTestRunner === 'vitest') &&
|
(options.bundler === 'vite' || options.unitTestRunner === 'vitest') &&
|
||||||
options.inSourceTests
|
options.inSourceTests
|
||||||
) {
|
) {
|
||||||
host.delete(
|
tree.delete(
|
||||||
joinPathFragments(
|
joinPathFragments(
|
||||||
options.appProjectRoot,
|
options.appProjectRoot,
|
||||||
`src/app/${options.fileName}.spec.tsx`
|
`src/app/${options.fileName}.spec.tsx`
|
||||||
@ -300,69 +135,33 @@ export async function applicationGeneratorInternal(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lintTask = await addLinting(host, options);
|
const lintTask = await addLinting(tree, options);
|
||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
|
|
||||||
const e2eTask = await addE2e(host, options);
|
const e2eTask = await addE2e(tree, options);
|
||||||
tasks.push(e2eTask);
|
tasks.push(e2eTask);
|
||||||
|
|
||||||
if (options.unitTestRunner === 'jest') {
|
if (options.unitTestRunner === 'jest') {
|
||||||
const jestTask = await addJest(host, options);
|
const jestTask = await addJest(tree, options);
|
||||||
tasks.push(jestTask);
|
tasks.push(jestTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tsconfig.spec.json for jest or vitest
|
// Handle tsconfig.spec.json for jest or vitest
|
||||||
updateSpecConfig(host, options);
|
updateSpecConfig(tree, options);
|
||||||
const stylePreprocessorTask = installCommonDependencies(host, options);
|
const stylePreprocessorTask = installCommonDependencies(tree, options);
|
||||||
tasks.push(stylePreprocessorTask);
|
tasks.push(stylePreprocessorTask);
|
||||||
const styledTask = addStyledModuleDependencies(host, options);
|
const styledTask = addStyledModuleDependencies(tree, options);
|
||||||
tasks.push(styledTask);
|
tasks.push(styledTask);
|
||||||
const routingTask = addRouting(host, options);
|
const routingTask = addRouting(tree, options);
|
||||||
tasks.push(routingTask);
|
tasks.push(routingTask);
|
||||||
setDefaults(host, options);
|
setDefaults(tree, options);
|
||||||
|
|
||||||
if (options.bundler === 'rspack' && options.style === 'styled-jsx') {
|
if (options.bundler === 'rspack' && options.style === 'styled-jsx') {
|
||||||
logger.warn(
|
handleStyledJsxForRspack(tasks, tree, options);
|
||||||
`${pc.bold('styled-jsx')} is not supported by ${pc.bold(
|
|
||||||
'Rspack'
|
|
||||||
)}. We've added ${pc.bold(
|
|
||||||
'babel-loader'
|
|
||||||
)} to your project, but using babel will slow down your build.`
|
|
||||||
);
|
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
addDependenciesToPackageJson(
|
|
||||||
host,
|
|
||||||
{},
|
|
||||||
{ 'babel-loader': babelLoaderVersion }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
host.write(
|
|
||||||
joinPathFragments(options.appProjectRoot, 'rspack.config.js'),
|
|
||||||
stripIndents`
|
|
||||||
const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
|
||||||
module.exports = composePlugins(withNx(), withReact(), (config) => {
|
|
||||||
config.module.rules.push({
|
|
||||||
test: /\\.[jt]sx$/i,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
presets: ['@babel/preset-typescript'],
|
|
||||||
plugins: ['styled-jsx/babel'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTsconfigFiles(
|
updateTsconfigFiles(
|
||||||
host,
|
tree,
|
||||||
options.appProjectRoot,
|
options.appProjectRoot,
|
||||||
'tsconfig.app.json',
|
'tsconfig.app.json',
|
||||||
{
|
{
|
||||||
@ -376,7 +175,7 @@ export async function applicationGeneratorInternal(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!options.skipFormat) {
|
if (!options.skipFormat) {
|
||||||
await formatFiles(host);
|
await formatFiles(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.push(() => {
|
tasks.push(() => {
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
<%_ if (routing) { _%>
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
<%_ } _%>
|
||||||
|
|
||||||
|
import App from './<%= fileName %>';
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
<%- appTests _%>
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title><%= className %></title>
|
||||||
|
<base href="/" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<%_ if (strict) { _%>import { StrictMode } from 'react';<%_ } _%>
|
||||||
|
import * as ReactDOM from 'react-dom/client';
|
||||||
|
<%_ if (routing) { _%>import { BrowserRouter } from 'react-router-dom';<%_ } _%>
|
||||||
|
import App from './app/<%= fileName %>';
|
||||||
|
<%_ if(hasStyleFile) { _%>
|
||||||
|
import './styles.<%= style %>'
|
||||||
|
<%_ } _%>
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||||
|
<%_ if(strict && !routing) { _%>
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<App/>
|
||||||
|
</StrictMode>
|
||||||
|
)
|
||||||
|
<%_ } _%>
|
||||||
|
<%_ if(!strict && routing) { _%>
|
||||||
|
root.render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<App/>
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
<%_ } _%>
|
||||||
|
<%_ if(strict && routing) { _%>
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App/>
|
||||||
|
</BrowserRouter>
|
||||||
|
</StrictMode>
|
||||||
|
)
|
||||||
|
<%_ } _%>
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<%_ if (isUsingTsSolutionSetup) { _%>{
|
||||||
|
"extends": "<%= offsetFromRoot%>tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"lib": ["dom"],
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
<%_ if (style === 'styled-jsx') { _%>"@nx/react/typings/styled-jsx.d.ts",<%_ } _%>
|
||||||
|
"@nx/react/typings/cssmodule.d.ts",
|
||||||
|
"@nx/react/typings/image.d.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "src/**/*.spec.tsx", "src/**/*.test.tsx", "src/**/*.spec.js", "src/**/*.test.js", "src/**/*.spec.jsx", "src/**/*.test.jsx"],
|
||||||
|
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||||
|
}<% } else { %>{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
<%_ if (style === 'styled-jsx') { _%>"@nx/react/typings/styled-jsx.d.ts",<%_ } _%>
|
||||||
|
"@nx/react/typings/cssmodule.d.ts",
|
||||||
|
"@nx/react/typings/image.d.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "src/**/*.spec.tsx", "src/**/*.test.tsx", "src/**/*.spec.js", "src/**/*.test.js", "src/**/*.spec.jsx", "src/**/*.test.jsx"],
|
||||||
|
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||||
|
}
|
||||||
|
<% } %>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
<%_ } if (!minimal) { _%>
|
<%_ } if (!minimal) { _%>
|
||||||
import NxWelcome from "./nx-welcome";
|
import NxWelcome from "./nx-welcome";
|
||||||
<%_ } if (bundler === "rspack") { _%>
|
<%_ } if (bundler === "rspack" || bundler === 'rsbuild') { _%>
|
||||||
import '../styles.css';
|
import '../styles.css';
|
||||||
<%_ } _%>
|
<%_ } _%>
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { nxVersion } from '../../../utils/versions';
|
|||||||
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
|
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
|
||||||
import { hasVitePlugin } from '../../../utils/has-vite-plugin';
|
import { hasVitePlugin } from '../../../utils/has-vite-plugin';
|
||||||
import { hasRspackPlugin } from '../../../utils/has-rspack-plugin';
|
import { hasRspackPlugin } from '../../../utils/has-rspack-plugin';
|
||||||
|
import { hasRsbuildPlugin } from '../../../utils/has-rsbuild-plugin';
|
||||||
import { NormalizedSchema } from '../schema';
|
import { NormalizedSchema } from '../schema';
|
||||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||||
@ -26,6 +27,7 @@ export async function addE2e(
|
|||||||
const hasNxBuildPlugin =
|
const hasNxBuildPlugin =
|
||||||
(options.bundler === 'webpack' && hasWebpackPlugin(tree)) ||
|
(options.bundler === 'webpack' && hasWebpackPlugin(tree)) ||
|
||||||
(options.bundler === 'rspack' && hasRspackPlugin(tree)) ||
|
(options.bundler === 'rspack' && hasRspackPlugin(tree)) ||
|
||||||
|
(options.bundler === 'rsbuild' && hasRsbuildPlugin(tree)) ||
|
||||||
(options.bundler === 'vite' && hasVitePlugin(tree));
|
(options.bundler === 'vite' && hasVitePlugin(tree));
|
||||||
|
|
||||||
let e2eWebServerInfo: E2EWebServerDetails = {
|
let e2eWebServerInfo: E2EWebServerDetails = {
|
||||||
@ -68,6 +70,22 @@ export async function addE2e(
|
|||||||
options.addPlugin,
|
options.addPlugin,
|
||||||
options.devServerPort ?? 4200
|
options.devServerPort ?? 4200
|
||||||
);
|
);
|
||||||
|
} else if (options.bundler === 'rsbuild') {
|
||||||
|
ensurePackage('@nx/rsbuild', nxVersion);
|
||||||
|
const { getRsbuildE2EWebServerInfo } = await import(
|
||||||
|
'@nx/rsbuild/config-utils'
|
||||||
|
);
|
||||||
|
|
||||||
|
e2eWebServerInfo = await getRsbuildE2EWebServerInfo(
|
||||||
|
tree,
|
||||||
|
options.projectName,
|
||||||
|
joinPathFragments(
|
||||||
|
options.appProjectRoot,
|
||||||
|
`rsbuild.config.${options.js ? 'js' : 'ts'}`
|
||||||
|
),
|
||||||
|
options.addPlugin,
|
||||||
|
options.devServerPort ?? 4200
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasNxBuildPlugin) {
|
if (!hasNxBuildPlugin) {
|
||||||
@ -114,7 +132,12 @@ export async function addE2e(
|
|||||||
project: options.e2eProjectName,
|
project: options.e2eProjectName,
|
||||||
directory: 'src',
|
directory: 'src',
|
||||||
// the name and root are already normalized, instruct the generator to use them as is
|
// the name and root are already normalized, instruct the generator to use them as is
|
||||||
bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler,
|
bundler:
|
||||||
|
options.bundler === 'rspack'
|
||||||
|
? 'webpack'
|
||||||
|
: options.bundler === 'rsbuild'
|
||||||
|
? 'none'
|
||||||
|
: options.bundler,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
|
devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
|
||||||
baseUrl: e2eWebServerInfo.e2eWebServerAddress,
|
baseUrl: e2eWebServerInfo.e2eWebServerAddress,
|
||||||
|
|||||||
69
packages/react/src/generators/application/lib/add-linting.ts
Normal file
69
packages/react/src/generators/application/lib/add-linting.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
type Tree,
|
||||||
|
type GeneratorCallback,
|
||||||
|
joinPathFragments,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { Linter, lintProjectGenerator } from '@nx/eslint';
|
||||||
|
import {
|
||||||
|
addExtendsToLintConfig,
|
||||||
|
addOverrideToLintConfig,
|
||||||
|
addPredefinedConfigToFlatLintConfig,
|
||||||
|
isEslintConfigSupported,
|
||||||
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||||
|
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||||
|
import { addDependenciesToPackageJson, runTasksInSerial } from '@nx/devkit';
|
||||||
|
import { addSwcDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies';
|
||||||
|
import { extraEslintDependencies } from '../../../utils/lint';
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
|
||||||
|
export async function addLinting(host: Tree, options: NormalizedSchema) {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
if (options.linter === Linter.EsLint) {
|
||||||
|
const lintTask = await lintProjectGenerator(host, {
|
||||||
|
linter: options.linter,
|
||||||
|
project: options.projectName,
|
||||||
|
tsConfigPaths: [
|
||||||
|
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
||||||
|
],
|
||||||
|
unitTestRunner: options.unitTestRunner,
|
||||||
|
skipFormat: true,
|
||||||
|
rootProject: options.rootProject,
|
||||||
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
addPlugin: options.addPlugin,
|
||||||
|
});
|
||||||
|
tasks.push(lintTask);
|
||||||
|
|
||||||
|
if (isEslintConfigSupported(host)) {
|
||||||
|
if (useFlatConfig(host)) {
|
||||||
|
addPredefinedConfigToFlatLintConfig(
|
||||||
|
host,
|
||||||
|
options.appProjectRoot,
|
||||||
|
'flat/react'
|
||||||
|
);
|
||||||
|
// Add an empty rules object to users know how to add/override rules
|
||||||
|
addOverrideToLintConfig(host, options.appProjectRoot, {
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const addExtendsTask = addExtendsToLintConfig(
|
||||||
|
host,
|
||||||
|
options.appProjectRoot,
|
||||||
|
{ name: 'plugin:@nx/react', needCompatFixup: true }
|
||||||
|
);
|
||||||
|
tasks.push(addExtendsTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.skipPackageJson) {
|
||||||
|
const installTask = addDependenciesToPackageJson(
|
||||||
|
host,
|
||||||
|
extraEslintDependencies.dependencies,
|
||||||
|
extraEslintDependencies.devDependencies
|
||||||
|
);
|
||||||
|
const addSwcTask = addSwcDependencies(host);
|
||||||
|
tasks.push(installTask, addSwcTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
import {
|
||||||
|
type Tree,
|
||||||
|
ensurePackage,
|
||||||
|
joinPathFragments,
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { nxVersion } from '../../../../utils/versions';
|
||||||
|
import { maybeJs } from '../../../../utils/maybe-js';
|
||||||
|
import { NormalizedSchema, Schema } from '../../schema';
|
||||||
|
|
||||||
|
export async function initRsbuild(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>,
|
||||||
|
tasks: any[]
|
||||||
|
) {
|
||||||
|
ensurePackage('@nx/rsbuild', nxVersion);
|
||||||
|
const { initGenerator } = await import('@nx/rsbuild/generators');
|
||||||
|
const initTask = await initGenerator(tree, {
|
||||||
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
addPlugin: true,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
tasks.push(initTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupRsbuildConfiguration(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>,
|
||||||
|
tasks: any[]
|
||||||
|
) {
|
||||||
|
ensurePackage('@nx/rsbuild', nxVersion);
|
||||||
|
const { configurationGenerator } = await import('@nx/rsbuild/generators');
|
||||||
|
const {
|
||||||
|
addBuildPlugin,
|
||||||
|
addCopyAssets,
|
||||||
|
addHtmlTemplatePath,
|
||||||
|
addExperimentalSwcPlugin,
|
||||||
|
versions,
|
||||||
|
} = await import('@nx/rsbuild/config-utils');
|
||||||
|
const rsbuildTask = await configurationGenerator(tree, {
|
||||||
|
project: options.projectName,
|
||||||
|
entry: maybeJs(
|
||||||
|
{
|
||||||
|
js: options.js,
|
||||||
|
useJsx: true,
|
||||||
|
},
|
||||||
|
`./src/main.tsx`
|
||||||
|
),
|
||||||
|
tsConfig: './tsconfig.app.json',
|
||||||
|
target: 'web',
|
||||||
|
devServerPort: options.devServerPort ?? 4200,
|
||||||
|
});
|
||||||
|
tasks.push(rsbuildTask);
|
||||||
|
|
||||||
|
const pathToConfigFile = joinPathFragments(
|
||||||
|
options.appProjectRoot,
|
||||||
|
'rsbuild.config.ts'
|
||||||
|
);
|
||||||
|
|
||||||
|
const deps = { '@rsbuild/plugin-react': versions.rsbuildPluginReactVersion };
|
||||||
|
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
pathToConfigFile,
|
||||||
|
'@rsbuild/plugin-react',
|
||||||
|
'pluginReact',
|
||||||
|
options.style === '@emotion/styled'
|
||||||
|
? `swcReactOptions: {\n\timportSource: '@emotion/react',\n}`
|
||||||
|
: undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.style === 'scss') {
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
pathToConfigFile,
|
||||||
|
'@rsbuild/plugin-sass',
|
||||||
|
'pluginSass'
|
||||||
|
);
|
||||||
|
deps['@rsbuild/plugin-sass'] = versions.rsbuildPluginSassVersion;
|
||||||
|
} else if (options.style === 'less') {
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
pathToConfigFile,
|
||||||
|
'@rsbuild/plugin-less',
|
||||||
|
'pluginLess'
|
||||||
|
);
|
||||||
|
deps['@rsbuild/plugin-less'] = versions.rsbuildPluginLessVersion;
|
||||||
|
} else if (options.style === '@emotion/styled') {
|
||||||
|
deps['@swc/plugin-emotion'] = versions.rsbuildSwcPluginEmotionVersion;
|
||||||
|
addExperimentalSwcPlugin(tree, pathToConfigFile, '@swc/plugin-emotion');
|
||||||
|
} else if (options.style === 'styled-jsx') {
|
||||||
|
deps['@swc/plugin-styled-jsx'] = versions.rsbuildSwcPluginStyledJsxVersion;
|
||||||
|
addExperimentalSwcPlugin(tree, pathToConfigFile, '@swc/plugin-styled-jsx');
|
||||||
|
} else if (options.style === 'styled-components') {
|
||||||
|
deps['@rsbuild/plugin-styled-components'] =
|
||||||
|
versions.rsbuildPluginStyledComponentsVersion;
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
pathToConfigFile,
|
||||||
|
'@rsbuild/plugin-styled-components',
|
||||||
|
'pluginStyledComponents'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addHtmlTemplatePath(tree, pathToConfigFile, './src/index.html');
|
||||||
|
addCopyAssets(tree, pathToConfigFile, './src/assets');
|
||||||
|
addCopyAssets(tree, pathToConfigFile, './src/favicon.ico');
|
||||||
|
tasks.push(addDependenciesToPackageJson(tree, {}, deps));
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
type Tree,
|
||||||
|
ensurePackage,
|
||||||
|
joinPathFragments,
|
||||||
|
logger,
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
stripIndents,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import * as pc from 'picocolors';
|
||||||
|
import { babelLoaderVersion, nxVersion } from '../../../../utils/versions';
|
||||||
|
import { maybeJs } from '../../../../utils/maybe-js';
|
||||||
|
import { NormalizedSchema, Schema } from '../../schema';
|
||||||
|
|
||||||
|
export async function initRspack(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>,
|
||||||
|
tasks: any[]
|
||||||
|
) {
|
||||||
|
const { rspackInitGenerator } = ensurePackage('@nx/rspack', nxVersion);
|
||||||
|
const rspackInitTask = await rspackInitGenerator(tree, {
|
||||||
|
...options,
|
||||||
|
addPlugin: false,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
tasks.push(rspackInitTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupRspackConfiguration(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>,
|
||||||
|
tasks: any[]
|
||||||
|
) {
|
||||||
|
const { configurationGenerator } = ensurePackage('@nx/rspack', nxVersion);
|
||||||
|
const rspackTask = await configurationGenerator(tree, {
|
||||||
|
project: options.projectName,
|
||||||
|
main: joinPathFragments(
|
||||||
|
options.appProjectRoot,
|
||||||
|
maybeJs(
|
||||||
|
{
|
||||||
|
js: options.js,
|
||||||
|
useJsx: true,
|
||||||
|
},
|
||||||
|
`src/main.tsx`
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
||||||
|
target: 'web',
|
||||||
|
newProject: true,
|
||||||
|
framework: 'react',
|
||||||
|
});
|
||||||
|
tasks.push(rspackTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleStyledJsxForRspack(
|
||||||
|
tasks: any[],
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>
|
||||||
|
) {
|
||||||
|
logger.warn(
|
||||||
|
`${pc.bold('styled-jsx')} is not supported by ${pc.bold(
|
||||||
|
'Rspack'
|
||||||
|
)}. We've added ${pc.bold(
|
||||||
|
'babel-loader'
|
||||||
|
)} to your project, but using babel will slow down your build.`
|
||||||
|
);
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{ 'babel-loader': babelLoaderVersion }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
joinPathFragments(options.appProjectRoot, 'rspack.config.js'),
|
||||||
|
stripIndents`
|
||||||
|
const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||||
|
module.exports = composePlugins(withNx(), withReact(), (config) => {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\\.[jt]sx$/i,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: ['@babel/preset-typescript'],
|
||||||
|
plugins: ['styled-jsx/babel'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { type Tree, ensurePackage, joinPathFragments } from '@nx/devkit';
|
||||||
|
import { nxVersion } from '../../../../utils/versions';
|
||||||
|
import { NormalizedSchema, Schema } from '../../schema';
|
||||||
|
|
||||||
|
export async function setupViteConfiguration(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>,
|
||||||
|
tasks: any[]
|
||||||
|
) {
|
||||||
|
const { createOrEditViteConfig, viteConfigurationGenerator } = ensurePackage<
|
||||||
|
typeof import('@nx/vite')
|
||||||
|
>('@nx/vite', nxVersion);
|
||||||
|
// We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development.
|
||||||
|
// See: https://vitejs.dev/guide/env-and-mode.html
|
||||||
|
if (
|
||||||
|
tree.exists(joinPathFragments(options.appProjectRoot, 'src/environments'))
|
||||||
|
) {
|
||||||
|
tree.delete(joinPathFragments(options.appProjectRoot, 'src/environments'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const viteTask = await viteConfigurationGenerator(tree, {
|
||||||
|
uiFramework: 'react',
|
||||||
|
project: options.projectName,
|
||||||
|
newProject: true,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
compiler: options.compiler,
|
||||||
|
skipFormat: true,
|
||||||
|
addPlugin: options.addPlugin,
|
||||||
|
projectType: 'application',
|
||||||
|
});
|
||||||
|
tasks.push(viteTask);
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: options.projectName,
|
||||||
|
includeLib: false,
|
||||||
|
includeVitest: options.unitTestRunner === 'vitest',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
rollupOptionsExternal: ["'react'", "'react-dom'", "'react/jsx-runtime'"],
|
||||||
|
imports: [
|
||||||
|
options.compiler === 'swc'
|
||||||
|
? `import react from '@vitejs/plugin-react-swc'`
|
||||||
|
: `import react from '@vitejs/plugin-react'`,
|
||||||
|
],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupVitestConfiguration(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>,
|
||||||
|
tasks: any[]
|
||||||
|
) {
|
||||||
|
const { createOrEditViteConfig, vitestGenerator } = ensurePackage<
|
||||||
|
typeof import('@nx/vite')
|
||||||
|
>('@nx/vite', nxVersion);
|
||||||
|
|
||||||
|
const vitestTask = await vitestGenerator(tree, {
|
||||||
|
uiFramework: 'react',
|
||||||
|
coverageProvider: 'v8',
|
||||||
|
project: options.projectName,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
skipFormat: true,
|
||||||
|
addPlugin: options.addPlugin,
|
||||||
|
});
|
||||||
|
tasks.push(vitestTask);
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: options.projectName,
|
||||||
|
includeLib: false,
|
||||||
|
includeVitest: true,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
rollupOptionsExternal: ["'react'", "'react-dom'", "'react/jsx-runtime'"],
|
||||||
|
imports: [
|
||||||
|
options.compiler === 'swc'
|
||||||
|
? `import react from '@vitejs/plugin-react-swc'`
|
||||||
|
: `import react from '@vitejs/plugin-react'`,
|
||||||
|
],
|
||||||
|
plugins: ['react()'],
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (options.bundler === 'rsbuild') {
|
||||||
|
tree.rename(
|
||||||
|
joinPathFragments(options.appProjectRoot, 'vite.config.ts'),
|
||||||
|
joinPathFragments(options.appProjectRoot, 'vitest.config.ts')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { type Tree, ensurePackage } from '@nx/devkit';
|
||||||
|
import { nxVersion } from '../../../../utils/versions';
|
||||||
|
import { Schema, NormalizedSchema } from '../../schema';
|
||||||
|
|
||||||
|
export async function initWebpack(
|
||||||
|
tree: Tree,
|
||||||
|
options: NormalizedSchema<Schema>,
|
||||||
|
tasks: any[]
|
||||||
|
) {
|
||||||
|
const { webpackInitGenerator } = ensurePackage<typeof import('@nx/webpack')>(
|
||||||
|
'@nx/webpack',
|
||||||
|
nxVersion
|
||||||
|
);
|
||||||
|
const webpackInitTask = await webpackInitGenerator(tree, {
|
||||||
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
skipFormat: true,
|
||||||
|
addPlugin: options.addPlugin,
|
||||||
|
});
|
||||||
|
tasks.push(webpackInitTask);
|
||||||
|
if (!options.skipPackageJson) {
|
||||||
|
const { ensureDependencies } = await import(
|
||||||
|
'@nx/webpack/src/utils/ensure-dependencies'
|
||||||
|
);
|
||||||
|
tasks.push(ensureDependencies(tree, { uiFramework: 'react' }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -155,6 +155,15 @@ export async function createApplicationFiles(
|
|||||||
: null,
|
: null,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} else if (options.bundler === 'rsbuild') {
|
||||||
|
generateFiles(
|
||||||
|
host,
|
||||||
|
join(__dirname, '../files/base-rsbuild'),
|
||||||
|
options.appProjectRoot,
|
||||||
|
{
|
||||||
|
...templateVariables,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export interface Schema {
|
|||||||
devServerPort?: number;
|
devServerPort?: number;
|
||||||
skipPackageJson?: boolean;
|
skipPackageJson?: boolean;
|
||||||
rootProject?: boolean;
|
rootProject?: boolean;
|
||||||
bundler?: 'webpack' | 'vite' | 'rspack';
|
bundler?: 'webpack' | 'vite' | 'rspack' | 'rsbuild';
|
||||||
minimal?: boolean;
|
minimal?: boolean;
|
||||||
// Internal options
|
// Internal options
|
||||||
addPlugin?: boolean;
|
addPlugin?: boolean;
|
||||||
|
|||||||
@ -101,7 +101,7 @@
|
|||||||
"bundler": {
|
"bundler": {
|
||||||
"description": "The bundler to use.",
|
"description": "The bundler to use.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["vite", "webpack", "rspack"],
|
"enum": ["vite", "webpack", "rspack", "rsbuild"],
|
||||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||||
"default": "vite",
|
"default": "vite",
|
||||||
"x-priority": "important"
|
"x-priority": "important"
|
||||||
|
|||||||
10
packages/react/src/utils/has-rsbuild-plugin.ts
Normal file
10
packages/react/src/utils/has-rsbuild-plugin.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { readNxJson, Tree } from '@nx/devkit';
|
||||||
|
|
||||||
|
export function hasRsbuildPlugin(tree: Tree) {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
return !!nxJson.plugins?.some((p) =>
|
||||||
|
typeof p === 'string'
|
||||||
|
? p === '@nx/rsbuild/plugin'
|
||||||
|
: p.plugin === '@nx/rsbuild/plugin'
|
||||||
|
);
|
||||||
|
}
|
||||||
8
packages/rsbuild/config-utils.ts
Normal file
8
packages/rsbuild/config-utils.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export { addBuildPlugin } from './src/utils/add-build-plugin';
|
||||||
|
export {
|
||||||
|
addCopyAssets,
|
||||||
|
addHtmlTemplatePath,
|
||||||
|
addExperimentalSwcPlugin,
|
||||||
|
} from './src/utils/ast-utils';
|
||||||
|
export * as versions from './src/utils/versions';
|
||||||
|
export { getRsbuildE2EWebServerInfo } from './src/utils/e2e-web-server-info-utils';
|
||||||
2
packages/rsbuild/generators.ts
Normal file
2
packages/rsbuild/generators.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { configurationGenerator } from './src/generators/configuration/configuration';
|
||||||
|
export { initGenerator } from './src/generators/init/init';
|
||||||
@ -33,10 +33,32 @@
|
|||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
"@nx/js": "file:../js",
|
"@nx/js": "file:../js",
|
||||||
"@rsbuild/core": "1.1.8",
|
"@rsbuild/core": "1.1.8",
|
||||||
"minimatch": "9.0.3"
|
"minimatch": "9.0.3",
|
||||||
|
"@phenomnomnominal/tsquery": "~5.0.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {},
|
"peerDependencies": {},
|
||||||
"nx-migrations": {
|
"nx-migrations": {
|
||||||
"migrations": "./migrations.json"
|
"migrations": "./migrations.json"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./package.json": {
|
||||||
|
"default": "./package.json"
|
||||||
|
},
|
||||||
|
"./generators": {
|
||||||
|
"types": "./generators.d.ts",
|
||||||
|
"default": "./generators.js"
|
||||||
|
},
|
||||||
|
"./config-utils": {
|
||||||
|
"types": "./config-utils.d.ts",
|
||||||
|
"default": "./config-utils.js"
|
||||||
|
},
|
||||||
|
"./plugin": {
|
||||||
|
"types": "./plugin.d.ts",
|
||||||
|
"default": "./plugin.js"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,6 +61,9 @@ describe('Rsbuild configuration generator', () => {
|
|||||||
index: './src/index.ts'
|
index: './src/index.ts'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
target: 'web',
|
target: 'web',
|
||||||
distPath: {
|
distPath: {
|
||||||
@ -94,6 +97,9 @@ describe('Rsbuild configuration generator', () => {
|
|||||||
index: './src/main.ts'
|
index: './src/main.ts'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
target: 'web',
|
target: 'web',
|
||||||
distPath: {
|
distPath: {
|
||||||
@ -127,6 +133,9 @@ describe('Rsbuild configuration generator', () => {
|
|||||||
index: './src/main.ts'
|
index: './src/main.ts'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
target: 'web',
|
target: 'web',
|
||||||
distPath: {
|
distPath: {
|
||||||
@ -157,6 +166,9 @@ describe('Rsbuild configuration generator', () => {
|
|||||||
},
|
},
|
||||||
tsconfigPath: './tsconfig.json',
|
tsconfigPath: './tsconfig.json',
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
target: 'web',
|
target: 'web',
|
||||||
distPath: {
|
distPath: {
|
||||||
|
|||||||
@ -18,11 +18,14 @@ import { join } from 'path';
|
|||||||
export async function configurationGenerator(tree: Tree, schema: Schema) {
|
export async function configurationGenerator(tree: Tree, schema: Schema) {
|
||||||
const projectGraph = await createProjectGraphAsync();
|
const projectGraph = await createProjectGraphAsync();
|
||||||
const projects = readProjectsConfigurationFromProjectGraph(projectGraph);
|
const projects = readProjectsConfigurationFromProjectGraph(projectGraph);
|
||||||
const project = projects.projects[schema.project];
|
let project = projects.projects[schema.project];
|
||||||
if (!project) {
|
if (!project) {
|
||||||
throw new Error(
|
project = readProjectConfiguration(tree, schema.project);
|
||||||
`Could not find project '${schema.project}'. Please choose a project that exists in the Nx Workspace.`
|
if (!project) {
|
||||||
);
|
throw new Error(
|
||||||
|
`Could not find project '${schema.project}'. Please choose a project that exists in the Nx Workspace.`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = await normalizeOptions(tree, schema, project);
|
const options = await normalizeOptions(tree, schema, project);
|
||||||
|
|||||||
@ -7,6 +7,9 @@ export default defineConfig({
|
|||||||
},<% if (tsConfig) { %>
|
},<% if (tsConfig) { %>
|
||||||
tsconfigPath: '<%= tsConfig %>',<% } %>
|
tsconfigPath: '<%= tsConfig %>',<% } %>
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
port: <%= devServerPort %>
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
target: '<%= target %>',
|
target: '<%= target %>',
|
||||||
distPath: {
|
distPath: {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { relative } from 'path';
|
|||||||
export interface NormalizedOptions extends Schema {
|
export interface NormalizedOptions extends Schema {
|
||||||
entry: string;
|
entry: string;
|
||||||
target: 'node' | 'web' | 'web-worker';
|
target: 'node' | 'web' | 'web-worker';
|
||||||
|
devServerPort: number;
|
||||||
tsConfig: string;
|
tsConfig: string;
|
||||||
projectRoot: string;
|
projectRoot: string;
|
||||||
}
|
}
|
||||||
@ -30,6 +31,7 @@ export async function normalizeOptions(
|
|||||||
schema.tsConfig ?? './tsconfig.json',
|
schema.tsConfig ?? './tsconfig.json',
|
||||||
project.root
|
project.root
|
||||||
),
|
),
|
||||||
|
devServerPort: schema.devServerPort ?? 4200,
|
||||||
projectRoot: project.root,
|
projectRoot: project.root,
|
||||||
skipFormat: schema.skipFormat ?? false,
|
skipFormat: schema.skipFormat ?? false,
|
||||||
skipValidation: schema.skipValidation ?? false,
|
skipValidation: schema.skipValidation ?? false,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ export interface Schema {
|
|||||||
project: string;
|
project: string;
|
||||||
entry?: string;
|
entry?: string;
|
||||||
tsConfig?: string;
|
tsConfig?: string;
|
||||||
|
devServerPort?: number;
|
||||||
target?: 'node' | 'web' | 'web-worker';
|
target?: 'node' | 'web' | 'web-worker';
|
||||||
skipValidation?: boolean;
|
skipValidation?: boolean;
|
||||||
skipFormat?: boolean;
|
skipFormat?: boolean;
|
||||||
|
|||||||
@ -26,6 +26,11 @@
|
|||||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||||
"x-priority": "important"
|
"x-priority": "important"
|
||||||
},
|
},
|
||||||
|
"devServerPort": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The port for the dev server to listen on.",
|
||||||
|
"default": 4200
|
||||||
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Target platform for the build, same as the Rsbuild output.target config option.",
|
"description": "Target platform for the build, same as the Rsbuild output.target config option.",
|
||||||
|
|||||||
@ -124,6 +124,10 @@ describe('@nx/rsbuild/plugin', () => {
|
|||||||
},
|
},
|
||||||
"preview-serve": {
|
"preview-serve": {
|
||||||
"command": "rsbuild preview",
|
"command": "rsbuild preview",
|
||||||
|
"dependsOn": [
|
||||||
|
"build-something",
|
||||||
|
"^build-something",
|
||||||
|
],
|
||||||
"options": {
|
"options": {
|
||||||
"args": [
|
"args": [
|
||||||
"--mode=production",
|
"--mode=production",
|
||||||
|
|||||||
@ -193,6 +193,7 @@ async function createRsbuildTargets(
|
|||||||
|
|
||||||
targets[options.previewTargetName] = {
|
targets[options.previewTargetName] = {
|
||||||
command: `rsbuild preview`,
|
command: `rsbuild preview`,
|
||||||
|
dependsOn: [`${options.buildTargetName}`, `^${options.buildTargetName}`],
|
||||||
options: {
|
options: {
|
||||||
cwd: projectRoot,
|
cwd: projectRoot,
|
||||||
args: ['--mode=production'],
|
args: ['--mode=production'],
|
||||||
|
|||||||
177
packages/rsbuild/src/utils/add-build-plugin.spec.ts
Normal file
177
packages/rsbuild/src/utils/add-build-plugin.spec.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import { addBuildPlugin } from './add-build-plugin';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
|
||||||
|
describe('addBuildPlugin', () => {
|
||||||
|
it('should add the plugin to the config file when plugins array does not exist', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import { defineConfig } from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@rsbuild/plugin-less',
|
||||||
|
'less'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { less } from '@rsbuild/plugin-less';
|
||||||
|
import { defineConfig } from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [less()],
|
||||||
|
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the plugin to the config file when plugins array exists and has other plugins', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import { defineConfig } from '@rsbuild/core';
|
||||||
|
import { less } from '@rsbuild/plugin-less';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [less()],
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@rsbuild/plugin-react',
|
||||||
|
'pluginReact'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { pluginReact } from '@rsbuild/plugin-react';
|
||||||
|
import { defineConfig } from '@rsbuild/core';
|
||||||
|
import { less } from '@rsbuild/plugin-less';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
less(),
|
||||||
|
pluginReact()
|
||||||
|
],
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the plugin to the config file when plugins array exists and is empty', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import { defineConfig } from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [],
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@rsbuild/plugin-react',
|
||||||
|
'pluginReact'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { pluginReact } from '@rsbuild/plugin-react';
|
||||||
|
import { defineConfig } from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
pluginReact()
|
||||||
|
],
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('should add the plugin to the config file when plugins doesnt not exist and its being added with options', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import { defineConfig } from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [],
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@rsbuild/plugin-react',
|
||||||
|
'pluginReact',
|
||||||
|
`swcReactOptions: {\n\timportSource: '@emotion/react',\n}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { pluginReact } from '@rsbuild/plugin-react';
|
||||||
|
import { defineConfig } from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
pluginReact({
|
||||||
|
swcReactOptions: {
|
||||||
|
importSource: '@emotion/react',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
68
packages/rsbuild/src/utils/add-build-plugin.ts
Normal file
68
packages/rsbuild/src/utils/add-build-plugin.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { type Tree } from '@nx/devkit';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import { indentBy } from './indent-by';
|
||||||
|
|
||||||
|
const DEFINE_CONFIG_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) > ObjectLiteralExpression';
|
||||||
|
const PLUGINS_ARRAY_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) PropertyAssignment:has(Identifier[name=plugins]) > ArrayLiteralExpression';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a plugin to the build configuration.
|
||||||
|
* @param tree - Nx Devkit Tree
|
||||||
|
* @param pathToConfigFile - Path to the build configuration file
|
||||||
|
* @param importPath - Path to the plugin
|
||||||
|
* @param pluginName - Name of the plugin
|
||||||
|
* @param options - Optional but should be defined as a string such as `property: {foo: 'bar'}`
|
||||||
|
*/
|
||||||
|
export function addBuildPlugin(
|
||||||
|
tree: Tree,
|
||||||
|
pathToConfigFile: string,
|
||||||
|
importPath: string,
|
||||||
|
pluginName: string,
|
||||||
|
options?: string
|
||||||
|
) {
|
||||||
|
let configContents = tree.read(pathToConfigFile, 'utf-8');
|
||||||
|
configContents = `import { ${pluginName} } from '${importPath}';
|
||||||
|
${configContents}`;
|
||||||
|
|
||||||
|
const ast = tsquery.ast(configContents);
|
||||||
|
|
||||||
|
const pluginsArrayNodes = tsquery(ast, PLUGINS_ARRAY_SELECTOR);
|
||||||
|
if (pluginsArrayNodes.length === 0) {
|
||||||
|
const defineConfigNodes = tsquery(ast, DEFINE_CONFIG_SELECTOR);
|
||||||
|
if (defineConfigNodes.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find defineConfig in the config file at ${pathToConfigFile}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const defineConfigNode = defineConfigNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
defineConfigNode.getStart() + 1
|
||||||
|
)}\n${indentBy(1)(
|
||||||
|
`plugins: [${pluginName}(${options ?? ''})],`
|
||||||
|
)}\n\t${configContents.slice(defineConfigNode.getStart() + 1)}`;
|
||||||
|
} else {
|
||||||
|
const pluginsArrayNode = pluginsArrayNodes[0];
|
||||||
|
const pluginsArrayContents = pluginsArrayNode.getText();
|
||||||
|
const newPluginsArrayContents = `[\n${indentBy(2)(
|
||||||
|
`${
|
||||||
|
pluginsArrayContents.length > 2
|
||||||
|
? `${pluginsArrayContents.slice(
|
||||||
|
1,
|
||||||
|
pluginsArrayContents.length - 1
|
||||||
|
)},\n${pluginName}`
|
||||||
|
: pluginName
|
||||||
|
}(${options ? `{\n${indentBy(1)(`${options}`)}\n}` : ''})`
|
||||||
|
)}\n\t]`;
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
pluginsArrayNode.getStart()
|
||||||
|
)}${newPluginsArrayContents}${configContents.slice(
|
||||||
|
pluginsArrayNode.getEnd()
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.write(pathToConfigFile, configContents);
|
||||||
|
}
|
||||||
437
packages/rsbuild/src/utils/ast-utils.spec.ts
Normal file
437
packages/rsbuild/src/utils/ast-utils.spec.ts
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import {
|
||||||
|
addHtmlTemplatePath,
|
||||||
|
addCopyAssets,
|
||||||
|
addExperimentalSwcPlugin,
|
||||||
|
} from './ast-utils';
|
||||||
|
|
||||||
|
describe('ast-utils', () => {
|
||||||
|
describe('addHtmlTemplatePath', () => {
|
||||||
|
it('should add the template path to the config when html object does not exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addHtmlTemplatePath(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'./src/index.html'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
html: {
|
||||||
|
template: './src/index.html'
|
||||||
|
},
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the template path to the config when html object exists but template is not', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
html: {
|
||||||
|
otherValue: true
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addHtmlTemplatePath(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'./src/index.html'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
html: {
|
||||||
|
template: './src/index.html',
|
||||||
|
|
||||||
|
otherValue: true
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the template path to the config when html object exists along with template', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
html: {
|
||||||
|
template: 'my.html'
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addHtmlTemplatePath(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'./src/index.html'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
html: {
|
||||||
|
template: './src/index.html',
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('addCopyAssets', () => {
|
||||||
|
it('should add the copy path to the config when output object does not exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addCopyAssets(tree, 'apps/my-app/rsbuild.config.ts', './src/assets');
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
output: {
|
||||||
|
copy: [{ from: './src/assets' }],
|
||||||
|
},
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the copy path to the config when outout object exists but copy does not', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
output: {
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addCopyAssets(tree, 'apps/my-app/rsbuild.config.ts', './src/assets');
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
output: {
|
||||||
|
copy: [{ from: './src/assets' }],
|
||||||
|
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the copy path to the config when output object exists along with copy object', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
output: {
|
||||||
|
copy: [
|
||||||
|
{ from: './src/assets' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addCopyAssets(tree, 'apps/my-app/rsbuild.config.ts', './src/favicon.ico');
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
output: {
|
||||||
|
copy: [
|
||||||
|
{ from: './src/favicon.ico' },
|
||||||
|
|
||||||
|
{ from: './src/assets' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('addExperimentalSwcPlugin', () => {
|
||||||
|
it('should add the swc plugin to the config when tools object does not exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';\nexport default defineConfig({\n});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addExperimentalSwcPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@swc/plugin-emotion'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {
|
||||||
|
plugins: [
|
||||||
|
['@swc/plugin-emotion', {}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the swc plugin to the config when swc object does not exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';\nexport default defineConfig({\n\ttools: {}\n});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addExperimentalSwcPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@swc/plugin-emotion'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {
|
||||||
|
plugins: [
|
||||||
|
['@swc/plugin-emotion', {}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the swc plugin to the config when jsc object does not exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';\nexport default defineConfig({\n\ttools: {\n\t\tswc: {}\n\t}\n});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addExperimentalSwcPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@swc/plugin-emotion'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {
|
||||||
|
plugins: [
|
||||||
|
['@swc/plugin-emotion', {}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the swc plugin to the config when experimental object does not exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addExperimentalSwcPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@swc/plugin-emotion'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {
|
||||||
|
plugins: [
|
||||||
|
['@swc/plugin-emotion', {}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the swc plugin to the config when plugins array does not exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addExperimentalSwcPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@swc/plugin-emotion'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {
|
||||||
|
plugins: [
|
||||||
|
['@swc/plugin-emotion', {}],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the swc plugin to the config when plugins array does exist', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
`import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {
|
||||||
|
plugins: [['@swc/plugin-styled-jsx', {}]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
addExperimentalSwcPlugin(
|
||||||
|
tree,
|
||||||
|
'apps/my-app/rsbuild.config.ts',
|
||||||
|
'@swc/plugin-emotion'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('apps/my-app/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import {defineConfig} from '@rsbuild/core';
|
||||||
|
export default defineConfig({
|
||||||
|
tools: {
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
experimental: {
|
||||||
|
plugins: [
|
||||||
|
['@swc/plugin-emotion', {}],
|
||||||
|
['@swc/plugin-styled-jsx', {}]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
217
packages/rsbuild/src/utils/ast-utils.ts
Normal file
217
packages/rsbuild/src/utils/ast-utils.ts
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import { type Tree } from '@nx/devkit';
|
||||||
|
import { indentBy } from './indent-by';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
|
||||||
|
const DEFINE_CONFIG_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) > ObjectLiteralExpression';
|
||||||
|
|
||||||
|
export function addHtmlTemplatePath(
|
||||||
|
tree: Tree,
|
||||||
|
configFilePath: string,
|
||||||
|
templatePath: string
|
||||||
|
) {
|
||||||
|
const HTML_CONFIG_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) ObjectLiteralExpression PropertyAssignment:has(Identifier[name=html]) > ObjectLiteralExpression';
|
||||||
|
const TEMPLATE_STRING_SELECTOR =
|
||||||
|
'PropertyAssignment:has(Identifier[name=template]) > StringLiteral';
|
||||||
|
|
||||||
|
let configContents = tree.read(configFilePath, 'utf-8');
|
||||||
|
const ast = tsquery.ast(configContents);
|
||||||
|
|
||||||
|
const htmlConfigNodes = tsquery(ast, HTML_CONFIG_SELECTOR);
|
||||||
|
if (htmlConfigNodes.length === 0) {
|
||||||
|
const defineConfigNodes = tsquery(ast, DEFINE_CONFIG_SELECTOR);
|
||||||
|
if (defineConfigNodes.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find 'defineConfig' in the config file at ${configFilePath}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const defineConfigNode = defineConfigNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
defineConfigNode.getStart() + 1
|
||||||
|
)}\n${indentBy(1)(
|
||||||
|
`html: {\n${indentBy(1)(`template: '${templatePath}'`)}\n},`
|
||||||
|
)}\t${configContents.slice(defineConfigNode.getStart() + 1)}`;
|
||||||
|
} else {
|
||||||
|
const htmlConfigNode = htmlConfigNodes[0];
|
||||||
|
const templateStringNodes = tsquery(
|
||||||
|
htmlConfigNode,
|
||||||
|
TEMPLATE_STRING_SELECTOR
|
||||||
|
);
|
||||||
|
if (templateStringNodes.length === 0) {
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
htmlConfigNode.getStart() + 1
|
||||||
|
)}\n${indentBy(2)(
|
||||||
|
`template: '${templatePath}',`
|
||||||
|
)}\n\t\t${configContents.slice(htmlConfigNode.getStart() + 1)}`;
|
||||||
|
} else {
|
||||||
|
const templateStringNode = templateStringNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
templateStringNode.getStart()
|
||||||
|
)}'${templatePath}',${configContents.slice(templateStringNode.getEnd())}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.write(configFilePath, configContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addCopyAssets(
|
||||||
|
tree: Tree,
|
||||||
|
configFilePath: string,
|
||||||
|
from: string
|
||||||
|
) {
|
||||||
|
const OUTPUT_CONFIG_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) ObjectLiteralExpression PropertyAssignment:has(Identifier[name=output]) > ObjectLiteralExpression';
|
||||||
|
const COPY_ARRAY_SELECTOR =
|
||||||
|
'PropertyAssignment:has(Identifier[name=copy]) > ArrayLiteralExpression';
|
||||||
|
|
||||||
|
const copyAssetArrayElement = `{ from: '${from}' }`;
|
||||||
|
let configContents = tree.read(configFilePath, 'utf-8');
|
||||||
|
const ast = tsquery.ast(configContents);
|
||||||
|
|
||||||
|
const outputConfigNodes = tsquery(ast, OUTPUT_CONFIG_SELECTOR);
|
||||||
|
if (outputConfigNodes.length === 0) {
|
||||||
|
const defineConfigNodes = tsquery(ast, DEFINE_CONFIG_SELECTOR);
|
||||||
|
if (defineConfigNodes.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find 'defineConfig' in the config file at ${configFilePath}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const defineConfigNode = defineConfigNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
defineConfigNode.getStart() + 1
|
||||||
|
)}\n${indentBy(1)(
|
||||||
|
`output: {\n${indentBy(2)(`copy: [${copyAssetArrayElement}]`)},\n}`
|
||||||
|
)},${configContents.slice(defineConfigNode.getStart() + 1)}`;
|
||||||
|
} else {
|
||||||
|
const outputConfigNode = outputConfigNodes[0];
|
||||||
|
const copyAssetsArrayNodes = tsquery(outputConfigNode, COPY_ARRAY_SELECTOR);
|
||||||
|
if (copyAssetsArrayNodes.length === 0) {
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
outputConfigNode.getStart() + 1
|
||||||
|
)}\n${indentBy(2)(
|
||||||
|
`copy: [${copyAssetArrayElement}],`
|
||||||
|
)}\n\t${configContents.slice(outputConfigNode.getStart() + 1)}`;
|
||||||
|
} else {
|
||||||
|
const copyAssetsArrayNode = copyAssetsArrayNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
copyAssetsArrayNode.getStart() + 1
|
||||||
|
)}\n${indentBy(2)(copyAssetArrayElement)},\n\t\t${configContents.slice(
|
||||||
|
copyAssetsArrayNode.getStart() + 1
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.write(configFilePath, configContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addExperimentalSwcPlugin(
|
||||||
|
tree: Tree,
|
||||||
|
configFilePath: string,
|
||||||
|
pluginName: string
|
||||||
|
) {
|
||||||
|
const SWC_JSC_EXPERIMENTAL_PLUGIN_ARRAY_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) ObjectLiteralExpression PropertyAssignment:has(Identifier[name=tools]) PropertyAssignment:has(Identifier[name=swc]) PropertyAssignment:has(Identifier[name=jsc]) PropertyAssignment:has(Identifier[name=experimental]) PropertyAssignment:has(Identifier[name=plugins]) > ArrayLiteralExpression';
|
||||||
|
const SWC_JSC_EXPERIMENTAL_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) ObjectLiteralExpression PropertyAssignment:has(Identifier[name=tools]) PropertyAssignment:has(Identifier[name=swc]) PropertyAssignment:has(Identifier[name=jsc]) PropertyAssignment:has(Identifier[name=experimental]) > ObjectLiteralExpression';
|
||||||
|
const SWC_JSC_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) ObjectLiteralExpression PropertyAssignment:has(Identifier[name=tools]) PropertyAssignment:has(Identifier[name=swc]) PropertyAssignment:has(Identifier[name=jsc]) > ObjectLiteralExpression';
|
||||||
|
const SWC_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) ObjectLiteralExpression PropertyAssignment:has(Identifier[name=tools]) PropertyAssignment:has(Identifier[name=swc]) > ObjectLiteralExpression';
|
||||||
|
const TOOLS_SELECTOR =
|
||||||
|
'CallExpression:has(Identifier[name=defineConfig]) ObjectLiteralExpression PropertyAssignment:has(Identifier[name=tools]) > ObjectLiteralExpression';
|
||||||
|
|
||||||
|
let configContents = tree.read(configFilePath, 'utf-8');
|
||||||
|
const ast = tsquery.ast(configContents);
|
||||||
|
|
||||||
|
const pluginToAdd = indentBy(1)(`['${pluginName}', {}],`);
|
||||||
|
const pluginsArrayToAdd = indentBy(1)(`plugins: [\n${pluginToAdd}\n],`);
|
||||||
|
const experimentalObjectToAdd = indentBy(1)(
|
||||||
|
`experimental: {\n${pluginsArrayToAdd} \n},`
|
||||||
|
);
|
||||||
|
const jscObjectToAdd = indentBy(1)(`jsc: {\n${experimentalObjectToAdd}\n},`);
|
||||||
|
const swcObjectToAdd = indentBy(1)(`swc: {\n${jscObjectToAdd}\n},`);
|
||||||
|
const toolsObjectToAdd = indentBy(1)(`tools: {\n${swcObjectToAdd}\n},`);
|
||||||
|
|
||||||
|
const toolsNodes = tsquery(ast, TOOLS_SELECTOR);
|
||||||
|
if (toolsNodes.length === 0) {
|
||||||
|
const defineConfigNodes = tsquery(ast, DEFINE_CONFIG_SELECTOR);
|
||||||
|
if (defineConfigNodes.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find 'defineConfig' in the config file at ${configFilePath}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const defineConfigNode = defineConfigNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
defineConfigNode.getStart() + 1
|
||||||
|
)}\n${toolsObjectToAdd}${configContents.slice(
|
||||||
|
defineConfigNode.getStart() + 1
|
||||||
|
)}`;
|
||||||
|
} else {
|
||||||
|
const swcNodes = tsquery(ast, SWC_SELECTOR);
|
||||||
|
if (swcNodes.length === 0) {
|
||||||
|
const toolsNode = toolsNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
toolsNode.getStart() + 1
|
||||||
|
)}\n${indentBy(1)(swcObjectToAdd)}\n\t${configContents.slice(
|
||||||
|
toolsNode.getStart() + 1
|
||||||
|
)}`;
|
||||||
|
} else {
|
||||||
|
const jscNodes = tsquery(ast, SWC_JSC_SELECTOR);
|
||||||
|
if (jscNodes.length === 0) {
|
||||||
|
const swcNode = swcNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
swcNode.getStart() + 1
|
||||||
|
)}\n${indentBy(2)(jscObjectToAdd)}\n\t\t${configContents.slice(
|
||||||
|
swcNode.getStart() + 1
|
||||||
|
)}`;
|
||||||
|
} else {
|
||||||
|
const experimentalNodes = tsquery(ast, SWC_JSC_EXPERIMENTAL_SELECTOR);
|
||||||
|
if (experimentalNodes.length === 0) {
|
||||||
|
const jscNode = jscNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
jscNode.getStart() + 1
|
||||||
|
)}\n${indentBy(3)(
|
||||||
|
experimentalObjectToAdd
|
||||||
|
)}\n\t\t\t${configContents.slice(jscNode.getStart() + 1)}`;
|
||||||
|
} else {
|
||||||
|
const pluginsArrayNodes = tsquery(
|
||||||
|
ast,
|
||||||
|
SWC_JSC_EXPERIMENTAL_PLUGIN_ARRAY_SELECTOR
|
||||||
|
);
|
||||||
|
if (pluginsArrayNodes.length === 0) {
|
||||||
|
const experimentalNode = experimentalNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
experimentalNode.getStart() + 1
|
||||||
|
)}\n${indentBy(4)(
|
||||||
|
pluginsArrayToAdd
|
||||||
|
)}\n\t\t\t\t${configContents.slice(
|
||||||
|
experimentalNode.getStart() + 1
|
||||||
|
)}`;
|
||||||
|
} else {
|
||||||
|
const pluginsArrayNode = pluginsArrayNodes[0];
|
||||||
|
configContents = `${configContents.slice(
|
||||||
|
0,
|
||||||
|
pluginsArrayNode.getStart() + 1
|
||||||
|
)}\n${indentBy(4)(pluginToAdd)}\n\t\t\t\t\t${configContents.slice(
|
||||||
|
pluginsArrayNode.getStart() + 1
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.write(configFilePath, configContents);
|
||||||
|
}
|
||||||
39
packages/rsbuild/src/utils/e2e-web-server-info-utils.ts
Normal file
39
packages/rsbuild/src/utils/e2e-web-server-info-utils.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { type Tree, readNxJson } from '@nx/devkit';
|
||||||
|
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
|
||||||
|
|
||||||
|
export async function getRsbuildE2EWebServerInfo(
|
||||||
|
tree: Tree,
|
||||||
|
projectName: string,
|
||||||
|
configFilePath: string,
|
||||||
|
isPluginBeingAdded: boolean,
|
||||||
|
e2ePortOverride?: number
|
||||||
|
) {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
let e2ePort = e2ePortOverride ?? 4200;
|
||||||
|
|
||||||
|
if (
|
||||||
|
nxJson.targetDefaults?.['dev'] &&
|
||||||
|
nxJson.targetDefaults?.['dev'].options?.port
|
||||||
|
) {
|
||||||
|
e2ePort = nxJson.targetDefaults?.['dev'].options?.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getE2EWebServerInfo(
|
||||||
|
tree,
|
||||||
|
projectName,
|
||||||
|
{
|
||||||
|
plugin: '@nx/rsbuild/plugin',
|
||||||
|
serveTargetName: 'devTargetName',
|
||||||
|
serveStaticTargetName: 'previewTargetName',
|
||||||
|
configFilePath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultServeTargetName: 'dev',
|
||||||
|
defaultServeStaticTargetName: 'preview',
|
||||||
|
defaultE2EWebServerAddress: `http://localhost:${e2ePort}`,
|
||||||
|
defaultE2ECiBaseUrl: 'http://localhost:4200',
|
||||||
|
defaultE2EPort: e2ePort,
|
||||||
|
},
|
||||||
|
isPluginBeingAdded
|
||||||
|
);
|
||||||
|
}
|
||||||
9
packages/rsbuild/src/utils/indent-by.ts
Normal file
9
packages/rsbuild/src/utils/indent-by.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function indentBy(tabNumber: number) {
|
||||||
|
return (str: string) => {
|
||||||
|
const indentation = '\t'.repeat(tabNumber);
|
||||||
|
return str
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => `${indentation}${line}`)
|
||||||
|
.join('\n');
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,2 +1,17 @@
|
|||||||
export const nxVersion = require('../../package.json').version;
|
export const nxVersion = require('../../package.json').version;
|
||||||
export const rsbuildVersion = '1.1.8';
|
export const rsbuildVersion = '1.1.10';
|
||||||
|
export const rsbuildPluginReactVersion = '1.1.0';
|
||||||
|
export const rsbuildPluginVueVersion = '1.0.5';
|
||||||
|
export const rsbuildPluginSassVersion = '1.1.2';
|
||||||
|
export const rsbuildPluginLessVersion = '1.1.0';
|
||||||
|
export const rsbuildPluginStyledComponentsVersion = '1.1.0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These versions need to line up with the version of the swc_core crate Rspack uses for the version of Rsbuild above
|
||||||
|
* Checking the `cargo.toml` at https://github.com/web-infra-dev/rspack/blob/main/Cargo.toml for the correct Rspack version
|
||||||
|
* is the best way to ensure that these versions are correct.
|
||||||
|
*
|
||||||
|
* The release notes for the packages below are very helpful in understanding what version of swc_core crate they require.
|
||||||
|
*/
|
||||||
|
export const rsbuildSwcPluginEmotionVersion = '^7.0.3';
|
||||||
|
export const rsbuildSwcPluginStyledJsxVersion = '^5.0.2';
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
"@nx/cypress",
|
"@nx/cypress",
|
||||||
"@nx/playwright",
|
"@nx/playwright",
|
||||||
"@nx/storybook",
|
"@nx/storybook",
|
||||||
|
"@nx/rsbuild",
|
||||||
"eslint"
|
"eslint"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -143,6 +143,66 @@ export default defineConfig({
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly for rsbuild 1`] = `
|
||||||
|
"import vue from '@vitejs/plugin-vue';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: __dirname,
|
||||||
|
cacheDir: '../node_modules/.vite/test',
|
||||||
|
plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), vue()],
|
||||||
|
// Uncomment this if you are using workers.
|
||||||
|
// worker: {
|
||||||
|
// plugins: [ nxViteTsPaths() ],
|
||||||
|
// },
|
||||||
|
test: {
|
||||||
|
watch: false,
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
reporters: ['default'],
|
||||||
|
coverage: { reportsDirectory: '../coverage/test', provider: 'v8' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly for rsbuild 3`] = `
|
||||||
|
[
|
||||||
|
".eslintignore",
|
||||||
|
".eslintrc.json",
|
||||||
|
".gitignore",
|
||||||
|
".prettierignore",
|
||||||
|
".prettierrc",
|
||||||
|
".vscode/extensions.json",
|
||||||
|
"nx.json",
|
||||||
|
"package.json",
|
||||||
|
"test-e2e/.eslintrc.json",
|
||||||
|
"test-e2e/playwright.config.ts",
|
||||||
|
"test-e2e/project.json",
|
||||||
|
"test-e2e/src/example.spec.ts",
|
||||||
|
"test-e2e/tsconfig.json",
|
||||||
|
"test/.eslintrc.json",
|
||||||
|
"test/index.html",
|
||||||
|
"test/project.json",
|
||||||
|
"test/rsbuild.config.ts",
|
||||||
|
"test/src/app/App.spec.ts",
|
||||||
|
"test/src/app/App.vue",
|
||||||
|
"test/src/app/NxWelcome.vue",
|
||||||
|
"test/src/main.ts",
|
||||||
|
"test/src/styles.css",
|
||||||
|
"test/src/vue-shims.d.ts",
|
||||||
|
"test/tsconfig.app.json",
|
||||||
|
"test/tsconfig.json",
|
||||||
|
"test/tsconfig.spec.json",
|
||||||
|
"test/vitest.config.ts",
|
||||||
|
"tsconfig.base.json",
|
||||||
|
"vitest.workspace.ts",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`application generator should set up project correctly with given options 1`] = `
|
exports[`application generator should set up project correctly with given options 1`] = `
|
||||||
"{
|
"{
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|||||||
@ -69,6 +69,67 @@ describe('application generator', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set up project correctly for rsbuild', async () => {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
...options,
|
||||||
|
bundler: 'rsbuild',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
e2eTestRunner: 'playwright',
|
||||||
|
addPlugin: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('test/vitest.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/rsbuild.config.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { pluginVue } from '@rsbuild/plugin-vue';
|
||||||
|
import { defineConfig } from '@rsbuild/core';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
html: {
|
||||||
|
template: './index.html',
|
||||||
|
},
|
||||||
|
plugins: [pluginVue()],
|
||||||
|
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/main.ts',
|
||||||
|
},
|
||||||
|
tsconfigPath: './tsconfig.app.json',
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 4200,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
target: 'web',
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(listFiles(tree)).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
readNxJson(tree).plugins.find(
|
||||||
|
(p) => typeof p !== 'string' && p.plugin === '@nx/rsbuild/plugin'
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"buildTargetName": "build",
|
||||||
|
"devTargetName": "dev",
|
||||||
|
"inspectTargetName": "inspect",
|
||||||
|
"previewTargetName": "preview",
|
||||||
|
"typecheckTargetName": "typecheck",
|
||||||
|
},
|
||||||
|
"plugin": "@nx/rsbuild/plugin",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should set up project correctly for cypress', async () => {
|
it('should set up project correctly for cypress', async () => {
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
nxJson.plugins ??= [];
|
nxJson.plugins ??= [];
|
||||||
|
|||||||
@ -17,7 +17,8 @@ import { vueInitGenerator } from '../init/init';
|
|||||||
import { addLinting } from '../../utils/add-linting';
|
import { addLinting } from '../../utils/add-linting';
|
||||||
import { addE2e } from './lib/add-e2e';
|
import { addE2e } from './lib/add-e2e';
|
||||||
import { createApplicationFiles } from './lib/create-application-files';
|
import { createApplicationFiles } from './lib/create-application-files';
|
||||||
import { addVite } from './lib/add-vite';
|
import { addVite, addVitest } from './lib/add-vite';
|
||||||
|
import { addRsbuild } from './lib/add-rsbuild';
|
||||||
import { extractTsConfigBase } from '../../utils/create-ts-config';
|
import { extractTsConfigBase } from '../../utils/create-ts-config';
|
||||||
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||||
@ -107,7 +108,16 @@ export async function applicationGeneratorInternal(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
tasks.push(await addVite(tree, options));
|
if (options.bundler === 'rsbuild') {
|
||||||
|
tasks.push(...(await addRsbuild(tree, options)));
|
||||||
|
tasks.push(...(await addVitest(tree, options)));
|
||||||
|
tree.rename(
|
||||||
|
joinPathFragments(options.appProjectRoot, 'vite.config.ts'),
|
||||||
|
joinPathFragments(options.appProjectRoot, 'vitest.config.ts')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tasks.push(await addVite(tree, options));
|
||||||
|
}
|
||||||
|
|
||||||
tasks.push(await addE2e(tree, options));
|
tasks.push(await addE2e(tree, options));
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { GeneratorCallback, Tree } from '@nx/devkit';
|
|||||||
import {
|
import {
|
||||||
addProjectConfiguration,
|
addProjectConfiguration,
|
||||||
ensurePackage,
|
ensurePackage,
|
||||||
getPackageManagerCommand,
|
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
readNxJson,
|
readNxJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
@ -12,31 +11,56 @@ import { nxVersion } from '../../../utils/versions';
|
|||||||
import { NormalizedSchema } from '../schema';
|
import { NormalizedSchema } from '../schema';
|
||||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||||
|
import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
|
||||||
|
|
||||||
export async function addE2e(
|
export async function addE2e(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: NormalizedSchema
|
options: NormalizedSchema
|
||||||
): Promise<GeneratorCallback> {
|
): Promise<GeneratorCallback> {
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
const hasPlugin = nxJson.plugins?.find((p) =>
|
const hasPlugin =
|
||||||
typeof p === 'string'
|
options.bundler === 'rsbuild'
|
||||||
? p === '@nx/vite/plugin'
|
? nxJson.plugins?.find((p) =>
|
||||||
: p.plugin === '@nx/vite/plugin'
|
typeof p === 'string'
|
||||||
);
|
? p === '@nx/rsbuild/plugin'
|
||||||
const { getViteE2EWebServerInfo } = ensurePackage<typeof import('@nx/vite')>(
|
: p.plugin === '@nx/rsbuild/plugin'
|
||||||
'@nx/vite',
|
)
|
||||||
nxVersion
|
: nxJson.plugins?.find((p) =>
|
||||||
);
|
typeof p === 'string'
|
||||||
const e2eWebServerInfo = await getViteE2EWebServerInfo(
|
? p === '@nx/vite/plugin'
|
||||||
tree,
|
: p.plugin === '@nx/vite/plugin'
|
||||||
options.projectName,
|
);
|
||||||
joinPathFragments(
|
let e2eWebServerInfo: E2EWebServerDetails;
|
||||||
options.appProjectRoot,
|
if (options.bundler === 'vite') {
|
||||||
`vite.config.${options.js ? 'js' : 'ts'}`
|
const { getViteE2EWebServerInfo } = ensurePackage<
|
||||||
),
|
typeof import('@nx/vite')
|
||||||
options.addPlugin,
|
>('@nx/vite', nxVersion);
|
||||||
options.devServerPort ?? 4200
|
e2eWebServerInfo = await getViteE2EWebServerInfo(
|
||||||
);
|
tree,
|
||||||
|
options.projectName,
|
||||||
|
joinPathFragments(
|
||||||
|
options.appProjectRoot,
|
||||||
|
`vite.config.${options.js ? 'js' : 'ts'}`
|
||||||
|
),
|
||||||
|
options.addPlugin,
|
||||||
|
options.devServerPort ?? 4200
|
||||||
|
);
|
||||||
|
} else if (options.bundler === 'rsbuild') {
|
||||||
|
ensurePackage('@nx/rsbuild', nxVersion);
|
||||||
|
const { getRsbuildE2EWebServerInfo } = await import(
|
||||||
|
'@nx/rsbuild/config-utils'
|
||||||
|
);
|
||||||
|
e2eWebServerInfo = await getRsbuildE2EWebServerInfo(
|
||||||
|
tree,
|
||||||
|
options.projectName,
|
||||||
|
joinPathFragments(
|
||||||
|
options.appProjectRoot,
|
||||||
|
`rsbuild.config.${options.js ? 'js' : 'ts'}`
|
||||||
|
),
|
||||||
|
options.addPlugin,
|
||||||
|
options.devServerPort ?? 4200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
switch (options.e2eTestRunner) {
|
switch (options.e2eTestRunner) {
|
||||||
case 'cypress': {
|
case 'cypress': {
|
||||||
|
|||||||
67
packages/vue/src/generators/application/lib/add-rsbuild.ts
Normal file
67
packages/vue/src/generators/application/lib/add-rsbuild.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
type Tree,
|
||||||
|
type GeneratorCallback,
|
||||||
|
joinPathFragments,
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
ensurePackage,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { NormalizedSchema } from '../schema';
|
||||||
|
import { nxVersion } from '../../../utils/versions';
|
||||||
|
|
||||||
|
export async function addRsbuild(tree: Tree, options: NormalizedSchema) {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
ensurePackage('@nx/rsbuild', nxVersion);
|
||||||
|
const { initGenerator, configurationGenerator } = await import(
|
||||||
|
'@nx/rsbuild/generators'
|
||||||
|
);
|
||||||
|
const initTask = await initGenerator(tree, {
|
||||||
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
addPlugin: true,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
tasks.push(initTask);
|
||||||
|
|
||||||
|
const rsbuildTask = await configurationGenerator(tree, {
|
||||||
|
project: options.projectName,
|
||||||
|
entry: `./src/main.ts`,
|
||||||
|
tsConfig: './tsconfig.app.json',
|
||||||
|
target: 'web',
|
||||||
|
devServerPort: options.devServerPort ?? 4200,
|
||||||
|
});
|
||||||
|
tasks.push(rsbuildTask);
|
||||||
|
|
||||||
|
const { addBuildPlugin, addHtmlTemplatePath, versions } = await import(
|
||||||
|
'@nx/rsbuild/config-utils'
|
||||||
|
);
|
||||||
|
|
||||||
|
const deps = { '@rsbuild/plugin-vue': versions.rsbuildPluginVueVersion };
|
||||||
|
|
||||||
|
const pathToConfigFile = joinPathFragments(
|
||||||
|
options.appProjectRoot,
|
||||||
|
'rsbuild.config.ts'
|
||||||
|
);
|
||||||
|
addBuildPlugin(tree, pathToConfigFile, '@rsbuild/plugin-vue', 'pluginVue');
|
||||||
|
|
||||||
|
if (options.style === 'scss') {
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
pathToConfigFile,
|
||||||
|
'@rsbuild/plugin-sass',
|
||||||
|
'pluginSass'
|
||||||
|
);
|
||||||
|
deps['@rsbuild/plugin-sass'] = versions.rsbuildPluginSassVersion;
|
||||||
|
} else if (options.style === 'less') {
|
||||||
|
addBuildPlugin(
|
||||||
|
tree,
|
||||||
|
pathToConfigFile,
|
||||||
|
'@rsbuild/plugin-less',
|
||||||
|
'pluginLess'
|
||||||
|
);
|
||||||
|
deps['@rsbuild/plugin-less'] = versions.rsbuildPluginLessVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
addHtmlTemplatePath(tree, pathToConfigFile, './index.html');
|
||||||
|
tasks.push(addDependenciesToPackageJson(tree, {}, deps));
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
@ -4,7 +4,11 @@ import {
|
|||||||
Tree,
|
Tree,
|
||||||
updateProjectConfiguration,
|
updateProjectConfiguration,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createOrEditViteConfig, viteConfigurationGenerator } from '@nx/vite';
|
import {
|
||||||
|
createOrEditViteConfig,
|
||||||
|
viteConfigurationGenerator,
|
||||||
|
vitestGenerator,
|
||||||
|
} from '@nx/vite';
|
||||||
|
|
||||||
import { NormalizedSchema } from '../schema';
|
import { NormalizedSchema } from '../schema';
|
||||||
|
|
||||||
@ -47,3 +51,33 @@ export async function addVite(
|
|||||||
|
|
||||||
return viteTask;
|
return viteTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addVitest(tree: Tree, options: NormalizedSchema) {
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
const vitestTask = await vitestGenerator(tree, {
|
||||||
|
uiFramework: 'none',
|
||||||
|
project: options.projectName,
|
||||||
|
coverageProvider: 'v8',
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
skipFormat: true,
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
addPlugin: options.addPlugin,
|
||||||
|
runtimeTsconfigFileName: 'tsconfig.app.json',
|
||||||
|
});
|
||||||
|
tasks.push(vitestTask);
|
||||||
|
|
||||||
|
createOrEditViteConfig(
|
||||||
|
tree,
|
||||||
|
{
|
||||||
|
project: options.projectName,
|
||||||
|
includeLib: false,
|
||||||
|
includeVitest: true,
|
||||||
|
inSourceTests: options.inSourceTests,
|
||||||
|
imports: [`import vue from '@vitejs/plugin-vue'`],
|
||||||
|
plugins: ['vue()'],
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export async function normalizeOptions(
|
|||||||
normalized.unitTestRunner ??= 'vitest';
|
normalized.unitTestRunner ??= 'vitest';
|
||||||
normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'playwright';
|
normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'playwright';
|
||||||
normalized.isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
|
normalized.isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
|
||||||
|
normalized.bundler = normalized.bundler ?? 'vite';
|
||||||
|
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export interface Schema {
|
|||||||
directory: string;
|
directory: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
style: 'none' | 'css' | 'scss' | 'less';
|
style: 'none' | 'css' | 'scss' | 'less';
|
||||||
|
bundler?: 'vite' | 'rsbuild';
|
||||||
skipFormat?: boolean;
|
skipFormat?: boolean;
|
||||||
tags?: string;
|
tags?: string;
|
||||||
unitTestRunner?: 'vitest' | 'none';
|
unitTestRunner?: 'vitest' | 'none';
|
||||||
|
|||||||
@ -60,6 +60,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": {
|
"routing": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Generate application with routes.",
|
"description": "Generate application with routes.",
|
||||||
|
|||||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@ -337,6 +337,9 @@ importers:
|
|||||||
'@nx/react':
|
'@nx/react':
|
||||||
specifier: 20.3.0-beta.0
|
specifier: 20.3.0-beta.0
|
||||||
version: 20.3.0-beta.0(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(next@14.2.16(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.3.0-beta.0(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))
|
version: 20.3.0-beta.0(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/helpers@0.5.11)(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(esbuild@0.19.5)(eslint@8.57.0)(next@14.2.16(@babel/core@7.25.2)(@playwright/test@1.47.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.3.0-beta.0(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))
|
||||||
|
'@nx/rsbuild':
|
||||||
|
specifier: 20.3.0-beta.0
|
||||||
|
version: 20.3.0-beta.0(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.3.0-beta.0(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.6.3)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))
|
||||||
'@nx/rspack':
|
'@nx/rspack':
|
||||||
specifier: 20.3.0-beta.0
|
specifier: 20.3.0-beta.0
|
||||||
version: 20.3.0-beta.0(oxln5c2nr22bidmz7io6uliuga)
|
version: 20.3.0-beta.0(oxln5c2nr22bidmz7io6uliuga)
|
||||||
@ -5262,6 +5265,9 @@ packages:
|
|||||||
'@nx/react@20.3.0-beta.0':
|
'@nx/react@20.3.0-beta.0':
|
||||||
resolution: {integrity: sha512-oUaF2NTgP8bKkVz61frF0XhD0Y2W/hMU3Vzdp8N9ew4h28DFTuLdNFITvgHG9vJWsw7+jrQ8DucntuU85QcR9A==}
|
resolution: {integrity: sha512-oUaF2NTgP8bKkVz61frF0XhD0Y2W/hMU3Vzdp8N9ew4h28DFTuLdNFITvgHG9vJWsw7+jrQ8DucntuU85QcR9A==}
|
||||||
|
|
||||||
|
'@nx/rsbuild@20.3.0-beta.0':
|
||||||
|
resolution: {integrity: sha512-xmNCtkr5P8YzxwoK18XJ8krOZNtTHXei5vh6tPz/Y02aOcrPEfTwrS0z2gLP3VNd+9i2IZCS8WEyRGW7f3o24g==}
|
||||||
|
|
||||||
'@nx/rspack@20.3.0-beta.0':
|
'@nx/rspack@20.3.0-beta.0':
|
||||||
resolution: {integrity: sha512-P6XOMoR8Pv/CtK/w/SRy/wVAJyZcAL6e5w0yLae50lRsNsd+FyNiqybovmpBGXpnre2Ovf6EIzK+jVU97bTgjw==}
|
resolution: {integrity: sha512-P6XOMoR8Pv/CtK/w/SRy/wVAJyZcAL6e5w0yLae50lRsNsd+FyNiqybovmpBGXpnre2Ovf6EIzK+jVU97bTgjw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -22641,6 +22647,25 @@ snapshots:
|
|||||||
- webpack
|
- webpack
|
||||||
- webpack-cli
|
- webpack-cli
|
||||||
|
|
||||||
|
'@nx/rsbuild@20.3.0-beta.0(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.3.0-beta.0(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.6.3)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))':
|
||||||
|
dependencies:
|
||||||
|
'@nx/devkit': 20.3.0-beta.0(nx@20.3.0-beta.0(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))
|
||||||
|
'@nx/js': 20.3.0-beta.0(@babel/traverse@7.25.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.3.0-beta.0(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.6.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.6.3)(verdaccio@5.32.2(encoding@0.1.13)(typanion@3.14.0))
|
||||||
|
'@rsbuild/core': 1.1.8
|
||||||
|
minimatch: 9.0.3
|
||||||
|
tslib: 2.8.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@babel/traverse'
|
||||||
|
- '@swc-node/register'
|
||||||
|
- '@swc/core'
|
||||||
|
- '@swc/wasm'
|
||||||
|
- '@types/node'
|
||||||
|
- debug
|
||||||
|
- nx
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
- verdaccio
|
||||||
|
|
||||||
'@nx/rspack@20.3.0-beta.0(oxln5c2nr22bidmz7io6uliuga)':
|
'@nx/rspack@20.3.0-beta.0(oxln5c2nr22bidmz7io6uliuga)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@module-federation/enhanced': 0.7.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))
|
'@module-federation/enhanced': 0.7.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.19.5)(webpack-cli@5.1.4))
|
||||||
|
|||||||
@ -132,8 +132,8 @@
|
|||||||
"@nx/remix/*": ["packages/remix/*"],
|
"@nx/remix/*": ["packages/remix/*"],
|
||||||
"@nx/rollup": ["packages/rollup"],
|
"@nx/rollup": ["packages/rollup"],
|
||||||
"@nx/rollup/*": ["packages/rollup/*"],
|
"@nx/rollup/*": ["packages/rollup/*"],
|
||||||
"@nx/rsbuild": ["packages/rsbuild/src"],
|
"@nx/rsbuild": ["packages/rsbuild/"],
|
||||||
"@nx/rsbuild/*": ["packages/rsbuild/src/*"],
|
"@nx/rsbuild/*": ["packages/rsbuild/*"],
|
||||||
"@nx/rspack": ["packages/rspack/src"],
|
"@nx/rspack": ["packages/rspack/src"],
|
||||||
"@nx/rspack/*": ["packages/rspack/src/*"],
|
"@nx/rspack/*": ["packages/rspack/src/*"],
|
||||||
"@nx/storybook": ["packages/storybook"],
|
"@nx/storybook": ["packages/storybook"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user