rasengan 1.2.1 → 1.2.2

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/lib/esm/core/plugins/index.js +10 -13
  3. package/lib/esm/entries/client/render.js +24 -7
  4. package/lib/esm/entries/server/entry.server.js +1 -1
  5. package/lib/esm/entries/server/error-template.js +14 -0
  6. package/lib/esm/entries/server/index.js +2 -1
  7. package/lib/esm/routing/components/index.js +12 -69
  8. package/lib/esm/routing/error-overlay/ErrorBoundaryFallback.js +42 -0
  9. package/lib/esm/routing/error-overlay/ErrorOverlay.js +60 -0
  10. package/lib/esm/routing/error-overlay/ErrorOverlayProvider.js +39 -0
  11. package/lib/esm/routing/error-overlay/error-overlay.css +472 -0
  12. package/lib/esm/routing/error-overlay/error-store.js +44 -0
  13. package/lib/esm/routing/error-overlay/index.js +2 -0
  14. package/lib/esm/routing/error-overlay/stack-utils.js +72 -0
  15. package/lib/esm/routing/utils/define-router.js +2 -2
  16. package/lib/esm/routing/utils/flat-routes.js +10 -5
  17. package/lib/esm/routing/utils/generate-routes.js +3 -2
  18. package/lib/esm/server/dev/handlers.js +9 -5
  19. package/lib/esm/server/dev/server.js +8 -1
  20. package/lib/esm/server/node/index.js +2 -4
  21. package/lib/esm/server/node/rendering.js +6 -4
  22. package/lib/tsconfig.esm.tsbuildinfo +1 -1
  23. package/lib/tsconfig.types.tsbuildinfo +1 -1
  24. package/lib/types/entries/server/entry.server.d.ts +2 -0
  25. package/lib/types/entries/server/error-template.d.ts +1 -0
  26. package/lib/types/routing/error-overlay/ErrorBoundaryFallback.d.ts +14 -0
  27. package/lib/types/routing/error-overlay/ErrorOverlay.d.ts +2 -0
  28. package/lib/types/routing/error-overlay/ErrorOverlayProvider.d.ts +7 -0
  29. package/lib/types/routing/error-overlay/error-store.d.ts +24 -0
  30. package/lib/types/routing/error-overlay/index.d.ts +3 -0
  31. package/lib/types/routing/error-overlay/stack-utils.d.ts +31 -0
  32. package/lib/types/server/node/rendering.d.ts +7 -1
  33. package/package.json +12 -11
  34. package/types/client.d.ts +1 -0
@@ -4,6 +4,7 @@ import { logRedirection as log } from '../../core/utils/log.js';
4
4
  import createRasenganRequest, { createRasenganHeaders, sendRasenganResponse, } from '../node/utils.js';
5
5
  import { join } from 'path';
6
6
  import { isStaticRedirectFromConfig, isRedirectResponse, extractMetaFromRRContext, extractHeadersFromRRContext, } from './utils.js';
7
+ import { renderErrorPage } from '../../entries/server/error-template.js';
7
8
  import { renderToString } from '../node/rendering.js';
8
9
  import { TemplateLayout } from '../../entries/server/index.js';
