├── .codesandbox └── ci.json ├── .github └── workflows │ ├── size-limit.yml │ └── tests.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .npmrc ├── .prettierignore ├── LICENSE ├── README.md ├── examples ├── example-client │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.tsx │ │ ├── helper │ │ │ └── transitionsHelper.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── pages │ │ │ ├── AboutPage.tsx │ │ │ ├── ArticlePage.tsx │ │ │ ├── BarPage.tsx │ │ │ ├── FooPage.tsx │ │ │ ├── HelloPage.tsx │ │ │ ├── HomePage.tsx │ │ │ ├── LaPage.tsx │ │ │ ├── NotFoundPage.tsx │ │ │ ├── OurPage.tsx │ │ │ └── YoloPage.tsx │ │ ├── routes.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── example-hash-history │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.tsx │ │ ├── helper │ │ │ └── transitionsHelper.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── pages │ │ │ ├── AboutPage.tsx │ │ │ ├── ArticlePage.tsx │ │ │ ├── BarPage.tsx │ │ │ ├── FooPage.tsx │ │ │ ├── HelloPage.tsx │ │ │ ├── HomePage.tsx │ │ │ ├── LaPage.tsx │ │ │ ├── NotFoundPage.tsx │ │ │ ├── OurPage.tsx │ │ │ └── YoloPage.tsx │ │ ├── routes.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── example-history-block │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── assets │ │ │ └── react.svg │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── example-ssr │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── prerender │ ├── exe-prerender.ts │ ├── helpers │ │ ├── ManifestParser.ts │ │ └── isRouteIndex.ts │ ├── prerender.ts │ └── urls.ts │ ├── server.js │ ├── src │ ├── assets │ │ └── pic.png │ ├── components │ │ └── App.tsx │ ├── helpers │ │ └── transitionsHelper.ts │ ├── index.css │ ├── index.tsx │ ├── langServiceInstance.ts │ ├── languages.ts │ ├── pages │ │ ├── AboutPage.tsx │ │ ├── ArticlePage.tsx │ │ ├── BarPage.tsx │ │ ├── ContactPage.tsx │ │ ├── FooPage.tsx │ │ ├── HomePage.tsx │ │ └── NotFoundPage.tsx │ ├── routes.ts │ ├── server │ │ ├── helpers │ │ │ ├── CherScripts.tsx │ │ │ ├── RawScript.tsx │ │ │ ├── ViteDevScripts.tsx │ │ │ ├── htmlReplacement.ts │ │ │ └── preventSlashes.ts │ │ └── index-server.tsx │ ├── store │ │ └── GlobalDataContext.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── vite.scripts.config.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── src ├── components │ ├── Link.tsx │ ├── Router.tsx │ └── Stack.tsx ├── core │ ├── LangService.ts │ ├── Routers.ts │ ├── core.ts │ ├── helpers.ts │ └── staticPropsCache.ts ├── hooks │ ├── useHistory.ts │ ├── useLang.ts │ ├── useLocation.ts │ ├── useRouteCounter.ts │ ├── useRouter.ts │ └── useStack.ts ├── index.ts └── tests │ ├── LangService.test.tsx │ ├── Link.test.tsx │ ├── Router.test.tsx │ ├── Stack.test.ts │ ├── _fixtures │ └── routeList.ts │ ├── core.addLangToUrl.test.ts │ ├── core.applyMiddlewaresToRoutes.test.ts │ ├── core.createUrl.test.ts │ ├── core.getLangPath.test.ts │ ├── core.getPathByRouteName.test.ts │ ├── core.getRouteFromUrl.test.ts │ ├── core.getStaticPropsFromRoute.test.ts │ ├── core.getSubRouterBase.test.ts │ ├── core.getSubRouterRoutes.test.ts │ ├── core.patchMissingRootRoute.test.ts │ ├── helpers.test.ts │ └── staticPropsCache.test.ts ├── tsconfig.json ├── tsup.config.ts └── turbo.json /.codesandbox/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "build", 3 | "sandboxes": [ 4 | "/examples/example-client", 5 | "/examples/example-ssr", 6 | "/examples/example-history-block", 7 | "/examples/example-hash-history" 8 | ], 9 | "node": "18" 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/size-limit.yml: -------------------------------------------------------------------------------- 1 | name: Size limit 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | jobs: 8 | size: 9 | runs-on: ubuntu-latest 10 | env: 11 | CI_JOB_NUMBER: 1 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Install pnpm 15 | uses: pnpm/action-setup@v2 16 | with: 17 | version: 8 18 | - name: Use Size limit 19 | uses: andresz1/size-limit-action@dd31dce7dcc72a041fd3e49abf0502b13fc4ce05 # support for pnpm 20 | with: 21 | github_token: ${{ secrets.REPO_TOKEN }} 22 | package_manager: pnpm 23 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node-version: [18.x] 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Use Node.js ${{ matrix.node-version }} 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: ${{ matrix.node-version }} 15 | 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Install pnpm 22 | uses: pnpm/action-setup@v2 23 | with: 24 | version: 8 25 | 26 | - name: Install dependencies 27 | run: pnpm install 28 | 29 | - name: build 30 | run: pnpm run build 31 | 32 | - name: test 33 | run: pnpm run test 34 | 35 | - name: size 36 | run: pnpm run size 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | .DS_Store 4 | .idea 5 | tsconfig.tsbuildinfo 6 | .cache 7 | dist 8 | example-client/dist 9 | .parcel-cache 10 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .cache 3 | .github 4 | node_modules 5 | src 6 | src/* 7 | src/** 8 | test 9 | example 10 | example-node 11 | .prettierignore 12 | .prettierrc 13 | tsconfig.json 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Willy Brauner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/example-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cher-ami-router 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/example-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-client", 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "@cher-ami/router": "^3.5.2", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "gsap": "^3.12.2" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^20.8.7", 15 | "@types/react": "^18.2.29", 16 | "@types/react-dom": "^18.2.13", 17 | "@vitejs/plugin-react": "^4.1.0", 18 | "typescript": "^5.2.2", 19 | "vite": "^4.5.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/example-client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link, Stack, TManageTransitions, useLang, useLocation } from "@cher-ami/router" 3 | const componentName = "App" 4 | 5 | /** 6 | * @name App 7 | */ 8 | export default function App() { 9 | const [lang, setLang] = useLang() 10 | const [location, setLocation] = useLocation() 11 | 12 | const customSenario = ({ 13 | previousPage, 14 | currentPage, 15 | unmountPreviousPage, 16 | }: TManageTransitions): Promise => { 17 | return new Promise(async (resolve) => { 18 | const $currentPageElement = currentPage?.$element 19 | if ($currentPageElement) { 20 | $currentPageElement.style.visibility = "hidden" 21 | } 22 | if (previousPage) previousPage.playOut() 23 | await currentPage?.isReadyPromise() 24 | if ($currentPageElement) { 25 | $currentPageElement.style.visibility = "visible" 26 | } 27 | await currentPage.playIn() 28 | resolve() 29 | }) 30 | } 31 | 32 | return ( 33 |
34 | {["en", "fr", "de"].map((el, i) => ( 35 |
62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /examples/example-client/src/helper/transitionsHelper.ts: -------------------------------------------------------------------------------- 1 | import { gsap } from "gsap" 2 | import debug from "@cher-ami/debug" 3 | const log = debug(`router:transitionsHelper`) 4 | 5 | export const transitionsHelper = ( 6 | el, 7 | show: boolean, 8 | from: any = {}, 9 | to: any = {}, 10 | ): Promise => { 11 | return new Promise((resolve) => { 12 | if (!el) { 13 | log("el doesnt exist", el) 14 | } 15 | 16 | gsap.fromTo( 17 | el, 18 | { autoAlpha: show ? 0 : 1, ...from }, 19 | 20 | { 21 | ...to, 22 | duration: 0.5, 23 | autoAlpha: show ? 1 : 0, 24 | ease: "power1.out", 25 | onComplete: resolve, 26 | }, 27 | ) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /examples/example-client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 1.2rem; 3 | font-family: sans-serif; 4 | background: #222; 5 | color: #eee; 6 | } 7 | 8 | code { 9 | color: #999; 10 | font-size: 0.9em; 11 | display: block; 12 | margin-top: 0.3em; 13 | } 14 | 15 | button { 16 | cursor: pointer; 17 | background: #000; 18 | border: none; 19 | border-radius: 0.5em; 20 | color: #eee; 21 | padding: 0.4rem 0.6rem; 22 | font-size: 0.9rem; 23 | outline: none; 24 | } 25 | button:hover { 26 | background: #444; 27 | } 28 | 29 | .Link { 30 | color: #999; 31 | } 32 | 33 | .active { 34 | color: orange; 35 | } 36 | 37 | .Stack { 38 | padding-left: 1rem; 39 | position: relative; 40 | } 41 | .Stack > * { 42 | padding-left: 2rem; 43 | position: absolute; 44 | width: 100%; 45 | top: 0; 46 | } 47 | -------------------------------------------------------------------------------- /examples/example-client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from "react-dom/client" 2 | import React from "react" 3 | import "./index.css" 4 | import App from "./App" 5 | import { Router, LangService } from "@cher-ami/router" 6 | import { routesList } from "./routes" 7 | import { createBrowserHistory, createHashHistory } from "history" 8 | 9 | const base = "/" 10 | type TLang = "en" | "fr" | "de" 11 | 12 | const langService = new LangService({ 13 | languages: [{ key: "en" }, { key: "fr" }, { key: "de" }], 14 | showDefaultLangInUrl: false, 15 | base, 16 | }) 17 | 18 | /** 19 | * Init Application 20 | */ 21 | const root = createRoot(document.getElementById("root")) 22 | 23 | root.render( 24 | 30 | 31 | , 32 | ) 33 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { 3 | getPathByRouteName, 4 | getSubRouterBase, 5 | getSubRouterRoutes, 6 | Stack, 7 | Link, 8 | Router, 9 | useRouter, 10 | useStack, 11 | useLang, 12 | } from "@cher-ami/router" 13 | import { transitionsHelper } from "../helper/transitionsHelper" 14 | import { routesList } from "../routes" 15 | 16 | const componentName: string = "AboutPage" 17 | 18 | const AboutPage = forwardRef((props, handleRef: ForwardedRef) => { 19 | const rootRef = useRef(null) 20 | const [lang] = useLang() 21 | const { currentRoute } = useRouter() 22 | 23 | useStack({ 24 | componentName, 25 | handleRef, 26 | rootRef, 27 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 28 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 29 | }) 30 | 31 | // prepare routes & base for subRouter 32 | const router = useRouter() 33 | const path = getPathByRouteName(routesList, "AboutPage") 34 | 35 | return ( 36 |
37 |

38 | {componentName} - {lang.key} 39 |

40 | Query Params : 41 |
    42 |
  • Foo : {currentRoute.queryParams?.foo}
  • 43 |
  • Zoo : {currentRoute.queryParams?.zoo}
  • 44 |
45 | Children : 46 | 51 |
52 | 62 | 63 | 64 |
65 |
66 |
67 | ) 68 | }) 69 | 70 | AboutPage.displayName = componentName 71 | export default AboutPage 72 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/ArticlePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useEffect, useRef } from "react" 2 | import { useLocation } from "@cher-ami/router" 3 | import { useStack } from "@cher-ami/router" 4 | import { transitionsHelper } from "../helper/transitionsHelper" 5 | import debug from "@cher-ami/debug" 6 | 7 | interface IProps { 8 | params?: { 9 | id: string 10 | } 11 | time: { 12 | datetime: string 13 | } 14 | } 15 | 16 | const componentName = "ArticlePage" 17 | const log = debug(`router:${componentName}`) 18 | 19 | /** 20 | * @name ArticlePage 21 | */ 22 | export const ArticlePage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 23 | const rootRef = useRef(null) 24 | const [location, setLocation] = useLocation() 25 | 26 | useStack({ 27 | componentName, 28 | handleRef, 29 | rootRef, 30 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 31 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 32 | }) 33 | 34 | useEffect(() => { 35 | log("props.time", props.time) 36 | }, [props.time]) 37 | return ( 38 |
39 |
fetch props datetime: {props.time?.datetime}
40 |
41 | {componentName} - id: {props?.params?.id} 42 |
43 |
44 | 51 | {` setLocation("/")`} 52 |
53 | 60 | {` setLocation({ name: "ArticlePage", params: { id: "hello" } })`} 61 |
62 |
63 | ) 64 | }) 65 | 66 | ArticlePage.displayName = componentName 67 | export default ArticlePage 68 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/BarPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { 3 | getPathByRouteName, 4 | getSubRouterBase, 5 | getSubRouterRoutes, 6 | useStack, 7 | Link, 8 | Router, 9 | Stack, 10 | useRouter, 11 | } from "@cher-ami/router" 12 | import { transitionsHelper } from "../helper/transitionsHelper" 13 | import { routesList } from "../routes" 14 | import debug from "@cher-ami/debug" 15 | 16 | const componentName: string = "BarPage" 17 | const log = debug(`router:${componentName}`) 18 | 19 | interface IProps {} 20 | 21 | export const BarPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 22 | const rootRef = useRef(null) 23 | 24 | useStack({ 25 | componentName, 26 | handleRef, 27 | rootRef, 28 | playIn: () => transitionsHelper(rootRef.current, true, { y: -20 }, { y: 0 }), 29 | playOut: () => transitionsHelper(rootRef.current, false, { y: -0 }, { y: 20 }), 30 | }) 31 | 32 | const router = useRouter() 33 | const path = getPathByRouteName(router.routes, "BarPage") 34 | const subBase = getSubRouterBase(path, router.base) 35 | const subRoutes = getSubRouterRoutes(path, router.routes) 36 | 37 | return ( 38 |
39 | {componentName} 40 | 41 |
42 | 52 | 53 |
54 |
55 |
56 | ) 57 | }) 58 | 59 | BarPage.displayName = componentName 60 | export default BarPage 61 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/FooPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { Link, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "FooPage" 5 | 6 | interface IProps {} 7 | 8 | const FooPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | 11 | useStack({ 12 | componentName, 13 | handleRef, 14 | rootRef, 15 | playIn: () => 16 | transitionsHelper(rootRef.current, true, { y: -50, autoAlpha: 1 }, { y: 0 }), 17 | playOut: () => 18 | transitionsHelper(rootRef.current, false, { y: -0 }, { y: 50, autoAlpha: 0 }), 19 | }) 20 | 21 | return ( 22 |
23 | {componentName} 24 |
25 | Article 26 |
27 | About 28 |
29 | ) 30 | }) 31 | 32 | FooPage.displayName = componentName 33 | export default FooPage 34 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/HelloPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | 5 | const componentName: string = "HelloPage" 6 | 7 | interface IProps {} 8 | 9 | export const HelloPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 10 | const rootRef = useRef(null) 11 | 12 | useStack({ 13 | componentName, 14 | handleRef, 15 | rootRef, 16 | playIn: () => transitionsHelper(rootRef.current, true), 17 | playOut: () => transitionsHelper(rootRef.current, false), 18 | }) 19 | 20 | return ( 21 |
22 | {componentName} 23 |
24 | ) 25 | }) 26 | 27 | HelloPage.displayName = componentName 28 | export default HelloPage 29 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { 3 | getPathByRouteName, 4 | getSubRouterBase, 5 | getSubRouterRoutes, 6 | Link, 7 | Router, 8 | Stack, 9 | useRouter, 10 | useStack, 11 | } from "@cher-ami/router" 12 | import { transitionsHelper } from "../helper/transitionsHelper" 13 | import debug from "@cher-ami/debug" 14 | 15 | const componentName: string = "HomePage" 16 | const log = debug(`router:${componentName}`) 17 | 18 | interface IProps { 19 | params: { 20 | lang: string 21 | } 22 | } 23 | 24 | const HomePage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 25 | const rootRef = useRef(null) 26 | 27 | useStack({ 28 | componentName, 29 | handleRef, 30 | rootRef, 31 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 32 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 33 | }) 34 | 35 | const router = useRouter() 36 | const path = getPathByRouteName(router.routes, "HomePage") 37 | const subBase = getSubRouterBase(path, router.base) 38 | const subRoutes = getSubRouterRoutes(path, router.routes) 39 | 40 | return ( 41 |
42 | {componentName} 43 | 44 |
45 | 55 | 56 |
57 |
58 |
59 | ) 60 | }) 61 | 62 | HomePage.displayName = componentName 63 | export default HomePage 64 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/LaPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "LaPage" 5 | 6 | interface IProps {} 7 | 8 | const LaPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | 11 | useStack({ 12 | componentName, 13 | handleRef, 14 | rootRef, 15 | playIn: () => transitionsHelper(rootRef.current, true), 16 | playOut: () => transitionsHelper(rootRef.current, false), 17 | }) 18 | 19 | return ( 20 |
21 | {componentName} 22 |
23 | ) 24 | }) 25 | 26 | LaPage.displayName = componentName 27 | export default LaPage 28 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/NotFoundPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "NotFoundPage" 5 | 6 | interface IProps {} 7 | 8 | const NotFoundPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | 11 | useStack({ 12 | componentName, 13 | handleRef, 14 | rootRef, 15 | playIn: () => transitionsHelper(rootRef.current, true), 16 | playOut: () => transitionsHelper(rootRef.current, false), 17 | }) 18 | 19 | return ( 20 |
21 | {componentName} 22 |
23 | ) 24 | }) 25 | 26 | NotFoundPage.displayName = componentName 27 | export default NotFoundPage 28 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/OurPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { Link, useLocation, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "OurPage" 5 | 6 | interface IProps {} 7 | 8 | const OurPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | 11 | const [location, setLocation] = useLocation() 12 | 13 | useStack({ 14 | componentName, 15 | handleRef, 16 | rootRef, 17 | playIn: () => transitionsHelper(rootRef.current, true), 18 | playOut: () => transitionsHelper(rootRef.current, false), 19 | }) 20 | 21 | return ( 22 |
23 | {componentName} 24 |
25 | {/*
32 | ) 33 | }) 34 | 35 | OurPage.displayName = componentName 36 | export default OurPage 37 | -------------------------------------------------------------------------------- /examples/example-client/src/pages/YoloPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useLocation, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "YoloPage" 5 | 6 | interface IProps { 7 | time: { 8 | datetime: string 9 | } 10 | } 11 | 12 | const YoloPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 13 | const rootRef = useRef(null) 14 | 15 | useStack({ 16 | componentName, 17 | handleRef, 18 | rootRef, 19 | playIn: () => transitionsHelper(rootRef.current, true), 20 | playOut: () => transitionsHelper(rootRef.current, false), 21 | }) 22 | 23 | const [location, setLocation] = useLocation() 24 | 25 | return ( 26 |
27 | {componentName} 28 |
fetch props datetime: {props.time?.datetime}
29 | 30 |
31 |
32 | 39 | {` setLocation({ name: "ArticlePage", params: { id: "form-sub-router" } })`} 40 |
41 | ) 42 | }) 43 | 44 | YoloPage.displayName = componentName 45 | export default YoloPage 46 | -------------------------------------------------------------------------------- /examples/example-client/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { TRoute } from "@cher-ami/router" 2 | 3 | import HomePage from "./pages/HomePage" 4 | import AboutPage from "./pages/AboutPage" 5 | import ArticlePage from "./pages/ArticlePage" 6 | import FooPage from "./pages/FooPage" 7 | import BarPage from "./pages/BarPage" 8 | import NotFoundPage from "./pages/NotFoundPage" 9 | import YoloPage from "./pages/YoloPage" 10 | import HelloPage from "./pages/HelloPage" 11 | import LaPage from "./pages/LaPage" 12 | import OurPage from "./pages/OurPage" 13 | 14 | /** 15 | * Define routes list 16 | */ 17 | export const routesList: TRoute[] = [ 18 | { 19 | path: "/", 20 | component: HomePage, 21 | children: [ 22 | { 23 | path: { en: "/foo", fr: "/foo-fr", de: "/foo-de" }, 24 | component: FooPage, 25 | }, 26 | { 27 | path: "/bar", 28 | component: BarPage, 29 | children: [ 30 | { 31 | path: "/yolo", 32 | component: YoloPage, 33 | getStaticProps: async (props, currentLang) => { 34 | const res = await fetch("https://worldtimeapi.org/api/ip") 35 | const time = await res.json() 36 | return { time } 37 | }, 38 | }, 39 | { 40 | path: "/hello", 41 | component: HelloPage, 42 | }, 43 | ], 44 | }, 45 | ], 46 | }, 47 | { 48 | // path: "/about", 49 | path: { en: "/about", fr: "/a-propos", de: "/uber" }, 50 | component: AboutPage, 51 | children: [ 52 | { 53 | path: "/la", 54 | component: LaPage, 55 | }, 56 | { 57 | path: "/our", 58 | component: OurPage, 59 | }, 60 | ], 61 | }, 62 | { 63 | // path: "/blog/:id", 64 | path: { en: "/blog/:id", fr: "/blog-fr/:id", de: "/blog-de/:id" }, 65 | component: ArticlePage, 66 | props: { 67 | color: "red", 68 | }, 69 | getStaticProps: async (props, currentLang) => { 70 | const res = await fetch("https://worldtimeapi.org/api/ip") 71 | const time = await res.json() 72 | return { time } 73 | }, 74 | }, 75 | { 76 | path: "/:rest", 77 | component: NotFoundPage, 78 | }, 79 | ] 80 | -------------------------------------------------------------------------------- /examples/example-client/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/example-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/example-client/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/example-client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import react from "@vitejs/plugin-react" 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { host: true }, 8 | }) 9 | -------------------------------------------------------------------------------- /examples/example-hash-history/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cher-ami-router 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/example-hash-history/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-hashhistory", 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "@cher-ami/router": "^3.5.2", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "gsap": "^3.12.2" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^20.8.7", 15 | "@types/react": "^18.2.29", 16 | "@types/react-dom": "^18.2.13", 17 | "@vitejs/plugin-react": "^4.1.0", 18 | "typescript": "^5.2.2", 19 | "vite": "^4.5.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link, Stack, TManageTransitions, useLang, useLocation } from "@cher-ami/router" 3 | const componentName = "App" 4 | 5 | /** 6 | * @name App 7 | */ 8 | export default function App() { 9 | const [lang, setLang] = useLang() 10 | const [location, setLocation] = useLocation() 11 | 12 | const customSenario = ({ 13 | previousPage, 14 | currentPage, 15 | unmountPreviousPage, 16 | }: TManageTransitions): Promise => { 17 | return new Promise(async (resolve) => { 18 | const $currentPageElement = currentPage?.$element 19 | if ($currentPageElement) { 20 | $currentPageElement.style.visibility = "hidden" 21 | } 22 | if (previousPage) previousPage.playOut() 23 | await currentPage?.isReadyPromise() 24 | if ($currentPageElement) { 25 | $currentPageElement.style.visibility = "visible" 26 | } 27 | await currentPage.playIn() 28 | resolve() 29 | }) 30 | } 31 | 32 | return ( 33 |
34 | {["en", "fr", "de"].map((el, i) => ( 35 |
62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/helper/transitionsHelper.ts: -------------------------------------------------------------------------------- 1 | import { gsap } from "gsap" 2 | import debug from "@cher-ami/debug" 3 | const log = debug(`router:transitionsHelper`) 4 | 5 | export const transitionsHelper = ( 6 | el, 7 | show: boolean, 8 | from: any = {}, 9 | to: any = {}, 10 | ): Promise => { 11 | return new Promise((resolve) => { 12 | if (!el) { 13 | log("el doesnt exist", el) 14 | } 15 | 16 | gsap.fromTo( 17 | el, 18 | { autoAlpha: show ? 0 : 1, ...from }, 19 | 20 | { 21 | ...to, 22 | duration: 0.5, 23 | autoAlpha: show ? 1 : 0, 24 | ease: "power1.out", 25 | onComplete: resolve, 26 | }, 27 | ) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 1.2rem; 3 | font-family: sans-serif; 4 | background: #222; 5 | color: #eee; 6 | } 7 | 8 | code { 9 | color: #999; 10 | font-size: 0.9em; 11 | display: block; 12 | margin-top: 0.3em; 13 | } 14 | 15 | button { 16 | cursor: pointer; 17 | background: #000; 18 | border: none; 19 | border-radius: 0.5em; 20 | color: #eee; 21 | padding: 0.4rem 0.6rem; 22 | font-size: 0.9rem; 23 | outline: none; 24 | } 25 | button:hover { 26 | background: #444; 27 | } 28 | 29 | .Link { 30 | color: #999; 31 | } 32 | 33 | .active { 34 | color: orange; 35 | } 36 | 37 | .Stack { 38 | padding-left: 1rem; 39 | position: relative; 40 | } 41 | .Stack > * { 42 | padding-left: 2rem; 43 | position: absolute; 44 | width: 100%; 45 | top: 0; 46 | } 47 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from "react-dom/client" 2 | import React from "react" 3 | import "./index.css" 4 | import App from "./App" 5 | import { Router, LangService } from "@cher-ami/router" 6 | import { routesList } from "./routes" 7 | import { createBrowserHistory, createHashHistory } from "history" 8 | 9 | const base = "/base/" 10 | type TLang = "en" | "fr" | "de" 11 | 12 | const isHashHistory = true 13 | const history = createHashHistory() 14 | 15 | const langService = new LangService({ 16 | languages: [{ key: "en" }, { key: "fr" }, { key: "de" }], 17 | showDefaultLangInUrl: false, 18 | base, 19 | isHashHistory, 20 | }) 21 | 22 | /** 23 | * Init Application 24 | */ 25 | const root = createRoot(document.getElementById("root")) 26 | 27 | root.render( 28 | 35 | 36 | , 37 | ) 38 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { 3 | getPathByRouteName, 4 | getSubRouterBase, 5 | getSubRouterRoutes, 6 | Stack, 7 | Link, 8 | Router, 9 | useRouter, 10 | useStack, 11 | useLang, 12 | } from "@cher-ami/router" 13 | import { transitionsHelper } from "../helper/transitionsHelper" 14 | import { routesList } from "../routes" 15 | 16 | const componentName: string = "AboutPage" 17 | 18 | const AboutPage = forwardRef((props, handleRef: ForwardedRef) => { 19 | const rootRef = useRef(null) 20 | const [lang] = useLang() 21 | const { currentRoute } = useRouter() 22 | 23 | useStack({ 24 | componentName, 25 | handleRef, 26 | rootRef, 27 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 28 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 29 | }) 30 | 31 | // prepare routes & base for subRouter 32 | const router = useRouter() 33 | const path = getPathByRouteName(routesList, "AboutPage") 34 | 35 | return ( 36 |
37 |

38 | {componentName} - {lang.key} 39 |

40 | Query Params : 41 |
    42 |
  • Foo : {currentRoute.queryParams?.foo}
  • 43 |
  • Zoo : {currentRoute.queryParams?.zoo}
  • 44 |
45 | Children : 46 | 52 |
53 | 63 | 64 | 65 |
66 |
67 |
68 | ) 69 | }) 70 | 71 | AboutPage.displayName = componentName 72 | export default AboutPage 73 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/ArticlePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useEffect, useRef } from "react" 2 | import { useLang, useLocation } from "@cher-ami/router" 3 | import { useStack } from "@cher-ami/router" 4 | import { transitionsHelper } from "../helper/transitionsHelper" 5 | import debug from "@cher-ami/debug" 6 | 7 | interface IProps { 8 | params?: { 9 | id: string 10 | } 11 | time: { 12 | datetime: string 13 | } 14 | } 15 | 16 | const componentName = "ArticlePage" 17 | const log = debug(`router:${componentName}`) 18 | 19 | /** 20 | * @name ArticlePage 21 | */ 22 | export const ArticlePage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 23 | const rootRef = useRef(null) 24 | const [lang] = useLang() 25 | 26 | const [location, setLocation] = useLocation() 27 | 28 | useStack({ 29 | componentName, 30 | handleRef, 31 | rootRef, 32 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 33 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 34 | }) 35 | 36 | useEffect(() => { 37 | log("props.time", props.time) 38 | }, [props.time]) 39 | return ( 40 |
41 |

42 | {componentName} - id: {props?.params?.id} - {lang.key} 43 |

44 |
45 |
fetch props datetime: {props.time?.datetime}
46 |
47 |
48 | 55 | {` setLocation("/")`} 56 |
57 | 64 | {` setLocation({ name: "ArticlePage", params: { id: "hello" } })`} 65 |
66 |
67 | ) 68 | }) 69 | 70 | ArticlePage.displayName = componentName 71 | export default ArticlePage 72 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/BarPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { 3 | getPathByRouteName, 4 | getSubRouterBase, 5 | getSubRouterRoutes, 6 | useStack, 7 | Link, 8 | Router, 9 | Stack, 10 | useRouter, 11 | useLang, 12 | } from "@cher-ami/router" 13 | import { transitionsHelper } from "../helper/transitionsHelper" 14 | import { routesList } from "../routes" 15 | import debug from "@cher-ami/debug" 16 | 17 | const componentName: string = "BarPage" 18 | const log = debug(`router:${componentName}`) 19 | 20 | interface IProps {} 21 | 22 | export const BarPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 23 | const rootRef = useRef(null) 24 | const [lang] = useLang() 25 | 26 | useStack({ 27 | componentName, 28 | handleRef, 29 | rootRef, 30 | playIn: () => transitionsHelper(rootRef.current, true, { y: -20 }, { y: 0 }), 31 | playOut: () => transitionsHelper(rootRef.current, false, { y: -0 }, { y: 20 }), 32 | }) 33 | 34 | const router = useRouter() 35 | const path = getPathByRouteName(router.routes, "BarPage") 36 | const subBase = getSubRouterBase(path, router.base) 37 | const subRoutes = getSubRouterRoutes(path, router.routes) 38 | 39 | return ( 40 |
41 |

42 | {componentName} - {lang.key} 43 |

44 | 45 |
46 | 56 | 57 |
58 |
59 |
60 | ) 61 | }) 62 | 63 | BarPage.displayName = componentName 64 | export default BarPage 65 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/FooPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { Link, useLang, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "FooPage" 5 | 6 | interface IProps {} 7 | 8 | const FooPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | const [lang] = useLang() 11 | 12 | useStack({ 13 | componentName, 14 | handleRef, 15 | rootRef, 16 | playIn: () => 17 | transitionsHelper(rootRef.current, true, { y: -50, autoAlpha: 1 }, { y: 0 }), 18 | playOut: () => 19 | transitionsHelper(rootRef.current, false, { y: -0 }, { y: 50, autoAlpha: 0 }), 20 | }) 21 | 22 | return ( 23 |
24 |

25 | {componentName} - {lang.key} 26 |

27 |
28 | Article 29 |
30 | About 31 |
32 | ) 33 | }) 34 | 35 | FooPage.displayName = componentName 36 | export default FooPage 37 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/HelloPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useLang, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | 5 | const componentName: string = "HelloPage" 6 | 7 | interface IProps {} 8 | 9 | export const HelloPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 10 | const rootRef = useRef(null) 11 | const [lang] = useLang() 12 | 13 | useStack({ 14 | componentName, 15 | handleRef, 16 | rootRef, 17 | playIn: () => transitionsHelper(rootRef.current, true), 18 | playOut: () => transitionsHelper(rootRef.current, false), 19 | }) 20 | 21 | return ( 22 |
23 |

24 | {componentName} - {lang.key} 25 |

26 |
27 | ) 28 | }) 29 | 30 | HelloPage.displayName = componentName 31 | export default HelloPage 32 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { 3 | getPathByRouteName, 4 | getSubRouterBase, 5 | getSubRouterRoutes, 6 | Link, 7 | Router, 8 | Stack, 9 | useLang, 10 | useRouter, 11 | useStack, 12 | } from "@cher-ami/router" 13 | import { transitionsHelper } from "../helper/transitionsHelper" 14 | import debug from "@cher-ami/debug" 15 | 16 | const componentName: string = "HomePage" 17 | const log = debug(`router:${componentName}`) 18 | 19 | interface IProps { 20 | params: { 21 | lang: string 22 | } 23 | } 24 | 25 | const HomePage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 26 | const rootRef = useRef(null) 27 | const [lang] = useLang() 28 | 29 | useStack({ 30 | componentName, 31 | handleRef, 32 | rootRef, 33 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 34 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 35 | }) 36 | 37 | const router = useRouter() 38 | const path = getPathByRouteName(router.routes, "HomePage") 39 | const subBase = getSubRouterBase(path, router.base) 40 | const subRoutes = getSubRouterRoutes(path, router.routes) 41 | 42 | return ( 43 |
44 |

45 | {componentName} {lang.key} 46 |

47 | 48 |
49 | 59 | 60 |
61 |
62 |
63 | ) 64 | }) 65 | 66 | HomePage.displayName = componentName 67 | export default HomePage 68 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/LaPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useLang, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "LaPage" 5 | 6 | interface IProps {} 7 | 8 | const LaPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | const [lang] = useLang() 11 | 12 | useStack({ 13 | componentName, 14 | handleRef, 15 | rootRef, 16 | playIn: () => transitionsHelper(rootRef.current, true), 17 | playOut: () => transitionsHelper(rootRef.current, false), 18 | }) 19 | 20 | return ( 21 |
22 |

23 | {componentName} - {lang.key} 24 |

25 |
26 | ) 27 | }) 28 | 29 | LaPage.displayName = componentName 30 | export default LaPage 31 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/NotFoundPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "NotFoundPage" 5 | 6 | interface IProps {} 7 | 8 | const NotFoundPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | 11 | useStack({ 12 | componentName, 13 | handleRef, 14 | rootRef, 15 | playIn: () => transitionsHelper(rootRef.current, true), 16 | playOut: () => transitionsHelper(rootRef.current, false), 17 | }) 18 | 19 | return ( 20 |
21 | {componentName} 22 |
23 | ) 24 | }) 25 | 26 | NotFoundPage.displayName = componentName 27 | export default NotFoundPage 28 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/OurPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { Link, useLang, useLocation, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "OurPage" 5 | 6 | interface IProps {} 7 | 8 | const OurPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 9 | const rootRef = useRef(null) 10 | const [lang] = useLang() 11 | 12 | const [location, setLocation] = useLocation() 13 | 14 | useStack({ 15 | componentName, 16 | handleRef, 17 | rootRef, 18 | playIn: () => transitionsHelper(rootRef.current, true), 19 | playOut: () => transitionsHelper(rootRef.current, false), 20 | }) 21 | 22 | return ( 23 |
24 |

25 | {componentName} - {lang.key} 26 |

27 |
28 | {/*
35 | ) 36 | }) 37 | 38 | OurPage.displayName = componentName 39 | export default OurPage 40 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/pages/YoloPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { ForwardedRef, forwardRef, useRef } from "react" 2 | import { useLang, useLocation, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helper/transitionsHelper" 4 | const componentName: string = "YoloPage" 5 | 6 | interface IProps { 7 | time: { 8 | datetime: string 9 | } 10 | } 11 | 12 | const YoloPage = forwardRef((props: IProps, handleRef: ForwardedRef) => { 13 | const rootRef = useRef(null) 14 | const [lang] = useLang() 15 | 16 | useStack({ 17 | componentName, 18 | handleRef, 19 | rootRef, 20 | playIn: () => transitionsHelper(rootRef.current, true), 21 | playOut: () => transitionsHelper(rootRef.current, false), 22 | }) 23 | 24 | const [location, setLocation] = useLocation() 25 | 26 | return ( 27 |
28 |

29 | {componentName} - {lang.key} 30 |

31 |
fetch props datetime: {props.time?.datetime}
32 | 33 |
34 |
35 | 42 | {` setLocation({ name: "ArticlePage", params: { id: "form-sub-router" } })`} 43 |
44 | ) 45 | }) 46 | 47 | YoloPage.displayName = componentName 48 | export default YoloPage 49 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { TRoute } from "@cher-ami/router" 2 | 3 | import HomePage from "./pages/HomePage" 4 | import AboutPage from "./pages/AboutPage" 5 | import ArticlePage from "./pages/ArticlePage" 6 | import FooPage from "./pages/FooPage" 7 | import BarPage from "./pages/BarPage" 8 | import NotFoundPage from "./pages/NotFoundPage" 9 | import YoloPage from "./pages/YoloPage" 10 | import HelloPage from "./pages/HelloPage" 11 | import LaPage from "./pages/LaPage" 12 | import OurPage from "./pages/OurPage" 13 | 14 | /** 15 | * Define routes list 16 | */ 17 | export const routesList: TRoute[] = [ 18 | { 19 | path: "/", 20 | component: HomePage, 21 | children: [ 22 | { 23 | path: { en: "/foo", fr: "/foo-fr", de: "/foo-de" }, 24 | component: FooPage, 25 | }, 26 | { 27 | path: "/bar", 28 | component: BarPage, 29 | children: [ 30 | { 31 | path: "/yolo", 32 | component: YoloPage, 33 | getStaticProps: async (props, currentLang) => { 34 | const res = await fetch("https://worldtimeapi.org/api/ip") 35 | const time = await res.json() 36 | return { time } 37 | }, 38 | }, 39 | { 40 | path: "/hello", 41 | component: HelloPage, 42 | }, 43 | ], 44 | }, 45 | ], 46 | }, 47 | { 48 | // path: "/about", 49 | path: { en: "/about", fr: "/a-propos", de: "/uber" }, 50 | component: AboutPage, 51 | children: [ 52 | { 53 | path: "/la", 54 | component: LaPage, 55 | }, 56 | { 57 | path: "/our", 58 | component: OurPage, 59 | }, 60 | ], 61 | }, 62 | { 63 | // path: "/blog/:id", 64 | path: { en: "/blog/:id", fr: "/blog-fr/:id", de: "/blog-de/:id" }, 65 | component: ArticlePage, 66 | props: { 67 | color: "red", 68 | }, 69 | getStaticProps: async (props, currentLang) => { 70 | const res = await fetch("https://worldtimeapi.org/api/ip") 71 | const time = await res.json() 72 | return { time } 73 | }, 74 | }, 75 | { 76 | path: "/:rest", 77 | component: NotFoundPage, 78 | }, 79 | ] 80 | -------------------------------------------------------------------------------- /examples/example-hash-history/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/example-hash-history/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/example-hash-history/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/example-hash-history/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import react from "@vitejs/plugin-react" 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { host: true }, 8 | }) 9 | -------------------------------------------------------------------------------- /examples/example-history-block/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | plugins: ["react-refresh"], 12 | rules: { 13 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /examples/example-history-block/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/example-history-block/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | parserOptions: { 18 | ecmaVersion: 'latest', 19 | sourceType: 'module', 20 | project: ['./tsconfig.json', './tsconfig.node.json'], 21 | tsconfigRootDir: __dirname, 22 | }, 23 | ``` 24 | 25 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 26 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 27 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 28 | -------------------------------------------------------------------------------- /examples/example-history-block/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/example-history-block/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-history-block", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@cher-ami/router": "^3.5.2", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.2.15", 19 | "@types/react-dom": "^18.2.7", 20 | "@typescript-eslint/eslint-plugin": "^6.0.0", 21 | "@typescript-eslint/parser": "^6.0.0", 22 | "@vitejs/plugin-react-swc": "^3.3.2", 23 | "eslint": "^8.45.0", 24 | "eslint-plugin-react-hooks": "^4.6.0", 25 | "eslint-plugin-react-refresh": "^0.4.3", 26 | "typescript": "^5.0.2", 27 | "vite": "^4.4.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/example-history-block/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/example-history-block/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cher-ami/router/59b4a212ca2c0acd1a8d50081e47282940a44237/examples/example-history-block/src/App.css -------------------------------------------------------------------------------- /examples/example-history-block/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Stack, useRouter } from "@cher-ami/router" 2 | import { useRef, useState } from "react" 3 | 4 | export function App() { 5 | const { history } = useRouter() 6 | const unblockRef = useRef() 7 | const [isBlock, setIsBlock] = useState(false) 8 | 9 | const crossedTransitions = ({ 10 | previousPage, 11 | currentPage, 12 | unmountPreviousPage, 13 | }: any): Promise => 14 | new Promise(async (resolve) => { 15 | const $current = currentPage?.$element 16 | if ($current) $current.style.visibility = "hidden" 17 | if (previousPage) { 18 | previousPage.playOut?.() 19 | } 20 | if (currentPage) { 21 | if ($current) $current.style.visibility = "visible" 22 | await currentPage.playIn?.() 23 | unmountPreviousPage() 24 | } 25 | resolve() 26 | }) 27 | 28 | return ( 29 |
30 | 34 | 35 |
36 | 46 |
47 | 55 |
56 | 57 |
History is blocked: {isBlock ? "true" : "false"}
58 | 59 | 60 |
61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /examples/example-history-block/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/example-history-block/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | font-size: 2rem; 20 | } 21 | 22 | button { 23 | font-size: 2rem; 24 | } 25 | -------------------------------------------------------------------------------- /examples/example-history-block/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client" 2 | import { App } from "./App.tsx" 3 | import "./index.css" 4 | import { LangService, Router } from "@cher-ami/router" 5 | import { createBrowserHistory } from "history" 6 | import { forwardRef } from "react" 7 | 8 | const base = "/base/" 9 | type TLang = "en" | "fr" | "de" 10 | 11 | const langService = new LangService({ 12 | languages: [{ key: "en" }, { key: "fr" }, { key: "de" }], 13 | showDefaultLangInUrl: false, 14 | base, 15 | }) 16 | 17 | const routesList = [ 18 | { 19 | path: "/a", 20 | component: forwardRef((_, ref: any) =>
A
), 21 | }, 22 | { 23 | path: "/b", 24 | component: forwardRef((_, ref: any) =>
B
), 25 | }, 26 | ] 27 | 28 | ReactDOM.createRoot(document.getElementById("root")!).render( 29 | 35 | 36 | , 37 | ) 38 | -------------------------------------------------------------------------------- /examples/example-history-block/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/example-history-block/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/example-history-block/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/example-history-block/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import react from "@vitejs/plugin-react-swc" 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/example-ssr/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /examples/example-ssr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SSR 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/example-ssr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-ssr", 3 | "main": "src/index.tsx", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "node server.js", 7 | "build:client": "vite build --outDir dist/client", 8 | "build:server": "vite build --ssr src/server/index-server.tsx --outDir dist/server", 9 | "build:scripts": "vite build -c vite.scripts.config.ts", 10 | "build:static": "vite build --outDir dist/static", 11 | "build": "npm run build:client && npm run build:server && npm run build:scripts && npm run build:static && npm run generate", 12 | "generate": "node dist/_scripts/exe-prerender.js", 13 | "preview": "serve dist/static" 14 | }, 15 | "dependencies": { 16 | "@cher-ami/router": "^3.5.2", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "gsap": "^3.12.2" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^20.8.2", 23 | "@types/react": "^18.2.24", 24 | "@types/react-dom": "^18.2.8", 25 | "@vitejs/plugin-react": "^4.1.0", 26 | "@cher-ami/debug": "^1.2.0", 27 | "@cher-ami/mfs": "^0.2.0", 28 | "chalk": "^5.3.0", 29 | "compression": "^1.7.4", 30 | "isomorphic-unfetch": "^4.0.2", 31 | "cross-fetch": "^4.0.0", 32 | "express": "^4.18.2", 33 | "fs-extra": "^11.1.1", 34 | "nodemon": "^3.0.1", 35 | "portfinder-sync": "^0.0.2", 36 | "vite": "^4.4.10" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/example-ssr/prerender/exe-prerender.ts: -------------------------------------------------------------------------------- 1 | import { prerender } from "./prerender" 2 | import { fetchAvailableUrls } from "./urls" 3 | ;(async () => { 4 | const urls = await fetchAvailableUrls() 5 | prerender(urls) 6 | })() 7 | -------------------------------------------------------------------------------- /examples/example-ssr/prerender/helpers/ManifestParser.ts: -------------------------------------------------------------------------------- 1 | export type TAssetsList = string[] 2 | export type TAssetsByType = { [x: string]: string[] } 3 | 4 | export type TScript = { tag: string; attr: { [x: string]: string } } 5 | export type TScriptsObj = { 6 | [ext: string]: TScript[] 7 | } 8 | 9 | /** 10 | * ManifestParser 11 | * Allow to get scriptTags 12 | * 13 | * 14 | * 15 | */ 16 | export class ManifestParser { 17 | /** 18 | * Directly get script Tags from raw manifest string 19 | * @param manifestRaw 20 | * @param base 21 | */ 22 | static getScriptTagFromManifest(manifestRaw: string, base = "/"): TScriptsObj { 23 | const assets = ManifestParser.getAssets(manifestRaw) 24 | const assetsByType = ManifestParser.sortAssetsByType(assets) 25 | return ManifestParser.getScripts(assetsByType, base) 26 | } 27 | 28 | /** 29 | * Get script tags 30 | * 31 | * ex: 32 | * { 33 | * js: [ 34 | * { 35 | * tag: 'script', 36 | * attr: { 37 | * src: "" 38 | * noModule: "", 39 | * } 40 | * }, 41 | * } 42 | * ... 43 | * 44 | * @param assetListByType 45 | * @param base 46 | */ 47 | static getScripts(assetListByType: TAssetsByType, base = "/"): TScriptsObj { 48 | if (typeof assetListByType !== "object" || !assetListByType) { 49 | console.error("assetListByType is not valid, return", assetListByType) 50 | return 51 | } 52 | // prettier-ignore 53 | return Object.keys(assetListByType).reduce((a, b: string) => 54 | { 55 | const scriptURLs = assetListByType[b] 56 | let scripts: TScript[] 57 | if (b === "js") { 58 | scripts = scriptURLs.map(url => { 59 | return { 60 | tag: "script", 61 | attr: { 62 | ...(url.includes("legacy") ? {noModule: ""} : {type: "module"}), 63 | crossOrigin:"anonymous", 64 | src: `${base}${url}` 65 | } 66 | } 67 | }) 68 | } 69 | else if (b === "css") { 70 | scripts = scriptURLs.map(url => ({ 71 | tag: "link", 72 | attr: { 73 | rel: "stylesheet", 74 | href: `${base}${url}` 75 | } 76 | })) 77 | } 78 | else if (b === "woff2") { 79 | scripts = scriptURLs.map(url => ({ 80 | tag: "link", 81 | attr: { 82 | rel: "preload", 83 | as: "font", 84 | type:"font/woff2", 85 | crossOrigin:"anonymous", 86 | href: `${base}${url}` 87 | } 88 | })) 89 | } 90 | return { ...a, ...(scripts ? {[b]: scripts} : {}) } 91 | },{}) 92 | } 93 | 94 | /** 95 | * 96 | * Get assets by type (by extension): 97 | * ex: 98 | * { 99 | * js: [ 100 | * 'index-legacy-e92b0b23.js', 101 | * 'polyfills-legacy-163e9122.js', 102 | * 'index-475b5da0.js' 103 | * ], 104 | * woff2: [ 105 | * 'roboto-regular-8cef0863.woff2', 106 | * 'roboto-regular-8cef0863.woff2' 107 | * ], 108 | * css: [ 109 | * 'index-ef71c845.css' 110 | * ], 111 | * ... 112 | * } 113 | * 114 | * Group by extensions 115 | * @param assetList 116 | */ 117 | static sortAssetsByType(assetList: TAssetsList): TAssetsByType { 118 | return assetList.reduce((a, b) => { 119 | const ext = b.split(".")[b.split(".").length - 1] 120 | if (a?.[ext] && !a[ext].includes(b)) { 121 | a[ext].push(b) 122 | return a 123 | } else { 124 | return { 125 | ...a, 126 | [ext]: [b], 127 | } 128 | } 129 | }, {}) 130 | } 131 | 132 | /** 133 | * Get assets list 134 | * 135 | * [ 136 | * 'index-legacy-e92b0b23.js', 137 | * 'roboto-regular-8cef0863.woff2', 138 | * 'roboto-regular-18ab5ae4.woff', 139 | * 'roboto-regular-b122d9b1.ttf', 140 | * 'polyfills-legacy-163e9122.js', 141 | * 'index-475b5da0.js', 142 | * ] 143 | * 144 | * 145 | * Return all assets 146 | * @param manifestRawFile 147 | */ 148 | static getAssets(manifestRawFile: string): TAssetsList { 149 | if (!manifestRawFile) return 150 | const jsonManifest = JSON.parse(manifestRawFile) 151 | 152 | const list = Object.keys(jsonManifest) 153 | .reduce( 154 | (a, b) => 155 | jsonManifest[b].isEntry 156 | ? [ 157 | ...a, 158 | jsonManifest[b].file, 159 | ...(jsonManifest[b]?.assets || []), 160 | ...(jsonManifest[b]?.css || []), 161 | ] 162 | : a, 163 | [], 164 | ) 165 | .filter((e) => e) 166 | 167 | return [...new Set(list)] 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /examples/example-ssr/prerender/helpers/isRouteIndex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Detect If current URL is a route index 3 | * If true, we want to generate /foo/index.html instead of "/foo.html" 4 | * 5 | * @param url Url to test 6 | * @param urls List of available URLs 7 | * @param log 8 | */ 9 | export const isRouteIndex = (url, urls, log = false): boolean => { 10 | if (!urls.includes(url)) { 11 | // console.warn(`isRouteIndex > ${url} isn't in the list, return false.`) 12 | return false 13 | } 14 | 15 | log && console.log("url", url) 16 | // if URL is "/" we want to generate /index.html 17 | if (url === "/") return true 18 | 19 | // If /url/ we want to generate /url/index.html 20 | if (url.endsWith("/")) return true 21 | 22 | // get every URL of the list witch starting with same base 23 | const group = urls.filter((e) => e.startsWith(url)) 24 | log && console.log("group", group) 25 | 26 | // check if on of others in group is subRoute and not only same level route 27 | // witch starting with the same string 28 | // ex: ["/foo", "/foo-bar"] are on the same level, 29 | // ex: ["/foo", "/foo/bar"] are two different level route 30 | const subRouteExist = group.some((e) => e.slice(url.length).includes("/")) 31 | log && console.log("subRouteExist", subRouteExist) 32 | 33 | // if group is [ '/thanks/form', '/thanks' ] 34 | // we want the base route: "/thanks" 35 | const baseRoute = group?.sort((a, b) => a.length - b.length)?.[0] 36 | log && console.log("baseRoute", baseRoute) 37 | 38 | return ( 39 | // if group as more than 1 URL, this is a sub router case (or root case "/") 40 | // & if baseRoute equal to param URL, param URL should be the index page 41 | group.length > 1 && baseRoute === url && subRouteExist 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /examples/example-ssr/prerender/prerender.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { render } from "~/server/index-server" 3 | import * as mfs from "@cher-ami/mfs" 4 | import path, { resolve } from "path" 5 | import chalk from "chalk" 6 | import { loadEnv } from "vite" 7 | import { isRouteIndex } from "./helpers/isRouteIndex" 8 | import { ManifestParser } from "./helpers/ManifestParser" 9 | import { renderToPipeableStream, renderToString } from "react-dom/server" 10 | import { ReactElement } from "react" 11 | import { htmlReplacement } from "~/server/helpers/htmlReplacement" 12 | 13 | /** 14 | * Prerender 15 | * Create static HTML files from react render DOM 16 | * @param urls: Urls to generate 17 | * @param outDirStatic: Generation destination directory 18 | */ 19 | export const prerender = async ( 20 | urls: string[], 21 | outDirStatic = resolve("dist/static"), 22 | ) => { 23 | const indexTemplateSrc = `${outDirStatic}/index-template.html` 24 | 25 | // copy index as template to avoid the override with the generated static index.html bellow 26 | if (!(await mfs.fileExists(indexTemplateSrc))) { 27 | await mfs.copyFile(`${outDirStatic}/index.html`, indexTemplateSrc) 28 | } 29 | 30 | // get script tags to inject in render 31 | const base = process.env.VITE_APP_BASE || loadEnv("", process.cwd(), "").VITE_APP_BASE 32 | const manifest = (await mfs.readFile(`${outDirStatic}/manifest.json`)) as string 33 | const scriptTags = ManifestParser.getScriptTagFromManifest(manifest, base) 34 | 35 | // pre-render each route 36 | for (let url of urls) { 37 | url = url.startsWith("/") ? url : `/${url}` 38 | 39 | try { 40 | // Request DOM 41 | const dom = await render(url, scriptTags, true) 42 | // create stream and generate current file when all DOM is ready 43 | renderToPipeableStream(dom, { 44 | onAllReady() { 45 | createHtmlFile(urls, url, outDirStatic, dom) 46 | }, 47 | onError(x) { 48 | console.error(x) 49 | }, 50 | }) 51 | } catch (e) { 52 | console.log(e) 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * Create a single HTML file 59 | * @param urls: All urls to generate 60 | * @param url: Current URL to generate 61 | * @param outDir: Generation destination directory 62 | * @param dom: React DOM from index-server.tsx 63 | */ 64 | const createHtmlFile = async ( 65 | urls: string[], 66 | url: string, 67 | outDir: string, 68 | dom: ReactElement, 69 | ): Promise => { 70 | // Prepare file 71 | if (isRouteIndex(url, urls)) url = `${url}/index` 72 | const routePath = path.resolve(`${outDir}/${url}`) 73 | const htmlFilePath = `${routePath}.html` 74 | // Create file 75 | await mfs.createFile(htmlFilePath, htmlReplacement(renderToString(dom))) 76 | console.log(chalk.green(` → ${htmlFilePath.split("static")[1]}`)) 77 | } 78 | -------------------------------------------------------------------------------- /examples/example-ssr/prerender/urls.ts: -------------------------------------------------------------------------------- 1 | import fetch from "cross-fetch" 2 | 3 | export const fetchAvailableUrls = async (): Promise => { 4 | /** 5 | If urls come from API, fetch and return URLS instead 6 | 7 | let data 8 | try { 9 | data = await fetch("urls.json").then((res) => res.json()) 10 | } catch (e) { 11 | console.log(e) 12 | } 13 | return data 14 | */ 15 | 16 | // return static urls 17 | // prettier-ignore 18 | return new Promise(resolve => { 19 | resolve([ 20 | "/", 21 | "/a-propos", 22 | "/a-propos/foo", 23 | "/a-propos/bar", 24 | "/article/article-1", 25 | "/article/article-2", 26 | "/contact", 27 | 28 | // "/fr", 29 | // "/fr/a-propos", 30 | // "/fr/a-propos/foo", 31 | // "/fr/a-propos/bar", 32 | // "/fr/article/article-1", 33 | // "/fr/article/article-2", 34 | // "/fr/contact", 35 | 36 | "/en", 37 | "/en/about", 38 | "/en/about/foo", 39 | "/en/about/bar", 40 | "/en/article/article-1", 41 | "/en/article/article-2", 42 | "/en/contact", 43 | 44 | "/404" 45 | ]) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /examples/example-ssr/server.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import portFinderSync from "portfinder-sync" 3 | import { renderToPipeableStream } from "react-dom/server" 4 | import { createServer } from "vite" 5 | 6 | const port = portFinderSync.getPort(3000) 7 | 8 | ;(async () => { 9 | /** 10 | * Dev server 11 | * 12 | * 13 | */ 14 | async function createDevServer() { 15 | const app = express() 16 | 17 | // dev script to inject 18 | const devScripts = { 19 | js: [{ tag: "script", attr: { type: "module", src: "/src/index.tsx" } }], 20 | } 21 | 22 | // Create Vite server in middleware mode. 23 | // This disables Vite's own HTML serving logic and let the parent server take control. 24 | // https://vitejs.dev/config/server-options.html#server-middlewaremode 25 | const vite = await createServer({ 26 | logLevel: "info", 27 | server: { 28 | middlewareMode: true, 29 | }, 30 | appType: "custom", 31 | }) 32 | 33 | // use vite's connect instance as middleware 34 | app.use(vite.middlewares) 35 | app.use("*", async (req, res, next) => { 36 | if (req.originalUrl === "/favicon.ico") return 37 | 38 | try { 39 | // Transforms the ESM source code to be usable in Node.js 40 | const { render } = await vite.ssrLoadModule(`src/server/index-server.tsx`) 41 | // Get react-dom from the render method 42 | 43 | const dom = await render(req.originalUrl, devScripts, false) 44 | // Create stream to support Suspense API 45 | const stream = renderToPipeableStream(dom, { 46 | onShellReady() { 47 | res.statusCode = 200 48 | res.setHeader("Content-type", "text/html") 49 | stream.pipe(res) 50 | }, 51 | onError(e) { 52 | res.statusCode = 500 53 | console.error(e) 54 | }, 55 | }) 56 | } catch (e) { 57 | vite.ssrFixStacktrace(e) 58 | next(e) 59 | } 60 | }) 61 | 62 | // return vite, app and server 63 | return { vite, app } 64 | } 65 | 66 | /** 67 | * Let's go! 68 | */ 69 | createDevServer().then(({ app }) => 70 | app.listen(port, () => { 71 | console.log(`http://localhost:${port}`) 72 | }), 73 | ) 74 | })() 75 | -------------------------------------------------------------------------------- /examples/example-ssr/src/assets/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cher-ami/router/59b4a212ca2c0acd1a8d50081e47282940a44237/examples/example-ssr/src/assets/pic.png -------------------------------------------------------------------------------- /examples/example-ssr/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react" 2 | import { Link, Stack, useLang } from "@cher-ami/router" 3 | import { languages } from "~/languages" 4 | import { EPages } from "~/routes" 5 | import { useRouter } from "@cher-ami/router" 6 | 7 | export function App() { 8 | const { langService } = useRouter() 9 | const [lang, setLang] = useLang() 10 | 11 | const crossedTransitions = ({ 12 | previousPage, 13 | currentPage, 14 | unmountPreviousPage, 15 | }): Promise => { 16 | return new Promise(async (resolve) => { 17 | const $current = currentPage?.$element 18 | if ($current) $current.style.visibility = "hidden" 19 | if (previousPage) { 20 | previousPage.playOut() 21 | } 22 | if (currentPage) { 23 | await currentPage.isReadyPromise() 24 | if ($current) $current.style.visibility = "visible" 25 | await currentPage.playIn() 26 | unmountPreviousPage() 27 | } 28 | resolve() 29 | }) 30 | } 31 | 32 | // prettier-ignore 33 | return ( 34 |
35 | {languages.map((el, i) => ( 36 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /examples/example-ssr/src/helpers/transitionsHelper.ts: -------------------------------------------------------------------------------- 1 | import { gsap } from "gsap" 2 | import debug from "@cher-ami/debug" 3 | const log = debug(`router:transitionsHelper`) 4 | 5 | export const transitionsHelper = ( 6 | el, 7 | show: boolean, 8 | from: any = {}, 9 | to: any = {}, 10 | ): Promise => { 11 | return new Promise((resolve) => { 12 | if (!el) { 13 | log("el doesnt exist", el) 14 | } 15 | 16 | gsap.fromTo( 17 | el, 18 | { autoAlpha: show ? 0 : 1, ...from }, 19 | 20 | { 21 | ...to, 22 | duration: 0.5, 23 | autoAlpha: show ? 1 : 0, 24 | ease: "power1.out", 25 | onComplete: resolve, 26 | }, 27 | ) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /examples/example-ssr/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(31, 31, 31); 3 | color: white; 4 | font-family: "Courier New", Courier, monospace; 5 | font-size: 1.5rem; 6 | } 7 | 8 | button { 9 | background-color: transparent; 10 | cursor: pointer; 11 | color: white; 12 | } 13 | 14 | nav { 15 | margin-bottom: 20px; 16 | } 17 | a { 18 | color: grey; 19 | } 20 | a.active { 21 | color: white; 22 | } 23 | a:hover { 24 | color: white; 25 | } 26 | 27 | .Stack > * { 28 | position: absolute; 29 | } 30 | -------------------------------------------------------------------------------- /examples/example-ssr/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { hydrateRoot } from "react-dom/client" 3 | import { App } from "./components/App" 4 | import { routes } from "./routes" 5 | import { createBrowserHistory } from "history" 6 | import "./index.css" 7 | import { Router } from "@cher-ami/router" 8 | import { langServiceInstance } from "./langServiceInstance" 9 | import { GlobalDataContext } from "~/store/GlobalDataContext" 10 | 11 | /** 12 | * Client side 13 | */ 14 | hydrateRoot( 15 | document.getElementById("root"), 16 | 24 | 25 | 26 | 27 | , 28 | ) 29 | -------------------------------------------------------------------------------- /examples/example-ssr/src/langServiceInstance.ts: -------------------------------------------------------------------------------- 1 | import { languages, showDefaultLangInUrl } from "./languages" 2 | import { LangService } from "@cher-ami/router" 3 | 4 | export const langServiceInstance = ( 5 | base = import.meta.env.VITE_APP_BASE || "/", 6 | url = window.location.pathname, 7 | ) => 8 | new LangService({ 9 | showDefaultLangInUrl, 10 | staticLocation: url, 11 | languages, 12 | base, 13 | }) 14 | -------------------------------------------------------------------------------- /examples/example-ssr/src/languages.ts: -------------------------------------------------------------------------------- 1 | import { TLanguage } from "@cher-ami/router" 2 | 3 | /** 4 | * Available languages 5 | * list of available languages 6 | */ 7 | export const languages: TLanguage[] = [ 8 | { 9 | key: "fr", 10 | default: true, 11 | }, 12 | { 13 | key: "en", 14 | }, 15 | ] 16 | 17 | /** 18 | * Show default lang in URL 19 | * Common configuration between server and client 20 | */ 21 | export const showDefaultLangInUrl = false 22 | -------------------------------------------------------------------------------- /examples/example-ssr/src/pages/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react" 2 | import { 3 | useStack, 4 | getSubRouterBase, 5 | getSubRouterRoutes, 6 | Link, 7 | Router, 8 | Stack, 9 | useRouter, 10 | } from "@cher-ami/router" 11 | import { transitionsHelper } from "../helpers/transitionsHelper" 12 | import { getPathByRouteName } from "@cher-ami/router" 13 | import debug from "@cher-ami/debug" 14 | import { useLang } from "@cher-ami/router" 15 | import { EPages } from "../routes" 16 | 17 | const componentName = "AboutPage" 18 | const log = debug(`front:${componentName}`) 19 | 20 | function AboutPage(props, handleRef) { 21 | const rootRef = useRef(null) 22 | const [lang] = useLang() 23 | 24 | useStack({ 25 | componentName, 26 | handleRef, 27 | rootRef, 28 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 29 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 30 | }) 31 | 32 | // prepare routes & base for subRouter 33 | const router = useRouter() 34 | const path = getPathByRouteName(router.routes, EPages.ABOUT) 35 | const subRouterBase = getSubRouterBase(path, router.base, true) 36 | const surRouterRoutes = getSubRouterRoutes(path, router.routes) 37 | 38 | return ( 39 |
40 | {componentName} {lang.key} 41 |
42 | Foo 43 |
44 | 45 | Bar 46 | 47 |
48 | 49 | 50 | 51 |
52 | ) 53 | } 54 | 55 | export default React.forwardRef(AboutPage) 56 | -------------------------------------------------------------------------------- /examples/example-ssr/src/pages/ArticlePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react" 2 | import { useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helpers/transitionsHelper" 4 | 5 | const componentName = "ArticlePage" 6 | function ArticlePage(props, handleRef) { 7 | const rootRef = useRef(null) 8 | 9 | useStack({ 10 | componentName, 11 | handleRef, 12 | rootRef, 13 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 14 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 15 | }) 16 | 17 | return ( 18 |
e).join(" ")} ref={rootRef}> 19 | {componentName} 20 |

article slug: {props.params.slug}

21 | 22 | {/*{props.todo?.map((e, i) =>
{e.title}
)}*/} 23 |
24 | ) 25 | } 26 | 27 | export default React.forwardRef(ArticlePage) 28 | -------------------------------------------------------------------------------- /examples/example-ssr/src/pages/BarPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react" 2 | import { Link, useRouter, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "~/helpers/transitionsHelper" 4 | import { EPages } from "~/routes" 5 | 6 | const componentName = "BarPage" 7 | function BarPage(props, handleRef) { 8 | const rootRef = useRef(null) 9 | const { currentRoute } = useRouter() 10 | 11 | useStack({ 12 | componentName, 13 | handleRef, 14 | rootRef, 15 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 16 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 17 | }) 18 | 19 | return ( 20 |
e).join(" ")} ref={rootRef}> 21 |

{componentName}

22 | Query Params : 23 |
    24 |
  • Hello : {currentRoute.queryParams?.hello}
  • 25 |
26 |
27 |
28 | link to FOO 29 |
30 | ) 31 | } 32 | 33 | export default React.forwardRef(BarPage) 34 | -------------------------------------------------------------------------------- /examples/example-ssr/src/pages/ContactPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react" 2 | import { useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "~/helpers/transitionsHelper" 4 | 5 | const componentName = "ContactPage" 6 | function ContactPage(props, handleRef) { 7 | const rootRef = useRef(null) 8 | 9 | useStack({ 10 | componentName, 11 | handleRef, 12 | rootRef, 13 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 14 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 15 | }) 16 | 17 | return ( 18 |
19 | {componentName} 20 |
21 | ) 22 | } 23 | 24 | export default React.forwardRef(ContactPage) 25 | -------------------------------------------------------------------------------- /examples/example-ssr/src/pages/FooPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react" 2 | import { useRouter, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helpers/transitionsHelper" 4 | import { useLang } from "@cher-ami/router" 5 | 6 | const componentName = "FooPage" 7 | function FooPage(props, handleRef) { 8 | const rootRef = useRef(null) 9 | const [lang] = useLang() 10 | const router = useRouter() 11 | 12 | useStack({ 13 | componentName, 14 | handleRef, 15 | rootRef, 16 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 17 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 18 | }) 19 | 20 | return ( 21 |
e).join(" ")} ref={rootRef}> 22 | {componentName} - langKey:{" "} 23 | {router.langService && router.langService.currentLang.key} 24 |
25 | ) 26 | } 27 | 28 | export default React.forwardRef(FooPage) 29 | -------------------------------------------------------------------------------- /examples/example-ssr/src/pages/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useContext, useEffect } from "react" 2 | import { useLang, useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "~/helpers/transitionsHelper" 4 | import { GlobalDataContext } from "~/store/GlobalDataContext" 5 | 6 | const componentName = "HomePage" 7 | function HomePage(props, handleRef) { 8 | const rootRef = useRef(null) 9 | const [n, setN] = useState(0) 10 | const globalData = useContext(GlobalDataContext) 11 | // console.log("globalData",globalData) 12 | const [lang] = useLang() 13 | 14 | useEffect(() => {}, []) 15 | 16 | useStack({ 17 | componentName, 18 | handleRef, 19 | rootRef, 20 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 21 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 22 | }) 23 | 24 | return ( 25 |
26 | {componentName} {lang.key} 27 |
28 |
29 | 30 |
31 |
32 |

"globalData" request result:

33 | {globalData.users.map((user, i) => ( 34 |
{user.name}
35 | ))} 36 |
37 | ) 38 | } 39 | 40 | export default React.forwardRef(HomePage) 41 | -------------------------------------------------------------------------------- /examples/example-ssr/src/pages/NotFoundPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react" 2 | import { useStack } from "@cher-ami/router" 3 | import { transitionsHelper } from "../helpers/transitionsHelper" 4 | 5 | const componentName = "NotFoundPage" 6 | function NotFoundPage(props, handleRef) { 7 | const rootRef = useRef(null) 8 | 9 | useStack({ 10 | componentName, 11 | handleRef, 12 | rootRef, 13 | playIn: () => transitionsHelper(rootRef.current, true, { x: -50 }, { x: 0 }), 14 | playOut: () => transitionsHelper(rootRef.current, false, { x: -0 }, { x: 50 }), 15 | }) 16 | 17 | return ( 18 |
19 | NOT FOUND NOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT 20 | FOUNDNOT FOUNDNOT FOUNDNOT NOT FOUND NOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT 21 | FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT NOT FOUND NOT FOUNDNOT 22 | FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT NOT 23 | FOUND NOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT FOUNDNOT 24 | FOUNDNOT FOUNDNOT 25 |
26 | ) 27 | } 28 | 29 | export default React.forwardRef(NotFoundPage) 30 | -------------------------------------------------------------------------------- /examples/example-ssr/src/routes.ts: -------------------------------------------------------------------------------- 1 | import HomePage from "./pages/HomePage" 2 | import AboutPage from "./pages/AboutPage" 3 | import ContactPage from "./pages/ContactPage" 4 | import NotFoundPage from "./pages/NotFoundPage" 5 | import { TRoute } from "@cher-ami/router" 6 | import ArticlePage from "./pages/ArticlePage" 7 | import FooPage from "./pages/FooPage" 8 | import BarPage from "./pages/BarPage" 9 | 10 | export enum EPages { 11 | HOME = "home", 12 | ABOUT = "about", 13 | FOO = "foo", 14 | BAR = "bar", 15 | ARTICLE = "article", 16 | CONTACT = "contact", 17 | NOT_FOUND = "notfound", 18 | } 19 | 20 | export const routes: TRoute[] = [ 21 | { 22 | path: "/", 23 | component: HomePage, 24 | name: EPages.HOME, 25 | getStaticProps: async (props, currentLang) => { 26 | const res = await fetch("https://worldtimeapi.org/api/ip") 27 | const time = await res.json() 28 | return { time } 29 | }, 30 | }, 31 | { 32 | path: { fr: "/a-propos", en: "/about" }, 33 | component: AboutPage, 34 | name: EPages.ABOUT, 35 | getStaticProps: async () => { 36 | const res = await fetch("https://jsonplaceholder.typicode.com/todos") 37 | const todo = await res.json() 38 | return { todo } 39 | }, 40 | children: [ 41 | { 42 | path: "/foo", 43 | component: FooPage, 44 | name: EPages.FOO, 45 | getStaticProps: async () => { 46 | const res = await fetch("https://jsonplaceholder.typicode.com/todos/1") 47 | const todo = await res.json() 48 | return { todo } 49 | }, 50 | }, 51 | { 52 | path: "/bar", 53 | component: BarPage, 54 | name: EPages.BAR, 55 | }, 56 | ], 57 | }, 58 | { 59 | path: "/article/:slug", 60 | component: ArticlePage, 61 | name: EPages.ARTICLE, 62 | props: { 63 | color: "blue", 64 | }, 65 | getStaticProps: async (props) => { 66 | const res = await fetch("https://jsonplaceholder.typicode.com/todos") 67 | const todo = await res.json() 68 | const mySlug = props.params.slug 69 | return { todo, mySlug } 70 | }, 71 | }, 72 | { 73 | path: "/contact", 74 | component: ContactPage, 75 | name: EPages.CONTACT, 76 | }, 77 | { 78 | path: "/:rest", 79 | component: NotFoundPage, 80 | name: EPages.NOT_FOUND, 81 | }, 82 | ] 83 | -------------------------------------------------------------------------------- /examples/example-ssr/src/server/helpers/CherScripts.tsx: -------------------------------------------------------------------------------- 1 | import { TScript } from "../../../prerender/helpers/ManifestParser" 2 | import * as React from "react" 3 | 4 | export const ScriptTag = ({ tag, attr }: TScript): JSX.Element => { 5 | const T = tag 6 | // @ts-ignore 7 | if (attr.noModule === "") return 8 | else return 9 | } 10 | 11 | export const CherScripts = ({ scripts }: { scripts: TScript[] }): JSX.Element => ( 12 | <>{scripts?.map((script, i) => )} 13 | ) 14 | -------------------------------------------------------------------------------- /examples/example-ssr/src/server/helpers/RawScript.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | /** 4 | * Insert raw script in window variable 5 | * @param name 6 | * @param obj 7 | */ 8 | export const RawScript = ({ name, data }) => { 9 | const stringify = (e): string => JSON.stringify(e, null, 2)?.replace(/\n\s+/g, "") 10 | return ( 11 | 20 | 21 | {/* react plugin HMR */} 22 |