rasengan 1.1.3 → 1.2.0-beta.0

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 (33) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/lib/esm/core/config/vite/defaults.js +1 -17
  3. package/lib/esm/core/dynamic/index.js +32 -0
  4. package/lib/esm/core/plugins/index.js +1 -1
  5. package/lib/esm/entries/client/render.js +45 -16
  6. package/lib/esm/entries/server/entry.server.js +1 -0
  7. package/lib/esm/entries/server/index.js +3 -3
  8. package/lib/esm/routing/components/fallback.js +8 -0
  9. package/lib/esm/routing/components/index.js +7 -4
  10. package/lib/esm/routing/components/template.js +8 -7
  11. package/lib/esm/routing/providers/metadata.js +23 -13
  12. package/lib/esm/routing/utils/define-router.js +10 -6
  13. package/lib/esm/routing/utils/flat-routes.js +46 -75
  14. package/lib/esm/routing/utils/generate-routes.js +279 -178
  15. package/lib/esm/server/build/manifest.js +19 -7
  16. package/lib/esm/server/dev/handlers.js +1 -1
  17. package/lib/esm/server/dev/server.js +19 -1
  18. package/lib/esm/server/node/index.js +8 -2
  19. package/lib/tsconfig.esm.tsbuildinfo +1 -1
  20. package/lib/tsconfig.types.tsbuildinfo +1 -1
  21. package/lib/types/core/dynamic/index.d.ts +15 -0
  22. package/lib/types/core/types.d.ts +1 -1
  23. package/lib/types/entries/client/render.d.ts +3 -2
  24. package/lib/types/routing/components/fallback.d.ts +5 -0
  25. package/lib/types/routing/components/template.d.ts +1 -1
  26. package/lib/types/routing/interfaces.d.ts +5 -4
  27. package/lib/types/routing/providers/metadata.d.ts +1 -3
  28. package/lib/types/routing/types.d.ts +21 -3
  29. package/lib/types/routing/utils/define-routes-group.d.ts +1 -1
  30. package/lib/types/routing/utils/flat-routes.d.ts +21 -3
  31. package/lib/types/routing/utils/generate-routes.d.ts +6 -6
  32. package/lib/types/server/build/manifest.d.ts +4 -4
  33. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 1.2.0-beta.0 (2025-09-18)
4
+
3
5
  ## 1.1.5 (2025-08-30)
4
6
 
5
7
  ## 1.1.4 (2025-08-30)
@@ -1,15 +1,8 @@
1
1
  // core/config/defaults.ts
2
2
  import { join } from 'node:path';
3
- // Define core external packages
4
- const CORE_EXTERNALS = [
5
- // '@rasenganjs/mdx',
6
- // '@rasenganjs/vercel',
7
- // '@rasenganjs/netlify',
8
- ];
9
3
  export const createDefaultViteConfig = (rootPath, __dirname, mode, config) => {
10
4
  // Combine core externals with user-defined externals
11
5
  const externals = [
12
- ...CORE_EXTERNALS,
13
6
  ...(Array.isArray(config.vite?.build?.external)
14
7
  ? config.vite.build.external
15
8
  : []),
@@ -28,21 +21,12 @@ export const createDefaultViteConfig = (rootPath, __dirname, mode, config) => {
28
21
  return 'vendor';
29
22
  if (id.includes('src/components'))
30
23
  return 'shared-components';
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
- }
38
- }
39
- }
40
24
  return undefined;
41
25
  },
42
26
  },
43
27
  },
44
28
  outDir: 'dist',
45
- chunkSizeWarningLimit: 1000,
29
+ chunkSizeWarningLimit: 2000,
46
30
  },
