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": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "configuration",
|
||||
"path": "/nx-api/rsbuild/generators/configuration",
|
||||
"name": "configuration",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
|
||||
@ -2909,6 +2909,15 @@
|
||||
"originalFilePath": "/packages/rsbuild/src/generators/init/schema.json",
|
||||
"path": "/nx-api/rsbuild/generators/init",
|
||||
"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"
|
||||
|
||||
@ -2878,6 +2878,15 @@
|
||||
"originalFilePath": "/packages/rsbuild/src/generators/init/schema.json",
|
||||
"path": "rsbuild/generators/init",
|
||||
"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",
|
||||
|
||||
@ -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)
|
||||
- [generators](/nx-api/rsbuild/generators)
|
||||
- [init](/nx-api/rsbuild/generators/init)
|
||||
- [configuration](/nx-api/rsbuild/generators/configuration)
|
||||
- [rspack](/nx-api/rspack)
|
||||
- [documents](/nx-api/rspack/documents)
|
||||
- [Overview](/nx-api/rspack/documents/overview)
|
||||
|
||||
@ -8,6 +8,11 @@
|
||||
"description": "Initialize the `@nx/rsbuild` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"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