@rs-x/cli 2.0.0-next.15 → 2.0.0-next.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/rsx.cjs CHANGED
@@ -27,6 +27,12 @@ const REACT_DEMO_TEMPLATE_DIR = path.join(
27
27
  'templates',
28
28
  'react-demo',
29
29
  );
30
+ const NEXT_DEMO_TEMPLATE_DIR = path.join(
31
+ __dirname,
32
+ '..',
33
+ 'templates',
34
+ 'next-demo',
35
+ );
30
36
  const RUNTIME_PACKAGES = [
31
37
  '@rs-x/core',
32
38
  '@rs-x/state-manager',
@@ -1681,6 +1687,112 @@ function applyReactDemoStarter(projectRoot, projectName, pm, flags) {
1681
1687
  }
1682
1688
  }
1683
1689
 
1690
+ function applyNextDemoStarter(projectRoot, projectName, pm, flags) {
1691
+ const dryRun = Boolean(flags['dry-run']);
1692
+ const tag = resolveInstallTag(flags);
1693
+ const tarballsDir =
1694
+ typeof flags['tarballs-dir'] === 'string'
1695
+ ? path.resolve(process.cwd(), flags['tarballs-dir'])
1696
+ : typeof process.env.RSX_TARBALLS_DIR === 'string' &&
1697
+ process.env.RSX_TARBALLS_DIR.trim().length > 0
1698
+ ? path.resolve(process.cwd(), process.env.RSX_TARBALLS_DIR)
1699
+ : null;
1700
+ const workspaceRoot = findRepoRoot(projectRoot);
1701
+ const rsxSpecs = resolveProjectRsxSpecs(
1702
+ projectRoot,
1703
+ workspaceRoot,
1704
+ tarballsDir,
1705
+ { tag, includeReactPackage: true },
1706
+ );
1707
+
1708
+ const templateFiles = ['README.md', 'app', 'components', 'hooks', 'lib'];
1709
+ for (const entry of templateFiles) {
1710
+ copyPathWithDryRun(
1711
+ path.join(NEXT_DEMO_TEMPLATE_DIR, entry),
1712
+ path.join(projectRoot, entry),
1713
+ dryRun,
1714
+ );
1715
+ }
1716
+
1717
+ const readmePath = path.join(projectRoot, 'README.md');
1718
+ if (fs.existsSync(readmePath)) {
1719
+ const readmeSource = fs.readFileSync(readmePath, 'utf8');
1720
+ const nextReadme = readmeSource.replace(
1721
+ /^#\s+rsx-next-example/mu,
1722
+ `# ${projectName}`,
1723
+ );
1724
+ if (dryRun) {
1725
+ logInfo(`[dry-run] patch ${readmePath}`);
1726
+ } else {
1727
+ fs.writeFileSync(readmePath, nextReadme, 'utf8');
1728
+ }
1729
+ }
1730
+
1731
+ const publicDir = path.join(projectRoot, 'public');
1732
+ removeFileOrDirectoryWithDryRun(publicDir, dryRun);
1733
+
1734
+ const packageJsonPath = path.join(projectRoot, 'package.json');
1735
+ if (!fs.existsSync(packageJsonPath)) {
1736
+ logError(`package.json not found in generated Next.js app: ${packageJsonPath}`);
1737
+ process.exit(1);
1738
+ }
1739
+
1740
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1741
+ packageJson.name = projectName;
1742
+ packageJson.private = true;
1743
+ packageJson.version = '0.1.0';
1744
+ packageJson.scripts = {
1745
+ ...packageJson.scripts,
1746
+ 'build:rsx': 'rsx build --project tsconfig.json --no-emit --prod',
1747
+ dev: 'npm run build:rsx && next dev',
1748
+ build: 'npm run build:rsx && next build',
1749
+ start: 'next start',
1750
+ };
1751
+ packageJson.rsx = {
1752
+ build: {
1753
+ preparse: true,
1754
+ preparseFile: 'app/rsx-generated/rsx-aot-preparsed.generated.ts',
1755
+ compiled: true,
1756
+ compiledFile: 'app/rsx-generated/rsx-aot-compiled.generated.ts',
1757
+ compiledResolvedEvaluator: false,
1758
+ },
1759
+ };
1760
+ packageJson.dependencies = {
1761
+ ...(packageJson.dependencies ?? {}),
1762
+ '@rs-x/core': rsxSpecs['@rs-x/core'],
1763
+ '@rs-x/state-manager': rsxSpecs['@rs-x/state-manager'],
1764
+ '@rs-x/expression-parser': rsxSpecs['@rs-x/expression-parser'],
1765
+ '@rs-x/react': rsxSpecs['@rs-x/react'],
1766
+ };
1767
+ packageJson.devDependencies = {
1768
+ ...(packageJson.devDependencies ?? {}),
1769
+ '@rs-x/cli': rsxSpecs['@rs-x/cli'],
1770
+ '@rs-x/compiler': rsxSpecs['@rs-x/compiler'],
1771
+ '@rs-x/typescript-plugin': rsxSpecs['@rs-x/typescript-plugin'],
1772
+ };
1773
+
1774
+ if (dryRun) {
1775
+ logInfo(`[dry-run] patch ${packageJsonPath}`);
1776
+ } else {
1777
+ fs.writeFileSync(
1778
+ packageJsonPath,
1779
+ `${JSON.stringify(packageJson, null, 2)}\n`,
1780
+ 'utf8',
1781
+ );
1782
+ }
1783
+
1784
+ const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
1785
+ if (fs.existsSync(tsConfigPath)) {
1786
+ upsertTypescriptPluginInTsConfig(tsConfigPath, dryRun);
1787
+ }
1788
+
1789
+ if (!Boolean(flags['skip-install'])) {
1790
+ logInfo(`Refreshing ${pm} dependencies for the RS-X Next.js starter...`);
1791
+ run(pm, ['install'], { dryRun });
1792
+ logOk('Next.js starter dependencies are up to date.');
1793
+ }
1794
+ }
1795
+
1684
1796
  async function runProjectWithTemplate(template, flags) {
1685
1797
  const normalizedTemplate = normalizeProjectTemplate(template);
1686
1798
  if (!normalizedTemplate) {
@@ -1720,7 +1832,7 @@ async function runProjectWithTemplate(template, flags) {
1720
1832
  return;
1721
1833
  }
1722
1834
  if (normalizedTemplate === 'nextjs') {
1723
- runSetupNext(flags);
1835
+ applyNextDemoStarter(projectRoot, projectName, pm, flags);
1724
1836
  return;
1725
1837
  }
1726
1838
  if (normalizedTemplate === 'vuejs') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rs-x/cli",
3
- "version": "2.0.0-next.15",
3
+ "version": "2.0.0-next.16",
4
4
  "description": "CLI for installing RS-X compiler tooling and VS Code integration",
5
5
  "bin": {
6
6
  "rsx": "./bin/rsx.cjs"
@@ -0,0 +1,26 @@
1
+ # rsx-next-example
2
+
3
+ Website & docs: https://www.rsxjs.com/
4
+
5
+ This starter shows how to use RS-X in a Next.js app-router project with a
6
+ million-row virtual table that keeps rendering and expression memory bounded.
7
+
8
+ ## Scripts
9
+
10
+ - `npm run dev` starts the Next.js dev server after running the RS-X build step
11
+ - `npm run build` generates RS-X artifacts and builds the production app
12
+ - `npm run start` starts the production server
13
+
14
+ ## Structure
15
+
16
+ - `app/` contains the Next.js route files and global styles
17
+ - `components/` contains the client-side UI components
18
+ - `hooks/` contains reusable React hooks
19
+ - `lib/` contains the RS-X bootstrap and virtual-table state/data utilities
20
+
21
+ ## Notes
22
+
23
+ - The demo defaults to dark mode.
24
+ - The UI uses `@rs-x/react` hooks in a Next.js client component tree.
25
+ - The generated RS-X cache files in `app/rsx-generated` are created by
26
+ `npm run build:rsx`; they are not checked into the starter template.
@@ -0,0 +1,431 @@
1
+ :root {
2
+ --font-sans:
3
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
4
+ sans-serif;
5
+ --font-mono:
6
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New',
7
+ monospace;
8
+ --bg: #f6f8fc;
9
+ --surface: rgba(255, 255, 255, 0.86);
10
+ --surface-2: rgba(255, 255, 255, 0.72);
11
+ --surface-solid: #ffffff;
12
+ --text: #0b1324;
13
+ --muted: rgba(11, 19, 36, 0.66);
14
+ --border: rgba(10, 25, 55, 0.14);
15
+ --border-soft: rgba(10, 25, 55, 0.1);
16
+ --brand: #0b66ff;
17
+ --brand-2: #2bb6a9;
18
+ --focus: rgba(11, 102, 255, 0.35);
19
+ --shadow-1:
20
+ 0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 34px rgba(16, 24, 40, 0.1);
21
+ --shadow-2:
22
+ 0 2px 10px rgba(16, 24, 40, 0.08), 0 18px 52px rgba(16, 24, 40, 0.12);
23
+ --page-glow-a: rgba(11, 102, 255, 0.06);
24
+ --page-glow-b: rgba(43, 182, 169, 0.05);
25
+ --hero-section-bg: linear-gradient(
26
+ 180deg,
27
+ rgba(255, 255, 255, 0.72),
28
+ rgba(255, 255, 255, 0.42)
29
+ );
30
+ --r-xl: 1.75rem;
31
+ --sp-2: 0.5rem;
32
+ --sp-3: 0.75rem;
33
+ --sp-4: 1rem;
34
+ --sp-5: 1.25rem;
35
+ --sp-6: 1.5rem;
36
+ --sp-7: 2rem;
37
+ --sp-8: 2.5rem;
38
+ --sp-9: 3rem;
39
+ --sp-10: 4rem;
40
+ --container: 1120px;
41
+ }
42
+
43
+ html[data-theme='dark'],
44
+ body[data-theme='dark'] {
45
+ --bg: #070b14;
46
+ --surface: rgba(12, 18, 35, 0.82);
47
+ --surface-2: rgba(12, 18, 35, 0.68);
48
+ --surface-solid: #0c1223;
49
+ --text: #edf2ff;
50
+ --muted: rgba(237, 242, 255, 0.7);
51
+ --border: rgba(237, 242, 255, 0.16);
52
+ --border-soft: rgba(237, 242, 255, 0.12);
53
+ --brand: #7ab6ff;
54
+ --brand-2: #49d9c8;
55
+ --focus: rgba(122, 182, 255, 0.35);
56
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.22), 0 12px 34px rgba(0, 0, 0, 0.32);
57
+ --shadow-2: 0 2px 10px rgba(0, 0, 0, 0.24), 0 18px 52px rgba(0, 0, 0, 0.34);
58
+ --page-glow-a: rgba(122, 182, 255, 0.1);
59
+ --page-glow-b: rgba(73, 217, 200, 0.08);
60
+ --hero-section-bg: linear-gradient(
61
+ 180deg,
62
+ rgba(12, 18, 35, 0.56),
63
+ rgba(12, 18, 35, 0.24)
64
+ );
65
+ }
66
+
67
+ *,
68
+ *::before,
69
+ *::after {
70
+ box-sizing: border-box;
71
+ }
72
+
73
+ html,
74
+ body,
75
+ #root {
76
+ min-height: 100%;
77
+ }
78
+
79
+ html {
80
+ color-scheme: dark;
81
+ background: var(--bg);
82
+ }
83
+
84
+ html[data-theme='light'] {
85
+ color-scheme: light;
86
+ }
87
+
88
+ body {
89
+ margin: 0;
90
+ font-family: var(--font-sans);
91
+ color: var(--text);
92
+ background-color: var(--bg);
93
+ background:
94
+ radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
95
+ radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
96
+ var(--bg);
97
+ transition:
98
+ background 180ms ease,
99
+ color 180ms ease;
100
+ }
101
+
102
+ button,
103
+ input,
104
+ select,
105
+ textarea {
106
+ font: inherit;
107
+ }
108
+
109
+ .container {
110
+ max-width: var(--container);
111
+ margin: 0 auto;
112
+ padding: 0 var(--sp-6);
113
+ }
114
+
115
+ .app-shell {
116
+ min-height: 100vh;
117
+ }
118
+
119
+ .hero {
120
+ position: relative;
121
+ padding: calc(var(--sp-10) + 1rem) 0 calc(var(--sp-8) + 0.5rem);
122
+ background: var(--hero-section-bg);
123
+ }
124
+
125
+ .heroGrid {
126
+ display: grid;
127
+ grid-template-columns: minmax(540px, 1.05fr) minmax(300px, 0.95fr);
128
+ column-gap: var(--sp-6);
129
+ row-gap: var(--sp-8);
130
+ align-items: center;
131
+ }
132
+
133
+ .heroLeft {
134
+ min-width: 0;
135
+ padding-top: var(--sp-4);
136
+ }
137
+
138
+ .app-eyebrow {
139
+ margin: 0 0 var(--sp-4);
140
+ letter-spacing: 0.2em;
141
+ text-transform: uppercase;
142
+ font-size: 0.82rem;
143
+ font-weight: 700;
144
+ color: var(--brand);
145
+ }
146
+
147
+ .hTitle {
148
+ margin: 0 0 var(--sp-5);
149
+ font-size: clamp(3.6rem, 5.2vw, 4.8rem);
150
+ line-height: 0.96;
151
+ letter-spacing: -0.052em;
152
+ }
153
+
154
+ .hSubhead {
155
+ margin: 0 0 var(--sp-2);
156
+ font-size: clamp(1.42rem, 2vw, 1.82rem);
157
+ font-weight: 640;
158
+ letter-spacing: -0.03em;
159
+ line-height: 1.08;
160
+ }
161
+
162
+ .hSub {
163
+ margin: var(--sp-5) 0 0;
164
+ color: var(--muted);
165
+ font-size: 1.1rem;
166
+ line-height: 1.72;
167
+ max-width: 31.5rem;
168
+ }
169
+
170
+ .heroActions {
171
+ display: flex;
172
+ flex-wrap: wrap;
173
+ gap: var(--sp-3);
174
+ margin-top: var(--sp-5);
175
+ }
176
+
177
+ .btn {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ gap: var(--sp-2);
182
+ padding: 0.82rem 1.18rem;
183
+ border-radius: 14px;
184
+ border: 1px solid transparent;
185
+ font-weight: 800;
186
+ line-height: 1;
187
+ white-space: nowrap;
188
+ transition:
189
+ transform 160ms ease,
190
+ background 240ms ease,
191
+ border-color 240ms ease,
192
+ box-shadow 240ms ease;
193
+ }
194
+
195
+ .btn:active {
196
+ transform: translateY(1px);
197
+ }
198
+
199
+ .btnGhost {
200
+ background: var(--surface-2);
201
+ color: var(--text);
202
+ border-color: var(--border);
203
+ box-shadow: none;
204
+ text-decoration: none;
205
+ }
206
+
207
+ .btnGhost:hover {
208
+ box-shadow: var(--shadow-1);
209
+ }
210
+
211
+ .card {
212
+ position: relative;
213
+ min-width: 0;
214
+ border: 1px solid var(--border-soft);
215
+ background: linear-gradient(180deg, var(--surface), var(--surface-2));
216
+ border-radius: var(--r-xl);
217
+ padding: var(--sp-6);
218
+ box-shadow: var(--shadow-1);
219
+ display: flex;
220
+ flex-direction: column;
221
+ }
222
+
223
+ .cardTitle {
224
+ margin: 0 0 var(--sp-3);
225
+ font-weight: 700;
226
+ font-size: clamp(1.12rem, 0.96vw, 1.22rem);
227
+ letter-spacing: -0.02em;
228
+ }
229
+
230
+ .cardText {
231
+ margin: 0;
232
+ color: var(--muted);
233
+ line-height: 1.62;
234
+ }
235
+
236
+ .heroNote {
237
+ align-self: center;
238
+ margin-top: 0;
239
+ }
240
+
241
+ .section {
242
+ padding: var(--sp-7) 0 var(--sp-10);
243
+ }
244
+
245
+ .app-panel {
246
+ backdrop-filter: blur(12px);
247
+ }
248
+
249
+ .theme-toggle {
250
+ cursor: pointer;
251
+ }
252
+
253
+ .table-toolbar {
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: space-between;
257
+ gap: 16px;
258
+ padding-bottom: 16px;
259
+ border-bottom: 1px solid var(--border-soft);
260
+ }
261
+
262
+ .toolbar-left h2 {
263
+ margin: 0;
264
+ font-size: 20px;
265
+ }
266
+
267
+ .toolbar-left p {
268
+ margin: 4px 0 0;
269
+ color: var(--muted);
270
+ font-size: 13px;
271
+ }
272
+
273
+ .toolbar-right {
274
+ display: flex;
275
+ flex-wrap: wrap;
276
+ gap: 8px;
277
+ }
278
+
279
+ .toolbar-right button {
280
+ border: 1px solid var(--border);
281
+ background: color-mix(in srgb, var(--surface-solid) 88%, var(--brand) 12%);
282
+ color: var(--text);
283
+ padding: 6px 12px;
284
+ border-radius: 999px;
285
+ font-size: 12px;
286
+ cursor: pointer;
287
+ }
288
+
289
+ .toolbar-right button:hover {
290
+ border-color: var(--focus);
291
+ }
292
+
293
+ .table-header {
294
+ display: grid;
295
+ grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
296
+ gap: 8px;
297
+ padding: 12px 8px;
298
+ font-size: 12px;
299
+ font-weight: 600;
300
+ color: var(--muted);
301
+ text-transform: uppercase;
302
+ letter-spacing: 0.06em;
303
+ }
304
+
305
+ .table-viewport {
306
+ position: relative;
307
+ height: 520px;
308
+ overflow: auto;
309
+ border: 1px solid var(--border-soft);
310
+ border-radius: 12px;
311
+ background: color-mix(in srgb, var(--surface-solid) 94%, transparent);
312
+ }
313
+
314
+ .table-spacer {
315
+ width: 100%;
316
+ }
317
+
318
+ .table-row {
319
+ position: absolute;
320
+ top: 0;
321
+ left: 0;
322
+ right: 0;
323
+ height: 36px;
324
+ display: grid;
325
+ grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
326
+ align-items: center;
327
+ gap: 8px;
328
+ padding: 0 8px;
329
+ border-bottom: 1px solid var(--border-soft);
330
+ font-size: 13px;
331
+ }
332
+
333
+ .table-row:nth-of-type(odd) {
334
+ background: color-mix(in srgb, var(--surface-solid) 84%, transparent);
335
+ }
336
+
337
+ .table-row .total {
338
+ font-weight: 600;
339
+ color: var(--brand);
340
+ }
341
+
342
+ .table-footer {
343
+ display: flex;
344
+ justify-content: space-between;
345
+ margin-top: 12px;
346
+ font-size: 12px;
347
+ color: var(--muted);
348
+ }
349
+
350
+ .table-loading {
351
+ display: grid;
352
+ place-items: center;
353
+ min-height: 520px;
354
+ color: var(--muted);
355
+ font-size: 1rem;
356
+ letter-spacing: 0.03em;
357
+ }
358
+
359
+ @media (max-width: 920px) {
360
+ .heroGrid {
361
+ grid-template-columns: 1fr;
362
+ }
363
+ }
364
+
365
+ @media (max-width: 900px) {
366
+ .table-header,
367
+ .table-row {
368
+ grid-template-columns: 72px 1.3fr 1fr 0.8fr 0.7fr 1fr;
369
+ }
370
+
371
+ .table-header span:last-child,
372
+ .table-row span:last-child {
373
+ display: none;
374
+ }
375
+ }
376
+
377
+ @media (max-width: 720px) {
378
+ .container {
379
+ padding: 0 var(--sp-4);
380
+ }
381
+
382
+ .hero {
383
+ padding: calc(var(--sp-8) + 0.5rem) 0 var(--sp-8);
384
+ }
385
+
386
+ .hTitle {
387
+ font-size: clamp(2.9rem, 14vw, 3.6rem);
388
+ }
389
+
390
+ .table-toolbar,
391
+ .table-footer {
392
+ flex-direction: column;
393
+ align-items: flex-start;
394
+ }
395
+
396
+ .table-header {
397
+ display: none;
398
+ }
399
+
400
+ .table-viewport {
401
+ height: 460px;
402
+ }
403
+
404
+ .table-row {
405
+ height: 168px;
406
+ grid-template-columns: repeat(2, minmax(0, 1fr));
407
+ align-content: start;
408
+ gap: 10px 16px;
409
+ padding: 14px 16px;
410
+ border: 1px solid var(--border-soft);
411
+ border-radius: 18px;
412
+ margin: 0 8px;
413
+ }
414
+
415
+ .table-row span {
416
+ display: flex;
417
+ flex-direction: column;
418
+ gap: 4px;
419
+ min-width: 0;
420
+ font-size: 0.95rem;
421
+ }
422
+
423
+ .table-row span::before {
424
+ content: attr(data-label);
425
+ color: var(--muted);
426
+ font-size: 0.72rem;
427
+ font-weight: 700;
428
+ letter-spacing: 0.06em;
429
+ text-transform: uppercase;
430
+ }
431
+ }
@@ -0,0 +1,22 @@
1
+ import type { Metadata } from 'next';
2
+ import type { ReactNode } from 'react';
3
+
4
+ import './globals.css';
5
+
6
+ export const metadata: Metadata = {
7
+ title: 'RS-X Next.js Demo',
8
+ description:
9
+ 'Million-row virtual scrolling with a fixed RS-X expression pool in Next.js.',
10
+ };
11
+
12
+ type RootLayoutProps = Readonly<{
13
+ children: ReactNode;
14
+ }>;
15
+
16
+ export default function RootLayout({ children }: RootLayoutProps) {
17
+ return (
18
+ <html lang="en" data-theme="dark">
19
+ <body data-theme="dark">{children}</body>
20
+ </html>
21
+ );
22
+ }
@@ -0,0 +1,5 @@
1
+ import { DemoApp } from '@/components/demo-app';
2
+
3
+ export default function Page() {
4
+ return <DemoApp />;
5
+ }