diff --git a/example-ssr/src/components/App.tsx b/example-ssr/src/components/App.tsx index 1934442d..b97d34b6 100644 --- a/example-ssr/src/components/App.tsx +++ b/example-ssr/src/components/App.tsx @@ -1,5 +1,5 @@ -import React from "react" -import { Link, Stack } from "../../../src" +import React from "react"; +import { Link, Stack } from "../../../src"; export function App() { const crossedTransitions = ({ @@ -8,28 +8,30 @@ export function App() { unmountPreviousPage, }): Promise => { return new Promise(async (resolve) => { - const $current = currentPage?.$element - if ($current) $current.style.visibility = "hidden" + const $current = currentPage?.$element; + if ($current) $current.style.visibility = "hidden"; if (previousPage) { - previousPage.playOut() + previousPage.playOut(); } if (currentPage) { - await currentPage.isReadyPromise() - if ($current) $current.style.visibility = "visible" - await currentPage.playIn() - unmountPreviousPage() + await currentPage.isReadyPromise(); + if ($current) $current.style.visibility = "visible"; + await currentPage.playIn(); + unmountPreviousPage(); } - resolve() - }) - } + resolve(); + }); + }; return (
- +
- ) + ); } diff --git a/example-ssr/src/index-server.tsx b/example-ssr/src/index-server.tsx index f8b1efd5..1d0e5853 100644 --- a/example-ssr/src/index-server.tsx +++ b/example-ssr/src/index-server.tsx @@ -1,22 +1,29 @@ +import "isomorphic-unfetch"; import * as React from "react"; import ReactDOMServer from "react-dom/server"; import { routes } from "./routes"; import { App } from "./components/App"; -import { Router, TRoute } from "../../src"; +import { LangService, Router, TRoute } from "../../src"; import { getNotFoundRoute, getRouteFromUrl } from "../../src/core/matcher"; import { formatRoutes } from "../../src/core/helpers"; import { GlobalDataContext } from "./GlobalDataContext"; -import 'isomorphic-unfetch'; +import languages from "./languages"; export async function render(url) { /** * 1. savoir quel composant je dois rendre dans mon router */ + const langService = new LangService({ + staticLocation: url, + languages, + }); + const matchingRoute = getRouteFromUrl({ pUrl: url, pBase: "/", - pRoutes: formatRoutes(routes), + pRoutes: formatRoutes(routes, null, langService), }); + const notFoundRoute = getNotFoundRoute(routes); if (!matchingRoute && !notFoundRoute) { console.error("matchingRoute not found & 'notFoundRoute' not found, return."); @@ -52,7 +59,12 @@ export async function render(url) { */ return { renderToString: ReactDOMServer.renderToString( - + {/* Provide Global data */} diff --git a/example-ssr/src/index.tsx b/example-ssr/src/index.tsx index 0e954011..043f9daa 100644 --- a/example-ssr/src/index.tsx +++ b/example-ssr/src/index.tsx @@ -4,8 +4,13 @@ import { App } from "./components/App"; import { routes } from "./routes"; import { createBrowserHistory } from "history"; import "./index.css"; -import { Router } from "../../src"; +import { LangService, Router } from "../../src"; import { GlobalDataContext } from "./GlobalDataContext"; +import languages from "./languages"; + +const langService = new LangService({ + languages, +}); /** * Client side @@ -16,6 +21,7 @@ const root = hydrateRoot( routes={routes} history={createBrowserHistory()} initialStaticProps={window["__SSR_STATIC_PROPS__"]} + langService={langService} > diff --git a/example-ssr/src/languages.ts b/example-ssr/src/languages.ts new file mode 100644 index 00000000..dbec6395 --- /dev/null +++ b/example-ssr/src/languages.ts @@ -0,0 +1 @@ +export default [{ key: "fr" }, { key: "en" }] diff --git a/example-ssr/src/pages/HomePage.tsx b/example-ssr/src/pages/HomePage.tsx index 9cf79af9..3f781730 100644 --- a/example-ssr/src/pages/HomePage.tsx +++ b/example-ssr/src/pages/HomePage.tsx @@ -1,16 +1,13 @@ -import React, {useState, useRef, useEffect, useContext} from "react" -import { useStack } from "../../../src" -import { transitionsHelper } from "../helpers/transitionsHelper" -import {GlobalDataContext} from "../GlobalDataContext"; +import React, { useState, useRef, useEffect, useContext } from "react"; +import { useStack } from "../../../src"; +import { transitionsHelper } from "../helpers/transitionsHelper"; +import { GlobalDataContext } from "../GlobalDataContext"; -const componentName = "HomePage" +const componentName = "HomePage"; function HomePage(props, handleRef) { - const rootRef = useRef(null) - const [n, setN] = useState(0) - const {globalData} = useContext(GlobalDataContext) - - console.log('HOME props ', props) - console.log("Global data", globalData) + const rootRef = useRef(null); + const [n, setN] = useState(0); + const { globalData } = useContext(GlobalDataContext); useStack({ componentName, @@ -18,7 +15,7 @@ function HomePage(props, handleRef) { rootRef, playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), - }) + }); return (
@@ -28,9 +25,11 @@ function HomePage(props, handleRef) { HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME HOME
- {globalData.users.map((user,i) =>

{user.name}

)} + {globalData.users.map((user, i) => ( +

{user.name}

+ ))}
- ) + ); } -export default React.forwardRef(HomePage) +export default React.forwardRef(HomePage); diff --git a/src/components/Router.tsx b/src/components/Router.tsx index 4400117c..e48f5c7f 100644 --- a/src/components/Router.tsx +++ b/src/components/Router.tsx @@ -2,7 +2,7 @@ import debug from "@wbe/debug"; import { BrowserHistory, HashHistory, MemoryHistory } from "history"; import { Match } from "path-to-regexp"; import React from "react"; -import { formatRoutes } from "../core/helpers"; +import { formatRoutes, isSSR } from "../core/helpers"; import { getNotFoundRoute, getRouteFromUrl } from "../core/matcher"; import { Routers } from "../core/Routers"; import LangService from "../core/LangService"; @@ -125,10 +125,18 @@ function Router(props: { * const { routes } = useRouter(); * return current Router instance routes list, not all routes given to the first instance. */ - const routes = React.useMemo( - () => formatRoutes(props.routes, props.middlewares, langService, props.id), - [props.routes, langService, props.middlewares, props.id] - ); + const routes = React.useMemo(() => { + const routesList = formatRoutes( + props.routes, + props.middlewares, + langService, + props.id + ); + + // if is first instance, register result in Routers + if (!Routers.routes) Routers.routes = routesList; + return routesList; + }, [props.routes, langService, props.middlewares, props.id]); /** * 2. base @@ -252,7 +260,7 @@ function Router(props: { // if no newRoute, do not continue if (!newRoute) return; - + if (props.initialStaticProps) { const cache = staticPropsCache(); @@ -262,10 +270,13 @@ function Router(props: { // first route visited (server & client) const isFirstRouteVisited = newRoute.name === props.initialStaticProps.name; + // In SSR context, we have to manage getStaticProps route properties from server and client if (isFirstRouteVisited) { - Object.assign(newRoute.props, props.initialStaticProps.props); + if (newRoute.props) { + Object.assign(newRoute.props, props.initialStaticProps?.props ?? {}); + } if (!dataFromCache) { - cache.set(newRoute.fullUrl, props.initialStaticProps.props); + cache.set(newRoute.fullUrl, props.initialStaticProps?.props ?? {}); } } // if NOT first route (client) diff --git a/src/core/LangService.ts b/src/core/LangService.ts index 8654a332..f7a4f229 100644 --- a/src/core/LangService.ts +++ b/src/core/LangService.ts @@ -1,13 +1,14 @@ -import { Routers } from "../core/Routers"; +import { Routers } from "./Routers"; import { compileUrl, createUrl, joinPaths, removeLastCharFromString, -} from "../core/helpers"; -import debug from "@wbe/debug"; + getLangPathByLang, + isSSR, +} from "./helpers"; import { TRoute } from "../components/Router"; -import { getLangPathByLang } from "../core/helpers"; +import debug from "@wbe/debug"; const log = debug(`router:LangService`); @@ -48,20 +49,28 @@ class LangService { */ public base: string; + /** + * Static Location used for SSR context + */ + public staticLocation: string; + /** * Init languages service * @param languages * @param showDefaultLangInUrl * @param base + * @param staticLocation */ public constructor({ languages, showDefaultLangInUrl = true, base = "/", + staticLocation, }: { languages: TLanguage[]; showDefaultLangInUrl?: boolean; base?: string; + staticLocation?: string; }) { if (languages?.length === 0) { throw new Error("ERROR, no language is set."); @@ -69,6 +78,7 @@ class LangService { this.languages = languages; // remove extract / at the end, if exist this.base = removeLastCharFromString(base, "/", true); + this.staticLocation = staticLocation; this.defaultLang = this.getDefaultLang(languages); this.currentLang = this.getLangFromUrl() || this.defaultLang; this.showDefaultLangInUrl = showDefaultLangInUrl; @@ -232,7 +242,12 @@ class LangService { routes: TRoute[], showLangInUrl = this.showLangInUrl() ): TRoute[] { - if (!this.isInit) return routes; + if (routes?.some((el) => !!el.langPath)) { + log( + "Routes have already been formatted by 'addLangParamToRoutes()', return routes." + ); + return routes; + } /** * Add :lang param on path @@ -308,7 +323,9 @@ class LangService { * Get current language from URL * @param pathname */ - protected getLangFromUrl(pathname = window.location.pathname): TLanguage { + protected getLangFromUrl( + pathname = this.staticLocation ?? window.location.pathname + ): TLanguage { let pathnameWithoutBase = pathname.replace(this.base, "/"); const firstPart = joinPaths([pathnameWithoutBase]).split("/")[1]; @@ -335,6 +352,7 @@ class LangService { * @protected */ protected reloadOrRefresh(newUrl: string, forcePageReload = true): void { + if (isSSR()) return; forcePageReload ? window?.open(newUrl, "_self") : Routers.history.push(newUrl); } } diff --git a/src/core/helpers.ts b/src/core/helpers.ts index 7723e348..c8bbefb2 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -144,7 +144,7 @@ export function getSubRouterRoutes( * openRoute push a route in history * the Stack component will render the new route * @param args can be string or TOpenRouteParams object - * @param availablesRoutes + * @param history */ export function openRoute(args: string | TOpenRouteParams, history = Routers?.history) { const url = typeof args === "string" ? args : createUrl(args); @@ -169,16 +169,19 @@ export function formatRoutes( console.error(id, "props.routes is missing or empty, return."); return; } + // For each instances let routesList = patchMissingRootRoute(routes); - // Only for first instance - if (!Routers.routes) { + // subRouter instances shouldn't inquired middlewares and LangService + if (middlewares) { routesList = applyMiddlewares(routesList, middlewares); - if (langService) routesList = langService.addLangParamToRoutes(routesList); - Routers.routes = routesList; } - log(id, "routesList", routesList); + // Only for first instance + if (langService) { + routesList = langService.addLangParamToRoutes(routesList); + } + return routesList; } @@ -507,3 +510,10 @@ export function removeBaseToUrl(path: string, base: string): string { let baseStartIndex = path.indexOf(base); return baseStartIndex == 0 ? path.substr(base.length, path.length) : path; } + +/** + * Check if we are in SRR context + */ +export function isSSR() { + return !(typeof window != "undefined" && window.document); +}