@rsc-xray/lsp-server
v0.3.0
Published
LSP server for RSC X-Ray analyzer (OSS version)
Maintainers
Readme
@rsc-xray/lsp-server
Universal LSP wrapper for React Server Components analysis
This package provides a thin wrapper around the OSS @rsc-xray/analyzer for Language Server Protocol (LSP) usage. It works in browser, Node.js, VS Code extensions, and GitHub Actions with 8 essential correctness rules.
Used by: OSS Demo, Pro Overlay, Pro VS Code, Pro CI
For Pro features (overlay visualization, VS Code integration, CI automation), see rsc-xray.dev/pro.
Features
8 Essential Rules (Free Forever)
| Rule | Description |
| --------------------------------------- | ------------------------------------------------- |
| client-forbidden-import | Detects Node.js imports in client components |
| suspense-boundary-missing | Finds async components without Suspense |
| suspense-boundary-opportunity | Suggests parallel Suspense opportunities |
| client-component-oversized | Warns about large client bundles (>50KB) |
| duplicate-dependencies | Finds shared dependencies bundled multiple times |
| react19-cache-opportunity | Suggests React 19 cache() API usage |
| route-segment-config-conflict | Detects Next.js route config conflicts |
| server-client-serialization-violation | Finds non-serializable props to client components |
Installation
npm install @rsc-xray/lsp-serverUsage
Basic Analysis
import { analyze } from '@rsc-xray/lsp-server';
const result = await analyze({
code: `
'use client';
import fs from 'fs';
export default function BadClient() {
fs.readFileSync('file.txt');
return <div>Client</div>;
}
`,
fileName: 'components/BadClient.tsx',
});
console.log(result.diagnostics);
// [
// {
// rule: 'client-forbidden-import',
// message: 'Client components cannot import Node.js built-ins like "fs"',
// severity: 'error',
// line: 2,
// column: 8,
// ...
// }
// ]With Scenario Filter
const result = await analyze({
code: serverComponentCode,
fileName: 'app/page.tsx',
scenario: 'serialization-boundary', // Only run this specific scenario
});With Rule Filter
const result = await analyze({
code: clientComponentCode,
fileName: 'components/Button.tsx',
rules: ['client-forbidden-import', 'client-component-oversized'], // Only these rules
});With Context
const result = await analyze({
code: routeCode,
fileName: 'app/page.tsx',
context: {
clientComponentPaths: ['./ClientButton', './Chart'],
routeConfig: {
dynamic: 'force-static',
revalidate: 60,
},
},
});API Reference
analyze(request: LspAnalysisRequest): Promise<LspAnalysisResponse>
Main analysis entry point.
Parameters:
interface LspAnalysisRequest {
/** Source code to analyze */
code: string;
/** File name (for determining .ts vs .tsx and diagnostics) */
fileName: string;
/** Specific scenario to analyze (if omitted, runs all applicable rules) */
scenario?:
| 'serialization-boundary'
| 'suspense-boundary'
| 'react19-cache'
| 'client-size'
| 'client-forbidden-imports'
| 'route-config';
/** Specific rule IDs to run (if omitted, runs all applicable rules) */
rules?: string[];
/** Additional context for rules that need it */
context?: {
clientBundles?: ClientComponentBundle[];
routeConfig?: RouteSegmentConfig;
reactVersion?: string;
clientComponentPaths?: string[];
};
}Returns:
interface LspAnalysisResponse {
/** Diagnostics and suggestions found */
diagnostics: Array<Diagnostic | Suggestion>;
/** Analysis duration in milliseconds */
duration: number;
/** Rules that were executed */
rulesExecuted: string[];
/** Version of the analyzer */
version: string;
/** Error information */
error?: {
code: string;
message: string;
details?: unknown;
};
}Use Cases
Interactive Browser Demo
// Perfect for Svelte-style tutorial interfaces
import { analyze } from '@rsc-xray/lsp-server';
function handleCodeChange(code: string) {
const result = await analyze({
code,
fileName: 'demo.tsx',
});
displayDiagnostics(result.diagnostics);
}CodeMirror Integration
import { analyze } from '@rsc-xray/lsp-server';
import { EditorView, Decoration } from '@codemirror/view';
async function lintCode(code: string) {
const result = await analyze({ code, fileName: 'page.tsx' });
return result.diagnostics.map((d) => ({
from: getOffset(d.line, d.column),
to: getOffset(d.endLine || d.line, d.endColumn || d.column),
severity: d.severity,
message: d.message,
}));
}Monaco Editor Integration
import * as monaco from 'monaco-editor';
import { analyze } from '@rsc-xray/lsp-server';
monaco.languages.registerCodeLensProvider('typescript', {
async provideCodeLenses(model) {
const code = model.getValue();
const result = await analyze({ code, fileName: model.uri.path });
return {
lenses: result.diagnostics.map((d) => ({
range: new monaco.Range(d.line, d.column, d.line, d.column),
command: {
id: 'show-diagnostic',
title: d.message,
},
})),
};
},
});Architecture
This package is a thin orchestration layer (DRY principle):
┌─────────────────────────────────────────────┐
│ @rsc-xray/lsp-server │
│ - Error handling wrapper │
│ - Version metadata │
│ - Consistent API shape │
│ - Type safety │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ @rsc-xray/analyzer │
│ - 8 OSS analysis rules │
│ - AST parsing │
│ - Diagnostic generation │
└─────────────────────────────────────────────┘Design Principles:
- ✅ Universal: Works in browser, Node.js, VS Code, GitHub Actions
- ✅ DRY: Single orchestration layer for all consumers
- ✅ Simple: Pure JavaScript, no server/auth/caching (keep it simple!)
- ✅ Consistent: All consumers get same response format
- ✅ Tested: 11 comprehensive tests ensure reliability
Used By:
- OSS Demo: Browser-side interactive tutorial
- Pro Overlay: Node.js-based development server visualization
- Pro VS Code: Extension host analysis
- Pro CI: GitHub Actions budget enforcement
Upgrading to Pro
For server-side deployment with advanced features, upgrade to @rsc-xray/pro-lsp-server:
Pro Features
🚀 8 Advanced Performance Rules
- Route waterfall analysis
- Cache invalidation tracking
- ISR/PPR policy optimization
- Dynamic rendering detection
- And more!
🔒 Server-Side Deployment
- Rate limiting (demo and customer modes)
- Redis caching
- License validation
- Enterprise privacy mode
🎨 Visual Overlay
- Live diagnostics in your browser
- Performance metrics
- Cache visualization
💻 VS Code Extension
- Inline diagnostics
- Quick fixes
- Auto-refactoring
Installation
# Configure private registry
echo "@rsc-xray:registry=https://npm.pkg.github.com" >> .npmrc
# Install Pro packages
npm install @rsc-xray/pro-lsp-serverUsage
import { analyze } from '@rsc-xray/pro-lsp-server';
const result = await analyze({
code: routeCode,
fileName: 'app/page.tsx',
license: process.env.LICENSE_KEY, // Pro license required
});
// Now includes Pro rules:
// - route-waterfall
// - cache-tag-impact
// - cache-policy-mismatch
// - static-dynamic-mismatch
// - And more!Testing
import { analyze } from '@rsc-xray/lsp-server';
import { describe, it, expect } from 'vitest';
describe('LSP Server', () => {
it('should detect forbidden imports', async () => {
const result = await analyze({
code: `'use client';\nimport fs from 'fs';`,
fileName: 'components/Bad.tsx',
});
expect(result.diagnostics.length).toBeGreaterThan(0);
expect(result.diagnostics[0].rule).toBe('client-forbidden-import');
});
});License
MIT
Support
- Documentation: https://rsc-xray.dev/docs
- Issues: https://github.com/rsc-xray/rsc-xray/issues
- Pro Support: https://rsc-xray.dev/support
Made with ❤️ for the React Server Components community
