rasengan 1.0.0-beta.57 → 1.0.0-beta.59

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 1.0.0-beta.59 (2025-03-16)
4
+
5
+ ## 1.0.0-beta.58 (2025-03-12)
6
+
3
7
  ## 1.0.0-beta.57 (2025-03-04)
4
8
 
5
9
  ## 1.0.0-beta.56 (2025-03-03)
@@ -21,7 +21,7 @@ export const defineConfig = async (loadedConfig) => {
21
21
  else {
22
22
  config = loadedConfig;
23
23
  }
24
- const { server, vite, redirects } = config;
24
+ const { ssr, server, vite, redirects } = config;
25
25
  // Define default values for vite config coming from loadedConfig.vite
26
26
  const defaultViteConfig = {
27
27
  ...vite,
@@ -41,6 +41,7 @@ export const defineConfig = async (loadedConfig) => {
41
41
  const defaultRedirectsConfig = redirects || (() => new Promise((resolve) => resolve([])));
42
42
  try {
43
43
  const config = {
44
+ ssr: ssr ?? true,
44
45
  server: defaultServerConfig,
45
46
  vite: {
46
47
  ...defaultViteConfig,
@@ -60,6 +61,7 @@ export const defineConfig = async (loadedConfig) => {
60
61
  }
61
62
  catch (error) {
62
63
  return {
64
+ ssr: true,
63
65
  vite: {
64
66
  appType: 'custom',
65
67
  resolve: {
@@ -28,11 +28,13 @@ export const createDefaultViteConfig = (rootPath, __dirname, mode, config) => {
28
28
  return 'vendor';
29
29
  if (id.includes('src/components'))
30
30
  return 'shared-components';
31
- if (id.includes('src/app') && id.includes('.page.')) {
32
- const parts = id.split('src/app')[1]?.split('/');
33
- if (parts?.length) {
34
- const pageName = parts.pop()?.split('.')[0];
35
- return pageName ? `page-${pageName}` : undefined;
31
+ if (config.ssr) {
32
+ if (id.includes('src/app') && id.includes('.page.')) {
33
+ const parts = id.split('src/app')[1]?.split('/');
34
+ if (parts?.length) {
35
+ const pageName = parts.pop()?.split('.')[0];
36
+ return pageName ? `page-${pageName}` : undefined;
37
+ }
36
38
  }
37
39
  }
38
40
  return undefined;
@@ -78,7 +80,9 @@ export const createDefaultViteConfig = (rootPath, __dirname, mode, config) => {
78
80
  },
79
81
  builder: {
80
82
  buildApp: async (builder) => {
81
- await builder.build(builder.environments.ssr);
83
+ if (config.ssr) {
84
+ await builder.build(builder.environments.ssr);
85
+ }
82
86
  await builder.build(builder.environments.client);
83
87
  },
84
88
  },
@@ -1,30 +1,34 @@
1
- import { resolve } from 'path';
1
+ import path, { resolve } from 'path';
2
2
  import fs from 'fs';
3
3
  import { loadModuleSSR } from '../config/utils/load-modules.js';
4
- function loadRasenganConfig() {
4
+ import { resolveBuildOptions } from '../../server.js';
5
+ import { renderIndexHTML } from '../../server/build/rendering.js';
6
+ function loadRasenganGlobal() {
5
7
  return {
6
8
  name: 'vite-plugin-rasengan-config',
7
9
  async config(_, { command }) {
8
10
  if (command !== 'build')
9
11
  return;
10
- const configPath = resolve(process.cwd(), 'rasengan.config.js');
11
- if (!fs.existsSync(configPath)) {
12
- throw new Error(`Configuration file not found at: ${configPath}`);
12
+ const packageJsonPath = resolve(process.cwd(), 'package.json');
13
+ if (!fs.existsSync(packageJsonPath)) {
14
+ throw new Error(`Package.json file not found at: ${packageJsonPath}`);
13
15
  }
14
- const rasenganConfig = await (await loadModuleSSR(configPath)).default;
15
- const partialConfig = {
16
- server: rasenganConfig.server ?? {},
17
- redirects: rasenganConfig.redirects
18
- ? await rasenganConfig.redirects()
19
- : [],
16
+ const packageJsonRaw = fs.readFileSync(packageJsonPath, {
17
+ encoding: 'utf-8',
18
+ });
19
+ const packageJson = JSON.parse(packageJsonRaw);
20
+ const rasenganConfig = {
21
+ version: packageJson.version,
22
+ ssr: true,
20
23
  };
21
24
  // Inject the configuration as a global constant
22
25
  return {
23
26
  define: {
24
- ['__RASENGAN_CONFIG__']: JSON.stringify(partialConfig),
27
+ ['Rasengan']: JSON.stringify(rasenganConfig),
25
28
  },
26
29
  };
27
30
  },
31
+ apply: 'build',
28
32
  };
29
33
  }
30
34
  function rasenganConfigPlugin() {
@@ -89,21 +93,67 @@ export const Adapters = {
89
93
  DEFAULT: '',
90
94
  };
91
95
  export function rasengan({ adapter = { name: Adapters.DEFAULT, prepare: async () => { } }, }) {
92
- let config = {};
96
+ let config;
97
+ let viteConfig;
98
+ const templateFileRegex = /template\.(tsx|jsx)$/;
99
+ const buildOptions = resolveBuildOptions({});
93
100
  return {
94
101
  name: 'vite-plugin-rasengan',
102
+ async config() {
103
+ // load rasengan.config.js
104
+ const configPath = resolve(process.cwd(), 'rasengan.config.js');
105
+ if (!fs.existsSync(configPath)) {
106
+ throw new Error(`Configuration file not found at: ${configPath}`);
107
+ }
108
+ const rasenganConfigHandler = await (await loadModuleSSR(configPath)).default;
109
+ config = await rasenganConfigHandler();
110
+ },
111
+ async load(id) {
112
+ if (id === 'virtual:rasengan-config') {
113
+ return `
114
+ export const __RASENGAN_CONFIG__ = ${JSON.stringify(config)};
115
+ `;
116
+ }
117
+ },
95
118
  configResolved(resolvedConfig) {
96
- config = resolvedConfig;
119
+ viteConfig = resolvedConfig;
120
+ },
121
+ async writeBundle(_) {
122
+ const modulePaths = ['template.jsx', 'template.tsx'].map((file) => {
123
+ return path.posix.join(process.cwd(), 'src', file);
124
+ });
125
+ const modulePath = modulePaths.find((modulePath) => {
126
+ return fs.existsSync(modulePath);
127
+ });
128
+ const module = await this.load({ id: modulePath });
129
+ // Generate the template.js file into the dist/assets
130
+ fs.writeFileSync(path.posix.join(process.cwd(), buildOptions.buildDirectory, buildOptions.clientPathDirectory, buildOptions.assetPathDirectory, 'template.js'), module.code, 'utf-8');
97
131
  },
98
132
  async closeBundle() {
99
133
  // We check here if the environment is client has been built because it's the
100
134
  // last environment to be built in the Vite build process
101
135
  if (this.environment.name === 'client') {
136
+ // Check if SPA mode is enabled
137
+ if (!config.ssr) {
138
+ // Load the template.js file
139
+ const templatePath = path.posix.join(process.cwd(), buildOptions.buildDirectory, buildOptions.clientPathDirectory, buildOptions.assetPathDirectory, 'template.js');
140
+ const Template = (await import(templatePath)).default;
141
+ // Render the index.html file
142
+ await renderIndexHTML(Template, {
143
+ rootPath: process.cwd(),
144
+ config,
145
+ });
146
+ }
147
+ // Generate a config.json file into the dist/client/assets
148
+ fs.writeFileSync(path.posix.join(process.cwd(), buildOptions.buildDirectory, buildOptions.clientPathDirectory, buildOptions.assetPathDirectory, 'config.json'), JSON.stringify({
149
+ buildOptions,
150
+ ssr: config.ssr,
151
+ }), 'utf-8');
102
152
  // Preparing app for deployment
103
153
  switch (adapter.name) {
104
154
  case Adapters.VERCEL: {
105
155
  console.log('Preparing app for deployment to Vercel');
106
- await adapter.prepare();
156
+ // await adapter.prepare();
107
157
  break;
108
158
  }
109
159
  default:
@@ -111,6 +161,7 @@ export function rasengan({ adapter = { name: Adapters.DEFAULT, prepare: async ()
111
161
  }
112
162
  }
113
163
  },
164
+ apply: 'build',
114
165
  };
115
166
  }
116
- export const plugins = [];
167
+ export const plugins = [loadRasenganGlobal()];
@@ -1,12 +1,24 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { hydrateRoot } from 'react-dom/client';
2
+ import { hydrateRoot, createRoot } from 'react-dom/client';
3
3
  import { StrictMode } from 'react';
4
4
  import { RootComponent } from '../../routing/components/template.js';
5
+ const isSpaMode = window.__RASENGAN_SPA_MODE__;
5
6
  export default function renderApp(App, options) {
6
7
  const root = document.getElementById('root');
7
8
  if (!root) {
8
9
  throw new Error('Root element not found');
9
10
  }
11
+ // If SPA mode, render the app
12
+ if (isSpaMode) {
13
+ if (options.reactStrictMode) {
14
+ createRoot(root).render(_jsx(StrictMode, { children: _jsx(App, { Component: RootComponent }) }));
15
+ }
16
+ else {
17
+ createRoot(root).render(_jsx(App, { Component: RootComponent }));
18
+ }
19
+ return;
20
+ }
21
+ // Handling hydration
10
22
  if (options.reactStrictMode) {
11
23
  hydrateRoot(root, _jsx(StrictMode, { children: _jsx(App, { Component: RootComponent }) }));
12
24
  }
@@ -2,9 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { BodyComponent, HeadComponent, RootComponent, ScriptComponent, } from '../../routing/components/template.js';
4
4
  import { isServerMode, ServerMode } from '../../server/runtime/mode.js';
5
- export const TemplateLayout = ({ StaticRouterComponent, metadata, assets, App, Template, }) => {
5
+ export const TemplateLayout = ({ StaticRouterComponent, metadata, assets, App, Template, isSpaMode = false, }) => {
6
6
  // inject vite refresh script to avoid "React refresh preamble was not loaded"
7
7
  let viteScripts = _jsx(React.Fragment, {});
8
+ let otherScripts = _jsx(React.Fragment, {});
8
9
  if (isServerMode(process.env.NODE_ENV) &&
9
10
  process.env.NODE_ENV === ServerMode.Development) {
10
11
  const refreshScript = `
@@ -16,5 +17,15 @@ export const TemplateLayout = ({ StaticRouterComponent, metadata, assets, App, T
16
17
  `;
17
18
  viteScripts = (_jsxs(React.Fragment, { children: [_jsx("script", { type: "module", src: "/@vite/client" }), _jsx("script", { type: "module", dangerouslySetInnerHTML: { __html: refreshScript } })] }));
18
19
  }
19
- return (_jsx(Template, { Head: ({ children }) => (_jsxs(HeadComponent, { metadata: metadata, assets: assets, children: [viteScripts, children] })), Body: ({ children }) => (_jsx(BodyComponent, { asChild: true, AppContent: _jsx(App, { Component: RootComponent, children: StaticRouterComponent }), children: children })), Script: ({ children }) => _jsx(ScriptComponent, { children: children }) }));
20
+ if (isSpaMode) {
21
+ otherScripts = (_jsxs(React.Fragment, { children: [_jsx("script", { type: "module", dangerouslySetInnerHTML: {
22
+ __html: `window.__RASENGAN_SPA_MODE__ = true;`,
23
+ } }), !assets && (_jsx("script", { type: "module", src: "/src/index", async: true }))] }));
24
+ }
25
+ else {
26
+ otherScripts = (_jsx(React.Fragment, { children: _jsx("script", { type: "module", dangerouslySetInnerHTML: {
27
+ __html: `window.__RASENGAN_SPA_MODE__ = false;`,
28
+ } }) }));
29
+ }
30
+ return (_jsx(Template, { Head: ({ children }) => (_jsxs(HeadComponent, { metadata: metadata, assets: assets, children: [viteScripts, otherScripts, children] })), Body: ({ children }) => (_jsx(BodyComponent, { asChild: App ? true : false, AppContent: App && _jsx(App, { Component: RootComponent, children: StaticRouterComponent }), children: children })), Script: ({ children }) => _jsx(ScriptComponent, { children: children }) }));
20
31
  };
@@ -23,18 +23,22 @@ export const HeadComponent = ({ metadata, assets = [], children = undefined, })
23
23
  // Generate meta tags
24
24
  const metaTags = React.useMemo(() => {
25
25
  const metadatas = [];
26
- if (metadata.page)
27
- metadatas.push(metadata.page);
28
- if (metadata.layout)
29
- metadatas.push(metadata.layout);
26
+ if (metadata) {
27
+ if (metadata.page)
28
+ metadatas.push(metadata.page);
29
+ if (metadata.layout)
30
+ metadatas.push(metadata.layout);
31
+ }
30
32
  return generateMetadata(metadatas);
31
33
  }, [metadata]);
32
34
  const { title, description } = useMemo(() => {
35
+ if (!metadata)
36
+ return { title: 'Rasengan', description: '' };
33
37
  const title = metadata.page.title;
34
38
  const description = metadata.page.description;
35
39
  return { title, description };
36
40
  }, [metadata]);
37
- return (_jsxs("head", { children: [metaTags, assets, _jsx("title", { children: title }), _jsx("meta", { name: "description", content: description, "data-rg": "true" }), children] }));
41
+ return (_jsxs("head", { children: [_jsx("meta", { name: "generator", content: "Rasengan.js" }), metaTags, assets, _jsx("title", { children: title }), _jsx("meta", { name: "description", content: description, "data-rg": "true" }), children] }));
38
42
  };
39
43
  /**
40
44
  * Body component
@@ -42,7 +46,7 @@ export const HeadComponent = ({ metadata, assets = [], children = undefined, })
42
46
  export const BodyComponent = ({ children = undefined, asChild = false, AppContent = undefined, }) => {
43
47
  return (_jsxs("body", { children: [_jsx("noscript", { dangerouslySetInnerHTML: {
44
48
  __html: `<b>Enable JavaScript to run this app.</b>`,
45
- } }), asChild ? (_jsx("div", { id: "root", children: AppContent })) : (_jsx("div", { id: "root", children: 'rasengan-body-app' })), children] }));
49
+ } }), _jsx("div", { id: "root", children: asChild && AppContent }), children] }));
46
50
  };
47
51
  /**
48
52
  * Scripts component
@@ -24,7 +24,7 @@ const generateRoutesGroup = (path, children) => {
24
24
  pages.push(...childrenPages);
25
25
  }
26
26
  else {
27
- const routePath = path[0] === '/' ? path : `/${path}`;
27
+ const routePath = path === '/' ? '' : path[0] === '/' ? path : `/${path}`;
28
28
  // Check if the page is a PageComponent
29
29
  if (page['path']) {
30
30
  const pagePath = page['path'][0] === '/' ? page['path'].slice(1) : page['path'];
@@ -20,16 +20,27 @@ export const getRouter = (routerInstance) => {
20
20
  /**
21
21
  * This function merge the metadata, giving priority to the ones comming from loader
22
22
  */
23
- const mergeMetaData = (responseMeta, meta) => {
23
+ const mergeMetaData = (responseMeta, meta, isLayout = false) => {
24
24
  let mergedMetaData = {
25
25
  metaTags: [],
26
26
  links: [],
27
+ openGraph: {
28
+ url: '',
29
+ image: '',
30
+ },
31
+ twitter: {
32
+ card: 'summary_large_image',
33
+ image: '',
34
+ title: '',
35
+ },
27
36
  };
28
- // merge title and description
29
- mergedMetaData['title'] =
30
- responseMeta.title ?? meta.title;
31
- mergedMetaData['description'] =
32
- responseMeta.description ?? meta.description;
37
+ if (!isLayout) {
38
+ // merge title and description
39
+ mergedMetaData['title'] =
40
+ responseMeta.title ?? meta.title;
41
+ mergedMetaData['description'] =
42
+ responseMeta.description ?? meta.description;
43
+ }
33
44
  // merge openGraph datas
34
45
  mergedMetaData['openGraph'] = {
35
46
  ...meta.openGraph,
@@ -100,7 +111,7 @@ const mergeMetaData = (responseMeta, meta) => {
100
111
  /**
101
112
  * This function create a loader function
102
113
  */
103
- const createLoaderFunction = ({ loader, metadata, }) => {
114
+ const createLoaderFunction = ({ loader, metadata, isLayout = false, }) => {
104
115
  return async ({ params, request }) => {
105
116
  try {
106
117
  // Check if the loader is defined
@@ -125,14 +136,26 @@ const createLoaderFunction = ({ loader, metadata, }) => {
125
136
  }
126
137
  return {
127
138
  props: response.props,
128
- meta: mergeMetaData(response.meta, metadata),
139
+ meta: mergeMetaData(response.meta ?? {}, metadata, isLayout),
129
140
  };
130
141
  }
131
142
  catch (error) {
132
143
  console.error(error);
133
144
  return {
134
145
  props: {},
135
- meta: {},
146
+ meta: {
147
+ openGraph: {
148
+ url: '',
149
+ image: '',
150
+ },
151
+ twitter: {
152
+ card: 'summary_large_image',
153
+ image: '',
154
+ title: '',
155
+ },
156
+ metaTags: [],
157
+ links: [],
158
+ },
136
159
  };
137
160
  }
138
161
  };
@@ -171,14 +194,30 @@ export const generateRoutes = (router, isRoot = true, parentLayout = undefined)
171
194
  },
172
195
  async loader({ params, request }) {
173
196
  // Extract metadata from the layout
174
- const metadata = Layout.metadata;
175
- return createLoaderFunction({ loader: Layout.loader, metadata })({
197
+ const metadata = {
198
+ openGraph: {
199
+ url: '',
200
+ image: '',
201
+ },
202
+ twitter: {
203
+ card: 'summary_large_image',
204
+ image: '',
205
+ title: '',
206
+ },
207
+ ...Layout.metadata,
208
+ };
209
+ return createLoaderFunction({
210
+ loader: Layout.loader,
211
+ metadata,
212
+ isLayout: true,
213
+ })({
176
214
  params,
177
215
  request,
178
216
  });
179
217
  },
180
218
  children: [],
181
219
  nested: router.useParentLayout,
220
+ hydrateFallbackElement: _jsx(_Fragment, {}), // TODO: Add hydration fallback
182
221
  };
183
222
  // Defining the page not found route
184
223
  if (isRoot || router.notFoundComponent) {
@@ -208,7 +247,18 @@ export const generateRoutes = (router, isRoot = true, parentLayout = undefined)
208
247
  path,
209
248
  async loader({ params, request }) {
210
249
  // Extracting metadata from the page
211
- const metadata = Page.metadata;
250
+ const metadata = {
251
+ openGraph: {
252
+ url: '',
253
+ image: '',
254
+ },
255
+ twitter: {
256
+ card: 'summary_large_image',
257
+ image: '',
258
+ title: '',
259
+ },
260
+ ...Page.metadata,
261
+ };
212
262
  return createLoaderFunction({ loader: Page.loader, metadata })({
213
263
  params,
214
264
  request,
@@ -225,7 +275,7 @@ export const generateRoutes = (router, isRoot = true, parentLayout = undefined)
225
275
  return (_jsx(Suspense, { fallback: _jsx(_Fragment, { children: "Loading" }), children: _jsx(RasenganPageComponent, { page: Page, data: loaderData }) }));
226
276
  },
227
277
  errorElement: _jsx(ErrorBoundary, {}),
228
- hydrateFallbackElement: _jsx(_Fragment, { children: "Loading" }),
278
+ hydrateFallbackElement: _jsx(_Fragment, {}), // TODO: Add hydration fallback
229
279
  };
230
280
  });
231
281
  // Add pages into children of the current route
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { TemplateLayout } from "../../entries/server/index.js";
3
+ import { renderToString } from "../node/rendering.js";
4
+ import { resolveBuildOptions } from "./index.js";
5
+ import { ManifestManager } from "./manifest.js";
6
+ import path from "node:path";
7
+ import fs from "node:fs/promises";
8
+ export const renderIndexHTML = async (template, options) => {
9
+ const { rootPath, config } = options;
10
+ const buildOptions = resolveBuildOptions({});
11
+ const manifest = new ManifestManager(path.posix.join(buildOptions.buildDirectory, buildOptions.clientPathDirectory, buildOptions.manifestPathDirectory, 'manifest.json'));
12
+ // Get assets tags
13
+ const assets = manifest.generateMetaTags(''); // TODO: Add the correct path
14
+ // Generate html from template
15
+ const html = renderToString(_jsx(TemplateLayout, { Template: template, assets: assets, isSpaMode: true }));
16
+ // Render the html into an index.html file
17
+ await fs.writeFile(path.posix.join(rootPath, buildOptions.buildDirectory, buildOptions.clientPathDirectory, 'index.html'), html, 'utf-8');
18
+ };
@@ -4,6 +4,8 @@ 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 { renderToString } from '../node/rendering.js';
8
+ import { TemplateLayout } from '../../entries/server/index.js';
7
9
  /**
8
10
  * Handle redirect request
9
11
  * @param req
@@ -94,3 +96,30 @@ export async function handleDataRequest(request, handler) {
94
96
  headers: { 'Content-Type': 'application/json' },
95
97
  });
96
98
  }
99
+ export async function handleSpaModeRequest(res, runner, options) {
100
+ try {
101
+ // Import Template
102
+ const Template = (await runner.import(`${options.rootPath}/src/template`))
103
+ .default;
104
+ // Convert TemplateLayout to string
105
+ const html = renderToString(_jsx(TemplateLayout, { Template: Template, isSpaMode: true }));
106
+ // Set status code
107
+ res.status(200);
108
+ // Set headers
109
+ res.setHeader('Content-Type', 'text/html');
110
+ // Send response
111
+ return res.send(html);
112
+ }
113
+ catch (error) {
114
+ console.error(error);
115
+ // Set status code
116
+ res.status(500);
117
+ // Set headers
118
+ res.setHeader('Content-Type', 'text/html');
119
+ // Send response
120
+ return res.send(`
121
+ <h1>Internal Server Error</h1>
122
+ <p>Something went wrong</p>
123
+ `);
124
+ }
125
+ }
@@ -9,7 +9,7 @@ import { loggerMiddleware } from '../../core/middlewares/index.js';
9
9
  import { isDataRequest, isDocumentRequest, logServerInfo } from './utils.js';
10
10
  import { getDirname, loadModuleSSR, } from '../../core/config/utils/load-modules.js';
11
11
  import { ServerMode } from '../runtime/mode.js';
12
- import { handleDataRequest, handleDocumentRequest } from './handlers.js';
12
+ import { handleDataRequest, handleDocumentRequest, handleSpaModeRequest, } from './handlers.js';
13
13
  import { createStaticHandler } from 'react-router';
14
14
  import { generateRoutes } from '../../routing/utils/generate-routes.js';
15
15
  /**
@@ -23,21 +23,27 @@ async function devRequestHandler(req, res, viteDevServer, options) {
23
23
  try {
24
24
  // Get the module runner through ssr environment
25
25
  const runner = createServerModuleRunner(viteDevServer.environments.ssr);
26
- // Load app-router
27
- const AppRouter = await (await runner.import(join(`${options.rootPath}/src/app/app.router`))).default;
28
- // Get static routes
29
- const staticRoutes = generateRoutes(AppRouter);
30
- // Create static handler
31
- let handler = createStaticHandler(staticRoutes);
32
- if (isDataRequest(req)) {
33
- // Handle data request
34
- return await handleDataRequest(req, handler);
26
+ if (options.config.ssr) {
27
+ // Load app-router
28
+ const AppRouter = await (await runner.import(join(`${options.rootPath}/src/app/app.router`))).default;
29
+ // Get static routes
30
+ const staticRoutes = generateRoutes(AppRouter);
31
+ // Create static handler
32
+ let handler = createStaticHandler(staticRoutes);
33
+ if (isDataRequest(req)) {
34
+ // Handle data request
35
+ return await handleDataRequest(req, handler);
36
+ }
37
+ if (isDocumentRequest(req)) {
38
+ return await handleDocumentRequest(req, res, runner, {
39
+ ...options,
40
+ handler,
41
+ });
42
+ }
35
43
  }
36
- if (isDocumentRequest(req)) {
37
- return await handleDocumentRequest(req, res, runner, {
38
- ...options,
39
- handler,
40
- });
44
+ else {
45
+ // handling spa mode here
46
+ return await handleSpaModeRequest(res, runner, options);
41
47
  }
42
48
  return res.status(404).send('Not found');
43
49
  }
@@ -38,7 +38,7 @@ export const renderToStream = async (Component, res) => {
38
38
  setTimeout(abort, ABORT_DELAY);
39
39
  });
40
40
  };
41
- export const renderToString = async (Component) => {
41
+ export const renderToString = (Component) => {
42
42
  const html = ReactDOM.renderToString(Component);
43
43
  return html;
44
44
  };
@@ -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/index.tsx","../src/routing/components/template.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.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/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/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.8.2"}
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/index.tsx","../src/routing/components/template.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.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/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.8.2"}
@@ -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/index.tsx","../src/routing/components/template.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.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/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/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.8.2"}
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/index.tsx","../src/routing/components/template.tsx","../src/routing/utils/define-router.tsx","../src/routing/utils/define-routes-group.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/mode.ts","../src/server/runtime/utils.ts","../src/server/virtual/index.ts","../types/client.d.ts"],"version":"5.8.2"}
@@ -31,6 +31,7 @@ export interface ViteConfig extends Omit<Vite.UserConfig, 'plugins' | 'environme
31
31
  appType?: 'spa' | 'mpa' | 'custom';
32
32
  }
33
33
  export type AppConfig = {
34
+ ssr?: boolean;
34
35
  /**
35
36
  * Configure server
36
37
  */
@@ -1,13 +1,14 @@
1
1
  import React, { FunctionComponent, JSX } from 'react';
2
2
  import { AppProps } from '../../core/types.js';
3
3
  import { Metadata, MetadataWithoutTitleAndDescription, TemplateProps } from '../../routing/types.js';
4
- export declare const TemplateLayout: ({ StaticRouterComponent, metadata, assets, App, Template, }: {
5
- StaticRouterComponent: React.ReactNode;
6
- metadata: {
4
+ export declare const TemplateLayout: ({ StaticRouterComponent, metadata, assets, App, Template, isSpaMode, }: {
5
+ StaticRouterComponent?: React.ReactNode;
6
+ metadata?: {
7
7
  page: Metadata;
8
8
  layout: MetadataWithoutTitleAndDescription;
9
9
  };
10
10
  assets?: JSX.Element[];
11
- App: FunctionComponent<AppProps>;
11
+ App?: FunctionComponent<AppProps>;
12
12
  Template: FunctionComponent<TemplateProps>;
13
+ isSpaMode?: boolean;
13
14
  }) => import("react/jsx-runtime.js").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { FunctionComponent } from "react";
2
+ import { AppConfig } from "../../core/config/type.js";
3
+ import { TemplateProps } from "../../routing/types.js";
4
+ export declare const renderIndexHTML: (template: FunctionComponent<TemplateProps>, options: {
5
+ rootPath: string;
6
+ config: AppConfig;
7
+ }) => Promise<void>;
@@ -20,3 +20,8 @@ export declare function handleDocumentRequest(req: Express.Request, res: Express
20
20
  handler: StaticHandler;
21
21
  }): Promise<any>;
22
22
  export declare function handleDataRequest(request: Express.Request, handler: StaticHandler): Promise<Response>;
23
+ export declare function handleSpaModeRequest(res: Express.Response, runner: ModuleRunner, options: {
24
+ rootPath: string;
25
+ __dirname: string;
26
+ config: AppConfig;
27
+ }): Promise<Express.Response<any, Record<string, any>>>;
@@ -6,4 +6,4 @@ import type * as Express from 'express';
6
6
  * @returns
7
7
  */
8
8
  export declare const renderToStream: (Component: React.ReactNode, res: Express.Response) => Promise<unknown>;
9
- export declare const renderToString: (Component: React.ReactNode) => Promise<string>;
9
+ export declare const renderToString: (Component: React.ReactNode) => string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rasengan",
3
3
  "private": false,
4
- "version": "1.0.0-beta.57",
4
+ "version": "1.0.0-beta.59",
5
5
  "description": "The modern React Framework",
6
6
  "type": "module",
7
7
  "main": "lib/esm/index.js",
@@ -79,7 +79,7 @@
79
79
  "sass": "*",
80
80
  "stylus": "*",
81
81
  "vite": "^6.0.0",
82
- "@rasenganjs/mdx": "^1.0.6"
82
+ "@rasenganjs/mdx": "^1.1.0-beta.1"
83
83
  },
84
84
  "peerDependenciesMeta": {
85
85
  "@types/node": {
package/types/client.d.ts CHANGED
@@ -67,4 +67,5 @@ declare module 'virtual:entry-server' {
67
67
 
68
68
  interface Window {
69
69
  __staticRouterHydrationData: any;
70
+ __RASENGAN_SPA_MODE__: boolean;
70
71
  }