Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/create-rstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ npx create-rstack --dir my-project --template app-vanilla-ts --no-git
- `doc` - Basic documentation site
- `doc-i18n` - Multilingual documentation site
- `turborepo` - Turborepo
- `nx` - Nx

## Documentation

Expand Down
10 changes: 7 additions & 3 deletions packages/create-rstack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const templateNames = [
'doc',
'doc-i18n',
'turborepo',
'nx',
];

const resolveTemplateName = (template: string): string => {
Expand All @@ -56,7 +57,7 @@ const resolveTemplateName = (template: string): string => {

const getTemplateName = async ({ template }: Argv): Promise<string> => {
if (typeof template === 'string') {
if (/^(?:app|lib|doc|turborepo)(?:-|$)/u.test(template)) {
if (/^(?:app|lib|doc|turborepo|nx)(?:-|$)/u.test(template)) {
return resolveTemplateName(template);
}

Expand All @@ -79,7 +80,10 @@ const getTemplateName = async ({ template }: Argv): Promise<string> => {
const monorepoType = checkCancel<string>(
await select({
message: 'Select monorepo tool',
options: [{ value: 'turborepo', label: 'Turborepo', hint: 'pnpm' }],
options: [
{ value: 'turborepo', label: 'Turborepo', hint: 'pnpm' },
{ value: 'nx', label: 'Nx', hint: 'pnpm' },
],
}),
);

Expand Down Expand Up @@ -235,6 +239,6 @@ await create({
builtinTools: [],
getTemplateName,
getPackageManager: ({ templateName }) =>
templateName === 'turborepo' ? 'pnpm' : undefined,
['turborepo', 'nx'].includes(templateName) ? 'pnpm' : undefined,
onGitResolved: injectStagedSetup,
});
35 changes: 35 additions & 0 deletions packages/create-rstack/template-nx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Nx + Rstack

## Setup

```bash
pnpm install
```

## Structure

- `apps/web`: React application.
- `packages/ui`: Shared UI components.
- `tsconfig.base.json`: Shared TypeScript configuration.

## Scripts

Run from the repository root:

- `pnpm run build`: Build all packages.
- `pnpm run dev`: Start the web app and watch the shared UI package.
- `pnpm run test`: Run tests.
- `pnpm run check`: Run lint, formatting, and TypeScript checks.
- `pnpm run lint`: Lint the workspace.
- `pnpm run format`: Format the workspace.

Nx discovers projects and tasks from workspace package manifests. Build and test tasks are cached, and dependencies are built first.

Run `pnpm run build` before `pnpm run check`.

Use `pnpm exec nx graph` to explore the project graph.

## Learn more

- [Rstack CLI monorepo guide](https://rstack.rs/guide/monorepo)
- [Nx documentation](https://nx.dev/docs/getting-started/intro)
26 changes: 26 additions & 0 deletions packages/create-rstack/template-nx/apps/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "web",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "rs build",
"dev": "rs dev",
"preview": "rs preview",
"test": "rs test"
},
"dependencies": {
"@repo/ui": "workspace:^",
"react": "^19.3.0",
"react-dom": "^19.3.0"
},
"devDependencies": {
"@rsbuild/plugin-react": "^2.1.1",
"@testing-library/dom": "^10.4.2",
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.3",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"happy-dom": "^21.0.1"
}
}
13 changes: 13 additions & 0 deletions packages/create-rstack/template-nx/apps/web/rstack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Configuration guide: https://rstack.rs/config
import { define } from 'rstack';

define.app(async () => {
const { pluginReact } = await import('@rsbuild/plugin-react');
return {
plugins: [pluginReact()],
};
});

define.test({
setupFiles: ['./tests/rstest.setup.ts'],
});
17 changes: 17 additions & 0 deletions packages/create-rstack/template-nx/apps/web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Button } from '@repo/ui/button';

export function App() {
return (
<main>
<h1>Web</h1>
<p>Nx + Rstack CLI</p>
<p>Edit apps/web/src/App.tsx to get started.</p>
<Button onClick={() => window.alert('Hello from @repo/ui!')}>
Shared button
</Button>
<nav>
<a href="https://rstack.rs">Rstack documentation</a>
</nav>
</main>
);
}
27 changes: 27 additions & 0 deletions packages/create-rstack/template-nx/apps/web/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #202124;
background: #f8f9fa;
}

main {
max-width: 640px;
margin: 15vh auto;
padding: 24px;
}

button {
padding: 10px 16px;
border: 0;
border-radius: 8px;
color: white;
background: #5755d9;
cursor: pointer;
}

nav {
display: flex;
gap: 20px;
margin-top: 24px;
}
13 changes: 13 additions & 0 deletions packages/create-rstack/template-nx/apps/web/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import './index.css';

const root = document.getElementById('root');
if (root) {
createRoot(root).render(
<StrictMode>
<App />
</StrictMode>,
);
}
11 changes: 11 additions & 0 deletions packages/create-rstack/template-nx/apps/web/tests/app.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expect, test } from 'rstack/test';
import { render, screen } from '@testing-library/react';
import { App } from '../src/App';

test('renders the web app with the shared UI package', () => {
render(<App />);
expect(screen.getByRole('heading', { name: 'Web' })).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Shared button' }),
).toBeInTheDocument();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { expect } from 'rstack/test';
import * as jestDomMatchers from '@testing-library/jest-dom/matchers';

expect.extend(jestDomMatchers);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["rstack/types", "node", "@testing-library/jest-dom"]
},
"include": ["./"]
}
4 changes: 4 additions & 0 deletions packages/create-rstack/template-nx/apps/web/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src", "rstack.config.ts"]
}
17 changes: 17 additions & 0 deletions packages/create-rstack/template-nx/gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Local
.DS_Store
*.local
*.log*

# Dist
node_modules
dist/

# IDE
.vscode/*
!.vscode/extensions.json
.idea

# Nx
.nx/cache/
.nx/workspace-data/
26 changes: 26 additions & 0 deletions packages/create-rstack/template-nx/nx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"sharedGlobals": [
"{workspaceRoot}/tsconfig.base.json",
"{workspaceRoot}/rstack.config.ts",
"{workspaceRoot}/pnpm-workspace.yaml"
]
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"cache": true,
"outputs": ["{projectRoot}/dist"]
},
"dev": {
"dependsOn": ["^build"],
"continuous": true
},
"test": {
"dependsOn": ["^build"],
"cache": true
}
}
}
20 changes: 20 additions & 0 deletions packages/create-rstack/template-nx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "rstack-nx",
"private": true,
"type": "module",
"scripts": {
"build": "nx run-many -t build",
"check": "rs check --type-check",
"dev": "nx run-many -t dev",
"format": "rs fmt",
"lint": "rs lint",
"test": "nx run-many -t test"
},
"devDependencies": {
"@types/node": "^26.6.5",
"nx": "^23.3.0",
"rstack": "^0.8.4",
"typescript": "^7.0.2"
},
"packageManager": "pnpm@12.11.2"
}
39 changes: 39 additions & 0 deletions packages/create-rstack/template-nx/packages/ui/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
"./button": {
"types": "./dist/button.d.ts",
"default": "./dist/button.js"
}
},
"scripts": {
"build": "rs lib",
"dev": "rs lib --watch --no-clean",
"test": "rs test"
},
"dependencies": {
"react": "^19.3.0"
},
"devDependencies": {
"@rsbuild/plugin-react": "^2.1.1",
"@testing-library/dom": "^10.4.2",
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.3",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"happy-dom": "^21.0.1",
"react-dom": "^19.3.0"
},
"nx": {
"targets": {
"dev": {
"dependsOn": [
"build"
]
}
}
}
}
20 changes: 20 additions & 0 deletions packages/create-rstack/template-nx/packages/ui/rstack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Configuration guide: https://rstack.rs/config
import { define } from 'rstack';

define.lib(async () => {
const { pluginReact } = await import('@rsbuild/plugin-react');
return {
dts: true,
source: {
entry: { button: './src/button.tsx' },
},
output: {
target: 'web',
},
plugins: [pluginReact()],
};
});

define.test({
setupFiles: ['./tests/rstest.setup.ts'],
});
8 changes: 8 additions & 0 deletions packages/create-rstack/template-nx/packages/ui/src/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ComponentProps } from 'react';

export function Button({
type = 'button',
...props
}: ComponentProps<'button'>) {
return <button type={type} {...props} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect, test } from 'rstack/test';
import { render, screen } from '@testing-library/react';
import { Button } from '../src/button';

test('defaults to a non-submitting button and forwards props', () => {
render(<Button disabled>Save</Button>);
const button = screen.getByRole('button', { name: 'Save' });
expect(button).toHaveAttribute('type', 'button');
expect(button).toBeDisabled();
});

test('allows a submit button', () => {
render(<Button type="submit">Submit</Button>);
expect(screen.getByRole('button', { name: 'Submit' })).toHaveAttribute(
'type',
'submit',
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { expect } from 'rstack/test';
import * as jestDomMatchers from '@testing-library/jest-dom/matchers';

expect.extend(jestDomMatchers);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
"types": ["rstack/types", "node", "@testing-library/jest-dom"]
},
"include": ["./", "../rstack.config.ts"]
}
7 changes: 7 additions & 0 deletions packages/create-rstack/template-nx/packages/ui/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src"
},
"include": ["src"]
}
6 changes: 6 additions & 0 deletions packages/create-rstack/template-nx/pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
packages:
- apps/*
- packages/*

allowBuilds:
nx: true
Loading
Loading