47
31
  environments: {
48
32
  client: {
@@ -19,3 +19,35 @@ export function dynamicLoad(load, fallback = _jsx(_Fragment, {}) // Default pend
19
19
  throw new Error(error);
20
20
  }
21
21
  }
22
+ /**
23
+ * It allows you to defer loading of page components,
24
+ * and only include them in the client bundle when they're needed
25
+ * @param filePath Path to the page component we want to load lazily
26
+ * @returns
27
+ */
28
+ export function lazyLoadPage(routePath, filePath) {
29
+ // use import.meta.glob with a fixed pattern that covers your pages
30
+ const modules = import.meta.glob('/src/app/**/*.{js,ts,jsx,tsx,md,mdx}');
31
+ // if started with ./ then removed it
32
+ const normalizePath = filePath.startsWith('./')
33
+ ? filePath.slice(2)
34
+ : filePath;
35
+ // Get the full path from the modules object
36
+ const fullPath = Object.keys(modules).find((key) => key.endsWith(normalizePath));
37
+ if (!fullPath) {
38
+ throw new Error(`[rasengan lazyLoad]: No module found for path "${filePath}". Did you include it in the glob?`);
39
+ }
40
+ const loader = modules[fullPath];
41
+ if (!loader) {
42
+ throw new Error(`[rasengan lazyLoad]: No module found for path "${filePath}". Did you include it in the glob?`);
43
+ }
44
+ const page = {
45
+ path: routePath,
46
+ fullPath,
47
+ segment: routePath, // TODO: Handle segments
48
+ module: loader,
49
+ source: filePath,
50
+ isLayout: false,
51
+ };
52
+ return page;
53
+ }
@@ -84,7 +84,7 @@ function flatRoutesPlugin() {
84
84
  '/src/app/_routes/**/layout.{js,ts,jsx,tsx}',
85
85
  '/src/app/_routes/**/*.page.{md,mdx,js,ts,jsx,tsx}',
86
86
  ],
87
- { eager: true }
87
+ // { eager: true }
88
88
  );
89
89
  });
90
90
 
@@ -2,27 +2,56 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
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__;
6
- export default function renderApp(App, options) {
5
+ import { generateRoutes, preloadMatches, } from '../../routing/utils/generate-routes.js';
6
+ import { createBrowserRouter, RouterProvider } from 'react-router';
7
+ const isSpaMode = Boolean(window.__RASENGAN_SPA_MODE__);
8
+ export default async function renderApp(App, AppRouter, options) {
9
+ // Get root element
10
+ // We need to get the root element to render the app - (SPA mode)
11
+ // or to hydrate the app - (SSR mode)
7
12
  const root = document.getElementById('root');
8
13
  if (!root) {
9
- throw new Error('Root element not found');
14
+ throw new Error('#root element not found in the DOM');
10
15
  }
11
- // If SPA mode, render the app
16
+ // Resolve app router
17
+ const RasenganRouter = await AppRouter;
18
+ // Generate routes for the browser routing
19
+ const routes = generateRoutes(RasenganRouter);
20
+ // Preload lazy routes
21
+ // We have to only preload the routes that are matched for the current URL
22
+ // The remaining routing will be lazy loaded on the client after routes change
23
+ await preloadMatches(window.location, routes);
24
+ // Create router
25
+ let router = createBrowserRouter(routes, {
26
+ hydrationData: window.__staticRouterHydrationData,
27
+ });
28
+ // Generate client router
29
+ const ClientRouter = () => _jsx(RouterProvider, { router: router });
30
+ // Generate app tree
31
+ const appTree = options.reactStrictMode ? (_jsx(StrictMode, { children: _jsx(App, { Component: (props) => (_jsx(RootComponent, { ...props, Router: ClientRouter })) }) })) : (_jsx(App, { Component: (props) => _jsx(RootComponent, { ...props, Router: ClientRouter }) }));
32
+ // Render app
12
33
  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
22
- if (options.reactStrictMode) {
23
- hydrateRoot(root, _jsx(StrictMode, { children: _jsx(App, { Component: RootComponent }) }));
34
+ // No SSR markup, so start fresh
35
+ createRoot(root, {
36
+ onCaughtError: (error) => {
37
+ console.error(error);
38
+ },
39
+ onRecoverableError(error, errorInfo) {
40
+ console.error(error);
41
+ console.error(errorInfo);
42
+ },
43
+ }).render(appTree);
24
44
  }
25
45
  else {
26
- hydrateRoot(root, _jsx(App, { Component: RootComponent }));
46
+ // SSR markup present, hydrate instead of re-rendering
47
+ hydrateRoot(root, appTree, {
48
+ onCaughtError: (error) => {
49
+ console.error(error);
50
+ },
51
+ onRecoverableError(error, errorInfo) {
52
+ console.error(error);
53
+ console.error(errorInfo);
54
+ },
55
+ });
27
56
  }
28
57
  }
@@ -16,6 +16,7 @@ export const render = async (StaticRouterComponent, res, options) => {
16
16
  const rootPath = process.cwd();
17
17
  let App;
18
18
  let Template;
19
+ // If build options are provided, that means we are in production mode
19
20
  if (buildOptions) {
20
21
  App = (await loadModuleSSR(posix.join(buildOptions.buildDirectory, buildOptions.serverPathDirectory, 'main.js'))).default;
21
22
  Template = (await loadModuleSSR(join(buildOptions.buildDirectory, buildOptions.serverPathDirectory, 'template.js'))).default;
@@ -19,13 +19,13 @@ export const TemplateLayout = ({ StaticRouterComponent, metadata, assets, App, T
19
19
  }
20
20
  if (isSpaMode) {
21
21
  otherScripts = (_jsxs(React.Fragment, { children: [_jsx("script", { type: "module", dangerouslySetInnerHTML: {
22
- __html: `window.__RASENGAN_SPA_MODE__ = true;`,
22
+ __html: `window.__RASENGAN_SPA_MODE__=true;`,
23
23
  } }), !assets && (_jsx("script", { type: "module", src: "/src/index", async: true }))] }));
24
24
  }
25
25
  else {
26
26
  otherScripts = (_jsx(React.Fragment, { children: _jsx("script", { type: "module", dangerouslySetInnerHTML: {
27
- __html: `window.__RASENGAN_SPA_MODE__ = false;`,
27
+ __html: `window.__RASENGAN_SPA_MODE__=false;`,
28
28
  } }) }));
29
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 }) }));
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: (props) => _jsx(RootComponent, { ...props }), children: StaticRouterComponent })), children: children })), Script: ({ children }) => _jsx(ScriptComponent, { children: children }) }));
31
31
  };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * This component is used as a fallback when the page is not yet hydrated
