├── .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 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/examples/example-ssr/src/server/helpers/ViteDevScripts.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | export const ViteDevScripts = () => {
4 | if (process.env.NODE_ENV !== "development") return null
5 | const base = process.env.VITE_APP_BASE || "/"
6 | return (
7 | <>
8 | {/* typescript plugin */}
9 |
17 |
18 | {/* vite HMR */}
19 |
20 |
21 | {/* react plugin HMR */}
22 |