rasengan 1.2.0-beta.0 → 1.2.0-beta.5

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 +15 -4
  2. package/lib/esm/core/config/utils/define-config.js +10 -1
  3. package/lib/esm/core/config/utils/path.js +1 -1
  4. package/lib/esm/core/config/vite/defaults.js +30 -2
  5. package/lib/esm/core/plugins/index.js +46 -13
  6. package/lib/esm/entries/server/entry.server.js +9 -3
  7. package/lib/esm/routing/index.js +1 -1
  8. package/lib/esm/routing/providers/metadata.js +33 -32
  9. package/lib/esm/routing/utils/define-router.js +19 -6
  10. package/lib/esm/routing/utils/define-static-paths.js +10 -0
  11. package/lib/esm/routing/utils/generate-routes.js +332 -242
  12. package/lib/esm/routing/utils/index.js +1 -0
  13. package/lib/esm/server/build/index.js +2 -1
  14. package/lib/esm/server/build/manifest.js +3 -0
  15. package/lib/esm/server/build/rendering.js +8 -6
  16. package/lib/esm/server/node/index.js +163 -3
  17. package/lib/esm/server/node/rendering.js +1 -4
  18. package/lib/esm/server/node/utils.js +131 -0
  19. package/lib/tsconfig.esm.tsbuildinfo +1 -1
  20. package/lib/tsconfig.types.tsbuildinfo +1 -1
  21. package/lib/types/core/config/type.d.ts +22 -0
  22. package/lib/types/entries/server/entry.server.d.ts +1 -1
  23. package/lib/types/routing/index.d.ts +1 -1
  24. package/lib/types/routing/types.d.ts +26 -3
  25. package/lib/types/routing/utils/define-static-paths.d.ts +10 -0
  26. package/lib/types/routing/utils/generate-routes.d.ts +8 -0
  27. package/lib/types/routing/utils/index.d.ts +1 -0
  28. package/lib/types/server/build/index.d.ts +3 -1
  29. package/lib/types/server/build/manifest.d.ts +11 -0
  30. package/lib/types/server/build/rendering.d.ts +1 -0
  31. package/lib/types/server/node/index.d.ts +30 -1
  32. package/lib/types/server/node/utils.d.ts +25 -0
  33. package/package.json +3 -3
  34. package/vite.config.ts +27 -1
package/CHANGELOG.md CHANGED
@@ -1,12 +1,23 @@
1
1
  ## Unreleased
2
2
 
3
- ## 1.2.0-beta.0 (2025-09-18)
3
+ ## 1.2.0-beta.5 (2026-01-03)
4
+
5
+ ## 1.2.0-beta.4 (2026-01-02)
6
+
7
+ ## 1.2.0-beta.3 (2026-01-02)
4
8
 
5
- ## 1.1.5 (2025-08-30)
9
+ ## 1.2.0-beta.2 (2026-01-02)
6
10
 