9
10
  /**
@@ -73,21 +74,24 @@ export async function handleDocumentRequest(req, res, runner, options) {
73
74
  // ...Object.fromEntries(headers),
74
75
  // });
75
76
  // }
76
- // Set headers
77
- res.writeHead(context.statusCode, {
78
- ...Object.fromEntries(headers),
79
- });
80
77
  const Router = (_jsx(StaticRouterProvider, { router: router, context: context, hydrate: true }));
81
78
  // If stream mode enabled, render the page as a plain text
82
79
  return await render(Router, res, {
83
80
  metadata,
81
+ statusCode: context.statusCode,
82
+ responseHeaders: Object.fromEntries(headers),
84
83
  });
85
84
  }
86
85
  return context;
87
86
  }
88
87
  catch (error) {
89
- // Just log the error for now
90
88
  console.error(error);
89
+ if (!res.headersSent) {
90
+ const html = renderErrorPage(error);
91
+ res.status(500);
92
+ res.setHeader('Content-Type', 'text/html');
93
+ return res.send(html);
94
+ }
91
95
  }
92
96
  }
93
97
  export async function handleDataRequest(request, handler) {
@@ -12,6 +12,7 @@ import { ServerMode } from '../runtime/mode.js';
12
12
  import { handleDataRequest, handleDocumentRequest, handleSpaModeRequest, } from './handlers.js';
13
13
  import { createStaticHandler } from 'react-router';
14
14
  import { generateRoutes, preloadMatches, } from '../../routing/utils/generate-routes.js';
15
+ import { renderErrorPage } from '../../entries/server/error-template.js';
15
16
  /**
16
17
  * Handle the request for the development environment
17
18
  * @param req
@@ -66,8 +67,13 @@ async function devRequestHandler(req, res, viteDevServer, options) {
66
67
  return res.status(404).send('Not found');
67
68
  }
68
69
  catch (error) {
69
- // Just log the error for now
70
70
  console.error(error);
71
+ if (res.headersSent)
72
+ return;
73
+ const html = renderErrorPage(error);
74
+ res.status(500);
75
+ res.setHeader('Content-Type', 'text/html');
76
+ return res.send(html);
71
77
  }
72
78
  }
73
79
  /**
@@ -133,6 +139,7 @@ async function createDevNodeServer({ port, base = '/', enableSearchingPort = fal
133
139
  hmr: {
134
140
  // TODO: Find a way to use a random port
135
141
  port: generateRandomPort(),
142
+ overlay: false,
136
143
  },
137
144
  },
138
145
  base,
@@ -70,16 +70,14 @@ export function createRequestHandler(options) {
70
70
  // Create static router
71
71
  let router = createStaticRouter(handler.dataRoutes, context);
72
72
  const headers = extractHeadersFromRRContext(context);
73
- // Set headers
74
- res.writeHead(context.statusCode, {
75
- ...Object.fromEntries(headers),
76
- });
77
73
  const Router = (_jsx(StaticRouterProvider, { router: router, context: context }));
78
74
  // If stream mode enabled, render the page as a plain text
79
75
  return await render(Router, res, {
80
76
  metadata,
81
77
  assets,
82
78
  buildOptions,
79
+ statusCode: context.statusCode,
80
+ responseHeaders: Object.fromEntries(headers),
83
81
  });
84
82
  }
85
83
  return context;
@@ -5,9 +5,10 @@ import { isServerMode, ServerMode } from '../runtime/mode.js';
5
5
  * Render a React component to a stream.
6
6
  * @param Component
7
7
  * @param res
8
+ * @param options
8
9
  * @returns
9
10
  */
