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": {
|
||||
"description": "The bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack", "rspack"],
|
||||
"enum": ["vite", "webpack", "rspack", "rsbuild"],
|
||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||
"default": "vite",
|
||||
"x-priority": "important"
|
||||
|
||||
@ -26,6 +26,11 @@
|
||||
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"devServerPort": {
|
||||
"type": "number",
|
||||
"description": "The port for the dev server to listen on.",
|
||||
"default": 4200
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"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": {
|
||||
"type": "boolean",
|
||||
"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', () => {
|
||||
let proj: string;
|
||||
@ -33,6 +40,29 @@ describe('Vue Plugin', () => {
|
||||
// }
|
||||
}, 200_000);
|
||||
|
||||
it('should serve application in dev mode with rsbuild', async () => {
|
||||
const app = uniq('app');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/vue:app ${app} --bundler=rsbuild --unitTestRunner=vitest --e2eTestRunner=playwright`
|
||||
);
|
||||
let result = runCLI(`test ${app}`);
|
||||
expect(result).toContain(`Successfully ran target test for project ${app}`);
|
||||
|
||||
result = runCLI(`build ${app}`);
|
||||
expect(result).toContain(
|
||||
`Successfully ran target build for project ${app}`
|
||||
);
|
||||
|
||||
// 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 () => {
|
||||
const lib = uniq('lib');
|
||||
|
||||
|
||||
@ -83,11 +83,12 @@
|
||||
"@nx/powerpack-enterprise-cloud": "1.1.0-beta.9",
|
||||
"@nx/powerpack-license": "1.1.0-beta.9",
|
||||
"@nx/react": "20.3.0-beta.0",
|
||||
"@nx/rsbuild": "20.3.0-beta.0",
|
||||
"@nx/rspack": "20.3.0-beta.0",
|
||||
"@nx/storybook": "20.3.0-beta.0",
|
||||
"@nx/vite": "20.3.0-beta.0",
|
||||
"@nx/web": "20.3.0-beta.0",
|
||||
"@nx/webpack": "20.3.0-beta.0",
|
||||
"@nx/vite": "20.3.0-beta.0",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"@playwright/test": "^1.36.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||
|
||||
@ -58,6 +58,7 @@
|
||||
"@nx/playwright",
|
||||
"@nx/jest",
|
||||
"@nx/rollup",
|
||||
"@nx/rsbuild",
|
||||
"@nx/storybook",
|
||||
"@nx/vite",
|
||||
"@nx/webpack",
|
||||
|
||||
@ -1,5 +1,192 @@
|
||||
// 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`] = `
|
||||
"// Uncomment this line to use CSS modules
|
||||
// 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 { NormalizedSchema, Schema } from './schema';
|
||||
import {
|
||||
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 { updateSpecConfig } from './lib/update-jest-config';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
@ -7,111 +21,43 @@ import { addProject } from './lib/add-project';
|
||||
import { addJest } from './lib/add-jest';
|
||||
import { addRouting } from './lib/add-routing';
|
||||
import { setDefaults } from './lib/set-defaults';
|
||||
import { addStyledModuleDependencies } from '../../rules/add-styled-dependencies';
|
||||
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 { addLinting } from './lib/add-linting';
|
||||
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 {
|
||||
addExtendsToLintConfig,
|
||||
addOverrideToLintConfig,
|
||||
addPredefinedConfigToFlatLintConfig,
|
||||
isEslintConfigSupported,
|
||||
} from '@nx/eslint/src/generators/utils/eslint-file';
|
||||
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||
import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind';
|
||||
import { useFlatConfig } from '@nx/eslint/src/utils/flat-config';
|
||||
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
|
||||
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);
|
||||
}
|
||||
handleStyledJsxForRspack,
|
||||
initRspack,
|
||||
setupRspackConfiguration,
|
||||
} from './lib/bundlers/add-rspack';
|
||||
import {
|
||||
initRsbuild,
|
||||
setupRsbuildConfiguration,
|
||||
} from './lib/bundlers/add-rsbuild';
|
||||
import {
|
||||
setupViteConfiguration,
|
||||
setupVitestConfiguration,
|
||||
} from './lib/bundlers/add-vite';
|
||||
import { Schema } from './schema';
|
||||
|
||||
export async function applicationGenerator(
|
||||
host: Tree,
|
||||
tree: Tree,
|
||||
schema: Schema
|
||||
): Promise<GeneratorCallback> {
|
||||
return await applicationGeneratorInternal(host, {
|
||||
return await applicationGeneratorInternal(tree, {
|
||||
addPlugin: false,
|
||||
...schema,
|
||||
});
|
||||
}
|
||||
|
||||
export async function applicationGeneratorInternal(
|
||||
host: Tree,
|
||||
tree: Tree,
|
||||
schema: Schema
|
||||
): Promise<GeneratorCallback> {
|
||||
const tasks = [];
|
||||
|
||||
const jsInitTask = await jsInitGenerator(host, {
|
||||
const jsInitTask = await jsInitGenerator(tree, {
|
||||
...schema,
|
||||
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
|
||||
skipFormat: true,
|
||||
@ -120,17 +66,17 @@ export async function applicationGeneratorInternal(
|
||||
});
|
||||
tasks.push(jsInitTask);
|
||||
|
||||
const options = await normalizeOptions(host, schema);
|
||||
showPossibleWarnings(host, options);
|
||||
const options = await normalizeOptions(tree, schema);
|
||||
showPossibleWarnings(tree, options);
|
||||
|
||||
const initTask = await reactInitGenerator(host, {
|
||||
const initTask = await reactInitGenerator(tree, {
|
||||
...options,
|
||||
skipFormat: true,
|
||||
});
|
||||
tasks.push(initTask);
|
||||
|
||||
if (!options.addPlugin) {
|
||||
const nxJson = readNxJson(host);
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.targetDefaults ??= {};
|
||||
if (!Object.keys(nxJson.targetDefaults).includes('build')) {
|
||||
nxJson.targetDefaults.build = {
|
||||
@ -140,159 +86,48 @@ export async function applicationGeneratorInternal(
|
||||
} else if (!nxJson.targetDefaults.build.dependsOn) {
|
||||
nxJson.targetDefaults.build.dependsOn = ['^build'];
|
||||
}
|
||||
updateNxJson(host, nxJson);
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
|
||||
if (options.bundler === 'webpack') {
|
||||
const { webpackInitGenerator } = ensurePackage<
|
||||
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' }));
|
||||
}
|
||||
await initWebpack(tree, options, tasks);
|
||||
} else if (options.bundler === 'rspack') {
|
||||
const { rspackInitGenerator } = ensurePackage('@nx/rspack', nxVersion);
|
||||
const rspackInitTask = await rspackInitGenerator(host, {
|
||||
...options,
|
||||
addPlugin: false,
|
||||
skipFormat: true,
|
||||
});
|
||||
tasks.push(rspackInitTask);
|
||||
await initRspack(tree, options, tasks);
|
||||
} else if (options.bundler === 'rsbuild') {
|
||||
await initRsbuild(tree, options, tasks);
|
||||
}
|
||||
|
||||
if (!options.rootProject) {
|
||||
extractTsConfigBase(host);
|
||||
extractTsConfigBase(tree);
|
||||
}
|
||||
|
||||
await createApplicationFiles(host, options);
|
||||
addProject(host, options);
|
||||
await createApplicationFiles(tree, options);
|
||||
addProject(tree, options);
|
||||
|
||||
if (options.style === 'tailwind') {
|
||||
const twTask = await setupTailwindGenerator(host, {
|
||||
const twTask = await setupTailwindGenerator(tree, {
|
||||
project: options.projectName,
|
||||
});
|
||||
tasks.push(twTask);
|
||||
}
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
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 (
|
||||
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
|
||||
);
|
||||
await setupViteConfiguration(tree, options, tasks);
|
||||
} else if (options.bundler === 'rspack') {
|
||||
const { configurationGenerator } = ensurePackage('@nx/rspack', nxVersion);
|
||||
const rspackTask = await configurationGenerator(host, {
|
||||
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);
|
||||
await setupRspackConfiguration(tree, options, tasks);
|
||||
} else if (options.bundler === 'rsbuild') {
|
||||
await setupRsbuildConfiguration(tree, options, tasks);
|
||||
}
|
||||
|
||||
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
||||
const { createOrEditViteConfig, vitestGenerator } = ensurePackage<
|
||||
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
|
||||
);
|
||||
await setupVitestConfiguration(tree, options, tasks);
|
||||
}
|
||||
|
||||
if (
|
||||
(options.bundler === 'vite' || options.unitTestRunner === 'vitest') &&
|
||||
options.inSourceTests
|
||||
) {
|
||||
host.delete(
|
||||
tree.delete(
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
`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);
|
||||
|
||||
const e2eTask = await addE2e(host, options);
|
||||
const e2eTask = await addE2e(tree, options);
|
||||
tasks.push(e2eTask);
|
||||
|
||||
if (options.unitTestRunner === 'jest') {
|
||||
const jestTask = await addJest(host, options);
|
||||
const jestTask = await addJest(tree, options);
|
||||
tasks.push(jestTask);
|
||||
}
|
||||
|
||||
// Handle tsconfig.spec.json for jest or vitest
|
||||
updateSpecConfig(host, options);
|
||||
const stylePreprocessorTask = installCommonDependencies(host, options);
|
||||
updateSpecConfig(tree, options);
|
||||
const stylePreprocessorTask = installCommonDependencies(tree, options);
|
||||
tasks.push(stylePreprocessorTask);
|
||||
const styledTask = addStyledModuleDependencies(host, options);
|
||||
const styledTask = addStyledModuleDependencies(tree, options);
|
||||
tasks.push(styledTask);
|
||||
const routingTask = addRouting(host, options);
|
||||
const routingTask = addRouting(tree, options);
|
||||
tasks.push(routingTask);
|
||||
setDefaults(host, options);
|
||||
setDefaults(tree, options);
|
||||
|
||||
if (options.bundler === 'rspack' && options.style === 'styled-jsx') {
|
||||
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(
|
||||
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;
|
||||
});
|
||||
`
|
||||
);
|
||||
handleStyledJsxForRspack(tasks, tree, options);
|
||||
}
|
||||
|
||||
updateTsconfigFiles(
|
||||
host,
|
||||
tree,
|
||||
options.appProjectRoot,
|
||||
'tsconfig.app.json',
|
||||
{
|
||||
@ -376,7 +175,7 @@ export async function applicationGeneratorInternal(
|
||||
);
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(host);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
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';
|
||||
<%_ } if (!minimal) { _%>
|
||||
import NxWelcome from "./nx-welcome";
|
||||
<%_ } if (bundler === "rspack") { _%>
|
||||
<%_ } if (bundler === "rspack" || bundler === 'rsbuild') { _%>
|
||||
import '../styles.css';
|
||||
<%_ } _%>
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import { nxVersion } from '../../../utils/versions';
|
||||
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
|
||||
import { hasVitePlugin } from '../../../utils/has-vite-plugin';
|
||||
import { hasRspackPlugin } from '../../../utils/has-rspack-plugin';
|
||||
import { hasRsbuildPlugin } from '../../../utils/has-rsbuild-plugin';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
@ -26,6 +27,7 @@ export async function addE2e(
|
||||
const hasNxBuildPlugin =
|
||||
(options.bundler === 'webpack' && hasWebpackPlugin(tree)) ||
|
||||
(options.bundler === 'rspack' && hasRspackPlugin(tree)) ||
|
||||
(options.bundler === 'rsbuild' && hasRsbuildPlugin(tree)) ||
|
||||
(options.bundler === 'vite' && hasVitePlugin(tree));
|
||||
|
||||
let e2eWebServerInfo: E2EWebServerDetails = {
|
||||
@ -68,6 +70,22 @@ export async function addE2e(
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasNxBuildPlugin) {
|
||||
@ -114,7 +132,12 @@ export async function addE2e(
|
||||
project: options.e2eProjectName,
|
||||
directory: 'src',
|
||||
// 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,
|
||||
devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
|
||||
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,
|
||||
}
|
||||
);
|
||||
} else if (options.bundler === 'rsbuild') {
|
||||
generateFiles(
|
||||
host,
|
||||
join(__dirname, '../files/base-rsbuild'),
|
||||
options.appProjectRoot,
|
||||
{
|
||||
...templateVariables,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@ -23,7 +23,7 @@ export interface Schema {
|
||||
devServerPort?: number;
|
||||
skipPackageJson?: boolean;
|
||||
rootProject?: boolean;
|
||||
bundler?: 'webpack' | 'vite' | 'rspack';
|
||||
bundler?: 'webpack' | 'vite' | 'rspack' | 'rsbuild';
|
||||
minimal?: boolean;
|
||||
// Internal options
|
||||
addPlugin?: boolean;
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
"bundler": {
|
||||
"description": "The bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack", "rspack"],
|
||||
"enum": ["vite", "webpack", "rspack", "rsbuild"],
|
||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||
"default": "vite",
|
||||
"x-priority": "important"
|
||||
|
||||
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/js": "file:../js",
|
||||
"@rsbuild/core": "1.1.8",
|
||||
"minimatch": "9.0.3"
|
||||
"minimatch": "9.0.3",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"nx-migrations": {
|
||||
"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'
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 4200
|
||||
},
|
||||
output: {
|
||||
target: 'web',
|
||||
distPath: {
|
||||
@ -94,6 +97,9 @@ describe('Rsbuild configuration generator', () => {
|
||||
index: './src/main.ts'
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 4200
|
||||
},
|
||||
output: {
|
||||
target: 'web',
|
||||
distPath: {
|
||||
@ -127,6 +133,9 @@ describe('Rsbuild configuration generator', () => {
|
||||
index: './src/main.ts'
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 4200
|
||||
},
|
||||
output: {
|
||||
target: 'web',
|
||||
distPath: {
|
||||
@ -157,6 +166,9 @@ describe('Rsbuild configuration generator', () => {
|
||||
},
|
||||
tsconfigPath: './tsconfig.json',
|
||||
},
|
||||
server: {
|
||||
port: 4200
|
||||
},
|
||||
output: {
|
||||
target: 'web',
|
||||
distPath: {
|
||||
|
||||
@ -18,11 +18,14 @@ import { join } from 'path';
|
||||
export async function configurationGenerator(tree: Tree, schema: Schema) {
|
||||
const projectGraph = await createProjectGraphAsync();
|
||||
const projects = readProjectsConfigurationFromProjectGraph(projectGraph);
|
||||
const project = projects.projects[schema.project];
|
||||
let project = projects.projects[schema.project];
|
||||
if (!project) {
|
||||
throw new Error(
|
||||
`Could not find project '${schema.project}'. Please choose a project that exists in the Nx Workspace.`
|
||||
);
|
||||
project = readProjectConfiguration(tree, schema.project);
|
||||
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);
|
||||
|
||||
@ -7,6 +7,9 @@ export default defineConfig({
|
||||
},<% if (tsConfig) { %>
|
||||
tsconfigPath: '<%= tsConfig %>',<% } %>
|
||||
},
|
||||
server: {
|
||||
port: <%= devServerPort %>
|
||||
},
|
||||
output: {
|
||||
target: '<%= target %>',
|
||||
distPath: {
|
||||
|
||||
@ -9,6 +9,7 @@ import { relative } from 'path';
|
||||
export interface NormalizedOptions extends Schema {
|
||||
entry: string;
|
||||
target: 'node' | 'web' | 'web-worker';
|
||||
devServerPort: number;
|
||||
tsConfig: string;
|
||||
projectRoot: string;
|
||||
}
|
||||
@ -30,6 +31,7 @@ export async function normalizeOptions(
|
||||
schema.tsConfig ?? './tsconfig.json',
|
||||
project.root
|
||||
),
|
||||
devServerPort: schema.devServerPort ?? 4200,
|
||||
projectRoot: project.root,
|
||||
skipFormat: schema.skipFormat ?? false,
|
||||
skipValidation: schema.skipValidation ?? false,
|
||||
|
||||
@ -2,6 +2,7 @@ export interface Schema {
|
||||
project: string;
|
||||
entry?: string;
|
||||
tsConfig?: string;
|
||||
devServerPort?: number;
|
||||
target?: 'node' | 'web' | 'web-worker';
|
||||
skipValidation?: 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'.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"devServerPort": {
|
||||
"type": "number",
|
||||
"description": "The port for the dev server to listen on.",
|
||||
"default": 4200
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target platform for the build, same as the Rsbuild output.target config option.",
|
||||
|
||||
@ -124,6 +124,10 @@ describe('@nx/rsbuild/plugin', () => {
|
||||
},
|
||||
"preview-serve": {
|
||||
"command": "rsbuild preview",
|
||||
"dependsOn": [
|
||||
"build-something",
|
||||
"^build-something",
|
||||
],
|
||||
"options": {
|
||||
"args": [
|
||||
"--mode=production",
|
||||
|
||||
@ -193,6 +193,7 @@ async function createRsbuildTargets(
|
||||
|
||||
targets[options.previewTargetName] = {
|
||||
command: `rsbuild preview`,
|
||||
dependsOn: [`${options.buildTargetName}`, `^${options.buildTargetName}`],
|
||||
options: {
|
||||
cwd: projectRoot,
|
||||
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 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/playwright",
|
||||
"@nx/storybook",
|
||||
"@nx/rsbuild",
|
||||
"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`] = `
|
||||
"{
|
||||
"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 () => {
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
|
||||
@ -17,7 +17,8 @@ import { vueInitGenerator } from '../init/init';
|
||||
import { addLinting } from '../../utils/add-linting';
|
||||
import { addE2e } from './lib/add-e2e';
|
||||
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 { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||
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));
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ import type { GeneratorCallback, Tree } from '@nx/devkit';
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
ensurePackage,
|
||||
getPackageManagerCommand,
|
||||
joinPathFragments,
|
||||
readNxJson,
|
||||
} from '@nx/devkit';
|
||||
@ -12,31 +11,56 @@ import { nxVersion } from '../../../utils/versions';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
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(
|
||||
tree: Tree,
|
||||
options: NormalizedSchema
|
||||
): Promise<GeneratorCallback> {
|
||||
const nxJson = readNxJson(tree);
|
||||
const hasPlugin = nxJson.plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/vite/plugin'
|
||||
: p.plugin === '@nx/vite/plugin'
|
||||
);
|
||||
const { getViteE2EWebServerInfo } = ensurePackage<typeof import('@nx/vite')>(
|
||||
'@nx/vite',
|
||||
nxVersion
|
||||
);
|
||||
const e2eWebServerInfo = await getViteE2EWebServerInfo(
|
||||
tree,
|
||||
options.projectName,
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
`vite.config.${options.js ? 'js' : 'ts'}`
|
||||
),
|
||||
options.addPlugin,
|
||||
options.devServerPort ?? 4200
|
||||
);
|
||||
const hasPlugin =
|
||||
options.bundler === 'rsbuild'
|
||||
? nxJson.plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/rsbuild/plugin'
|
||||
: p.plugin === '@nx/rsbuild/plugin'
|
||||
)
|
||||
: nxJson.plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/vite/plugin'
|
||||
: p.plugin === '@nx/vite/plugin'
|
||||
);
|
||||
let e2eWebServerInfo: E2EWebServerDetails;
|
||||
if (options.bundler === 'vite') {
|
||||
const { getViteE2EWebServerInfo } = ensurePackage<
|
||||
typeof import('@nx/vite')
|
||||
>('@nx/vite', nxVersion);
|
||||
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) {
|
||||
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,
|
||||
updateProjectConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import { createOrEditViteConfig, viteConfigurationGenerator } from '@nx/vite';
|
||||
import {
|
||||
createOrEditViteConfig,
|
||||
viteConfigurationGenerator,
|
||||
vitestGenerator,
|
||||
} from '@nx/vite';
|
||||
|
||||
import { NormalizedSchema } from '../schema';
|
||||
|
||||
@ -47,3 +51,33 @@ export async function addVite(
|
||||
|
||||
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.e2eTestRunner = normalized.e2eTestRunner ?? 'playwright';
|
||||
normalized.isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
|
||||
normalized.bundler = normalized.bundler ?? 'vite';
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ export interface Schema {
|
||||
directory: string;
|
||||
name?: string;
|
||||
style: 'none' | 'css' | 'scss' | 'less';
|
||||
bundler?: 'vite' | 'rsbuild';
|
||||
skipFormat?: boolean;
|
||||
tags?: string;
|
||||
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": {
|
||||
"type": "boolean",
|
||||
"description": "Generate application with routes.",
|
||||
|
||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@ -337,6 +337,9 @@ importers:
|
||||
'@nx/react':
|
||||
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))
|
||||
'@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':
|
||||
specifier: 20.3.0-beta.0
|
||||
version: 20.3.0-beta.0(oxln5c2nr22bidmz7io6uliuga)
|
||||
@ -5262,6 +5265,9 @@ packages:
|
||||
'@nx/react@20.3.0-beta.0':
|
||||
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':
|
||||
resolution: {integrity: sha512-P6XOMoR8Pv/CtK/w/SRy/wVAJyZcAL6e5w0yLae50lRsNsd+FyNiqybovmpBGXpnre2Ovf6EIzK+jVU97bTgjw==}
|
||||
peerDependencies:
|
||||
@ -22641,6 +22647,25 @@ snapshots:
|
||||
- webpack
|
||||
- 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)':
|
||||
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))
|
||||
|
||||
@ -132,8 +132,8 @@
|
||||
"@nx/remix/*": ["packages/remix/*"],
|
||||
"@nx/rollup": ["packages/rollup"],
|
||||
"@nx/rollup/*": ["packages/rollup/*"],
|
||||
"@nx/rsbuild": ["packages/rsbuild/src"],
|
||||
"@nx/rsbuild/*": ["packages/rsbuild/src/*"],
|
||||
"@nx/rsbuild": ["packages/rsbuild/"],
|
||||
"@nx/rsbuild/*": ["packages/rsbuild/*"],
|
||||
"@nx/rspack": ["packages/rspack/src"],
|
||||
"@nx/rspack/*": ["packages/rspack/src/*"],
|
||||
"@nx/storybook": ["packages/storybook"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user