feat(rsbuild): add rsbuild configuration generator (#29321)
## Current Behavior Nx currently does not offer a generator to help scaffold configuration for an Rsbuild project ## Expected Behavior Add a `configuration` generator to the `@nx/rsbuild` package to help scaffold a configuration for a basic app
This commit is contained in:
parent
22cec78331
commit
36eaafdcfd
@ -9902,6 +9902,14 @@
|
|||||||
"children": [],
|
"children": [],
|
||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "configuration",
|
||||||
|
"path": "/nx-api/rsbuild/generators/configuration",
|
||||||
|
"name": "configuration",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
|
|||||||
@ -2909,6 +2909,15 @@
|
|||||||
"originalFilePath": "/packages/rsbuild/src/generators/init/schema.json",
|
"originalFilePath": "/packages/rsbuild/src/generators/init/schema.json",
|
||||||
"path": "/nx-api/rsbuild/generators/init",
|
"path": "/nx-api/rsbuild/generators/init",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
|
},
|
||||||
|
"/nx-api/rsbuild/generators/configuration": {
|
||||||
|
"description": "Add an Rsbuild configuration for the provided project.",
|
||||||
|
"file": "generated/packages/rsbuild/generators/configuration.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "configuration",
|
||||||
|
"originalFilePath": "/packages/rsbuild/src/generators/configuration/schema.json",
|
||||||
|
"path": "/nx-api/rsbuild/generators/configuration",
|
||||||
|
"type": "generator"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"path": "/nx-api/rsbuild"
|
"path": "/nx-api/rsbuild"
|
||||||
|
|||||||
@ -2878,6 +2878,15 @@
|
|||||||
"originalFilePath": "/packages/rsbuild/src/generators/init/schema.json",
|
"originalFilePath": "/packages/rsbuild/src/generators/init/schema.json",
|
||||||
"path": "rsbuild/generators/init",
|
"path": "rsbuild/generators/init",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Add an Rsbuild configuration for the provided project.",
|
||||||
|
"file": "generated/packages/rsbuild/generators/configuration.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "configuration",
|
||||||
|
"originalFilePath": "/packages/rsbuild/src/generators/configuration/schema.json",
|
||||||
|
"path": "rsbuild/generators/configuration",
|
||||||
|
"type": "generator"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "configuration",
|
||||||
|
"factory": "./src/generators/configuration/configuration",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "Rsbuild",
|
||||||
|
"title": "Nx Rsbuild Configuration Generator",
|
||||||
|
"description": "Rsbuild configuration generator.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-dropdown": "project",
|
||||||
|
"x-prompt": "What is the name of the project to set up a Rsbuild for?",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"entry": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path relative to the workspace root for the entry file. Defaults to '<projectRoot>/src/index.ts'.",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"tsConfig": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Target platform for the build, same as the Rsbuild output.target config option.",
|
||||||
|
"enum": ["node", "web", "web-worker"],
|
||||||
|
"default": "web"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Add an Rsbuild configuration for the provided project.",
|
||||||
|
"implementation": "/packages/rsbuild/src/generators/configuration/configuration.ts",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/rsbuild/src/generators/configuration/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
@ -688,6 +688,7 @@
|
|||||||
- [rsbuild](/nx-api/rsbuild)
|
- [rsbuild](/nx-api/rsbuild)
|
||||||
- [generators](/nx-api/rsbuild/generators)
|
- [generators](/nx-api/rsbuild/generators)
|
||||||
- [init](/nx-api/rsbuild/generators/init)
|
- [init](/nx-api/rsbuild/generators/init)
|
||||||
|
- [configuration](/nx-api/rsbuild/generators/configuration)
|
||||||
- [rspack](/nx-api/rspack)
|
- [rspack](/nx-api/rspack)
|
||||||
- [documents](/nx-api/rspack/documents)
|
- [documents](/nx-api/rspack/documents)
|
||||||
- [Overview](/nx-api/rspack/documents/overview)
|
- [Overview](/nx-api/rspack/documents/overview)
|
||||||
|
|||||||
@ -8,6 +8,11 @@
|
|||||||
"description": "Initialize the `@nx/rsbuild` plugin.",
|
"description": "Initialize the `@nx/rsbuild` plugin.",
|
||||||
"aliases": ["ng-add"],
|
"aliases": ["ng-add"],
|
||||||
"hidden": true
|
"hidden": true
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"factory": "./src/generators/configuration/configuration",
|
||||||
|
"schema": "./src/generators/configuration/schema.json",
|
||||||
|
"description": "Add an Rsbuild configuration for the provided project."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,170 @@
|
|||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { type Tree } from '@nx/devkit';
|
||||||
|
import configurationGenerator from './configuration';
|
||||||
|
|
||||||
|
jest.mock('@nx/devkit', () => {
|
||||||
|
const original = jest.requireActual('@nx/devkit');
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
createProjectGraphAsync: jest.fn().mockResolvedValue({
|
||||||
|
dependencies: {},
|
||||||
|
nodes: {
|
||||||
|
myapp: {
|
||||||
|
name: 'myapp',
|
||||||
|
type: 'app',
|
||||||
|
data: {
|
||||||
|
root: 'apps/myapp',
|
||||||
|
sourceRoot: 'apps/myapp/src',
|
||||||
|
targets: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Rsbuild configuration generator', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/project.json',
|
||||||
|
JSON.stringify({
|
||||||
|
name: 'myapp',
|
||||||
|
projectType: 'application',
|
||||||
|
root: 'apps/myapp',
|
||||||
|
sourceRoot: 'apps/myapp/src',
|
||||||
|
targets: {},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/src/index.ts',
|
||||||
|
'export function main() { console.log("Hello world"); }'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate Rsbuild configuration files', async () => {
|
||||||
|
await configurationGenerator(tree, {
|
||||||
|
project: 'myapp',
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.exists('apps/myapp/rsbuild.config.ts')).toBeTruthy();
|
||||||
|
expect(tree.read('apps/myapp/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from '@rsbuild/core';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
target: 'web',
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate Rsbuild configuration with custom entry file', async () => {
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/src/main.ts',
|
||||||
|
'export function main() { console.log("Hello world"); }'
|
||||||
|
);
|
||||||
|
await configurationGenerator(tree, {
|
||||||
|
project: 'myapp',
|
||||||
|
entry: 'src/main.ts',
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.exists('apps/myapp/rsbuild.config.ts')).toBeTruthy();
|
||||||
|
expect(tree.read('apps/myapp/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from '@rsbuild/core';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/main.ts'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
target: 'web',
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate Rsbuild configuration with custom entry file with project root path', async () => {
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/src/main.ts',
|
||||||
|
'export function main() { console.log("Hello world"); }'
|
||||||
|
);
|
||||||
|
await configurationGenerator(tree, {
|
||||||
|
project: 'myapp',
|
||||||
|
entry: 'apps/myapp/src/main.ts',
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.exists('apps/myapp/rsbuild.config.ts')).toBeTruthy();
|
||||||
|
expect(tree.read('apps/myapp/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from '@rsbuild/core';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/main.ts'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
target: 'web',
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate Rsbuild configuration with custom tsconfig file', async () => {
|
||||||
|
await configurationGenerator(tree, {
|
||||||
|
project: 'myapp',
|
||||||
|
tsConfig: 'apps/myapp/tsconfig.json',
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.exists('apps/myapp/rsbuild.config.ts')).toBeTruthy();
|
||||||
|
expect(tree.read('apps/myapp/rsbuild.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { defineConfig } from '@rsbuild/core';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts'
|
||||||
|
},
|
||||||
|
tsconfigPath: './tsconfig.json',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
target: 'web',
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
createProjectGraphAsync,
|
||||||
|
generateFiles,
|
||||||
|
GeneratorCallback,
|
||||||
|
readProjectConfiguration,
|
||||||
|
readProjectsConfigurationFromProjectGraph,
|
||||||
|
runTasksInSerial,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { type Schema } from './schema';
|
||||||
|
import { normalizeOptions } from './lib';
|
||||||
|
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||||
|
import { initGenerator } from '../init/init';
|
||||||
|
import { rsbuildVersion } from '../../utils/versions';
|
||||||
|
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];
|
||||||
|
if (!project) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find project '${schema.project}'. Please choose a project that exists in the Nx Workspace.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = await normalizeOptions(tree, schema, project);
|
||||||
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|
||||||
|
const jsInitTask = await jsInitGenerator(tree, {
|
||||||
|
...schema,
|
||||||
|
skipFormat: true,
|
||||||
|
tsConfigName:
|
||||||
|
options.projectRoot === '.' ? 'tsconfig.json' : 'tsconfig.base.json',
|
||||||
|
});
|
||||||
|
tasks.push(jsInitTask);
|
||||||
|
const initTask = await initGenerator(tree, { skipFormat: true });
|
||||||
|
tasks.push(initTask);
|
||||||
|
|
||||||
|
if (options.skipValidation) {
|
||||||
|
const projectJson = readProjectConfiguration(tree, project.name);
|
||||||
|
if (projectJson.targets['build']) {
|
||||||
|
delete projectJson.targets['build'];
|
||||||
|
}
|
||||||
|
if (projectJson.targets['serve']) {
|
||||||
|
delete projectJson.targets['serve'];
|
||||||
|
}
|
||||||
|
if (projectJson.targets['dev']) {
|
||||||
|
delete projectJson.targets['dev'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.push(
|
||||||
|
addDependenciesToPackageJson(tree, {}, { '@rsbuild/core': rsbuildVersion })
|
||||||
|
);
|
||||||
|
|
||||||
|
generateFiles(tree, join(__dirname, 'files'), options.projectRoot, {
|
||||||
|
...options,
|
||||||
|
tpl: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
return runTasksInSerial(...tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default configurationGenerator;
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { defineConfig } from '@rsbuild/core';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
source: {
|
||||||
|
entry: {
|
||||||
|
index: '<%= entry %>'
|
||||||
|
},<% if (tsConfig) { %>
|
||||||
|
tsconfigPath: '<%= tsConfig %>',<% } %>
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
target: '<%= target %>',
|
||||||
|
distPath: {
|
||||||
|
root: 'dist',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './normalize-options';
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
joinPathFragments,
|
||||||
|
type Tree,
|
||||||
|
type ProjectConfiguration,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { type Schema } from '../schema';
|
||||||
|
import { relative } from 'path';
|
||||||
|
|
||||||
|
export interface NormalizedOptions extends Schema {
|
||||||
|
entry: string;
|
||||||
|
target: 'node' | 'web' | 'web-worker';
|
||||||
|
tsConfig: string;
|
||||||
|
projectRoot: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function normalizeOptions(
|
||||||
|
tree: Tree,
|
||||||
|
schema: Schema,
|
||||||
|
project: ProjectConfiguration
|
||||||
|
) {
|
||||||
|
// Paths should be relative to the project root because inferred task will run from project root
|
||||||
|
let options: NormalizedOptions = {
|
||||||
|
...schema,
|
||||||
|
target: schema.target ?? 'web',
|
||||||
|
entry: normalizeRelativePath(
|
||||||
|
schema.entry ?? './src/index.ts',
|
||||||
|
project.root
|
||||||
|
),
|
||||||
|
tsConfig: normalizeRelativePath(
|
||||||
|
schema.tsConfig ?? './tsconfig.json',
|
||||||
|
project.root
|
||||||
|
),
|
||||||
|
projectRoot: project.root,
|
||||||
|
skipFormat: schema.skipFormat ?? false,
|
||||||
|
skipValidation: schema.skipValidation ?? false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!schema.tsConfig) {
|
||||||
|
const possibleTsConfigPaths = [
|
||||||
|
'./tsconfig.app.json',
|
||||||
|
'./tsconfig.lib.json',
|
||||||
|
'./tsconfig.json',
|
||||||
|
];
|
||||||
|
const tsConfigPath = possibleTsConfigPaths.find((p) =>
|
||||||
|
tree.exists(joinPathFragments(project.root, p))
|
||||||
|
);
|
||||||
|
options.tsConfig = tsConfigPath ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRelativePath(filePath: string, projectRoot: string) {
|
||||||
|
if (filePath.startsWith('./')) {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
filePath = filePath.startsWith(projectRoot)
|
||||||
|
? relative(projectRoot, filePath)
|
||||||
|
: filePath;
|
||||||
|
return `./${filePath}`;
|
||||||
|
}
|
||||||
8
packages/rsbuild/src/generators/configuration/schema.d.ts
vendored
Normal file
8
packages/rsbuild/src/generators/configuration/schema.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface Schema {
|
||||||
|
project: string;
|
||||||
|
entry?: string;
|
||||||
|
tsConfig?: string;
|
||||||
|
target?: 'node' | 'web' | 'web-worker';
|
||||||
|
skipValidation?: boolean;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
}
|
||||||
42
packages/rsbuild/src/generators/configuration/schema.json
Normal file
42
packages/rsbuild/src/generators/configuration/schema.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"$id": "Rsbuild",
|
||||||
|
"title": "Nx Rsbuild Configuration Generator",
|
||||||
|
"description": "Rsbuild configuration generator.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-dropdown": "project",
|
||||||
|
"x-prompt": "What is the name of the project to set up a Rsbuild for?",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"entry": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path relative to the workspace root for the entry file. Defaults to '<projectRoot>/src/index.ts'.",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"tsConfig": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path relative to the workspace root for the tsconfig file to build with. Defaults to '<projectRoot>/tsconfig.app.json'.",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Target platform for the build, same as the Rsbuild output.target config option.",
|
||||||
|
"enum": ["node", "web", "web-worker"],
|
||||||
|
"default": "web"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"description": "Skip formatting files.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"x-priority": "internal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user