10
- export const renderToStream = async (Component, res) => {
11
+ export const renderToStream = async (Component, res, options) => {
11
12
  const ABORT_DELAY = 10_000;
12
13
  let bootstrap = [];
13
14
  if (isServerMode(process.env.NODE_ENV) &&
@@ -20,6 +21,9 @@ export const renderToStream = async (Component, res) => {
20
21
  bootstrapModules: bootstrap,
21
22
  onShellReady() {
22
23
  shellRendered = true;
24
+ if (!res.headersSent && options?.responseHeaders) {
25
+ res.writeHead(options.statusCode ?? 200, options.responseHeaders);
26
+ }
23
27
  resolve(res);
24
28
  pipe(res);
25
29
  },
@@ -27,11 +31,9 @@ export const renderToStream = async (Component, res) => {
27
31
  reject(error);
28
32
  },
29
33
  onError(error) {
30
- // Log streaming rendering errors from inside the shell. Don't log
31
- // errors encountered during initial shell rendering since they'll
32
- // reject and get logged in handleDocumentRequest.
33
34
  if (shellRendered) {
34
35
  console.error(error);
36
+ abort();
35
37
  }
36
38
  },
37
39
  });
@@ -1 +1 @@
1
- {"root":["../src/client.ts","../src/index.ts","../src/plugin.ts","../src/server.ts","../src/cli/index.ts","../src/core/index.ts","../src/core/types.ts","../src/core/config/index.ts","../src/core/config/type.ts","../src/core/config/utils/define-config.ts","../src/core/config/utils/load-modules.ts","../src/core/config/utils/path.ts","../src/core/config/vite/defaults.ts","../src/core/dynamic/index.tsx","../src/core/middlewares/index.ts","../src/core/middlewares/logger.ts","../src/core/plugins/index.ts","../src/core/utils/log.ts","../src/entries/client/render.tsx","../src/entries/server/entry.server.tsx","../src/entries/server/index.tsx","../src/routing/index.ts","../src/routing/interfaces.tsx","../src/routing/types.ts","../src/routing/components/fallback.tsx","../src/routing/components/index.tsx","../src/routing/components/template.tsx","../src/routing/providers/metadata.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.tsx","../src/routing/utils/define-static-paths.tsx","../src/routing/utils/flat-routes.tsx","../src/routing/utils/generate-metadata.tsx","../src/routing/utils/generate-routes.tsx","../src/routing/utils/index.tsx","../src/scripts/build-command.js","../src/scripts/generate-package-json.js","../src/scripts/utils/check-os.js","../src/scripts/utils/copy.js","../src/server/build/index.ts","../src/server/build/manifest.tsx","../src/server/build/rendering.tsx","../src/server/dev/handlers.tsx","../src/server/dev/server.ts","../src/server/dev/utils.ts","../src/server/node/index.tsx","../src/server/node/rendering.ts","../src/server/node/stream.ts","../src/server/node/utils.ts","../src/server/runtime/detect-runtime.ts","../src/server/runtime/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.9.3"}
1
+ {"root":["../src/client.ts","../src/index.ts","../src/plugin.ts","../src/server.ts","../src/cli/index.ts","../src/core/index.ts","../src/core/types.ts","../src/core/config/index.ts","../src/core/config/type.ts","../src/core/config/utils/define-config.ts","../src/core/config/utils/load-modules.ts","../src/core/config/utils/path.ts","../src/core/config/vite/defaults.ts","../src/core/dynamic/index.tsx","../src/core/middlewares/index.ts","../src/core/middlewares/logger.ts","../src/core/plugins/index.ts","../src/core/utils/log.ts","../src/entries/client/render.tsx","../src/entries/server/entry.server.tsx","../src/entries/server/error-template.tsx","../src/entries/server/index.tsx","../src/routing/index.ts","../src/routing/interfaces.tsx","../src/routing/types.ts","../src/routing/components/fallback.tsx","../src/routing/components/index.tsx","../src/routing/components/template.tsx","../src/routing/error-overlay/ErrorBoundaryFallback.tsx","../src/routing/error-overlay/ErrorOverlay.tsx","../src/routing/error-overlay/ErrorOverlayProvider.tsx","../src/routing/error-overlay/error-store.ts","../src/routing/error-overlay/index.ts","../src/routing/error-overlay/stack-utils.ts","../src/routing/providers/metadata.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.tsx","../src/routing/utils/define-static-paths.tsx","../src/routing/utils/flat-routes.tsx","../src/routing/utils/generate-metadata.tsx","../src/routing/utils/generate-routes.tsx","../src/routing/utils/index.tsx","../src/scripts/build-command.js","../src/scripts/generate-package-json.js","../src/scripts/utils/check-os.js","../src/scripts/utils/copy.js","../src/server/build/index.ts","../src/server/build/manifest.tsx","../src/server/build/rendering.tsx","../src/server/dev/handlers.tsx","../src/server/dev/server.ts","../src/server/dev/utils.ts","../src/server/node/index.tsx","../src/server/node/rendering.ts","../src/server/node/stream.ts","../src/server/node/utils.ts","../src/server/runtime/detect-runtime.ts","../src/server/runtime/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.9.3"}
@@ -1 +1 @@
1
- {"root":["../src/client.ts","../src/index.ts","../src/plugin.ts","../src/server.ts","../src/cli/index.ts","../src/core/index.ts","../src/core/types.ts","../src/core/config/index.ts","../src/core/config/type.ts","../src/core/config/utils/define-config.ts","../src/core/config/utils/load-modules.ts","../src/core/config/utils/path.ts","../src/core/config/vite/defaults.ts","../src/core/dynamic/index.tsx","../src/core/middlewares/index.ts","../src/core/middlewares/logger.ts","../src/core/plugins/index.ts","../src/core/utils/log.ts","../src/entries/client/render.tsx","../src/entries/server/entry.server.tsx","../src/entries/server/index.tsx","../src/routing/index.ts","../src/routing/interfaces.tsx","../src/routing/types.ts","../src/routing/components/fallback.tsx","../src/routing/components/index.tsx","../src/routing/components/template.tsx","../src/routing/providers/metadata.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.tsx","../src/routing/utils/define-static-paths.tsx","../src/routing/utils/flat-routes.tsx","../src/routing/utils/generate-metadata.tsx","../src/routing/utils/generate-routes.tsx","../src/routing/utils/index.tsx","../src/scripts/build-command.js","../src/scripts/generate-package-json.js","../src/scripts/utils/check-os.js","../src/scripts/utils/copy.js","../src/server/build/index.ts","../src/server/build/manifest.tsx","../src/server/build/rendering.tsx","../src/server/dev/handlers.tsx","../src/server/dev/server.ts","../src/server/dev/utils.ts","../src/server/node/index.tsx","../src/server/node/rendering.ts","../src/server/node/stream.ts","../src/server/node/utils.ts","../src/server/runtime/detect-runtime.ts","../src/server/runtime/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.9.3"}
1
+ {"root":["../src/client.ts","../src/index.ts","../src/plugin.ts","../src/server.ts","../src/cli/index.ts","../src/core/index.ts","../src/core/types.ts","../src/core/config/index.ts","../src/core/config/type.ts","../src/core/config/utils/define-config.ts","../src/core/config/utils/load-modules.ts","../src/core/config/utils/path.ts","../src/core/config/vite/defaults.ts","../src/core/dynamic/index.tsx","../src/core/middlewares/index.ts","../src/core/middlewares/logger.ts","../src/core/plugins/index.ts","../src/core/utils/log.ts","../src/entries/client/render.tsx","../src/entries/server/entry.server.tsx","../src/entries/server/error-template.tsx","../src/entries/server/index.tsx","../src/routing/index.ts","../src/routing/interfaces.tsx","../src/routing/types.ts","../src/routing/components/fallback.tsx","../src/routing/components/index.tsx","../src/routing/components/template.tsx","../src/routing/error-overlay/ErrorBoundaryFallback.tsx","../src/routing/error-overlay/ErrorOverlay.tsx","../src/routing/error-overlay/ErrorOverlayProvider.tsx","../src/routing/error-overlay/error-store.ts","../src/routing/error-overlay/index.ts","../src/routing/error-overlay/stack-utils.ts","../src/routing/providers/metadata.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.tsx","../src/routing/utils/define-static-paths.tsx","../src/routing/utils/flat-routes.tsx","../src/routing/utils/generate-metadata.tsx","../src/routing/utils/generate-routes.tsx","../src/routing/utils/index.tsx","../src/scripts/build-command.js","../src/scripts/generate-package-json.js","../src/scripts/utils/check-os.js","../src/scripts/utils/copy.js","../src/server/build/index.ts","../src/server/build/manifest.tsx","../src/server/build/rendering.tsx","../src/server/dev/handlers.tsx","../src/server/dev/server.ts","../src/server/dev/utils.ts","../src/server/node/index.tsx","../src/server/node/rendering.ts","../src/server/node/stream.ts","../src/server/node/utils.ts","../src/server/runtime/detect-runtime.ts","../src/server/runtime/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.9.3"}
@@ -9,6 +9,8 @@ export type RenderStreamFunction = (StaticRouterComponent: React.ReactNode, res:
9
9
  };
10
10
  assets?: JSX.Element[];
11
11
  buildOptions?: BuildOptions;
12
+ statusCode?: number;
13
+ responseHeaders?: Record<string, string>;
12
14
  }, stream?: boolean) => Promise<void | string>;
13
15
  /**
14
16
  * Render the app to a stream
@@ -0,0 +1 @@
1
+ export declare function renderErrorPage(error: unknown): string;
@@ -0,0 +1,14 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ interface Props {
3
+ children: ReactNode;
4
+ }
5
+ interface State {
6
+ hasError: boolean;
7
+ }
8
+ export declare class ErrorBoundaryFallback extends Component<Props, State> {
9
+ constructor(props: Props);
10
+ static getDerivedStateFromError(): State;
11
+ componentDidCatch(error: Error, info: ErrorInfo): void;
12
+ render(): ReactNode;
13
+ }
14
+ export {};
@@ -0,0 +1,2 @@
1
+ import './error-overlay.css';
2
+ export declare function ErrorOverlay(): import("react").ReactPortal;
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from 'react';
2
+ interface Props {
3
+ children: ReactNode;
4
+ devMode: boolean;
5
+ }
6
+ export declare function ErrorOverlayProvider({ children, devMode }: Props): import("react/jsx-runtime.js").JSX.Element;
7
+ export {};
@@ -0,0 +1,24 @@
1
+ type ErrorSource = 'route' | 'react' | 'vite' | 'global';
2
+ type ErrorEntry = {
3
+ id: number;
4
+ error: Error;
5
+ source: ErrorSource;
6
+ timestamp: number;
7
+ componentStack?: string;
8
+ };
9
+ type Listener = () => void;
10
+ declare class ErrorStore {
11
+ private errors;
12
+ private listeners;
13
+ private nextId;
14
+ private minimized;
15
+ addError(error: Error, source?: ErrorSource, componentStack?: string): void;
16
+ clearAll(): void;
17
+ toggleMinimize(): void;
18
+ getErrors(): ErrorEntry[];
19
+ isMinimized(): boolean;
20
+ subscribe(listener: Listener): () => void;
21
+ private notify;
22
+ }
23
+ export declare const errorStore: ErrorStore;
24
+ export type { ErrorEntry, ErrorSource };
@@ -0,0 +1,3 @@
1
+ export { ErrorOverlayProvider } from './ErrorOverlayProvider.js';
2
+ export { errorStore } from './error-store.js';
3
+ export type { ErrorEntry, ErrorSource } from './error-store.js';
@@ -0,0 +1,31 @@
1
+ type StackFrame = {
2
+ file: string;
3
+ line: number;
4
+ column: number;
5
+ };
6
+ /**
7
+ * Parse an error stack trace and extract the first meaningful user frame.
8
+ * Handles Chrome/V8 and Firefox stack trace formats.
9
+ * Skips the "Error:" header line.
10
+ *
11
+ * @param stack - The full error.stack string
12
+ * @returns The first parsed StackFrame, or null if none found
13
+ */
14
+ export declare function parseStackFrame(stack: string): StackFrame | null;
15
+ /**
16
+ * Fetch the source file from the Vite dev server (using ?raw to get original
17
+ * source) and return a snippet of lines surrounding the error line.
18
+ *
19
+ * @param file - File path (absolute, e.g. /src/app/page.tsx)
20
+ * @param errorLine - 1-indexed line number where the error occurred
21
+ * @param contextLines - Number of lines to show above and below the error
22
+ * @returns An object with the snippet string, the 0-indexed error line
23
+ * position within the snippet, and the total file line count,
24
+ * or null on failure
25
+ */
26
+ export declare function fetchSourceSnippet(file: string, errorLine: number, contextLines?: number): Promise<{
27
+ snippet: string;
28
+ errorLineIndex: number;
29
+ totalLines: number;
30
+ } | null>;
31
+ export {};
@@ -1,9 +1,15 @@
1
1
  import type * as Express from 'express';
2
+ type StreamOptions = {
3
+ statusCode?: number;
4
+ responseHeaders?: Record<string, string>;
5
+ };
2
6
  /**
3
7
  * Render a React component to a stream.
4
8
  * @param Component
5
9
  * @param res
10
+ * @param options
6
11
  * @returns
7
12
  */
8
- export declare const renderToStream: (Component: React.ReactNode, res: Express.Response) => Promise<unknown>;
13
+ export declare const renderToStream: (Component: React.ReactNode, res: Express.Response, options?: StreamOptions) => Promise<unknown>;
9
14
  export declare const renderToString: (Component: React.ReactNode) => string;
15
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rasengan",
3
3
  "private": false,
4
- "version": "1.2.1",
4
+ "version": "1.2.2",
5
5
  "description": "The modern React Framework",
6
6
  "type": "module",
7
7
  "main": "lib/esm/index.js",
@@ -59,6 +59,15 @@
59
59
  "issues": "https://github.com/rasengan-dev/rasenganjs/issues"
60
60
  },
61
61
  "homepage": "https://rasengan.dev",
62
+ "scripts": {
63
+ "build:compile": "tsc -b ./tsconfig.esm.json ./tsconfig.types.json",
64
+ "build:clean": "rimraf ./lib",
65
+ "build:copy-assets": "mkdir -p lib/esm/routing/error-overlay && cp src/routing/error-overlay/error-overlay.css lib/esm/routing/error-overlay/",
66
+ "build": "pnpm run build:clean & pnpm run build:compile && pnpm run build:copy-assets",
67
+ "deploy": "pnpm publish --access public",
68
+ "deploy:beta": "pnpm run deploy --tag beta",
69
+ "pack": "pnpm pack --pack-destination ./release/"
70
+ },
62
71
  "dependencies": {
63
72
  "@vitejs/plugin-react": "^5.0.0",
64
73
  "chalk": "^5.3.0",
@@ -131,13 +140,5 @@
131
140
  "ssr",
132
141
  "ssg",
133
142
  "spa"
134
- ],
135
- "scripts": {
136
- "build:compile": "tsc -b ./tsconfig.esm.json ./tsconfig.types.json",
137
- "build:clean": "rimraf ./lib",
138
- "build": "pnpm run build:clean & pnpm run build:compile",
139
- "deploy": "pnpm publish --access public",
140
- "deploy:beta": "pnpm run deploy --tag beta",
141
- "pack": "pnpm pack --pack-destination ./release/"
142
- }
143
- }
143
+ ]
144
+ }
package/types/client.d.ts CHANGED
@@ -50,4 +50,5 @@ declare module 'virtual:rasengan/router' {
50
50
  interface Window {
51
51
  __staticRouterHydrationData: any;
52
52
  __RASENGAN_SPA_MODE__: boolean;
53
+ __RASENGAN_SSR_ERROR__?: { message: string; stack?: string; name: string };
53
54
  }