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.
- package/CHANGELOG.md +2 -0
- package/lib/esm/core/config/vite/defaults.js +1 -17
- package/lib/esm/core/dynamic/index.js +32 -0
- package/lib/esm/core/plugins/index.js +1 -1
- package/lib/esm/entries/client/render.js +45 -16
- package/lib/esm/entries/server/entry.server.js +1 -0
- package/lib/esm/entries/server/index.js +3 -3
- package/lib/esm/routing/components/fallback.js +8 -0
- package/lib/esm/routing/components/index.js +7 -4
- package/lib/esm/routing/components/template.js +8 -7
- package/lib/esm/routing/providers/metadata.js +23 -13
- package/lib/esm/routing/utils/define-router.js +10 -6
- package/lib/esm/routing/utils/flat-routes.js +46 -75
- package/lib/esm/routing/utils/generate-routes.js +279 -178
- package/lib/esm/server/build/manifest.js +19 -7
- package/lib/esm/server/dev/handlers.js +1 -1
- package/lib/esm/server/dev/server.js +19 -1
- package/lib/esm/server/node/index.js +8 -2
- package/lib/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/tsconfig.types.tsbuildinfo +1 -1
- package/lib/types/core/dynamic/index.d.ts +15 -0
- package/lib/types/core/types.d.ts +1 -1
- package/lib/types/entries/client/render.d.ts +3 -2
- package/lib/types/routing/components/fallback.d.ts +5 -0
- package/lib/types/routing/components/template.d.ts +1 -1
- package/lib/types/routing/interfaces.d.ts +5 -4
- package/lib/types/routing/providers/metadata.d.ts +1 -3
- package/lib/types/routing/types.d.ts +21 -3
- package/lib/types/routing/utils/define-routes-group.d.ts +1 -1
- package/lib/types/routing/utils/flat-routes.d.ts +21 -3
- package/lib/types/routing/utils/generate-routes.d.ts +6 -6
- package/lib/types/server/build/manifest.d.ts +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -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:
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
6
|
-
|
|
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('
|
|
14
|
+
throw new Error('#root element not found in the DOM');
|
|
10
15
|
}
|
|
11
|
-
//
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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__
|
|
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__
|
|
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
|
};
|
|
@@ -5,12 +5,15 @@ import { useEffect, useRef } from 'react';
|
|
|
5
5
|
const extractEnv = () => {
|
|
6
6
|
try {
|
|
7
7
|
const env = import.meta.env;
|
|
8
|
-
|
|
8
|
+
// If not env, use process.env on the server only
|
|
9
|
+
if (!env) {
|
|
10
|
+
const serverEnv = process.env;
|
|
9
11
|
return {
|
|
10
|
-
DEV:
|
|
11
|
-
PROD:
|
|
12
|
-
TEST:
|
|
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, {
|
|
3
|
-
import { generateMetadata
|
|
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 = ({
|
|
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
|
-
|
|
13
|
-
|
|
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,
|
|
7
|
-
const
|
|
6
|
+
export default function MetadataProvider({ children, }) {
|
|
7
|
+
const location = useLocation();
|
|
8
|
+
const routes = useMatches();
|
|
8
9
|
useEffect(() => {
|
|
9
|
-
|
|
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 =
|
|
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',
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
161
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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.
|
|
204
|
-
|
|
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
|
-
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|