Releases: cher-ami/router
@cher-ami/router V3 is out 🎉
What's new?
Almost a year ago, we start to implement an SSR support to our router. After a lot of tests and projects based on beta, it was time to release this v3. this release includes a lot of codebase core's and test's refactoring due to this new feature. An SSR example as been added to this repo too.
How it works?
@cher-ami/router
is compatible with SSR due to using staticLocation
props instead of history
props on the Router instance.
In this case, the router will match only with staticLocation
props value and render the appropiate route without invoking the browser history.
<Router
routes={routesList}
staticLocation={"/foo"}
// history={createBrowserHistory()}
>
// ...
</Router>
In order to use this router on server side, we need to be able to request API on the server side too.
In this case, request will be print as javascript window object on the renderToString html server response.
So the client will get this response.
To be able to request on server side (and on client side too), getStaticProps
route property is now available on the route object:
{
path: "/article/:slug",
component: ArticlePage,
name: "Article",
getStaticProps: async (props, currentLang) => {
// props contains route props and params (ex: slug: "article-1")
const res = await fetch(`https://api.com/posts/${currentLang.key}/${props.params.slug}`);
const api = await res.json();
return { api };
}
}
Then, get the response data populated in page component props:
function HomePage({ api }) {
return <div>{api.title}</div>;
}
For larger example, check the example-ssr folder.
changelog
- Add SSR support ⚡️ (#96) - fix #90 - v3.0.0-beta.0
Fix the Router on strictMode (#98) - v3.0.0-beta.1 Support StrictMode - Rollback Support StrictMode (98) because of conflicts with SSR routes management (#101) - v3.0.0-beta.2.1
- Update dependencies (#103) - v3.0.0-beta.3
- Fix hash history reload (#99) - v3.0.0-beta.4
- Listen history error SSR render (#104) - v3.0.0-beta.5
- Add
getStaticProps
API on route object ⚡️ (#105) - v3.0.0-beta.6 - Fix matcher on sub-router (#116) - v3.0.0-beta.7
- Add/Update core tests (#117)
- Fix context route format (#118) - v3.0.0-beta.8
- Fix
getStaticProps
on first visited route (#119) - v3.0.0-beta.9 - Fix SSR
LangService
currentLang
property (#121) - v3.0.0-beta.10 - Fix get sub router base get sub router routes (#122) - v3.0.0-beta.11
- Fix SSR routes on refresh (#123) - v3.0.0-beta.12
- Update SSR check for using
useLayoutEffect
inStack
(#125) - v3.0.0-beta.13 - Fix sub-router
getStaticProps
(#127) - v3.0.0-beta.14 - Create
redirectToBrowserLang
method ⚡️ (#129) - v3.0.0-beta.15 fix #128 - Expose
currentLang
ingetStaticProps
param (#130) - v3.0.0-beta.16
v3.0.0-beta.16
Feature
#130 Expose currentLang in getStaticProps param
currentLang object is needed in getStaticProps route property to request URL by language.
{
path: "/article/:slug",
component: ArticlePage,
name: "Article",
getStaticProps: async (props, currentLang) => {
// props contains route props and params (ex: slug: "article-1")
const res = await fetch(`https://api.com/posts/${currentLang.key}/${props.params.slug}`);
const api = await res.json();
return { api };
}
}
v3.0.0-beta.15
Feature
- create
langService.redirectToBrowserLang()
- rename
langService.redirect()
tolangService.redirectToDefaultLang()
v3.0.0-beta.14
Fix
Fix sub-router route getStaticProps
#127
v3.0.0-beta.9
Fix #119
v3.0.0-beta.7
feature & breaking changes
Fix/matcher sub router (#116)
routes list:
{
path: "/about",
component: AboutPage,
children: [
{
path: "/foo",
component: FooPage,
},
{
path: "/bar",
component: BarPage,
},
],
},
In this example, we have a main router (1) and a subRouter (2) instance.
When we were in AboutPage
, and the URL was /about/foo
, currentRoute
was returned the route object needed by Stack
component and not really the current route from URL.
before, URL is "/about/foo" & log curentRoute
in AboutPage
:
// currentRoute obj
{
name: "AboutPage,
path: "/about"
...
}
more logical after, URL is "/about/foo" & log curentRoute
in AboutPage
:
// currentRoute obj
{
name: "FooPage,
path: "/foo"
_context: {
name: "AboutPage",
path: "/about"
}
...
}
The main issue is the mix between the parse URL logic (witch need to return the appropriate route object from URL) and the stack render.
currentRoute
object is the result of getRouteFromUrl( url )
. If url
param is /about/foo
, we expect to receive the object corresponding to this URL. BUT the stack render need the current "context", first router instance will not return FooPage
but AboutPage
. So The stack have to check the _context
key object if he is has a subrouter.
- reorganize route Object :
(add "_" on internal properties)
type TRoute = {
path: string | { [x: string]: string };
component: React.ComponentType<any>;
base: string;
name: string;
parser: Match;
props: TRouteProps;
children: TRoute[];
url: string;
getStaticProps: (props: TRouteProps) => Promise<any>;
_fullUrl: string; // full URL who not depend of current instance
_fullPath: string; // full Path /base/:lang/foo/second-foo
_langPath: { [x: string]: string } | null;
_context: TRoute;
};
v3.0.0-beta.6
About
In order to use this router on server side, we need to be able to request API on the server side too. In this case, request will be print as javascript window object on the renderToString html server response. The client will got this response.
Strategy
- Add
getStaticProps()
property to each route object.
const routes = [
{
path: "/",
component: HomePage,
name: "Home",
getStaticProps: async (props) => {
const res = await fetch("https://api.com/home");
const api = await res.json();
return { api };
},
},
{
path: "/article/:slug",
component: ArticlePage,
name: "Article",
getStaticProps: async (props) => {
// props contains route props and params (ex: slug: "article-1")
const res = await fetch(`https://api.com/posts/${props.params.slug}`);
const api = await res.json();
return { api };
},
},
]
- Then, get response data populated in page component props
function HomePage({ api }) {
return <div>{ api.title }</div>
}
Changes
TRoute
name
is required on getStaticProps
usage.
TODO
- add
getStaticProps
to TRoute to allows api fetching on the server side (@willybrauner) - fetch
getStaticProps
promise in Router, and associate response ton newRoute.props (@willybrauner) - expose props (included params as
getStaticProps
Arg (@willybrauner)
- Create getStaticProps cache in
Router
(@willybrauner) - Import isomorphic-unfetch (@Hlefrant)
- Request Global api on server side (@Hlefrant)
- Adapt langService for SSR usage (@willybrauner)
// add staticLocation as instance param
const langService = new LangService({
staticLocation: url,
languages,
});
-
Hash url check on server-side (@Hlefrant) - index-server.tsx -> create global function
requestStaticPropsFromRoute
(@willybrauner)
Request static props of current route on server side
const ssrStaticProps: { props: any , name: string } = await requestStaticPropsFromRoute({
url: "/hello",
base: "/",
routes: routesList,
langService: langService,
});
- update repo struct: put examples as independents workspaces in
examples
folder / install pnpm for managin workspaces.
Now install dependencies with
pnpm i
# run client example
npm run example-client:dev
# run ssr example
npm run example-ssr:dev
- fix circular dependencies on build
- write documentation (@willybrauner)
cher-ami router v2 is out 🎉
Structure improvement
-
(Replace eventEmitter by React context #65)
Using react context API, it's one less external tool to use in the project. -
(Internalize router manager in router #79)
There is no more RouterManager class instantiated inRouter.tsx
. All the route changing listening is made insideRouter.tsx
who call external matcher helper function. It's now easier to test this matcher function an avoids having to communicate between the independent Router instance and the react Router component. -
(Update debug and dev server dependencies #74)
Replacedebug
lib by the smaller lib @wbe/debug
Features
-
(Replace path-parser by path-to-regexp #75)
path-to-regexp allows optional params witch was not possible with path-parser. route path can be now defined as/path/:id?
./path
and/path/hello
will match. -
(Infinit sub Routers #79)
It was working on one level, it's now possible to nest as many routers as needed. -
new helper
createUrl()
Create a formated URL by string, or TOpenRouteParams. This method is available everywhere inside component.
import { createUrl } from "@cher-ami/router"
createUrl({ name: "ArticlePage", params: {id: "foo"} }) // will return a path as `/articles/foo` (depend of route paths)
- new helper
openRoute()
Push new route in current history. Stack(s) component(s) will return the appropriate route. This method is available everywhere inside component.
import { openRoute } from "@cher-ami/router"
openRoute({ name: "ArticlePage", params: {id: "foo"} }) // stack will render the appropriate component
- (add Setlang hook #81)
If LangService instance is set to the Router, we can use this hook to quickly get current language and change language.
const [lang, setLang] = useLang();
-
(extended Link props with anchor html attributes #78)
-
(Export type #83)
ExposeTRoute
type -
(Expose routers #76)
GlobalRouters
object is now exposed. It contains informations available for all routers instances. -
Add
getPaused()
andsetPaused()
as router instance properties, allows to paused handleHistory internal method. It's pretty usefull to prevent some bugs during routes transitions. #89
const { getPaused, setPaused } = useRouter()
// paused handle history
setPaused(true)
// get current paused state
const isPaused = getPaused() // true
- Update all dependencies. Move to React 18. #92
breaking changes
useRouter
useRoute()
as been removed.currentRoute
&previousRoute
are now available fromuseRouter()
// before
const { currentRoute, previousRoute } = useRoute();
// v2
const { currentRoute, previousRoute } = useRouter();
LangService
- LangService is now set to the router as an independent instance.
// before - it was a singleton
LangService.init([{ key: "en" }, { key: "fr" }], true, "/")
// ...
<Router middlewares={langMiddleware}> ... </Router>
// v2 - independent instance
const langService = new LangService({
languages: [{ key: "en" }, { key: "fr" }],
showDefaultLangInUrl: true,
base: "/",
});
// ...
<Router langService={langService}> ... </Router>
Sub Router
Sub Router declaration is not as automatic and"magical" as before. In v2, we need to define manually what is the sub router base & routes.
getPathByRouteName
, getSubRoutersBase
& getSubRoutersRoutes
are available to help use.
// before - we deduced internally what was sub routes and full base URL.
<Router base={"/foo"}>
</Router>
// v2
// Get parent router context
const { base, routes } = useRouter();
// Parsed routes list and get path by route name
const path = getPathByRouteName(routesList, "FooPage"); // "/foo"
// ...
return (
<div>
<Router
// -> "/base/:lang/foo" (if last param is false, ':lang' will be not added)
base={getSubRoutersBase(path, base, true)}
// children routes array of FooPage
routes={getSubRoutersRoutes(path, routes)}
>
<Stack />
</Router>
</div>
);
useHistory
useHistory
hook is returning now the whole of history object instead of an array of history location.
const history = useHistory(()=> {
}, [])
//...
history.push() // etc...
Fix
Fix #86 bug when a route is defined after a route witch contains subroutes.
{
path: "/",
component: HomePage,
},
{
path: "/about",
component: AboutPage,
children: [
{
path: "/foo",
component: FooPage,
},
{
path: "/bar",
component: BarPage,
}
],
},
// this last route is now properly rendered
{
path: "/last",
component: LastPage,
},
}
v2.0.0-beta-11
feature
- #92 Update all dependencies. Move to React 18.
v2.0.0-beta-10
Feature
Add getPaused()
and setPaused()
as router instance properties, allows to paused handleHistory internal method. It's pretty usefull to prevent some bugs during routes transitions.
const { getPaused, setPaused } = useRouter()
// paused handle history
setPaused(true)
// get current paused state
const isPaused = getPaused() // true