7
- ## 1.1.4 (2025-08-30)
11
+ ## 1.2.0-beta.1 (2025-09-19)
12
+
13
+ - fix(rasengan): falling back to route.data if route.loaderData is undefined [3cc418](https://github.com/rasengan-dev/rasenganjs/3cc4186e34a5d115e6ef69e8c8b36538aa8562ed)
14
+
15
+ ## 1.2.0-beta.0 (2025-09-18)
8
16
 
9
- ## 1.1.4 (2025-08-30)
17
+ - feat: adding experiment lazyLoadPage function [017b26](https://github.com/rasengan-dev/rasenganjs/017b26a480815ff480a0f99368fa8fc0c094d5ab)
18
+ - feat(rasengan): synchronizing metadata on client navigation [75374b](https://github.com/rasengan-dev/rasenganjs/75374b958180ad1f46bb00f91235e22bfa11a321)
19
+ - feat(rasengan): adding support for lazy route loading into file-base routing [1161bc](https://github.com/rasengan-dev/rasenganjs/1161bc409679630e293a376536b2e749cbeeaab8)
20
+ - feat: start adding support for lazy loading pages into file-based routing [cbcfdb](https://github.com/rasengan-dev/rasenganjs/cbcfdb6a1f4992b6cfacc280c68dc0d8fe42059c)
10
21
 
11
22
  ## 1.1.3 (2025-08-30)
12
23
 
@@ -21,7 +21,10 @@ export const defineConfig = async (loadedConfig) => {
21
21
  else {
22
22
  config = loadedConfig;
23
23
  }
24
- const { ssr, server, vite, redirects } = config;
24
+ const { ssr, prerender, sageMode, server, vite, redirects } = config;
25
+ const defaultSageModeConfig = {
26
+ reactCompiler: sageMode?.reactCompiler ?? false,
27
+ };
25
28
  // Define default values for vite config coming from loadedConfig.vite
26
29
  const defaultViteConfig = {
27
30
  ...vite,
@@ -42,7 +45,9 @@ export const defineConfig = async (loadedConfig) => {
42
45
  try {
43
46
  const config = {
44
47
  ssr: ssr ?? true,
48
+ prerender: prerender ?? false,
45
49
  server: defaultServerConfig,
50
+ sageMode: defaultSageModeConfig,
46
51
  vite: {
47
52
  ...defaultViteConfig,
48
53
  resolve: {
@@ -62,6 +67,10 @@ export const defineConfig = async (loadedConfig) => {
62
67
  catch (error) {
63
68
  return {
64
69
  ssr: true,
70
+ prerender: false,
71
+ sageMode: {
72
+ reactCompiler: false,
73
+ },
65
74
  vite: {
66
75
  appType: 'custom',
67
76
  resolve: {
@@ -12,5 +12,5 @@ export const resolvePath = (pathValue) => {
12
12
  if (isWindows) {
13
13
  return `file:///${path.normalize(pathValue)}`;
14
14
  }
15
- return path.normalize(pathValue);
15
+ return path.resolve(path.normalize(pathValue));
16
16
  };
@@ -29,15 +29,21 @@ export const createDefaultViteConfig = (rootPath, __dirname, mode, config) => {
29
29
  chunkSizeWarningLimit: 2000,
30
30
  },
31
31
  environments: {
32
+ /**
33
+ * Client Environment
34
+ */
32
35
  client: {
33
36
  build: {
34
37
  manifest: true,
35
- outDir: config.ssr ? 'dist/client' : 'dist',
38
+ outDir: config.ssr || config.prerender ? 'dist/client' : 'dist',
36
39
  rollupOptions: {
37
40
  input: './src/index',
38
41
  },
39
42
  },
40
43
  },
44
+ /**
45
+ * SSR Environment
46
+ */
41
47
  ssr: {
42
48
  build: {
43
49
  outDir: 'dist/server',
@@ -52,6 +58,23 @@ export const createDefaultViteConfig = (rootPath, __dirname, mode, config) => {
52
58
  ssrEmitAssets: false,
53
59
  },
54
60
  },
61
+ /**
62
+ * SSG Environment
63
+ */
64
+ ssg: {
65
+ build: {
66
+ outDir: 'dist/prerender',
67
+ rollupOptions: {
68
+ input: {
69
+ 'entry.server': join(__dirname, './lib/esm/entries/server/entry.server.js'),
70
+ 'app.router': './src/app/app.router',
71
+ main: './src/main',
72
+ template: './src/template',
73
+ },
74
+ },
75
+ ssrEmitAssets: false,
76
+ },
77
+ },
55
78
  },
56
79
  // Resolve config
57
80
  resolve: {
@@ -66,7 +89,12 @@ export const createDefaultViteConfig = (rootPath, __dirname, mode, config) => {
66
89
  builder: {
67
90
  buildApp: async (builder) => {
68
91
  if (config.ssr) {
69
- await builder.build(builder.environments.ssr);
92
+ if (!config.prerender) {
93
+ await builder.build(builder.environments.ssr);
94
+ }
95
+ }
96
+ if (config.prerender) {
97
+ await builder.build(builder.environments.ssg);
70
98
  }
71
99
  await builder.build(builder.environments.client);
72
100
  },
@@ -5,6 +5,7 @@ import { resolveBuildOptions } from '../../server.js';
5
5
  import { renderIndexHTML } from '../../server/build/rendering.js';
6
6
  import { createVirtualModule } from '../../server/virtual/index.js';
7
7
  import { pathToFileURL } from 'url';
8
+ import { preRenderApp } from '../../server/node/index.js';
8
9
  function loadRasenganGlobal() {
9
10
  return {
10
11
  name: 'vite-plugin-rasengan-config',
@@ -84,7 +85,6 @@ function flatRoutesPlugin() {
84
85
  '/src/app/_routes/**/layout.{js,ts,jsx,tsx}',
85
86
  '/src/app/_routes/**/*.page.{md,mdx,js,ts,jsx,tsx}',
86
87
  ],
87
- // { eager: true }
88
88
  );
89
89
  });
90
90
 
@@ -179,32 +179,65 @@ export function rasengan({ adapter = { name: Adapters.DEFAULT, prepare: async ()
179
179
  const module = await this.load({ id: modulePath });
180
180
  // SPA mode only
181
181
  if (!config.ssr) {
182
- // Generate the template.js file into the dist/assets
183
- fs.writeFileSync(path.posix.join(process.cwd(), buildOptions.buildDirectory, buildOptions.assetPathDirectory, 'template.js'), module.code, 'utf-8');
182
+ if (!config.prerender) {
183
+ // Generate the template.js file into the dist/assets
184
+ fs.writeFileSync(path.posix.join(process.cwd(), buildOptions.buildDirectory, buildOptions.assetPathDirectory, 'template.js'), module.code, 'utf-8');
185
+ }
184
186
  }
185
187
  },
186
188
  async closeBundle() {
187
189
  // We check here if the environment is client has been built because it's the
188
190
  // last environment to be built in the Vite build process
189
191
  if (this.environment.name === 'client') {
190
- // Check if SPA mode is enabled
191
- if (!config.ssr) {
192
+ // Generate a config.json file into the dist/client/assets or dist/assets
193
+ const minimizedConfig = {
194
+ buildOptions,
195
+ ssr: config.ssr,
196
+ prerender: !!config.prerender,
197
+ redirects: await config.redirects(),
198
+ };
199
+ fs.writeFileSync(path.posix.join(process.cwd(), buildOptions.buildDirectory, config.ssr || config.prerender
200
+ ? buildOptions.clientPathDirectory
201
+ : '', buildOptions.assetPathDirectory, 'config.json'), JSON.stringify(minimizedConfig), 'utf-8');
202
+ // Enable the generation of spa-fallback.html during pre-rendering
203
+ // Only if every pages are not generated
204
+ // @default to false - we assume that all pages are not generated
205
+ let enableIndexFallback = false;
206
+ // Handling the prerendering
207
+ if (config.prerender) {
208
+ let routes = [];
209
+ const buildOptions = resolveBuildOptions({
210
+ serverPathDirectory: 'prerender',
211
+ });
212
+ if (typeof config.prerender === 'object') {
213
+ routes = config.prerender.routes || [];
214
+ }
215
+ const outDir = `${process.cwd()}/static`;
216
+ const { isIndexPrerendered } = await preRenderApp({
217
+ build: buildOptions,
218
+ outDir,
219
+ routes,
220
+ });
221
+ enableIndexFallback = isIndexPrerendered;
222
+ }
223
+ // Check if SPA or SSG mode is enabled
224
+ if (!config.ssr || config.prerender) {
192
225
  // Load the template.js file
193
- const templatePath = path.posix.join(process.cwd(), buildOptions.buildDirectory, buildOptions.assetPathDirectory, 'template.js');
226
+ let templatePath = '';
227
+ if (config.prerender) {
228
+ templatePath = path.posix.join(process.cwd(), buildOptions.buildDirectory, 'prerender', 'template.js');
229
+ }
230
+ else {
231
+ templatePath = path.posix.join(process.cwd(), buildOptions.buildDirectory, buildOptions.assetPathDirectory, 'template.js');
232
+ }
194
233
  const Template = (await import(templatePath)).default;
195
234
  // Render the index.html file
196
235
  await renderIndexHTML(Template, {
197
236
  rootPath: process.cwd(),
198
237
  config,
238
+ enableIndexFallback,
199
239
  });
200
240
  }
201
- // Generate a config.json file into the dist/client/assets or dist/assets
202
- const minimizedConfig = {
203
- buildOptions,
204
- ssr: config.ssr,
205
- redirects: await config.redirects(),
206
- };
207
- fs.writeFileSync(path.posix.join(process.cwd(), buildOptions.buildDirectory, config.ssr ? buildOptions.clientPathDirectory : '', buildOptions.assetPathDirectory, 'config.json'), JSON.stringify(minimizedConfig), 'utf-8');
208
241
  // Prepare the app for deployment
209
242
  await prepareToDeploy(adapter);
210
243
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { loadModuleSSR } from '../../core/config/utils/load-modules.js';
3
3
  import { TemplateLayout } from './index.js';
4
4
  import { join, posix } from 'path/posix';
5
- import { renderToStream } from '../../server/node/rendering.js';
5
+ import { renderToStream, renderToString } from '../../server/node/rendering.js';
6
6
  /**
7
7
  * Render the app to a stream
8
8
  * @param StaticRouterComponent
@@ -10,7 +10,7 @@ import { renderToStream } from '../../server/node/rendering.js';
10
10
  * @param res
11
11
  * @returns
12
12
  */
13
- export const render = async (StaticRouterComponent, res, options) => {
13
+ export const render = async (StaticRouterComponent, res, options, stream = true) => {
14
14
  const { metadata, assets, buildOptions } = options;
15
15
  // Root path
16
16
  const rootPath = process.cwd();
@@ -27,5 +27,11 @@ export const render = async (StaticRouterComponent, res, options) => {
27
27
  // Import Template
28
28
  Template = (await loadModuleSSR(`${rootPath}/src/template`)).default;
29
29
  }
30
- await renderToStream(_jsx(TemplateLayout, { StaticRouterComponent: StaticRouterComponent, metadata: metadata, assets: assets, App: App, Template: Template }), res);
30
+ if (stream) {
31
+ await renderToStream(_jsx(TemplateLayout, { StaticRouterComponent: StaticRouterComponent, metadata: metadata, assets: assets, App: App, Template: Template }), res);
32
+ }
33
+ else {
34
+ const html = renderToString(_jsx(TemplateLayout, { StaticRouterComponent: StaticRouterComponent, metadata: metadata, assets: assets, App: App, Template: Template }));
35
+ return html;
36
+ }
31
37
  };
@@ -1,5 +1,5 @@
1
1
  import { CustomLink, ScrollRestoration } from './components/index.js';
2
- export { defineRouter, defineRoutesGroup, flatRoutes } from './utils/index.js';
2
+ export { defineRouter, defineRoutesGroup, defineStaticPaths, flatRoutes, } from './utils/index.js';
3
3
  export { RouterComponent } from './interfaces.js';
4
4
  export { Outlet, useLocation, useNavigate, useNavigation, useParams, useSearchParams, useFetcher, useMatch, useRoutes, useResolvedPath, matchRoutes, generatePath, matchPath, createRoutesFromChildren, Navigate, NavLink, } from 'react-router';
5
5
  export { CustomLink as Link, ScrollRestoration };
@@ -10,43 +10,44 @@ export default function MetadataProvider({ children, }) {
10
10
  if (typeof window === 'undefined')
11
11
  return;
12
12
  (async () => {
13
- const loadersData = routes.map((route) => route.loaderData);
14
- await handleApplyMetadata(loadersData);
13
+ const loadersData = routes.map((route) => route.loaderData ?? route.data // Normally the route.data is deprecated, we need to consider route.loaderData, but in some cases, route.loaderData is undefined and I don't know why, that's why we are using route.data instead
14
+ );
15
+ handleInjectMetadata(loadersData);
15
16
  })();
16
17
  }, [location]);
17
- const handleApplyMetadata = async (loadersData) => {
18
+ /**
19
+ * This function is responsible to inject the correct metadata on client routing based on the current URL of the page
20
+ * @param loadersData The metadata coming from loaders functions
21
+ * @returns
22
+ */
23
+ const handleInjectMetadata = (loadersData) => {
18
24
  // We generate the metadata
19
- const metadatas = generateMetadata(loadersData.map((item) => item.meta));
25
+ const metadatas = generateMetadata(loadersData.map((item) => (item.meta ? item.meta : {})));
20
26
  // We get the last metadata
21
27
  // This is the metadata of the page
22
- const leafMetadata = loadersData.at(-1)?.meta;
23
- handleInjectMetadata(metadatas, leafMetadata);
24
- };
25
- const handleInjectMetadata = (metaTags, leafMetadata) => {
26
- // Check if we are on the browser
27
- if (typeof window !== 'undefined') {
28
- // Find all meta tags with data-rg attribute and remove them
29
- const metaTagsToRemove = document.querySelectorAll('meta[data-rg="true"]');
30
- metaTagsToRemove.forEach((metaTag) => {
31
- metaTag.remove();
32
- });
33
- // Inject the meta tags
34
- metaTags.forEach((metaTag) => {
35
- // Convert React element to string
36
- const metaTagString = ReactDOMServer.renderToStaticMarkup(metaTag);
37
- document.head.insertAdjacentHTML('beforeend', metaTagString);
38
- });
39
- if (!leafMetadata)
40
- return;
41
- // Change the title of the page
42
- document.title = leafMetadata.title;
43
- // Change the description of the page
44
- const metaDescription = document.createElement('meta');
45
- metaDescription.setAttribute('name', 'description');
46
- metaDescription.setAttribute('content', leafMetadata.description);
47
- metaDescription.setAttribute('data-rg', 'true');
48
- document.head.appendChild(metaDescription);
49
- }
28
+ const leaf = loadersData.at(-1);
29
+ const leafMetadata = leaf ? leaf.meta : {};
30
+ // Find all meta tags with data-rg attribute and remove them
31
+ const metaTagsToRemove = document.querySelectorAll('meta[data-rg="true"]');
32
+ metaTagsToRemove.forEach((metaTag) => {
33
+ metaTag.remove();
34
+ });
35
+ // Inject the meta tags
36
+ metadatas.forEach((metaTag) => {
37
+ // Convert React element to string
38
+ const metaTagString = ReactDOMServer.renderToStaticMarkup(metaTag);
39
+ document.head.insertAdjacentHTML('beforeend', metaTagString);
40
+ });
41
+ if (!leafMetadata)
42
+ return;
43
+ // Change the title of the page
44
+ document.title = leafMetadata.title;
45
+ // Change the description of the page
46
+ const metaDescription = document.createElement('meta');
47
+ metaDescription.setAttribute('name', 'description');
48
+ metaDescription.setAttribute('content', leafMetadata.description);
49
+ metaDescription.setAttribute('data-rg', 'true');
50
+ document.head.appendChild(metaDescription);
50
51
  };
51
52
  return _jsx(_Fragment, { children: children });
52
53
  }
@@ -71,14 +71,27 @@ export const isMDXPage = (page) => {
71
71
  }
72
72
  return false;
73
73
  };
74
+ /**
75
+ * Load thr MDXRenderer is the dedicated package is installed
76
+ * @returns
77
+ */
74
78
  const loadMDXRenderer = async () => {
75
79
  try {
76
- // @ts-ignore
77
- const { MDXRenderer } = await import('@rasenganjs/mdx');
78
- return MDXRenderer;
80
+ // Dynamically import only if the package exists
81
+ const mod = await import('@rasenganjs/mdx');
82
+ return mod.MDXRenderer;
79
83
  }
80
- catch (e) {
81
- console.error(e);
82
- throw e;
84
+ catch (error) {
85
+ // Handle the case when the package is not installed
86
+ if (error.code === 'MODULE_NOT_FOUND' ||
87
+ /Cannot find module '@rasenganjs\/mdx'/.test(error.message)) {
88
+ if (process.env.NODE_ENV === 'development') {
89
+ console.warn('[Rasengan.js] MDX package not found — skipping MDX rendering.');
90
+ }
91
+ return null;
92
+ }
93
+ // Other unexpected errors (e.g. runtime bug in the module)
94
+ console.error('[Rasengan.js] Unexpected error while loading MDX module:', error);
95
+ throw error;
83
96
  }
84
97
  };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Define static paths for a page
3
+ * @param items - List of params
4
+ * @returns
5
+ */
6
+ export function defineStaticPaths(items) {
7
+ return {
8
+ paths: items.map((params) => ({ params })),
9
+ };
10
+ }