routerino 2.2.1 → 2.2.2-rc1
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/README.md +23 -1
- package/package.json +1 -1
- package/routerino-forge.js +89 -4
package/README.md
CHANGED
|
@@ -683,7 +683,7 @@ export default defineConfig({
|
|
|
683
683
|
routerinoForge({
|
|
684
684
|
baseUrl: "https://example.com", // Your production URL (no trailing slash)
|
|
685
685
|
// Optional settings (these are the defaults):
|
|
686
|
-
// routes: "./src/routes.jsx", // Your routes file
|
|
686
|
+
// routes: "./src/routes.jsx", // Your routes file (and App.jsx for full layout)
|
|
687
687
|
// outputDir: "dist",
|
|
688
688
|
// generateSitemap: true,
|
|
689
689
|
// useTrailingSlash: true, // Set to false for /about instead of /about/
|
|
@@ -836,6 +836,28 @@ In your netlify.toml:
|
|
|
836
836
|
|
|
837
837
|
**Critical for SSG**: Routes MUST be exported for the build plugin to discover them. The plugin needs to import your routes at build time, so inline route definitions won't work.
|
|
838
838
|
|
|
839
|
+
To include your full layout (headers, footers, etc.) in static generated HTML, export routes from the same file as your App component:
|
|
840
|
+
|
|
841
|
+
```jsx
|
|
842
|
+
// App.jsx - export routes from here for full layout SSG
|
|
843
|
+
export const routes = [
|
|
844
|
+
{ path: "/", element: <HomePage />, title: "Home" },
|
|
845
|
+
{ path: "/about/", element: <AboutPage />, title: "About" },
|
|
846
|
+
];
|
|
847
|
+
|
|
848
|
+
export default function App() {
|
|
849
|
+
return (
|
|
850
|
+
<main>
|
|
851
|
+
<Header />
|
|
852
|
+
<Routerino routes={routes} />
|
|
853
|
+
<Footer />
|
|
854
|
+
</main>
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
This ensures your entire App layout is rendered during static site generation. Without this, SSG renders only the individual route elements.
|
|
860
|
+
|
|
839
861
|
Define routes with an `element` property containing JSX elements:
|
|
840
862
|
|
|
841
863
|
```jsx
|
package/package.json
CHANGED
package/routerino-forge.js
CHANGED
|
@@ -468,22 +468,103 @@ export function routerinoForge(options = {}) {
|
|
|
468
468
|
import React from 'react';
|
|
469
469
|
import ReactDOMServer from 'react-dom/server';
|
|
470
470
|
import * as routesModule from '${relativePath.startsWith(".") ? relativePath : "./" + relativePath}';
|
|
471
|
+
|
|
471
472
|
// Support different export patterns
|
|
472
473
|
const routes = routesModule.routes || routesModule.default;
|
|
473
474
|
const notFoundTemplate = routesModule.notFoundTemplate;
|
|
475
|
+
|
|
476
|
+
// Check if App component is exported from routes file
|
|
477
|
+
const App = routesModule.App || routesModule.default?.App;
|
|
478
|
+
|
|
474
479
|
if (!routes) {
|
|
475
480
|
throw new Error('Could not find routes export. Expected "export const routes" or "export default" from ${relativePath}');
|
|
476
481
|
}
|
|
482
|
+
|
|
477
483
|
// Helper to check if a route is dynamic (contains :param)
|
|
478
484
|
const isDynamicRoute = (path) => path.split("/").some(segment => segment.startsWith(":"));
|
|
479
485
|
export { routes };
|
|
480
|
-
|
|
486
|
+
|
|
487
|
+
// Mock minimal window object for SSG
|
|
488
|
+
function mockWindow(url, baseUrl) {
|
|
489
|
+
const urlObj = new URL(url, baseUrl || 'http://localhost');
|
|
490
|
+
global.window = {
|
|
491
|
+
location: {
|
|
492
|
+
href: urlObj.href,
|
|
493
|
+
pathname: urlObj.pathname,
|
|
494
|
+
search: urlObj.search,
|
|
495
|
+
hash: urlObj.hash,
|
|
496
|
+
origin: urlObj.origin,
|
|
497
|
+
protocol: urlObj.protocol,
|
|
498
|
+
host: urlObj.host,
|
|
499
|
+
hostname: urlObj.hostname,
|
|
500
|
+
port: urlObj.port
|
|
501
|
+
},
|
|
502
|
+
history: {
|
|
503
|
+
pushState: () => {},
|
|
504
|
+
replaceState: () => {}
|
|
505
|
+
},
|
|
506
|
+
scrollTo: () => {},
|
|
507
|
+
addEventListener: () => {},
|
|
508
|
+
removeEventListener: () => {},
|
|
509
|
+
dispatchEvent: () => {}
|
|
510
|
+
};
|
|
511
|
+
global.document = {
|
|
512
|
+
addEventListener: () => {},
|
|
513
|
+
removeEventListener: () => {},
|
|
514
|
+
querySelector: () => null,
|
|
515
|
+
createElement: () => ({
|
|
516
|
+
setAttribute: () => {},
|
|
517
|
+
appendChild: () => {}
|
|
518
|
+
}),
|
|
519
|
+
head: {
|
|
520
|
+
appendChild: () => {}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function render(url, baseUrl) {
|
|
526
|
+
// Check if we should render the full App or just the route element
|
|
527
|
+
if (App) {
|
|
528
|
+
// Mock window for the current route
|
|
529
|
+
mockWindow(url, baseUrl);
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
// Render the full App component (which includes Routerino)
|
|
533
|
+
const html = ReactDOMServer.renderToString(React.createElement(App));
|
|
534
|
+
|
|
535
|
+
// Find the rendered route to get its metadata
|
|
536
|
+
const route = routes.find(r => {
|
|
537
|
+
if (r.path === url) return true;
|
|
538
|
+
if (r.path === '/' && url === '/') return true;
|
|
539
|
+
if (isDynamicRoute(r.path)) return false;
|
|
540
|
+
return r.path === url;
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
html,
|
|
545
|
+
title: route?.title,
|
|
546
|
+
description: route?.description,
|
|
547
|
+
imageUrl: route?.imageUrl,
|
|
548
|
+
notFound: !route
|
|
549
|
+
};
|
|
550
|
+
} catch (error) {
|
|
551
|
+
console.error(\`[Routerino Forge] Failed to render App for route \${url}:\`, error.message);
|
|
552
|
+
// Fall back to route-only rendering
|
|
553
|
+
} finally {
|
|
554
|
+
// Clean up global mocks
|
|
555
|
+
delete global.window;
|
|
556
|
+
delete global.document;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Original behavior: render just the route element
|
|
481
561
|
const route = routes.find(r => {
|
|
482
562
|
if (r.path === url) return true;
|
|
483
563
|
if (r.path === '/' && url === '/') return true;
|
|
484
564
|
if (isDynamicRoute(r.path)) return false;
|
|
485
565
|
return r.path === url;
|
|
486
566
|
});
|
|
567
|
+
|
|
487
568
|
if (!route) {
|
|
488
569
|
if (notFoundTemplate) {
|
|
489
570
|
const notFoundHTML = ReactDOMServer.renderToString(notFoundTemplate);
|
|
@@ -491,6 +572,7 @@ export function render(url) {
|
|
|
491
572
|
}
|
|
492
573
|
return { html: '<div><h1>404 - Page Not Found</h1><p>The page you are looking for does not exist.</p></div>', notFound: true };
|
|
493
574
|
}
|
|
575
|
+
|
|
494
576
|
try {
|
|
495
577
|
const html = ReactDOMServer.renderToString(route.element);
|
|
496
578
|
return {
|
|
@@ -744,8 +826,8 @@ async function generateStaticPages({
|
|
|
744
826
|
}
|
|
745
827
|
|
|
746
828
|
try {
|
|
747
|
-
// Use the render function to generate HTML
|
|
748
|
-
const renderResult = render(route.path);
|
|
829
|
+
// Use the render function to generate HTML (pass baseUrl for window mocking)
|
|
830
|
+
const renderResult = render(route.path, config.baseUrl);
|
|
749
831
|
|
|
750
832
|
let renderedHTML = "";
|
|
751
833
|
if (renderResult.notFound) {
|
|
@@ -981,7 +1063,10 @@ async function generate404Page({ template, outputDir, config, render }) {
|
|
|
981
1063
|
|
|
982
1064
|
try {
|
|
983
1065
|
// Render a non-existent route to get the notFoundTemplate content
|
|
984
|
-
const renderResult = render(
|
|
1066
|
+
const renderResult = render(
|
|
1067
|
+
"/this-route-does-not-exist-404",
|
|
1068
|
+
config.baseUrl
|
|
1069
|
+
);
|
|
985
1070
|
|
|
986
1071
|
// The render function will return the notFoundTemplate HTML
|
|
987
1072
|
const renderedHTML = renderResult.html || "404 - Page Not Found";
|