4
+ * @returns
5
+ */
6
+ export const HydrationFallback = () => {
7
+ return _jsx("div", { children: "Loading page..." });
8
+ };
@@ -5,12 +5,15 @@ import { useEffect, useRef } from 'react';
5
5
  const extractEnv = () => {
6
6
  try {
7
7
  const env = import.meta.env;
8
- if (!env)
8
+ // If not env, use process.env on the server only
9
+ if (!env) {
10
+ const serverEnv = process.env;
9
11
  return {
10
- DEV: false,
11
- PROD: true,
12
- TEST: false,
12
+ DEV: serverEnv.NODE_ENV === 'development',
13
+ PROD: serverEnv.NODE_ENV === 'production',
14
+ TEST: serverEnv.NODE_ENV === 'test',
13
15
  };
16
+ }
14
17
  return {
15
18
  DEV: env.DEV,
16
19
  PROD: env.PROD,
@@ -1,17 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { use, useMemo } from 'react';
3
- import { generateMetadata, getRouter } from '../utils/index.js';
2
+ import React, { useMemo } from 'react';
3
+ import { generateMetadata } from '../utils/index.js';
4
4
  import { Outlet } from 'react-router';
5
5
  /**
6
6
  * App component that represent the entry point of the application
7
7
  */
8
- export const RootComponent = ({ router: AppRouterPromise, children = undefined, }) => {
8
+ export const RootComponent = ({ Router, // Client Router
9
+ children = undefined, // Static Router for SSR when provided
10
+ }) => {
9
11
  // Return children if they exist
10
12
  if (children)
11
- return children;
12
- const AppRouter = use(AppRouterPromise);
13
- // Otherwise, get the router and return it
14
- let Router = getRouter(AppRouter);
13
+ return children; // For the SSR
14
+ if (!Router)
15
+ return null;
15
16
  return _jsx(Router, {});
16
17
  };
17
18
  /**
@@ -1,22 +1,30 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import { useLocation } from 'react-router';
2
+ import { useLocation, useMatches } from 'react-router';
3
3
  import { useEffect } from 'react';
4
4
  import { generateMetadata } from '../utils/generate-metadata.js';
5
5
  import ReactDOMServer from 'react-dom/server';
6
- export default function MetadataProvider({ children, metadataMapping, }) {
7
- const { pathname } = useLocation();
6
+ export default function MetadataProvider({ children, }) {
7
+ const location = useLocation();
8
+ const routes = useMatches();
8
9
  useEffect(() => {
9
- const metadata = metadataMapping[pathname];
10
- if (!metadata)
11
- return;
12
- handleInjectMetadata(metadata);
13
- }, [pathname]);
14
- const handleInjectMetadata = (metadata) => {
15
- if (!metadata)
10
+ if (typeof window === 'undefined')
16
11
  return;
12
+ (async () => {
13
+ const loadersData = routes.map((route) => route.loaderData);
14
+ await handleApplyMetadata(loadersData);
15
+ })();
16
+ }, [location]);
17
+ const handleApplyMetadata = async (loadersData) => {
18
+ // We generate the metadata
19
+ const metadatas = generateMetadata(loadersData.map((item) => item.meta));
20
+ // We get the last metadata
21
+ // This is the metadata of the page
22
+ const leafMetadata = loadersData.at(-1)?.meta;
23
+ handleInjectMetadata(metadatas, leafMetadata);
24
+ };
25
+ const handleInjectMetadata = (metaTags, leafMetadata) => {
17
26
  // Check if we are on the browser
18
27
  if (typeof window !== 'undefined') {
19
- const metaTags = generateMetadata([metadata]);
20
28
  // Find all meta tags with data-rg attribute and remove them
21
29
  const metaTagsToRemove = document.querySelectorAll('meta[data-rg="true"]');
22
30
  metaTagsToRemove.forEach((metaTag) => {
@@ -28,12 +36,14 @@ export default function MetadataProvider({ children, metadataMapping, }) {
28
36
  const metaTagString = ReactDOMServer.renderToStaticMarkup(metaTag);
29
37
  document.head.insertAdjacentHTML('beforeend', metaTagString);
30
38
  });
39
+ if (!leafMetadata)
40
+ return;
31
41
  // Change the title of the page
32
- document.title = metadata.title;
42
+ document.title = leafMetadata.title;
33
43
  // Change the description of the page
34
44
  const metaDescription = document.createElement('meta');
35
45
  metaDescription.setAttribute('name', 'description');
36
- metaDescription.setAttribute('content', metadata.description);
46
+ metaDescription.setAttribute('content', leafMetadata.description);
37
47
  metaDescription.setAttribute('data-rg', 'true');
38
48
  document.head.appendChild(metaDescription);
39
49
  }
@@ -8,11 +8,6 @@ import { DefaultLayout } from '../components/template.js';
8
8
  export const defineRouter = (option) => {
9
9
  const { imports, layout, pages, loaderComponent, notFoundComponent, useParentLayout, } = option;
10
10
  return async (Router) => {
11
- // Handle errors
12
- // if (!option.pages)
13
- // throw new Error(
14
- // 'You must provide a list of pages in the router option object'
15
- // );
16
11
  // Create router
17
12
  const router = new Router();
18
13
  // List of pages component
@@ -21,6 +16,10 @@ export const defineRouter = (option) => {
21
16
  // Check if p is an array
22
17
  if (Array.isArray(p)) {
23
18
  for (let page of p) {
19
+ if ('source' in page) {
20
+ pageComponentList.push(page);
21
+ continue;
22
+ }
24
23
  if (isMDXPage(page)) {
25
24
  const Page = await convertMDXPageToPageComponent(page);
26
25
  pageComponentList.push(Page);
@@ -31,6 +30,10 @@ export const defineRouter = (option) => {
31
30
  }
32
31
  continue;
33
32
  }
33
+ if ('source' in p) {
34
+ pageComponentList.push(p);
35
+ continue;
36
+ }
34
37
  // When p is a MDXPageComponent
35
38
  if (isMDXPage(p)) {
36
39
  const Page = await convertMDXPageToPageComponent(p);
@@ -75,6 +78,7 @@ const loadMDXRenderer = async () => {
75
78
  return MDXRenderer;
76
79
  }
77
80
  catch (e) {
78
- throw new Error('Failed to load MDXRenderer component from @rasenganjs/mdx, make sure you have installed the package');
81
+ console.error(e);
82
+ throw e;
79
83
  }
80
84
  };
@@ -1,6 +1,5 @@
1
1
  import { DefaultLayout } from '../components/template.js';
2
2
  import { RouterComponent } from '../interfaces.js';
3
- import { convertMDXPageToPageComponent, isMDXPage } from './define-router.js';
4
3
  const basePath = '/src/app/_routes/';
5
4
  /**
6
5
  * Normalize a segment
@@ -29,7 +28,7 @@ function normalizeSegment(segment) {
29
28
  * @param foldersOnly Whether to return only folders
30
29
  * @returns Path segments
31
30
  */
32
- function getPathSegments(filePath, foldersOnly = false) {
31
+ export function getPathSegments(filePath, foldersOnly = false) {
33
32
  const relative = filePath.replace(basePath, ''); // eg. /src/app/_routes/docs/layout.tsx => docs/layout.tsx
34
33
  if (!foldersOnly) {
35
34
  let withoutExtension = '';
@@ -60,6 +59,7 @@ function generateSkeletonTree(tree, modules) {
60
59
  segment: '_',
61
60
  isLayout: true,
62
61
  children: [],
62
+ source: '',
63
63
  };
64
64
  currentLevel.push(root);
65
65
  currentLevel = root.children ?? []; // change level
@@ -86,6 +86,7 @@ function generateSkeletonTree(tree, modules) {
86
86
  segment,
87
87
  isLayout: false,
88
88
  children: [],
89
+ source: '',
89
90
  };
90
91
  tmpLevel.push(node);
91
92
  tmpLevel = node.children ?? []; // change level
@@ -99,9 +100,10 @@ function insertNodeToTree(tree, segments, routeInfo) {
99
100
  // Handle the root layout
100
101
  if (segments.length === 1 && segments[0] === '_') {
101
102
  currentNode.isLayout = true;
102
- currentNode.component = routeInfo.component;
103
+ currentNode.component = routeInfo.component; // Has to be considered in the case where the developer doesn't provide a layout
103
104
  currentNode.metadata = routeInfo.metadata;
104
- currentNode.loader = routeInfo.loader;
105
+ currentNode.module = routeInfo.module;
106
+ currentNode.source = routeInfo.source;
105
107
  return;
106
108
  }
107
109
  let fullPath = '';
@@ -125,7 +127,8 @@ function insertNodeToTree(tree, segments, routeInfo) {
125
127
  currentNode.isLayout = true;
126
128
  currentNode.component = routeInfo.component;
127
129
  currentNode.metadata = routeInfo.metadata;
128
- currentNode.loader = routeInfo.loader;
130
+ currentNode.module = routeInfo.module;
131
+ currentNode.source = routeInfo.source;
129
132
  }
130
133
  else {
131
134
  let path = '';
@@ -157,9 +160,8 @@ function insertNodeToTree(tree, segments, routeInfo) {
157
160
  fullPath: fullPath + '/' + (lastSegment === '.' ? '' : lastSegment),
158
161
  segment: lastSegment,
159
162
  isLayout: false,
160
- component: routeInfo.component,
161
- metadata: routeInfo.metadata,
162
- loader: routeInfo.loader,
163
+ module: routeInfo.module,
164
+ source: routeInfo.source,
163
165
  };
164
166
  currentLevel.push(node);
165
167
  }
@@ -173,20 +175,27 @@ async function generateRouter(tree) {
173
175
  const root = tree[0];
174
176
  // Generate the base router
175
177
  const router = new RouterComponent();
178
+ // use default layout if not defined
179
+ let layout;
176
180
  // Get layout if defined
177
181
  if (root.isLayout) {
178
- // use default layout if not defined
179
- const layout = (root.component || DefaultLayout);
180
- layout.path = root.path || DefaultLayout.path;
181
- // TODO: Add metadata here
182
- router.layout = layout;
183
- router.useParentLayout = true;
182
+ if ('source' in root) {
183
+ layout = root;
184
+ }
185
+ else {
186
+ layout = root.component;
187
+ layout.path = root.path;
188
+ layout.metadata = root.metadata;
189
+ }
184
190
  }
185
191
  // Get pages
186
192
  const { routes: pages, routers } = await generateRoutes(root.children);
187
193
  // Add pages to the router
188
194
  router.pages = pages;
189
195
  router.routers = routers;
196
+ // Add layout to the router
197
+ router.layout = layout;
198
+ router.useParentLayout = true;
190
199
  return router;
191
200
  }
192
201
  /**
@@ -200,37 +209,26 @@ async function generateRoutes(tree) {
200
209
  const routers = [];
201
210
  for (const node of tree) {
202
211
  // Handle page
203
- if (!node.isLayout && node.component) {
204
- const page = node.component;
205
- if (!page) {
206
- console.warn(`Page component is not exported by default for route: ${node.path}`);
207
- continue;
208
- }
209
- if (isMDXPage(page)) {
210
- // Convert PageComponent to MDXPageComponent (to make ts happy)
211
- const mdxPage = page;
212
- mdxPage.metadata.path = node.path;
213
- mdxPage.metadata.metadata = node.metadata;
214
- const pageComponent = await convertMDXPageToPageComponent(mdxPage);
215
- routes.push(pageComponent);
216
- continue;
217
- }
218
- page.path = node.path;
219
- page.metadata = node.metadata;
220
- page.loader = node.loader;
221
- routes.push(page);
212
+ if (!node.isLayout && node.module) {
213
+ routes.push(node);
222
214
  continue;
223
215
  }
224
216
  // Handle layout
225
217
  if (node.isLayout) {
226
- const layout = node.component;
227
- if (!layout) {
228
- console.warn(`Layout component is not defined for route: ${node.path}`);
229
- continue;
218
+ let layout;
219
+ if ('source' in node) {
220
+ layout = node;
221
+ }
222
+ else {
223
+ layout = node.component;
224
+ if (!layout) {
225
+ console.warn(`Layout component is not defined for route: ${node.path}`);
226
+ continue;
227
+ }
228
+ layout.path = node.path;
229
+ layout.metadata = node.metadata;
230
+ layout.source = node.source;
230
231
  }
231
- layout.path = node.path;
232
- layout.metadata = node.metadata;
233
- layout.loader = node.loader;
234
232
  if (node.children) {
235
233
  // Loop through children
236
234
  const { routes: subRoutes, routers: subRouters } = await generateRoutes(node.children);
@@ -270,19 +268,10 @@ async function generateRoutes(tree) {
270
268
  export async function flatRoutes(fn) {
271
269
  try {
272
270
  let modules = fn();
273
- // import.meta.glob can be undefined in some cases (because it's unavailable outside a vite env)
274
- // if (import.meta.glob) {
275
- // let modules = import.meta.glob(
276
- // [
277
- // '/src/app/_routes/**/layout.{jsx,tsx}',
278
- // '/src/app/_routes/**/*.page.{md,mdx,jsx,tsx}',
279
- // ],
280
- // { eager: true }
281
- // );
282
- // }
283
271
  const tree = [];
284
272
  const foldersMap = new Map();
285
273
  const modulesMap = new Map();
274
+ // Map the modules and extract segments for folders and modules (layouts and pages)
286
275
  for (const [filePath, mod] of Object.entries(modules)) {
287
276
  const foldersSegments = getPathSegments(filePath, true);
288
277
  const modulesSegments = getPathSegments(filePath);
@@ -293,6 +282,7 @@ export async function flatRoutes(fn) {
293
282
  generateSkeletonTree(tree, foldersMap);
294
283
  // Filter out layouts
295
284
  const layoutModulesMap = new Map([...modulesMap.entries()].filter(([filePath]) => filePath.includes('layout.')));
285
+ // Filter out pages
296
286
  const pageModulesMap = new Map([...modulesMap.entries()].filter(([filePath]) => filePath.includes('.page.')));
297
287
  // Handle the case where modules are empty
298
288
  if (layoutModulesMap.size === 0) {
@@ -302,39 +292,20 @@ export async function flatRoutes(fn) {
302
292
  isLayout: true,
303
293
  });
304
294
  }
295
+ // Insert every layout into the tree
305
296
  for (const [filePath, { segments, mod }] of layoutModulesMap) {
306
- if (!mod.default) {
307
- console.warn(`Layout component is not exported by default from: ${filePath}}`);
308
- continue;
309
- }
310
- let metadata = mod.default.metadata;
311
- let loader = mod.default.loader;
312
297
  insertNodeToTree(tree, segments, {
313
- component: mod.default,
314
- metadata: metadata ?? {},
315
- loader,
298
+ module: mod, // Only present for lazy routes, instead prefer component attribute
316
299
  isLayout: true,
300
+ source: filePath,
317
301
  });
318
302
  }
303
+ // Insert every pages into the tree
319
304
  for (const [filePath, { segments, mod }] of pageModulesMap) {
320
- if (!mod.default) {
321
- console.warn(`Page component is not exported by default from: ${filePath}}`);
322
- continue;
323
- }
324
- let metadata = mod.default.metadata;
325
- let loader = mod.default.loader;
326
- // extracting the metadata
327
- if (isMDXPage(mod.default)) {
328
- metadata = mod.default.metadata.metadata;
329
- }
330
305
  insertNodeToTree(tree, segments, {
331
- component: mod.default,
332
- metadata: metadata ?? {
333
- title: mod.default.name,
334
- description: '',
335
- },
336
- loader,
306
+ module: mod, // Only present for lazy routes, instead prefer component attribute
337
307
  isLayout: false,
308
+ source: filePath,
338
309
  });
339
310
  }
340
311
  // Convert the tree into a router component instance