diff --git a/.prettierrc b/.prettierrc index 15ea64f5..89dc3f5c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "printWidth": 90 + "printWidth": 90, + "semi": false, } diff --git a/src/components/Router.tsx b/src/components/Router.tsx index b9682779..bcb9a6c8 100644 --- a/src/components/Router.tsx +++ b/src/components/Router.tsx @@ -1,6 +1,6 @@ -import debug from "@wbe/debug"; -import { BrowserHistory, HashHistory, MemoryHistory } from "history"; -import { Match } from "path-to-regexp"; +import debug from "@wbe/debug" +import { BrowserHistory, HashHistory, MemoryHistory } from "history" +import { Match } from "path-to-regexp" import React, { createContext, memo, @@ -9,65 +9,65 @@ import React, { useMemo, useReducer, useRef, -} from "react"; -import { formatRoutes, TParams, TQueryParams } from "../core/core"; -import { getNotFoundRoute, getRouteFromUrl } from "../core/core"; -import { Routers } from "../core/Routers"; -import LangService, { TLanguage } from "../core/LangService"; -import { staticPropsCache } from "../core/staticPropsCache"; -import { isSSR, removeLastCharFromString } from "../core/helpers"; +} from "react" +import { formatRoutes, TParams, TQueryParams } from "../core/core" +import { getNotFoundRoute, getRouteFromUrl } from "../core/core" +import { Routers } from "../core/Routers" +import LangService, { TLanguage } from "../core/LangService" +import { staticPropsCache } from "../core/staticPropsCache" +import { isSSR, removeLastCharFromString } from "../core/helpers" // -------------------------------------------------------------------------------- TYPES export type TRouteProps = { - params?: TParams; - [x: string]: any; -}; + params?: TParams + [x: string]: any +} export type TRoute = Partial<{ - path: string | { [x: string]: string }; - component: React.ComponentType; - base: string; - name: string; - parser: Match; - props: TRouteProps; - children: TRoute[]; - url: string; - params?: TParams; - queryParams?: TQueryParams; - hash?: string; - getStaticProps: (props: TRouteProps, currentLang: TLanguage) => Promise; - _fullUrl: string; // full URL who not depends on current instance - _fullPath: string; // full Path /base/:lang/foo/second-foo - _langPath: { [x: string]: string } | null; - _context: TRoute; -}>; + path: string | { [x: string]: string } + component: React.ComponentType + base: string + name: string + parser: Match + props: TRouteProps + children: TRoute[] + url: string + params?: TParams + queryParams?: TQueryParams + hash?: string + getStaticProps: (props: TRouteProps, currentLang: TLanguage) => Promise + _fullUrl: string // full URL who not depends on current instance + _fullPath: string // full Path /base/:lang/foo/second-foo + _langPath: { [x: string]: string } | null + _context: TRoute +}> export interface IRouterContextStackStates { - unmountPreviousPage?: () => void; - previousPageIsMount?: boolean; + unmountPreviousPage?: () => void + previousPageIsMount?: boolean } export interface IRouterContext extends IRouterContextStackStates { - base: string; - routes: TRoute[]; - history: BrowserHistory | HashHistory | MemoryHistory | undefined; - staticLocation: string; - currentRoute: TRoute; - previousRoute: TRoute; - langService: LangService; - routeIndex: number; - previousPageIsMount: boolean; - unmountPreviousPage: () => void; - getPaused: () => boolean; - setPaused: (value: boolean) => void; + base: string + routes: TRoute[] + history: BrowserHistory | HashHistory | MemoryHistory | undefined + staticLocation: string + currentRoute: TRoute + previousRoute: TRoute + langService: LangService + routeIndex: number + previousPageIsMount: boolean + unmountPreviousPage: () => void + getPaused: () => boolean + setPaused: (value: boolean) => void } // -------------------------------------------------------------------------------- PREPARE / CONTEXT -const componentName = "Router"; -const log = debug(`router:${componentName}`); -const isServer = isSSR(); +const componentName = "Router" +const log = debug(`router:${componentName}`) +const isServer = isSSR() /** * Router Context * Router instance will be keep on this context @@ -87,13 +87,13 @@ export const RouterContext = createContext({ unmountPreviousPage: () => {}, getPaused: () => false, setPaused: (value: boolean) => {}, -}); -RouterContext.displayName = "RouterContext"; +}) +RouterContext.displayName = "RouterContext" Router.defaultProps = { base: "/", id: 1, -}; +} // -------------------------------------------------------------------------------- COMPONENT @@ -103,15 +103,15 @@ Router.defaultProps = { * @returns JSX.Element */ function Router(props: { - children: ReactNode; - routes: TRoute[]; - base: string; - history?: BrowserHistory | HashHistory | MemoryHistory | undefined; - staticLocation?: string; - middlewares?: ((routes: TRoute[]) => TRoute[])[]; - langService?: LangService; - id?: number | string; - initialStaticProps?: { props: any; name: string; url: string }; + children: ReactNode + routes: TRoute[] + base: string + history?: BrowserHistory | HashHistory | MemoryHistory | undefined + staticLocation?: string + middlewares?: ((routes: TRoute[]) => TRoute[])[] + langService?: LangService + id?: number | string + initialStaticProps?: { props: any; name: string; url: string } }): JSX.Element { /** * 0. LangService @@ -121,10 +121,10 @@ function Router(props: { const langService = useMemo(() => { if (!Routers.langService || (props.langService && isServer)) { - Routers.langService = props.langService; + Routers.langService = props.langService } - return Routers.langService; - }, [props.langService]); + return Routers.langService + }, [props.langService]) /** * 1. routes @@ -141,14 +141,14 @@ function Router(props: { langService, props.middlewares, props.id, - ); + ) // if is the first instance, register routes in Routers if (!Routers.routes || isServer) { - Routers.routes = routesList; + Routers.routes = routesList } - return routesList; - }, [props.routes, langService, props.middlewares, props.id]); + return routesList + }, [props.routes, langService, props.middlewares, props.id]) /** * 2. base @@ -158,9 +158,9 @@ function Router(props: { */ if (!Routers.base) { - Routers.base = props.base; + Routers.base = props.base } - const base = Routers.base; + const base = Routers.base /** * 3. history @@ -169,9 +169,9 @@ function Router(props: { */ if (props.history && !Routers.history) { - Routers.history = props.history; + Routers.history = props.history } - const history = Routers.history; + const history = Routers.history /** * 4 static location @@ -179,15 +179,15 @@ function Router(props: { */ if (props.staticLocation) { - Routers.staticLocation = props.staticLocation; + Routers.staticLocation = props.staticLocation } - const staticLocation: string | undefined = Routers.staticLocation; + const staticLocation: string | undefined = Routers.staticLocation /** * 5. reset is fist route visited */ if (isServer) { - Routers.isFirstRoute = true; + Routers.isFirstRoute = true } // -------------------------------------------------------------------------------- ROUTE CHANGE @@ -208,9 +208,9 @@ function Router(props: { currentRoute: action.value, routeIndex: state.routeIndex + 1, previousPageIsMount: true, - }; + } case "unmount-previous-page": - return { ...state, previousPageIsMount: false }; + return { ...state, previousPageIsMount: false } } }, { @@ -219,23 +219,23 @@ function Router(props: { previousPageIsMount: false, routeIndex: 0, }, - ); + ) /** * Enable paused on Router instance */ - const _waitingUrl = useRef(null); - const _paused = useRef(false); - const getPaused = () => _paused.current; + const _waitingUrl = useRef(null) + const _paused = useRef(false) + const getPaused = () => _paused.current const setPaused = (value: boolean) => { - _paused.current = value; + _paused.current = value if (!value && _waitingUrl.current) { - handleHistory(_waitingUrl.current); - _waitingUrl.current = null; + handleHistory(_waitingUrl.current) + _waitingUrl.current = null } - }; + } - const currentRouteRef = useRef(); + const currentRouteRef = useRef() /** * Handle history @@ -245,8 +245,8 @@ function Router(props: { const handleHistory = async (url = ""): Promise => { if (_paused.current) { - _waitingUrl.current = url; - return; + _waitingUrl.current = url + return } const matchingRoute = getRouteFromUrl({ @@ -254,33 +254,33 @@ function Router(props: { pRoutes: routes, pBase: props.base, id: props.id, - }); + }) - const notFoundRoute = getNotFoundRoute(props.routes); + const notFoundRoute = getNotFoundRoute(props.routes) if (!matchingRoute && !notFoundRoute) { - log(props.id, "matchingRoute not found & 'notFoundRoute' not found, return."); - return; + log(props.id, "matchingRoute not found & 'notFoundRoute' not found, return.") + return } const currentRouteUrl = - currentRouteRef.current?._context?.url ?? currentRouteRef.current?.url; - const matchingRouteUrl = matchingRoute?._context?.url ?? matchingRoute?.url; + currentRouteRef.current?._context?.url ?? currentRouteRef.current?.url + const matchingRouteUrl = matchingRoute?._context?.url ?? matchingRoute?.url if (currentRouteUrl === matchingRouteUrl) { - log(props.id, "this is the same route 'url', return."); - return; + log(props.id, "this is the same route 'url', return.") + return } // new route - const newRoute: TRoute = matchingRoute || notFoundRoute; - if (!newRoute) return; + const newRoute: TRoute = matchingRoute || notFoundRoute + if (!newRoute) return // prepare cache for new route data staticProps - const cache = staticPropsCache(); + const cache = staticPropsCache() // check if new route data as been store in cache // the matcher will match even if the URL ends with a slash - const fullUrl = removeLastCharFromString(newRoute._fullUrl, "/"); - const [urlWithoutHash] = fullUrl.split("#"); + const fullUrl = removeLastCharFromString(newRoute._fullUrl, "/") + const [urlWithoutHash] = fullUrl.split("#") /** * Request static props and cache it @@ -290,13 +290,13 @@ function Router(props: { const request = await newRoute.getStaticProps( newRoute.props, langService?.currentLang, - ); - Object.assign(newRoute.props, request); - cache.set(urlWithoutHash, request); + ) + Object.assign(newRoute.props, request) + cache.set(urlWithoutHash, request) } catch (e) { - console.error("requestStaticProps failed"); + console.error("requestStaticProps failed") } - }; + } // SERVER (first route) // prettier-ignore @@ -337,13 +337,13 @@ function Router(props: { } // Final process: update context currentRoute from dispatch method \o/ ! - dispatch({ type: "update-current-route", value: newRoute }); + dispatch({ type: "update-current-route", value: newRoute }) // & register this new route as currentRoute in local and in Routers store - currentRouteRef.current = newRoute; - Routers.currentRoute = newRoute; - Routers.isFirstRoute = false; - }; + currentRouteRef.current = newRoute + Routers.currentRoute = newRoute + Routers.isFirstRoute = false + } /** * Here we go! @@ -353,46 +353,46 @@ function Router(props: { * - Dispatch new routes states from RouterContext */ const historyListener = useMemo(() => { - if (!routes) return; + if (!routes) return const historyListener = () => { if (staticLocation && history) { - console.error(`You can't set history and staticLocation props together, return.`); - return; + console.error(`You can't set history and staticLocation props together, return.`) + return } // server if (staticLocation) { - handleHistory(staticLocation); - return; + handleHistory(staticLocation) + return // client } else if (history) { handleHistory( window.location.pathname + window.location.search + window.location.hash, - ); + ) return history?.listen(({ location }) => { - handleHistory(location.pathname + location.search + location.hash); - }); + handleHistory(location.pathname + location.search + location.hash) + }) // log error } else { - console.error(`An history or staticLocation props is required, return.`); - return; + console.error(`An history or staticLocation props is required, return.`) + return } - }; - return historyListener(); - }, [routes, history]); + } + return historyListener() + }, [routes, history]) // remove listener useEffect(() => { return () => { - log(props.id, "Stop to listen history."); - historyListener(); - }; - }, [historyListener]); + log(props.id, "Stop to listen history.") + historyListener() + } + }, [historyListener]) // -------------------------------------------------------------------------------- RENDER - const { currentRoute, previousRoute, routeIndex, previousPageIsMount } = reducerState; - const unmountPreviousPage = () => dispatch({ type: "unmount-previous-page" }); + const { currentRoute, previousRoute, routeIndex, previousPageIsMount } = reducerState + const unmountPreviousPage = () => dispatch({ type: "unmount-previous-page" }) return ( - ); + ) } -Router.displayName = componentName; -const MemoizedRouter = memo(Router); -export { MemoizedRouter as Router }; +Router.displayName = componentName +const MemoizedRouter = memo(Router) +export { MemoizedRouter as Router } diff --git a/src/core/LangService.ts b/src/core/LangService.ts index ab0e4a71..4f846c7f 100644 --- a/src/core/LangService.ts +++ b/src/core/LangService.ts @@ -1,52 +1,52 @@ -import { Routers } from "./Routers"; -import { compileUrl, createUrl } from "./core"; -import { isSSR, joinPaths, removeLastCharFromString } from "./helpers"; -import { TRoute } from "../components/Router"; -import debug from "@wbe/debug"; +import { Routers } from "./Routers" +import { compileUrl, createUrl } from "./core" +import { isSSR, joinPaths, removeLastCharFromString } from "./helpers" +import { TRoute } from "../components/Router" +import debug from "@wbe/debug" -const log = debug(`router:LangService`); +const log = debug(`router:LangService`) export type TLanguage = { - key: T | string; - name?: string; - default?: boolean; -}; + key: T | string + name?: string + default?: boolean +} class LangService { /** * contains available languages */ - public languages: TLanguage[]; + public languages: TLanguage[] /** * Current language object */ - public currentLang: TLanguage; + public currentLang: TLanguage /** * Default language object */ - public defaultLang: TLanguage; + public defaultLang: TLanguage /** * Browser language */ - public browserLang: TLanguage; + public browserLang: TLanguage /** * Show default language in URL */ - public showDefaultLangInUrl: boolean; + public showDefaultLangInUrl: boolean /** * Base URL of the app */ - public base: string; + public base: string /** * Static Location used for SSR context */ - public staticLocation: string; + public staticLocation: string /** * Init languages service @@ -61,22 +61,22 @@ class LangService { base = "/", staticLocation, }: { - languages: TLanguage[]; - showDefaultLangInUrl?: boolean; - base?: string; - staticLocation?: string; + languages: TLanguage[] + showDefaultLangInUrl?: boolean + base?: string + staticLocation?: string }) { if (languages?.length === 0) { - throw new Error("ERROR, no language is set."); + throw new Error("ERROR, no language is set.") } - this.languages = languages; + 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.getLangFromString() || this.defaultLang; - this.browserLang = this.getBrowserLang(languages); - this.showDefaultLangInUrl = showDefaultLangInUrl; + this.base = removeLastCharFromString(base, "/", true) + this.staticLocation = staticLocation + this.defaultLang = this.getDefaultLang(languages) + this.currentLang = this.getLangFromString() || this.defaultLang + this.browserLang = this.getBrowserLang(languages) + this.showDefaultLangInUrl = showDefaultLangInUrl } /** @@ -97,15 +97,15 @@ class LangService { public setLang( toLang: TLanguage, forcePageReload = true, - currentRoute: TRoute = Routers.currentRoute + currentRoute: TRoute = Routers.currentRoute, ): void { if (toLang.key === this.currentLang.key) { - log("setLang: This is the same language, exit."); - return; + log("setLang: This is the same language, exit.") + return } if (!this.langIsAvailable(toLang)) { - log(`setLang: lang ${toLang.key} is not available in languages list, exit.`); - return; + log(`setLang: lang ${toLang.key} is not available in languages list, exit.`) + return } // Translate currentRoute URL to new lang URL @@ -116,66 +116,66 @@ class LangService { ...(currentRoute.props?.params || {}), lang: toLang.key, }, - }); - log("preparedNewUrl", preparedNewUrl); + }) + log("preparedNewUrl", preparedNewUrl) // create newUrl variable to set in each condition - let newUrl: string; + let newUrl: string // choose force page reload in condition below - let chooseForcePageReload = forcePageReload; + let chooseForcePageReload = forcePageReload // 1. if default language should be always visible in URL if (this.showDefaultLangInUrl) { - newUrl = preparedNewUrl; + newUrl = preparedNewUrl } // 2. if toLang is default lang, need to REMOVE lang from URL else if (!this.showDefaultLangInUrl && this.isDefaultLangKey(toLang.key)) { - const urlPartToRemove = `${this.base}/${toLang.key}`; + const urlPartToRemove = `${this.base}/${toLang.key}` const newUrlWithoutBaseAndLang = preparedNewUrl.substring( urlPartToRemove.length, - preparedNewUrl.length - ); - newUrl = joinPaths([this.base, newUrlWithoutBaseAndLang]); - chooseForcePageReload = true; - log("2. after remove lang from URL", newUrl); + preparedNewUrl.length, + ) + newUrl = joinPaths([this.base, newUrlWithoutBaseAndLang]) + chooseForcePageReload = true + log("2. after remove lang from URL", newUrl) } // 3. if current lang is default lang, add /currentLang.key after base else if (!this.showDefaultLangInUrl && this.isDefaultLangKey(this.currentLang.key)) { const newUrlWithoutBase = preparedNewUrl.substring( this.base.length, - preparedNewUrl.length - ); - newUrl = joinPaths([this.base, "/", toLang.key as string, "/", newUrlWithoutBase]); - log("3. after add lang in URL", newUrl); + preparedNewUrl.length, + ) + newUrl = joinPaths([this.base, "/", toLang.key as string, "/", newUrlWithoutBase]) + log("3. after add lang in URL", newUrl) } // 4. other cases else { - newUrl = preparedNewUrl; - log("4, other case"); + newUrl = preparedNewUrl + log("4, other case") } if (!newUrl) { - log("newUrl is no set, do not reload or refresh, return.", newUrl); - return; + log("newUrl is no set, do not reload or refresh, return.", newUrl) + return } // register current language (not useful if we reload the app.) - this.currentLang = toLang; + this.currentLang = toLang // remove last / if exist and if he is not alone - newUrl = removeLastCharFromString(newUrl, "/", true); + newUrl = removeLastCharFromString(newUrl, "/", true) // reload or refresh with new URL - this.reloadOrRefresh(newUrl, chooseForcePageReload); + this.reloadOrRefresh(newUrl, chooseForcePageReload) } public redirectToBrowserLang(forcePageReload: boolean = true) { - log("browserLang object", this.browserLang); + log("browserLang object", this.browserLang) // If browser language doesn't match, redirect to default lang if (!this.browserLang) { - log("browserLang is not set, redirect to defaultLang"); - this.redirectToDefaultLang(forcePageReload); - return; + log("browserLang is not set, redirect to defaultLang") + this.redirectToDefaultLang(forcePageReload) + return } // We want to redirect only in case user is on / or /base/ if ( @@ -185,10 +185,10 @@ class LangService { // prepare path and build URL const newUrl = compileUrl(joinPaths([this.base, "/:lang"]), { lang: this.browserLang.key, - }); - log("redirect: to browser language >", { newUrl }); + }) + log("redirect: to browser language >", { newUrl }) // reload or refresh all application - this.reloadOrRefresh(newUrl, forcePageReload); + this.reloadOrRefresh(newUrl, forcePageReload) } } @@ -197,15 +197,15 @@ class LangService { * @param forcePageReload */ public redirectToDefaultLang(forcePageReload: boolean = true): void { - if (isSSR()) return; + if (isSSR()) return if (!this.showDefaultLangInUrl) { - log("redirect: URLs have a lang param or language is valid, don't redirect."); - return; + log("redirect: URLs have a lang param or language is valid, don't redirect.") + return } if (this.langIsAvailable(this.getLangFromString())) { - log("redirect: lang from URL is valid, don't redirect"); - return; + log("redirect: lang from URL is valid, don't redirect") + return } // We want to redirect only in case user is on / or /base/ if ( @@ -215,10 +215,10 @@ class LangService { // prepare path & build new URL const newUrl = compileUrl(joinPaths([this.base, "/:lang"]), { lang: this.defaultLang.key, - }); - log("redirect to default lang >", { newUrl }); + }) + log("redirect to default lang >", { newUrl }) // reload or refresh all application - this.reloadOrRefresh(newUrl, forcePageReload); + this.reloadOrRefresh(newUrl, forcePageReload) } } @@ -226,7 +226,7 @@ class LangService { * Current lang is default lang */ public isDefaultLangKey(langKey = this.currentLang.key): boolean { - return langKey === this.defaultLang.key; + return langKey === this.defaultLang.key } /** @@ -235,11 +235,11 @@ class LangService { public showLangInUrl(): boolean { // if option is true, always display lang in URL if (this.showDefaultLangInUrl) { - return true; + return true // if this option is false } else { // show in URL only if whe are not on the default lang - return !this.isDefaultLangKey(this.currentLang.key); + return !this.isDefaultLangKey(this.currentLang.key) } } @@ -259,10 +259,10 @@ class LangService { */ public addLangParamToRoutes( routes: TRoute[], - showLangInUrl = this.showLangInUrl() + showLangInUrl = this.showLangInUrl(), ): TRoute[] { if (routes?.some((el) => !!el._langPath)) { - return routes; + return routes } /** @@ -273,8 +273,8 @@ class LangService { const patchLangParam = (pPath: string, pShowLang): string => removeLastCharFromString( joinPaths([pShowLang && "/:lang", pPath !== "/" ? pPath : "/"]), - "/" - ); + "/", + ) /** * Patch routes @@ -293,15 +293,15 @@ class LangService { */ const patchRoutes = (pRoutes, children = false) => { return pRoutes.map((route: TRoute) => { - const path = this.getLangPathByLang(route); - const hasChildren = route.children?.length > 0; - const showLang = !children && showLangInUrl; + const path = this.getLangPathByLang(route) + const hasChildren = route.children?.length > 0 + const showLang = !children && showLangInUrl - let langPath = {}; + let langPath = {} if (typeof route.path === "object") { Object.keys(route.path).forEach((lang) => { - langPath[lang] = patchLangParam(route.path[lang], showLang); - }); + langPath[lang] = patchLangParam(route.path[lang], showLang) + }) } // even if route.path is not an object, add his value to route.langPath object property @@ -309,8 +309,8 @@ class LangService { this.languages .map((el) => el.key) .forEach((key: string) => { - langPath[key] = patchLangParam(route.path as string, showLang); - }); + langPath[key] = patchLangParam(route.path as string, showLang) + }) } return { @@ -318,10 +318,10 @@ class LangService { path: patchLangParam(path, showLang), _langPath: Object.entries(langPath).length !== 0 ? langPath : null, ...(hasChildren ? { children: patchRoutes(route.children, true) } : {}), - }; - }); - }; - return patchRoutes(routes); + } + }) + } + return patchRoutes(routes) } // --------------------------------------------------------------------------- LOCAL @@ -342,17 +342,17 @@ class LangService { protected getLangPathByLang( route: TRoute, lang = this.getLangFromString(this.staticLocation || window.location.pathname)?.key || - this.defaultLang.key + this.defaultLang.key, ): string { - let selectedPath: string; + let selectedPath: string if (typeof route.path === "string") { - selectedPath = route.path; + selectedPath = route.path } else if (typeof route.path === "object") { Object.keys(route.path).find((el) => { - if (el === lang) selectedPath = route.path?.[el]; - }); + if (el === lang) selectedPath = route.path?.[el] + }) } - return selectedPath; + return selectedPath } /** @@ -361,7 +361,7 @@ class LangService { * @param languages */ protected getDefaultLang(languages: TLanguage[]): TLanguage { - return languages.find((el) => el?.default) ?? languages[0]; + return languages.find((el) => el?.default) ?? languages[0] } /** @@ -369,16 +369,16 @@ class LangService { * @protected */ protected getBrowserLang(languages: TLanguage[]): TLanguage { - if (typeof navigator === "undefined") return; + if (typeof navigator === "undefined") return - const browserLanguage = navigator.language; - log("Browser language detected", browserLanguage); + const browserLanguage = navigator.language + log("Browser language detected", browserLanguage) return languages.find((lang) => lang.key.includes("-") ? (lang.key as string) === browserLanguage.toLowerCase() - : (lang.key as string) === browserLanguage.toLowerCase().split("-")[0] - ); + : (lang.key as string) === browserLanguage.toLowerCase().split("-")[0], + ) } /** @@ -386,13 +386,13 @@ class LangService { * @param pathname */ public getLangFromString( - pathname = this.staticLocation || window.location.pathname + pathname = this.staticLocation || window.location.pathname, ): TLanguage { - let pathnameWithoutBase = pathname.replace(this.base, "/"); - const firstPart = joinPaths([pathnameWithoutBase]).split("/")[1]; + let pathnameWithoutBase = pathname.replace(this.base, "/") + const firstPart = joinPaths([pathnameWithoutBase]).split("/")[1] return this.languages.find((language) => { - return firstPart === language.key; - }); + return firstPart === language.key + }) } /** @@ -401,9 +401,9 @@ class LangService { */ protected langIsAvailable( langObject: TLanguage, - languesList = this.languages + languesList = this.languages, ): boolean { - return languesList.some((lang) => lang.key === langObject?.key); + return languesList.some((lang) => lang.key === langObject?.key) } /** @@ -413,9 +413,9 @@ class LangService { * @protected */ protected reloadOrRefresh(newUrl: string, forcePageReload = true): void { - if (isSSR()) return; - forcePageReload ? window?.open(newUrl, "_self") : Routers.history.push(newUrl); + if (isSSR()) return + forcePageReload ? window?.open(newUrl, "_self") : Routers.history.push(newUrl) } } -export default LangService; +export default LangService diff --git a/src/core/core.ts b/src/core/core.ts index b8446e39..7b01893c 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -1,22 +1,22 @@ -import { Routers } from "./Routers"; -import debug from "@wbe/debug"; -import { compile, match } from "path-to-regexp"; -import { TRoute } from "../components/Router"; -import LangService from "./LangService"; -import { joinPaths, removeLastCharFromString } from "./helpers"; +import { Routers } from "./Routers" +import debug from "@wbe/debug" +import { compile, match } from "path-to-regexp" +import { TRoute } from "../components/Router" +import LangService from "./LangService" +import { joinPaths, removeLastCharFromString } from "./helpers" -const componentName: string = "core"; -const log = debug(`router:${componentName}`); +const componentName: string = "core" +const log = debug(`router:${componentName}`) -export type TParams = { [x: string]: any }; -export type TQueryParams = { [x: string]: string }; +export type TParams = { [x: string]: any } +export type TQueryParams = { [x: string]: string } export type TOpenRouteParams = { - name: string; - params?: TParams; - queryParams?: TQueryParams; - hash?: string; -}; + name: string + params?: TParams + queryParams?: TQueryParams + hash?: string +} // ----------------------------------------------------------------------------- PUBLIC @@ -35,20 +35,20 @@ export function createUrl( allRoutes: TRoute[] = Routers.routes, langService = Routers.langService, ): string { - if (!allRoutes) return; - let urlToPush: string; + if (!allRoutes) return + let urlToPush: string if (typeof args === "object" && !langService) { log( "route.path object is not supported without langService. Use should use route.path string instead.", - ); + ) } // in case we receive a string if (typeof args === "string") { - urlToPush = args as string; + urlToPush = args as string if (!!langService) { - urlToPush = addLangToUrl(urlToPush); + urlToPush = addLangToUrl(urlToPush) } } @@ -59,40 +59,40 @@ export function createUrl( args.params = { ...args.params, lang: langService.currentLang.key, - }; + } } // add params to URL if exist - let queryParams = ""; + let queryParams = "" if (args?.queryParams) { - queryParams = "?"; + queryParams = "?" queryParams += Object.keys(args.queryParams) .map((key) => `${key}=${args?.queryParams[key]}`) - .join("&"); + .join("&") } // add hash to URL if exist - let hash = ""; + let hash = "" if (args?.hash) { - hash = "#" + args.hash; + hash = "#" + args.hash } // Get URL by the route name - urlToPush = getUrlByRouteName(allRoutes, args) + queryParams + hash; + urlToPush = getUrlByRouteName(allRoutes, args) + queryParams + hash // in other case return. } else { - console.warn("createUrl param isn't valid. to use createUrl return.", args); - return; + console.warn("createUrl param isn't valid. to use createUrl return.", args) + return } // Add base const addBaseToUrl = (url: string, base = Routers.base): string => - joinPaths([base === "/" ? "" : base, url]); + joinPaths([base === "/" ? "" : base, url]) // compile base if contains lang params - const newBase = compileUrl(base, { lang: Routers.langService?.currentLang.key }); + const newBase = compileUrl(base, { lang: Routers.langService?.currentLang.key }) // in each case, add base URL - urlToPush = addBaseToUrl(urlToPush, newBase); - return urlToPush; + urlToPush = addBaseToUrl(urlToPush, newBase) + return urlToPush } /** @@ -111,9 +111,9 @@ export function getSubRouterBase( ): string { // case langService is init, and we don't want to show default lang in URL, and we are on default lang. // /:lang is return as path, but we want to get no lang in returned base string - const addLang = Routers.langService?.showLangInUrl() && addLangToUrl ? "/:lang" : ""; - const pathAfterLang = path === "/:lang" ? getLangPath("/") : getLangPath(path); - return joinPaths([base, addLang, pathAfterLang]); + const addLang = Routers.langService?.showLangInUrl() && addLangToUrl ? "/:lang" : "" + const pathAfterLang = path === "/:lang" ? getLangPath("/") : getLangPath(path) + return joinPaths([base, addLang, pathAfterLang]) } /** @@ -129,11 +129,11 @@ export function getSubRouterRoutes( // case langService is init, and we don't want to show default lang in URL, and we are on default lang. // /:lang is return as path, but we want to search path with "/" instead const formattedPath = - !Routers.langService?.showLangInUrl() && path === "/:lang" ? "/" : path; + !Routers.langService?.showLangInUrl() && path === "/:lang" ? "/" : path return routes.find((route) => { - return getLangPath(route.path) === getLangPath(formattedPath); - })?.children; + return getLangPath(route.path) === getLangPath(formattedPath) + })?.children } /** @@ -153,15 +153,15 @@ export function getPathByRouteName( // specific case, we want to retrieve path of route with "/" route and langService is used, // we need to patch it with lang param if (route.path === "/" && Routers.langService) { - return "/:lang"; + return "/:lang" } else { - return route.path; + return route.path } } else { if (route.children) { - const next = getPathByRouteName(route.children, name); + const next = getPathByRouteName(route.children, name) if (next) { - return next; + return next } } } @@ -175,8 +175,8 @@ export function getPathByRouteName( * @param history */ export function openRoute(args: string | TOpenRouteParams, history = Routers?.history) { - const url = typeof args === "string" ? args : createUrl(args); - history?.push(url); + const url = typeof args === "string" ? args : createUrl(args) + history?.push(url) } /** @@ -193,31 +193,31 @@ export async function requestStaticPropsFromRoute({ middlewares, id, }: { - url: string; - base: string; - routes: TRoute[]; - langService?: LangService; - middlewares?: ((routes: TRoute[]) => TRoute[])[]; - id?: string | number; + url: string + base: string + routes: TRoute[] + langService?: LangService + middlewares?: ((routes: TRoute[]) => TRoute[])[] + id?: string | number }): Promise<{ props: any; name: string; url: string }> { const currentRoute = getRouteFromUrl({ pUrl: url, pBase: base, pRoutes: formatRoutes(routes, langService, middlewares, id), id, - }); + }) - const notFoundRoute = getNotFoundRoute(routes); + const notFoundRoute = getNotFoundRoute(routes) if (!currentRoute && !notFoundRoute) { - log(id, "currentRoute not found & 'notFoundRoute' not found, return."); - return; + log(id, "currentRoute not found & 'notFoundRoute' not found, return.") + return } // get out if (!currentRoute) { - log("No currentRoute, return"); - return; + log("No currentRoute, return") + return } // prepare returned obj @@ -225,7 +225,7 @@ export async function requestStaticPropsFromRoute({ props: null, name: currentRoute.name, url, - }; + } // await promise from getStaticProps if (currentRoute?.getStaticProps) { @@ -233,26 +233,26 @@ export async function requestStaticPropsFromRoute({ SSR_STATIC_PROPS.props = await currentRoute.getStaticProps( currentRoute.props, langService?.currentLang, - ); + ) } catch (e) { - log("fetch getStatic Props data error"); + log("fetch getStatic Props data error") } } - return SSR_STATIC_PROPS; + return SSR_STATIC_PROPS } // ----------------------------------------------------------------------------- MATCHER type TGetRouteFromUrl = { - pUrl: string; - pRoutes?: TRoute[]; - pBase?: string; - pCurrentRoute?: TRoute; - pMatcher?: any; - pParent?: TRoute; - id?: number | string; - urlWithoutHashAndQuery?: string; -}; + pUrl: string + pRoutes?: TRoute[] + pBase?: string + pCurrentRoute?: TRoute + pMatcher?: any + pParent?: TRoute + id?: number | string + urlWithoutHashAndQuery?: string +} /** * Get current route from URL, using path-to-regex @@ -265,10 +265,10 @@ export function getRouteFromUrl({ pMatcher, id, }: TGetRouteFromUrl): TRoute { - if (!pRoutes || pRoutes?.length === 0) return; + if (!pRoutes || pRoutes?.length === 0) return // extract queryParams params and hash - const { queryParams, hash, urlWithoutHashAndQuery } = extractQueryParamsAndHash(pUrl); + const { queryParams, hash, urlWithoutHashAndQuery } = extractQueryParamsAndHash(pUrl) function next({ pUrl, @@ -285,17 +285,17 @@ export function getRouteFromUrl({ const currentRoutePath = removeLastCharFromString( joinPaths([pBase, currentRoute.path as string]), "/", - ); - const matcher = match(currentRoutePath)(urlWithoutHashAndQuery); + ) + const matcher = match(currentRoutePath)(urlWithoutHashAndQuery) // prettier-ignore log(id, `url "${urlWithoutHashAndQuery}" match path "${currentRoutePath}"?`,!!matcher); // if current route path match with the param url if (matcher) { - const params = pMatcher?.params || matcher?.params; + const params = pMatcher?.params || matcher?.params const formatRouteObj = (route) => { - if (!route) return; + if (!route) return return { path: route?.path, url: compile(route.path as string)(params), @@ -315,17 +315,17 @@ export function getRouteFromUrl({ _fullPath: currentRoutePath, _fullUrl: pUrl, _langPath: route?._langPath, - }; - }; + } + } - const formattedCurrentRoute = formatRouteObj(currentRoute); + const formattedCurrentRoute = formatRouteObj(currentRoute) const routeObj = { ...formattedCurrentRoute, _context: pParent ? formatRouteObj(pParent) : formattedCurrentRoute, - }; + } - log(id, "match", routeObj); - return routeObj; + log(id, "match", routeObj) + return routeObj } // if not match @@ -339,17 +339,17 @@ export function getRouteFromUrl({ pParent: pParent || currentRoute, pBase: currentRoutePath, // parent base pMatcher: matcher, - }); + }) // only if matching, return this match, else continue to next iteration if (matchingChildren) { - return matchingChildren; + return matchingChildren } } } } - return next({ pUrl, urlWithoutHashAndQuery, pRoutes, pBase, pMatcher, id }); + return next({ pUrl, urlWithoutHashAndQuery, pRoutes, pBase, pMatcher, id }) } /** @@ -360,44 +360,44 @@ export function getRouteFromUrl({ export function getNotFoundRoute(routes: TRoute[]): TRoute { return routes?.find( (el) => el.path === "/:rest" || el.component?.displayName === "NotFoundPage", - ); + ) } export const extractQueryParamsAndHash = ( url: string, ): { - queryParams: { [x: string]: string }; - hash: string; - urlWithoutHashAndQuery: string; + queryParams: { [x: string]: string } + hash: string + urlWithoutHashAndQuery: string } => { - let queryParams = {}; - let hash = null; - const queryIndex = url.indexOf("?"); - const hashIndex = url.indexOf("#"); + let queryParams = {} + let hash = null + const queryIndex = url.indexOf("?") + const hashIndex = url.indexOf("#") if (queryIndex === -1 && hashIndex === -1) { - return { queryParams, hash, urlWithoutHashAndQuery: url }; + return { queryParams, hash, urlWithoutHashAndQuery: url } } // Extract hash if (hashIndex !== -1) { - hash = url.slice(hashIndex + 1); + hash = url.slice(hashIndex + 1) } // Extract queryParams parameters if (queryIndex !== -1) { const queryString = url.slice( queryIndex + 1, hashIndex !== -1 ? hashIndex : undefined, - ); - const searchParams = new URLSearchParams(queryString); - searchParams.forEach((value, key) => (queryParams[key] = value)); + ) + const searchParams = new URLSearchParams(queryString) + searchParams.forEach((value, key) => (queryParams[key] = value)) } // finally remove queryParams and hash from pathname for (let e of ["?", "#"]) { - url = url.includes(e) ? url.split(e)[0] : url; + url = url.includes(e) ? url.split(e)[0] : url } - return { queryParams, hash, urlWithoutHashAndQuery: url }; -}; + return { queryParams, hash, urlWithoutHashAndQuery: url } +} // ----------------------------------------------------------------------------- ROUTES @@ -420,8 +420,8 @@ export const extractQueryParamsAndHash = ( */ export function patchMissingRootRoute(routes: TRoute[] = Routers.routes): TRoute[] { if (!routes) { - log("routes doesnt exist, return", routes); - return; + log("routes doesnt exist, return", routes) + return } const rootPathExist = routes.some( (route) => @@ -431,15 +431,15 @@ export function patchMissingRootRoute(routes: TRoute[] = Routers.routes): TRoute )) || route.path === "/" || route.path === "/:lang", - ); + ) if (!rootPathExist) { routes.unshift({ path: "/", component: null, name: `auto-generate-slash-route-${Math.random()}`, - }); + }) } - return routes; + return routes } /** @@ -457,7 +457,7 @@ export function applyMiddlewaresToRoutes( (routes, middleware) => middleware(routes), preMiddlewareRoutes, ) || preMiddlewareRoutes - ); + ) } /** @@ -472,23 +472,23 @@ export function formatRoutes( id?: number | string, ): TRoute[] { if (!routes) { - console.error(id, "props.routes is missing or empty, return."); - return; + console.error(id, "props.routes is missing or empty, return.") + return } // For each instances - let routesList = patchMissingRootRoute(routes); + let routesList = patchMissingRootRoute(routes) // subRouter instances shouldn't inquired middlewares and LangService if (middlewares) { - routesList = applyMiddlewaresToRoutes(routesList, middlewares); + routesList = applyMiddlewaresToRoutes(routesList, middlewares) } // Only for first instance if (langService) { - routesList = langService.addLangParamToRoutes(routesList); + routesList = langService.addLangParamToRoutes(routesList) } - return routesList; + return routesList } // ----------------------------------------------------------------------------- URLS / PATH @@ -499,7 +499,7 @@ export function formatRoutes( * compile("foo/:id")({id: example-client}) // "foo/example-client" */ export function compileUrl(path: string, params?: TParams): string { - return compile(path)(params); + return compile(path)(params) } /** @@ -515,19 +515,19 @@ export function getFullPathByPath( lang: string = Routers.langService?.currentLang.key || undefined, basePath: string = null, ): string { - let localPath: string[] = [basePath]; + let localPath: string[] = [basePath] for (let route of routes) { - const langPath = route._langPath?.[lang]; - const routePath = route.path as string; + const langPath = route._langPath?.[lang] + const routePath = route.path as string const pathMatch = - (langPath === path || routePath === path) && route.name === routeName; + (langPath === path || routePath === path) && route.name === routeName // if path match on first level, keep path in local array and return it, stop here. if (pathMatch) { - localPath.push(langPath || routePath); - return joinPaths(localPath); + localPath.push(langPath || routePath) + return joinPaths(localPath) } // if not matching but as children, return it @@ -539,11 +539,11 @@ export function getFullPathByPath( routeName, lang, joinPaths(localPath), - ); + ) // return recursive Fn only if match, else continue to next iteration if (matchChildrenPath) { // keep path in local array - localPath.push(langPath || routePath); + localPath.push(langPath || routePath) // Return the function after localPath push return getFullPathByPath( route.children, @@ -551,7 +551,7 @@ export function getFullPathByPath( routeName, lang, joinPaths(localPath), - ); + ) } } } @@ -567,17 +567,17 @@ export function getUrlByRouteName(pRoutes: TRoute[], pParams: TOpenRouteParams): const next = (routes: TRoute[], params: TOpenRouteParams): string => { for (let route of routes) { const match = - route?.name === params.name || route.component?.displayName === params.name; + route?.name === params.name || route.component?.displayName === params.name if (match) { if (!route?.path) { - log("getUrlByRouteName > There is no route with this name, exit", params.name); - return; + log("getUrlByRouteName > There is no route with this name, exit", params.name) + return } let path = typeof route.path === "object" ? route.path[Object.keys(route.path)[0]] - : route.path; + : route.path // get full path const _fullPath = getFullPathByPath( @@ -585,22 +585,22 @@ export function getUrlByRouteName(pRoutes: TRoute[], pParams: TOpenRouteParams): path, route.name, pParams?.params?.lang, - ); + ) // build URL // console.log("_fullPath", _fullPath, params); - return compileUrl(_fullPath, params.params); + return compileUrl(_fullPath, params.params) } // if route has children else if (route.children?.length > 0) { // getUrlByRouteName > no match, recall recursively on children - const match = next(route.children, params); + const match = next(route.children, params) // return recursive Fn only if match, else, continue to next iteration - if (match) return match; + if (match) return match } } - }; - return next(pRoutes, pParams); + } + return next(pRoutes, pParams) } /** @@ -614,20 +614,20 @@ export function getLangPath( langPath: string | { [p: string]: string }, lang: string = Routers.langService?.currentLang.key, ) { - let path; + let path if (typeof langPath === "string") { - path = langPath; + path = langPath } else if (typeof langPath === "object") { - path = langPath?.[lang]; + path = langPath?.[lang] } const removeLangFromPath = (path: string): string => { if (path?.includes(`/:lang`)) { - return path?.replace("/:lang", ""); - } else return path; - }; + return path?.replace("/:lang", "") + } else return path + } - return removeLangFromPath(path); + return removeLangFromPath(path) } /** @@ -651,7 +651,7 @@ export function addLangToUrl( lang: string = Routers.langService?.currentLang.key, enable = Routers.langService?.showLangInUrl(), ): string { - if (!enable) return url; - url = joinPaths([`/${lang}`, url === "/" ? "" : url]); - return url; + if (!enable) return url + url = joinPaths([`/${lang}`, url === "/" ? "" : url]) + return url } diff --git a/src/core/staticPropsCache.ts b/src/core/staticPropsCache.ts index 28659e19..a2557574 100644 --- a/src/core/staticPropsCache.ts +++ b/src/core/staticPropsCache.ts @@ -1,7 +1,7 @@ -import { Routers } from "./Routers"; -import debug from "@wbe/debug"; -const componentName: string = "cache"; -const log = debug(`router:${componentName}`); +import { Routers } from "./Routers" +import debug from "@wbe/debug" +const componentName: string = "cache" +const log = debug(`router:${componentName}`) /** * Cache used to store getStaticProps result @@ -12,25 +12,25 @@ export function staticPropsCache(cache = Routers.staticPropsCache) { * Get data in static props cache */ const get = (key: string): any => { - const dataAlreadyExist = Object.keys(cache).some((el) => el === key); + const dataAlreadyExist = Object.keys(cache).some((el) => el === key) if (!dataAlreadyExist) { - log(`"${key}" data doesn't exist in cache.`); - return null; + log(`"${key}" data doesn't exist in cache.`) + return null } - const dataCache = cache[key]; - log("data is already in cache, return it.", dataCache); - return dataCache; - }; + const dataCache = cache[key] + log("data is already in cache, return it.", dataCache) + return dataCache + } /** * Set Data in static props cache */ const set = (key: string, data): void => { - cache[key] = data; - log("cache after set", cache); - }; + cache[key] = data + log("cache after set", cache) + } return Object.freeze({ get, set, - }); + }) } diff --git a/turbo.json b/turbo.json new file mode 100644 index 00000000..4571ee7b --- /dev/null +++ b/turbo.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "outputs": ["dist/**"] + }, + "dev": { + "cache": false + }, + "test": { + "cache": false + } + } +} +