@lomray/vite-ssr-boost 3.3.5 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/entry.d.ts +2 -3
- package/browser/entry.js +1 -1
- package/browser/entry.js.map +1 -1
- package/components/navigate.d.ts +1 -1
- package/components/navigate.js +1 -1
- package/components/navigate.js.map +1 -1
- package/components/scroll-to-top.js +1 -1
- package/components/scroll-to-top.js.map +1 -1
- package/helpers/build-router-state.d.ts +1 -1
- package/helpers/build-router-state.js.map +1 -1
- package/helpers/handle-response.d.ts +1 -1
- package/helpers/handle-response.js.map +1 -1
- package/helpers/import-route.d.ts +3 -243
- package/helpers/import-route.js.map +1 -1
- package/helpers/serialize-errors.d.ts +1 -1
- package/helpers/serialize-errors.js +1 -1
- package/helpers/serialize-errors.js.map +1 -1
- package/interfaces/fc-route.d.ts +2 -2
- package/interfaces/fc-route.js.map +1 -1
- package/interfaces/route-object.d.ts +1 -1
- package/node/entry.d.ts +1 -1
- package/node/entry.js +1 -1
- package/node/entry.js.map +1 -1
- package/node/render.d.ts +1 -2
- package/node/render.js +1 -1
- package/node/render.js.map +1 -1
- package/package.json +6 -6
- package/services/ssr-manifest.d.ts +3 -4
- package/services/ssr-manifest.js.map +1 -1
package/browser/entry.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/// <reference types="react-dom" />
|
|
2
|
-
import { Router as RemixRouter } from '@remix-run/router/dist/router';
|
|
3
2
|
import { FC, PropsWithChildren } from 'react';
|
|
4
3
|
import ReactDOM from 'react-dom/client';
|
|
5
|
-
import { createBrowserRouter } from 'react-router
|
|
4
|
+
import { DataRouter, createBrowserRouter } from 'react-router';
|
|
6
5
|
import { TRouteObject } from "../interfaces/route-object.js";
|
|
7
6
|
interface IAppClientProps<T = undefined> {
|
|
8
7
|
client: T;
|
|
9
8
|
}
|
|
10
9
|
interface IInitPropsParams {
|
|
11
10
|
isSSRMode: boolean;
|
|
12
|
-
router:
|
|
11
|
+
router: DataRouter;
|
|
13
12
|
}
|
|
14
13
|
type TApp<T> = FC<PropsWithChildren<IAppClientProps<T>>>;
|
|
15
14
|
interface IEntryClientOptions<T> {
|
package/browser/entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import t from"react";import e from"react-dom/client";import{matchRoutes as o,createBrowserRouter as r,RouterProvider as a}from"react-router
|
|
1
|
+
import t from"react";import e from"react-dom/client";import{matchRoutes as o,createBrowserRouter as r,RouterProvider as a}from"react-router";import{IS_SSR_MODE as n}from"../constants/common.js";async function c(c,i,{init:l,routerOptions:m,createRouter:s=r,rootId:u="root"}={}){const d=o(i,window.location,m?.basename)?.filter((t=>t.route.lazy));d&&d?.length>0&&await Promise.all(d.map((async t=>{const e=await(t.route.lazy?.());Object.assign(t.route,{...e,lazy:void 0})})));const f=s(i,m),p=document.getElementById(u),y=await(l?.({isSSRMode:n,router:f})),w=()=>t.createElement(c,{client:y},t.createElement(a,{router:f}));return n&&"1"!==p.dataset.forceSpa?e.hydrateRoot(p,t.createElement(w,null)):e.createRoot(p).render(t.createElement(w,null))}export{c as default};
|
|
2
2
|
//# sourceMappingURL=entry.js.map
|
package/browser/entry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sources":["../../src/browser/entry.tsx"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"entry.js","sources":["../../src/browser/entry.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport type { DataRouter, RouteObject } from 'react-router';\nimport { createBrowserRouter, matchRoutes, RouterProvider } from 'react-router';\nimport { IS_SSR_MODE } from '@constants/common';\nimport type { TRouteObject } from '@interfaces/route-object';\n\nexport interface IAppClientProps<T = undefined> {\n client: T;\n}\n\nexport interface IInitPropsParams {\n isSSRMode: boolean;\n router: DataRouter;\n}\n\nexport type TApp<T> = FC<PropsWithChildren<IAppClientProps<T>>>;\n\nexport interface IEntryClientOptions<T> {\n init?: (params: IInitPropsParams) => Promise<T>;\n routerOptions?: Parameters<typeof createBrowserRouter>[1];\n createRouter?: typeof createBrowserRouter;\n rootId?: string;\n}\n\n/**\n * Render client side application\n */\nasync function entry<TAppProps>(\n App: TApp<TAppProps>,\n routes: TRouteObject[],\n {\n init,\n routerOptions,\n createRouter = createBrowserRouter,\n rootId = 'root',\n }: IEntryClientOptions<TAppProps> = {},\n): Promise<ReactDOM.Root | void> {\n const lazyMatches = matchRoutes(\n routes as RouteObject[],\n window.location,\n routerOptions?.basename,\n )?.filter((m) => m.route.lazy);\n\n // Load the lazy matches and update the routes before creating router,\n // so we can hydrate the SSR-rendered content synchronously\n if (lazyMatches && lazyMatches?.length > 0) {\n await Promise.all(\n lazyMatches.map(async (m) => {\n const routeModule = await m.route.lazy?.();\n\n Object.assign(m.route, {\n ...routeModule,\n lazy: undefined,\n });\n }),\n );\n }\n\n const router = createRouter(routes as RouteObject[], routerOptions);\n const root = document.getElementById(rootId) as HTMLElement;\n const appProps = (await init?.({ isSSRMode: IS_SSR_MODE, router })) as TAppProps;\n\n const AppComponent: FC = () => (\n <App client={appProps}>\n <RouterProvider router={router} />\n </App>\n );\n\n if (!IS_SSR_MODE || root.dataset['forceSpa'] === '1') {\n return ReactDOM.createRoot(root).render(<AppComponent />);\n }\n\n return ReactDOM.hydrateRoot(root, <AppComponent />);\n}\n\nexport default entry;\n"],"names":["async","entry","App","routes","init","routerOptions","createRouter","createBrowserRouter","rootId","lazyMatches","matchRoutes","window","location","basename","filter","m","route","lazy","length","Promise","all","map","routeModule","Object","assign","undefined","router","root","document","getElementById","appProps","isSSRMode","IS_SSR_MODE","AppComponent","React","createElement","client","RouterProvider","dataset","ReactDOM","hydrateRoot","createRoot","render"],"mappings":"kMA6BAA,eAAeC,EACbC,EACAC,GACAC,KACEA,EAAIC,cACJA,EAAaC,aACbA,EAAeC,EAAmBC,OAClCA,EAAS,QACyB,CAAA,GAEpC,MAAMC,EAAcC,EAClBP,EACAQ,OAAOC,SACPP,GAAeQ,WACdC,QAAQC,GAAMA,EAAEC,MAAMC,OAIrBR,GAAeA,GAAaS,OAAS,SACjCC,QAAQC,IACZX,EAAYY,KAAIrB,MAAOe,IACrB,MAAMO,QAAoBP,EAAEC,MAAMC,UAElCM,OAAOC,OAAOT,EAAEC,MAAO,IAClBM,EACHL,UAAMQ,GACN,KAKR,MAAMC,EAASpB,EAAaH,EAAyBE,GAC/CsB,EAAOC,SAASC,eAAerB,GAC/BsB,QAAkB1B,IAAO,CAAE2B,UAAWC,EAAaN,YAEnDO,EAAmB,IACvBC,EAAAC,cAACjC,EAAG,CAACkC,OAAQN,GACXI,EAACC,cAAAE,GAAeX,OAAQA,KAI5B,OAAKM,GAA4C,MAA7BL,EAAKW,QAAkB,SAIpCC,EAASC,YAAYb,EAAMO,EAACC,cAAAF,EAAe,OAHzCM,EAASE,WAAWd,GAAMe,OAAOR,EAAAC,cAACF,EAAY,MAIzD"}
|
package/components/navigate.d.ts
CHANGED
package/components/navigate.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"react";import{Navigate as t}from"react-router
|
|
1
|
+
import e from"react";import{Navigate as t}from"react-router";import{useServerContext as r}from"../context/server.js";const a=({to:a,status:o=301,...s})=>{const n=r();if(!n.isServer)return e.createElement(t,{to:a,...s});if(n){const{basename:e}=n,t=[e,"string"==typeof a?a:[a.pathname,a.search,a.hash]].flat().filter(Boolean).join("").replace(/\/+/g,"/");n.response=new Response("",{status:o,headers:new Headers({Location:t})})}return null};export{a as default};
|
|
2
2
|
//# sourceMappingURL=navigate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigate.js","sources":["../../src/components/navigate.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport React from 'react';\nimport type { NavigateProps } from 'react-router
|
|
1
|
+
{"version":3,"file":"navigate.js","sources":["../../src/components/navigate.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport React from 'react';\nimport type { NavigateProps } from 'react-router';\nimport { Navigate as DefaultNavigate } from 'react-router';\nimport { useServerContext } from '@context/server';\n\ninterface INavigate {\n status?: number;\n}\n\ntype TProps = INavigate & NavigateProps;\n\n/**\n * React router navigate with server support\n * @constructor\n */\nconst Navigate: FC<TProps> = ({ to, status = 301, ...rest }) => {\n const context = useServerContext();\n\n if (!context.isServer) {\n return <DefaultNavigate to={to} {...rest} />;\n }\n\n if (context) {\n const { basename } = context;\n const location = [basename, typeof to === 'string' ? to : [to.pathname, to.search, to.hash]]\n .flat()\n .filter(Boolean)\n .join('')\n .replace(/\\/+/g, '/');\n\n context.response = new Response('', { status, headers: new Headers({ Location: location }) });\n }\n\n return null;\n};\n\nexport default Navigate;\n"],"names":["Navigate","to","status","rest","context","useServerContext","isServer","React","createElement","DefaultNavigate","basename","location","pathname","search","hash","flat","filter","Boolean","join","replace","response","Response","headers","Headers","Location"],"mappings":"qHAgBA,MAAMA,EAAuB,EAAGC,KAAIC,SAAS,OAAQC,MACnD,MAAMC,EAAUC,IAEhB,IAAKD,EAAQE,SACX,OAAOC,EAAAC,cAACC,EAAgB,CAAAR,GAAIA,KAAQE,IAGtC,GAAIC,EAAS,CACX,MAAMM,SAAEA,GAAaN,EACfO,EAAW,CAACD,EAAwB,iBAAPT,EAAkBA,EAAK,CAACA,EAAGW,SAAUX,EAAGY,OAAQZ,EAAGa,OACnFC,OACAC,OAAOC,SACPC,KAAK,IACLC,QAAQ,OAAQ,KAEnBf,EAAQgB,SAAW,IAAIC,SAAS,GAAI,CAAEnB,SAAQoB,QAAS,IAAIC,QAAQ,CAAEC,SAAUb,KAChF,CAED,OAAO,IAAI"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useRef as r,useEffect as
|
|
1
|
+
import{useRef as r,useEffect as t}from"react";import{useLocation as o}from"react-router";const e=({shouldReloadReset:e=!1})=>{const{pathname:n}=o(),a=r(n);return t((()=>{(e||a.current!==n)&&(a.current=n,window.scrollTo(0,0))}),[n]),null};export{e as default};
|
|
2
2
|
//# sourceMappingURL=scroll-to-top.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-to-top.js","sources":["../../src/components/scroll-to-top.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useRef } from 'react';\nimport { useLocation } from 'react-router
|
|
1
|
+
{"version":3,"file":"scroll-to-top.js","sources":["../../src/components/scroll-to-top.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useRef } from 'react';\nimport { useLocation } from 'react-router';\n\ninterface IScrollToTop {\n shouldReloadReset?: boolean;\n}\n\n/**\n * Scroll page to top on every pathname (url) change\n * @constructor\n */\nconst ScrollToTop: FC<IScrollToTop> = ({ shouldReloadReset = false }) => {\n const { pathname } = useLocation();\n const prev = useRef(pathname);\n\n useEffect(() => {\n if (!shouldReloadReset && prev.current === pathname) {\n return;\n }\n\n prev.current = pathname;\n window.scrollTo(0, 0);\n }, [pathname]);\n\n return null;\n};\n\nexport default ScrollToTop;\n"],"names":["ScrollToTop","shouldReloadReset","pathname","useLocation","prev","useRef","useEffect","current","window","scrollTo"],"mappings":"yFAYM,MAAAA,EAAgC,EAAGC,qBAAoB,MAC3D,MAAMC,SAAEA,GAAaC,IACfC,EAAOC,EAAOH,GAWpB,OATAI,GAAU,MACHL,GAAqBG,EAAKG,UAAYL,KAI3CE,EAAKG,QAAUL,EACfM,OAAOC,SAAS,EAAG,GAAE,GACpB,CAACP,IAEG,IAAI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-router-state.js","sources":["../../src/helpers/build-router-state.ts"],"sourcesContent":["import type { StaticHandlerContext } from 'react-router
|
|
1
|
+
{"version":3,"file":"build-router-state.js","sources":["../../src/helpers/build-router-state.ts"],"sourcesContent":["import type { StaticHandlerContext } from 'react-router';\nimport htmlEscape from '@helpers/html-escape';\nimport serializeErrors from '@helpers/serialize-errors';\n\n/**\n * Build router state\n */\nfunction buildRouterState(context: StaticHandlerContext): string {\n const { loaderData, actionData, errors } = context;\n const routerState = {\n loaderData,\n actionData,\n errors: serializeErrors(errors),\n };\n const json = htmlEscape(JSON.stringify(JSON.stringify(routerState)));\n\n return `<script async>window.__staticRouterHydrationData = JSON.parse(${json});</script>`;\n}\n\nexport default buildRouterState;\n"],"names":["buildRouterState","context","loaderData","actionData","errors","routerState","serializeErrors","htmlEscape","JSON","stringify"],"mappings":"qEAOA,SAASA,EAAiBC,GACxB,MAAMC,WAAEA,EAAUC,WAAEA,EAAUC,OAAEA,GAAWH,EACrCI,EAAc,CAClBH,aACAC,aACAC,OAAQE,EAAgBF,IAI1B,MAAO,iEAFMG,EAAWC,KAAKC,UAAUD,KAAKC,UAAUJ,kBAGxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-response.js","sources":["../../src/helpers/handle-response.ts"],"sourcesContent":["import type { Response as ExpressResponse } from 'express';\nimport type { StaticHandlerContext } from 'react-router
|
|
1
|
+
{"version":3,"file":"handle-response.js","sources":["../../src/helpers/handle-response.ts"],"sourcesContent":["import type { Response as ExpressResponse } from 'express';\nimport type { StaticHandlerContext } from 'react-router';\n\n/**\n * Handle router or server context response\n */\nconst handleResponse = (\n res: ExpressResponse,\n response: Response | StaticHandlerContext | null,\n defaultStatus = 200,\n): number | undefined => {\n if (!(response instanceof Response)) {\n return defaultStatus;\n }\n\n // redirect\n if (response.status >= 300 && response.status < 400) {\n res.redirect(response.status, response.headers.get('Location')!);\n\n return;\n }\n\n return response.status ?? defaultStatus;\n};\n\nexport default handleResponse;\n"],"names":["handleResponse","res","response","defaultStatus","Response","status","redirect","headers","get"],"mappings":"AAMM,MAAAA,EAAiB,CACrBC,EACAC,EACAC,EAAgB,MAEVD,aAAoBE,SAKtBF,EAASG,QAAU,KAAOH,EAASG,OAAS,SAC9CJ,EAAIK,SAASJ,EAASG,OAAQH,EAASK,QAAQC,IAAI,aAK9CN,EAASG,QAAUF,EAVjBA"}
|
|
@@ -1,249 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import { IndexRouteObject, NonIndexRouteObject } from 'react-router-dom';
|
|
1
|
+
import { IndexRouteObject, NonIndexRouteObject } from 'react-router';
|
|
3
2
|
import { FCCRoute, FCRoute } from "../interfaces/fc-route.js";
|
|
4
|
-
declare enum ResultType {
|
|
5
|
-
data = "data",
|
|
6
|
-
deferred = "deferred",
|
|
7
|
-
redirect = "redirect",
|
|
8
|
-
error = "error"
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Successful result from a loader or action
|
|
12
|
-
*/
|
|
13
|
-
interface SuccessResult {
|
|
14
|
-
type: ResultType.data;
|
|
15
|
-
data: any;
|
|
16
|
-
statusCode?: number;
|
|
17
|
-
headers?: Headers;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Successful defer() result from a loader or action
|
|
21
|
-
*/
|
|
22
|
-
interface DeferredResult {
|
|
23
|
-
type: ResultType.deferred;
|
|
24
|
-
deferredData: DeferredData;
|
|
25
|
-
statusCode?: number;
|
|
26
|
-
headers?: Headers;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Redirect result from a loader or action
|
|
30
|
-
*/
|
|
31
|
-
interface RedirectResult {
|
|
32
|
-
type: ResultType.redirect;
|
|
33
|
-
status: number;
|
|
34
|
-
location: string;
|
|
35
|
-
revalidate: boolean;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Unsuccessful result from a loader or action
|
|
39
|
-
*/
|
|
40
|
-
interface ErrorResult {
|
|
41
|
-
type: ResultType.error;
|
|
42
|
-
error: any;
|
|
43
|
-
headers?: Headers;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Result from a loader or action - potentially successful or unsuccessful
|
|
47
|
-
*/
|
|
48
|
-
type DataResult = SuccessResult | DeferredResult | RedirectResult | ErrorResult;
|
|
49
|
-
type LowerCaseFormMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
50
|
-
type UpperCaseFormMethod = Uppercase<LowerCaseFormMethod>;
|
|
51
|
-
/**
|
|
52
|
-
* Active navigation/fetcher form methods are exposed in lowercase on the
|
|
53
|
-
* RouterState
|
|
54
|
-
*/
|
|
55
|
-
type FormMethod = LowerCaseFormMethod;
|
|
56
|
-
/**
|
|
57
|
-
* In v7, active navigation/fetcher form methods are exposed in uppercase on the
|
|
58
|
-
* RouterState. This is to align with the normalization done via fetch().
|
|
59
|
-
*/
|
|
60
|
-
type V7_FormMethod = UpperCaseFormMethod;
|
|
61
|
-
type FormEncType = "application/x-www-form-urlencoded" | "multipart/form-data";
|
|
62
|
-
/**
|
|
63
|
-
* @private
|
|
64
|
-
* Internal interface to pass around for action submissions, not intended for
|
|
65
|
-
* external consumption
|
|
66
|
-
*/
|
|
67
|
-
interface Submission {
|
|
68
|
-
formMethod: FormMethod | V7_FormMethod;
|
|
69
|
-
formAction: string;
|
|
70
|
-
formEncType: FormEncType;
|
|
71
|
-
formData: FormData;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* @private
|
|
75
|
-
* Arguments passed to route loader/action functions. Same for now but we keep
|
|
76
|
-
* this as a private implementation detail in case they diverge in the future.
|
|
77
|
-
*/
|
|
78
|
-
interface DataFunctionArgs {
|
|
79
|
-
request: Request;
|
|
80
|
-
params: Params;
|
|
81
|
-
context?: any;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Arguments passed to loader functions
|
|
85
|
-
*/
|
|
86
|
-
interface LoaderFunctionArgs extends DataFunctionArgs {
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Arguments passed to action functions
|
|
90
|
-
*/
|
|
91
|
-
interface ActionFunctionArgs extends DataFunctionArgs {
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Loaders and actions can return anything except `undefined` (`null` is a
|
|
95
|
-
* valid return value if there is no data to return). Responses are preferred
|
|
96
|
-
* and will ease any future migration to Remix
|
|
97
|
-
*/
|
|
98
|
-
type DataFunctionValue = Response | NonNullable<unknown> | null;
|
|
99
|
-
/**
|
|
100
|
-
* Route loader function signature
|
|
101
|
-
*/
|
|
102
|
-
interface LoaderFunction {
|
|
103
|
-
(args: LoaderFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Route action function signature
|
|
107
|
-
*/
|
|
108
|
-
interface ActionFunction {
|
|
109
|
-
(args: ActionFunctionArgs): Promise<DataFunctionValue> | DataFunctionValue;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Route shouldRevalidate function signature. This runs after any submission
|
|
113
|
-
* (navigation or fetcher), so we flatten the navigation/fetcher submission
|
|
114
|
-
* onto the arguments. It shouldn't matter whether it came from a navigation
|
|
115
|
-
* or a fetcher, what really matters is the URLs and the formData since loaders
|
|
116
|
-
* have to re-run based on the data models that were potentially mutated.
|
|
117
|
-
*/
|
|
118
|
-
interface ShouldRevalidateFunction {
|
|
119
|
-
(args: {
|
|
120
|
-
currentUrl: URL;
|
|
121
|
-
currentParams: AgnosticDataRouteMatch["params"];
|
|
122
|
-
nextUrl: URL;
|
|
123
|
-
nextParams: AgnosticDataRouteMatch["params"];
|
|
124
|
-
formMethod?: Submission["formMethod"];
|
|
125
|
-
formAction?: Submission["formAction"];
|
|
126
|
-
formEncType?: Submission["formEncType"];
|
|
127
|
-
formData?: Submission["formData"];
|
|
128
|
-
actionResult?: DataResult;
|
|
129
|
-
defaultShouldRevalidate: boolean;
|
|
130
|
-
}): boolean;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Keys we cannot change from within a lazy() function. We spread all other keys
|
|
134
|
-
* onto the route. Either they're meaningful to the router, or they'll get
|
|
135
|
-
* ignored.
|
|
136
|
-
*/
|
|
137
|
-
type ImmutableRouteKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children";
|
|
138
|
-
/**
|
|
139
|
-
* lazy() function to load a route definition, which can add non-matching
|
|
140
|
-
* related properties to a route
|
|
141
|
-
*/
|
|
142
|
-
interface LazyRouteFunction<R extends AgnosticRouteObject> {
|
|
143
|
-
(): Promise<Omit<R, ImmutableRouteKey>>;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Base RouteObject with common props shared by all types of routes
|
|
147
|
-
*/
|
|
148
|
-
type AgnosticBaseRouteObject = {
|
|
149
|
-
caseSensitive?: boolean;
|
|
150
|
-
path?: string;
|
|
151
|
-
id?: string;
|
|
152
|
-
loader?: LoaderFunction;
|
|
153
|
-
action?: ActionFunction;
|
|
154
|
-
hasErrorBoundary?: boolean;
|
|
155
|
-
shouldRevalidate?: ShouldRevalidateFunction;
|
|
156
|
-
handle?: any;
|
|
157
|
-
lazy?: LazyRouteFunction<AgnosticBaseRouteObject>;
|
|
158
|
-
};
|
|
159
|
-
/**
|
|
160
|
-
* Index routes must not have children
|
|
161
|
-
*/
|
|
162
|
-
type AgnosticIndexRouteObject = AgnosticBaseRouteObject & {
|
|
163
|
-
children?: undefined;
|
|
164
|
-
index: true;
|
|
165
|
-
};
|
|
166
|
-
/**
|
|
167
|
-
* Non-index routes may have children, but cannot have index
|
|
168
|
-
*/
|
|
169
|
-
type AgnosticNonIndexRouteObject = AgnosticBaseRouteObject & {
|
|
170
|
-
children?: AgnosticRouteObject[];
|
|
171
|
-
index?: false;
|
|
172
|
-
};
|
|
173
|
-
/**
|
|
174
|
-
* A route object represents a logical route, with (optionally) its child
|
|
175
|
-
* routes organized in a tree-like structure.
|
|
176
|
-
*/
|
|
177
|
-
type AgnosticRouteObject = AgnosticIndexRouteObject | AgnosticNonIndexRouteObject;
|
|
178
|
-
type AgnosticDataIndexRouteObject = AgnosticIndexRouteObject & {
|
|
179
|
-
id: string;
|
|
180
|
-
};
|
|
181
|
-
type AgnosticDataNonIndexRouteObject = AgnosticNonIndexRouteObject & {
|
|
182
|
-
children?: AgnosticDataRouteObject[];
|
|
183
|
-
id: string;
|
|
184
|
-
};
|
|
185
|
-
/**
|
|
186
|
-
* A data route object, which is just a RouteObject with a required unique ID
|
|
187
|
-
*/
|
|
188
|
-
type AgnosticDataRouteObject = AgnosticDataIndexRouteObject | AgnosticDataNonIndexRouteObject;
|
|
189
|
-
/**
|
|
190
|
-
* The parameters that were parsed from the URL path.
|
|
191
|
-
*/
|
|
192
|
-
type Params<Key extends string = string> = {
|
|
193
|
-
readonly [key in Key]: string | undefined;
|
|
194
|
-
};
|
|
195
|
-
/**
|
|
196
|
-
* A RouteMatch contains info about how a route matched a URL.
|
|
197
|
-
*/
|
|
198
|
-
interface AgnosticRouteMatch<ParamKey extends string = string, RouteObjectType extends AgnosticRouteObject = AgnosticRouteObject> {
|
|
199
|
-
/**
|
|
200
|
-
* The names and values of dynamic parameters in the URL.
|
|
201
|
-
*/
|
|
202
|
-
params: Params<ParamKey>;
|
|
203
|
-
/**
|
|
204
|
-
* The portion of the URL pathname that was matched.
|
|
205
|
-
*/
|
|
206
|
-
pathname: string;
|
|
207
|
-
/**
|
|
208
|
-
* The portion of the URL pathname that was matched before child routes.
|
|
209
|
-
*/
|
|
210
|
-
pathnameBase: string;
|
|
211
|
-
/**
|
|
212
|
-
* The route object that was used to match.
|
|
213
|
-
*/
|
|
214
|
-
route: RouteObjectType;
|
|
215
|
-
}
|
|
216
|
-
interface AgnosticDataRouteMatch extends AgnosticRouteMatch<string, AgnosticDataRouteObject> {
|
|
217
|
-
}
|
|
218
|
-
declare class DeferredData {
|
|
219
|
-
private pendingKeysSet;
|
|
220
|
-
private controller;
|
|
221
|
-
private abortPromise;
|
|
222
|
-
private unlistenAbortSignal;
|
|
223
|
-
private subscribers;
|
|
224
|
-
data: Record<string, unknown>;
|
|
225
|
-
init?: ResponseInit;
|
|
226
|
-
deferredKeys: string[];
|
|
227
|
-
constructor(data: Record<string, unknown>, responseInit?: ResponseInit);
|
|
228
|
-
private trackPromise;
|
|
229
|
-
private onSettle;
|
|
230
|
-
private emit;
|
|
231
|
-
subscribe(fn: (aborted: boolean, settledKey?: string) => void): () => boolean;
|
|
232
|
-
cancel(): void;
|
|
233
|
-
resolveData(signal: AbortSignal): Promise<boolean>;
|
|
234
|
-
get done(): boolean;
|
|
235
|
-
get unwrappedData(): {};
|
|
236
|
-
get pendingKeys(): string[];
|
|
237
|
-
}
|
|
238
|
-
type RedirectFunction = (url: string, init?: number | ResponseInit) => Response;
|
|
239
|
-
/**
|
|
240
|
-
* A redirect response. Sets the status code and the `Location` header.
|
|
241
|
-
* Defaults to "302 Found".
|
|
242
|
-
*/
|
|
243
|
-
declare const redirect: RedirectFunction;
|
|
244
3
|
type IDynamicRoute = () => Promise<{
|
|
245
4
|
default: FCRoute | FCCRoute<any>;
|
|
246
5
|
}>;
|
|
6
|
+
type ImmutableRouteKey = 'lazy' | 'caseSensitive' | 'path' | 'id' | 'index' | 'children';
|
|
247
7
|
type IAsyncRoute = {
|
|
248
8
|
pathId?: string;
|
|
249
9
|
} & (Omit<IndexRouteObject, ImmutableRouteKey> | Omit<NonIndexRouteObject, ImmutableRouteKey>);
|
|
@@ -251,4 +11,4 @@ type IAsyncRoute = {
|
|
|
251
11
|
* Import dynamic route
|
|
252
12
|
*/
|
|
253
13
|
declare const importRoute: (route: IDynamicRoute, id?: string) => (() => Promise<IAsyncRoute>);
|
|
254
|
-
export { importRoute as default, IDynamicRoute, IAsyncRoute };
|
|
14
|
+
export { importRoute as default, IDynamicRoute, ImmutableRouteKey, IAsyncRoute };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-route.js","sources":["../../src/helpers/import-route.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"import-route.js","sources":["../../src/helpers/import-route.ts"],"sourcesContent":["import type { IndexRouteObject, NonIndexRouteObject } from 'react-router';\nimport withSuspense from '@components/with-suspense';\nimport type { FCCRoute, FCRoute } from '@interfaces/fc-route';\nimport { keys } from '@interfaces/fc-route';\n\nexport type IDynamicRoute = () => Promise<{ default: FCRoute | FCCRoute<any> }>;\n\nexport type ImmutableRouteKey = 'lazy' | 'caseSensitive' | 'path' | 'id' | 'index' | 'children';\n\nexport type IAsyncRoute = { pathId?: string } & (\n | Omit<IndexRouteObject, ImmutableRouteKey>\n | Omit<NonIndexRouteObject, ImmutableRouteKey>\n);\n\n/**\n * Import dynamic route\n */\nconst importRoute = (route: IDynamicRoute, id?: string): (() => Promise<IAsyncRoute>) => {\n return async (): Promise<IAsyncRoute> => {\n const resolved = await route();\n\n // fallback to react router export style\n if ('Component' in resolved) {\n return { ...resolved, pathId: id } as IAsyncRoute;\n }\n\n const Component = resolved.default;\n const result: IAsyncRoute = { Component, pathId: id };\n\n keys.forEach((key) => {\n if (Component[key]) {\n // @ts-ignore\n result[key] = Component[key] as NonNullable<IAsyncRoute[typeof key]>;\n }\n });\n\n if (Component.Suspense) {\n result.Component = withSuspense(Component, Component.Suspense);\n }\n\n return result;\n };\n};\n\nexport default importRoute;\n"],"names":["importRoute","route","id","async","resolved","pathId","Component","default","result","keys","forEach","key","Suspense","withSuspense"],"mappings":"+FAiBA,MAAMA,EAAc,CAACC,EAAsBC,IAClCC,UACL,MAAMC,QAAiBH,IAGvB,GAAI,cAAeG,EACjB,MAAO,IAAKA,EAAUC,OAAQH,GAGhC,MAAMI,EAAYF,EAASG,QACrBC,EAAsB,CAAEF,YAAWD,OAAQH,GAajD,OAXAO,EAAKC,SAASC,IACRL,EAAUK,KAEZH,EAAOG,GAAOL,EAAUK,GACzB,IAGCL,EAAUM,WACZJ,EAAOF,UAAYO,EAAaP,EAAWA,EAAUM,WAGhDJ,CAAM"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{isRouteErrorResponse as r}from"react-router
|
|
1
|
+
import{isRouteErrorResponse as r}from"react-router";function e(e){if(!e)return null;const t=Object.entries(e),o={};for(const[e,n]of t)r(n)?o[e]={...n,__type:"RouteErrorResponse"}:n instanceof Error?o[e]={message:n.message,__type:"Error"}:o[e]=n;return o}export{e as default};
|
|
2
2
|
//# sourceMappingURL=serialize-errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialize-errors.js","sources":["../../src/helpers/serialize-errors.ts"],"sourcesContent":["import { isRouteErrorResponse } from 'react-router
|
|
1
|
+
{"version":3,"file":"serialize-errors.js","sources":["../../src/helpers/serialize-errors.ts"],"sourcesContent":["import { isRouteErrorResponse } from 'react-router';\nimport type { StaticHandlerContext } from 'react-router';\n\n/**\n * Serialize react router errors\n * @see https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/server.tsx#LL166C1-L188C2\n * https://github.com/remix-run/react-router/blob/main/LICENSE.md\n */\nfunction serializeErrors(errors: StaticHandlerContext['errors']): StaticHandlerContext['errors'] {\n if (!errors) {\n return null;\n }\n\n const entries = Object.entries(errors);\n const serialized: StaticHandlerContext['errors'] = {};\n for (const [key, val] of entries) {\n // Hey you! If you change this, please change the corresponding logic in\n // deserializeErrors in react-router-dom/index.tsx :)\n if (isRouteErrorResponse(val)) {\n serialized[key] = { ...val, __type: 'RouteErrorResponse' };\n } else if (val instanceof Error) {\n // Do not serialize stack traces from SSR for security reasons\n serialized[key] = {\n message: val.message,\n __type: 'Error',\n };\n } else {\n serialized[key] = val as unknown;\n }\n }\n\n return serialized;\n}\n\nexport default serializeErrors;\n"],"names":["serializeErrors","errors","entries","Object","serialized","key","val","isRouteErrorResponse","__type","Error","message"],"mappings":"oDAQA,SAASA,EAAgBC,GACvB,IAAKA,EACH,OAAO,KAGT,MAAMC,EAAUC,OAAOD,QAAQD,GACzBG,EAA6C,CAAA,EACnD,IAAK,MAAOC,EAAKC,KAAQJ,EAGnBK,EAAqBD,GACvBF,EAAWC,GAAO,IAAKC,EAAKE,OAAQ,sBAC3BF,aAAeG,MAExBL,EAAWC,GAAO,CAChBK,QAASJ,EAAII,QACbF,OAAQ,SAGVJ,EAAWC,GAAOC,EAItB,OAAOF,CACT"}
|
package/interfaces/fc-route.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FC, PropsWithChildren } from 'react';
|
|
2
|
-
import { RouteObject } from 'react-router
|
|
2
|
+
import { RouteObject } from 'react-router';
|
|
3
3
|
import { FCC } from "./fc.js";
|
|
4
4
|
import { IRequestContext } from "../node/render.js";
|
|
5
|
-
declare module '
|
|
5
|
+
declare module 'react-router' {
|
|
6
6
|
interface LoaderFunctionArgs {
|
|
7
7
|
context?: IRequestContext;
|
|
8
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fc-route.js","sources":["../../src/interfaces/fc-route.ts"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport type { RouteObject } from 'react-router
|
|
1
|
+
{"version":3,"file":"fc-route.js","sources":["../../src/interfaces/fc-route.ts"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport type { RouteObject } from 'react-router';\nimport type { FCC } from '@interfaces/fc';\nimport type { IRequestContext } from '@node/render';\n\ndeclare module 'react-router' {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n export interface LoaderFunctionArgs {\n context?: IRequestContext;\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n export interface ActionFunctionArgs {\n context?: IRequestContext;\n }\n}\n\nconst keys = ['loader', 'action', 'ErrorBoundary', 'errorElement'] as const;\n\ntype IRouteParams = Pick<RouteObject, (typeof keys)[number]> & {\n Suspense?: FCC<Record<string, any>>;\n};\n\ntype FCRoute<TProps = Record<string, any>> = FC<TProps> & IRouteParams;\ntype FCCRoute<TProps = Record<string, any>> = FC<PropsWithChildren<TProps>> & IRouteParams;\n\nexport type { FCRoute, FCCRoute, IRouteParams };\n\nexport { keys };\n"],"names":["keys"],"mappings":"AAiBM,MAAAA,EAAO,CAAC,SAAU,SAAU,gBAAiB"}
|
package/node/entry.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { CompressionOptions } from 'compression';
|
|
|
4
4
|
import { Express, Request } from 'express';
|
|
5
5
|
import { Response as ExpressResponse } from "express";
|
|
6
6
|
import { FC, PropsWithChildren } from 'react';
|
|
7
|
-
import { createStaticHandler } from 'react-router
|
|
7
|
+
import { createStaticHandler } from 'react-router';
|
|
8
8
|
import { ServeStaticOptions } from 'serve-static';
|
|
9
9
|
import { Logger } from 'vite';
|
|
10
10
|
import { TRouteObject } from "../interfaces/route-object.js";
|
package/node/entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createStaticHandler as r}from"react-router
|
|
1
|
+
import{createStaticHandler as r}from"react-router";import t from"./render.js";function e(e,n,{init:o,routerOptions:i,...u}={}){const p=r(n,i);return{render:t.bind(null,{handler:p,App:e}),init:o,routes:n,...u}}export{e as default};
|
|
2
2
|
//# sourceMappingURL=entry.js.map
|
package/node/entry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sources":["../../src/node/entry.tsx"],"sourcesContent":["import type { Server } from 'node:net';\nimport type { CompressionOptions } from 'compression';\nimport type { Express, Request, Response as ExpressResponse } from 'express';\nimport type { FC, PropsWithChildren } from 'react';\nimport type { RouteObject } from 'react-router
|
|
1
|
+
{"version":3,"file":"entry.js","sources":["../../src/node/entry.tsx"],"sourcesContent":["import type { Server } from 'node:net';\nimport type { CompressionOptions } from 'compression';\nimport type { Express, Request, Response as ExpressResponse } from 'express';\nimport type { FC, PropsWithChildren } from 'react';\nimport type { RouteObject } from 'react-router';\nimport { createStaticHandler } from 'react-router';\nimport type { ServeStaticOptions } from 'serve-static';\nimport type { Logger } from 'vite';\nimport type { TRouteObject } from '@interfaces/route-object';\nimport type { IRenderOptions, IRenderParams, TRender } from '@node/render';\nimport render from '@node/render';\nimport type ServerApi from '@services/server-api';\nimport type ServerConfig from '@services/server-config';\n\nexport interface IInitServerRequestOut<T = Record<string, any>> {\n appProps?: T;\n hasEarlyHints?: boolean;\n shouldSkip?: boolean;\n}\n\nexport interface IEntrypointOptions<TAppProps = Record<string, any>> {\n onServerCreated?: (app: Express, serverApi: ServerApi) => Promise<void> | void;\n onServerStarted?: (app: Express, serverApi: ServerApi, server: Server) => Promise<void> | void;\n onRequest?: (\n req: Request,\n res: ExpressResponse,\n ) => Promise<IInitServerRequestOut<TAppProps>> | IInitServerRequestOut<TAppProps>;\n onRouterReady?: IRenderOptions<TAppProps>['onRouterReady'];\n onShellReady?: IRenderOptions<TAppProps>['onShellReady'];\n onShellError?: IRenderOptions<TAppProps>['onShellError'];\n onResponse?: IRenderOptions<TAppProps>['onResponse'];\n onError?: IRenderOptions<TAppProps>['onError'];\n getState?: IRenderOptions<TAppProps>['getState'];\n}\n\nexport interface IPrepareRenderOut<TAppProps = Record<string, any>> {\n render: TRender;\n init: IEntryServerOptions<TAppProps>['init'];\n routes: TRouteObject[];\n abortDelay?: number;\n loggerProd?: Logger;\n loggerDev?: Logger;\n middlewares?: {\n compression?: CompressionOptions | false;\n // basename should be same as vite 'base' config\n expressStatic?: (ServeStaticOptions & { basename?: string }) | false;\n };\n}\n\nexport interface IAppServerProps<T = Record<string, any>> {\n server: T;\n}\n\nexport type TApp<T> = FC<PropsWithChildren<Record<string, any> & IAppServerProps<T>>>;\n\nexport interface IEntryServerOptions<TAppProps = Record<string, any>> {\n abortDelay?: number;\n init?: (params: {\n config: ServerConfig;\n }) => IEntrypointOptions<TAppProps> | Promise<IEntrypointOptions<TAppProps>>;\n loggerProd?: IPrepareRenderOut['loggerProd'];\n loggerDev?: IPrepareRenderOut['loggerDev'];\n middlewares?: IPrepareRenderOut['middlewares'];\n routerOptions?: Parameters<typeof createStaticHandler>[1];\n}\n\n/**\n * Render server side application\n */\nfunction entry<TAppProps>(\n App: TApp<TAppProps>,\n routes: TRouteObject[],\n { init, routerOptions, ...rest }: IEntryServerOptions<TAppProps> = {},\n): IPrepareRenderOut<TAppProps> {\n const handler = createStaticHandler(routes as RouteObject[], routerOptions);\n\n return {\n render: render.bind(null, { handler, App } as IRenderParams<TAppProps>) as TRender,\n init,\n routes,\n ...rest,\n };\n}\n\nexport default entry;\n"],"names":["entry","App","routes","init","routerOptions","rest","handler","createStaticHandler","render","bind"],"mappings":"8EAqEA,SAASA,EACPC,EACAC,GACAC,KAAEA,EAAIC,cAAEA,KAAkBC,GAAyC,IAEnE,MAAMC,EAAUC,EAAoBL,EAAyBE,GAE7D,MAAO,CACLI,OAAQA,EAAOC,KAAK,KAAM,CAAEH,UAASL,QACrCE,OACAD,YACGG,EAEP"}
|
package/node/render.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { StaticHandler } from '@remix-run/router';
|
|
2
1
|
import { Request } from 'express';
|
|
3
2
|
import { Response as ExpressResponse } from "express";
|
|
4
|
-
import { StaticHandlerContext } from 'react-router
|
|
3
|
+
import { StaticHandlerContext, StaticHandler } from 'react-router';
|
|
5
4
|
import StreamError from "../constants/stream-error.js";
|
|
6
5
|
import { IServerContext } from "../context/server.js";
|
|
7
6
|
import { IObtainStreamErrorOut } from "../helpers/obtain-stream-error.js";
|
package/node/render.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"chalk";import r from"react";import{renderToPipeableStream as t}from"react-dom/server";import{createStaticRouter as o,StaticRouterProvider as n}from"react-router
|
|
1
|
+
import e from"chalk";import r from"react";import{renderToPipeableStream as t}from"react-dom/server";import{createStaticRouter as o,StaticRouterProvider as n}from"react-router";import s from"../constants/stream-error.js";import{ServerProvider as a}from"../context/server.js";import m from"../helpers/handle-response.js";import i from"../helpers/obtain-stream-error.js";import c from"./create-fetch-request.js";import d from"./write-response.js";import l from"../services/ssr-manifest.js";async function p({App:p,handler:u},f,h,{onRouterReady:x,onShellReady:S,onResponse:R,onShellError:g,onError:y,getState:C,abortDelay:E=15e3}){const{req:j,res:b}=h,v=c(j);h.routerContext=await u.query(v,{requestContext:h});const w=m(b,h.routerContext);if(!w)return;l.get(f).injectAssets(h);const{isStream:q=!0}=await(x?.({context:h}))??{};h.isStream=q,h.serverContext={response:null,isServer:!0,basename:h.routerContext?.basename};const T=o(u.dataRoutes,h.routerContext),A=b.write.bind(b),B=f.getLogger();let $;b.write=(e,...r)=>{const t="string"==typeof e,o=t?e:Buffer.from(e).toString(),n=R?.({context:h,html:o});return n?A(t?n:Buffer.from(n),...r):A(e,...r)};const{serverContext:k,routerContext:D,appProps:H}=h,{pipe:L,abort:P}=t(r.createElement(a,{context:k},r.createElement(p,{server:{...H,req:j}},r.createElement(n,{router:T,context:D,hydrate:!1}))),{onShellReady(){q&&d(h,{pipe:L,statusCode:w,onShellReady:S,getState:C})},onAllReady(){clearTimeout($),q||d(h,{pipe:L,statusCode:w,onShellReady:S,getState:C})},onShellError(e){const r=g?.({context:h,error:e})||`<!doctype html><p>Something went wrong: ${e.message}</p>`;b.status(500),b.setHeader("content-type","text/html"),b.send(r)},onError(r){clearTimeout($);const t=i(r),{code:o,message:n}=t,{didError:a}=h;h.didError=a??o,y?.({context:h,error:t}),B.info(e.red(`Stream error. Code: ${o}`)),[s.RenderAborted,s.RenderTimeout,s.RenderCancel].includes(o)?B.info(e.dim(n)):B.error(r)}});$=setTimeout((()=>{h.didError=s.RenderTimeout,P()}),E),j.on("close",(()=>{h.didError=s.RenderCancel,P()}))}export{p as default};
|
|
2
2
|
//# sourceMappingURL=render.js.map
|
package/node/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sources":["../../src/node/render.tsx"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"render.js","sources":["../../src/node/render.tsx"],"sourcesContent":["import chalk from 'chalk';\nimport type { Request, Response as ExpressResponse } from 'express';\nimport React from 'react';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport type { StaticHandlerContext, StaticHandler } from 'react-router';\nimport { createStaticRouter, StaticRouterProvider } from 'react-router';\nimport StreamError from '@constants/stream-error';\nimport type { IServerContext } from '@context/server';\nimport { ServerProvider } from '@context/server';\nimport handleResponse from '@helpers/handle-response';\nimport type { IObtainStreamErrorOut } from '@helpers/obtain-stream-error';\nimport obtainStreamError from '@helpers/obtain-stream-error';\nimport createFetchRequest from '@node/create-fetch-request';\nimport type { TApp } from '@node/entry';\nimport writeResponse from '@node/write-response';\nimport type ServerConfig from '@services/server-config';\nimport SsrManifest from '@services/ssr-manifest';\n\nexport interface IRequestContext<TAppProps = Record<any, any>> {\n req: Request;\n res: ExpressResponse;\n appProps: NonNullable<TAppProps>;\n html: { header: string; footer: string };\n routerContext?: StaticHandlerContext;\n serverContext?: IServerContext;\n isStream?: boolean;\n hasEarlyHints?: boolean;\n didError?: StreamError;\n}\n\nexport type TRender<TAppProps = Record<any, any>> = (\n config: ServerConfig,\n context: IRequestContext<TAppProps>,\n options: IRenderOptions,\n) => Promise<void>;\n\nexport interface IRenderParams<TAppProps = Record<string, any>> {\n App: TApp<TAppProps>;\n handler: StaticHandler;\n}\n\nexport interface IRenderOptions<TAppProps = Record<string, any>> {\n abortDelay?: number;\n onRouterReady?: (params: {\n context: IRequestContext<TAppProps>;\n }) => Promise<IRouterReadyOut> | IRouterReadyOut;\n onShellReady?: (params: { context: IRequestContext<TAppProps> }) => IShellReadyOut;\n onShellError?: (params: {\n context: IRequestContext<TAppProps>;\n error: Error;\n }) => string | undefined | void; // return html or undefined\n onError?: (params: { context: IRequestContext<TAppProps>; error: IObtainStreamErrorOut }) => void;\n onResponse?: (params: {\n context: IRequestContext<TAppProps>;\n html: string;\n }) => string | undefined | void;\n getState?: (params: {\n context: IRequestContext<TAppProps>;\n }) => Record<string, Record<string, any>> | undefined | void;\n}\n\nexport interface IRouterReadyOut {\n isStream?: boolean;\n}\n\nexport interface IShellReadyOut {\n header?: string;\n footer?: string;\n}\n\n/**\n * Render application\n */\nasync function render(\n { App, handler }: IRenderParams, // @see entry (bind)\n config: ServerConfig,\n context: IRequestContext,\n {\n onRouterReady,\n onShellReady,\n onResponse,\n onShellError,\n onError,\n getState,\n abortDelay = 15000,\n }: IRenderOptions,\n): Promise<void> {\n const { req, res } = context;\n const fetchRequest = createFetchRequest(req);\n\n context.routerContext = (await handler.query(fetchRequest, {\n requestContext: context,\n })) as StaticHandlerContext;\n\n /**\n * Handle response from page loader, router context can be Response\n */\n const statusCode = handleResponse(res, context.routerContext);\n\n if (!statusCode) {\n return;\n }\n\n SsrManifest.get(config).injectAssets(context);\n\n const { isStream = true } = (await onRouterReady?.({ context })) ?? {};\n\n context.isStream = isStream;\n context.serverContext = {\n response: null,\n isServer: true,\n basename: context.routerContext?.basename,\n };\n\n const router = createStaticRouter(handler.dataRoutes, context.routerContext);\n const write = res.write.bind(res) as ExpressResponse['write'];\n const Logger = config.getLogger();\n let abortTimer: NodeJS.Timer | undefined = undefined;\n\n /**\n * Listen response and stream to add possibility modify html on fly\n * E.g. listen stream and append some data\n */\n res.write = (data: string | Uint8Array, ...args): boolean => {\n const isString = typeof data === 'string';\n const html = isString ? data : Buffer.from(data).toString();\n const modifiedHtml = onResponse?.({ context, html });\n\n if (modifiedHtml) {\n // @ts-ignore\n return write(isString ? modifiedHtml : Buffer.from(modifiedHtml), ...args) as boolean;\n }\n\n // @ts-ignore\n return write(data, ...args) as boolean;\n };\n\n const { serverContext, routerContext, appProps } = context;\n\n const { pipe, abort } = renderToPipeableStream(\n <ServerProvider context={serverContext}>\n <App server={{ ...appProps, req }}>\n <StaticRouterProvider router={router} context={routerContext} hydrate={false} />\n </App>\n </ServerProvider>,\n {\n onShellReady(): void {\n if (!isStream) {\n return;\n }\n\n writeResponse(context, {\n pipe,\n statusCode,\n onShellReady,\n getState,\n });\n },\n onAllReady(): void {\n clearTimeout(abortTimer);\n\n if (isStream) {\n return;\n }\n\n writeResponse(context, {\n pipe,\n statusCode,\n onShellReady,\n getState,\n });\n },\n onShellError(e: Error): void {\n const htmlError =\n onShellError?.({ context, error: e }) ||\n `<!doctype html><p>Something went wrong: ${e.message}</p>`;\n\n res.status(500);\n res.setHeader('content-type', 'text/html');\n res.send(htmlError);\n },\n onError(err): void {\n clearTimeout(abortTimer);\n\n const error = obtainStreamError(err);\n const { code, message } = error;\n const { didError } = context;\n\n context.didError = didError ?? code;\n\n onError?.({ context, error });\n Logger.info(chalk.red(`Stream error. Code: ${code}`));\n\n if (\n [StreamError.RenderAborted, StreamError.RenderTimeout, StreamError.RenderCancel].includes(\n code,\n )\n ) {\n Logger.info(chalk.dim(message));\n\n return;\n }\n\n Logger.error(err as string);\n },\n },\n );\n\n // Abandon and switch to client rendering if enough time passes.\n abortTimer = setTimeout(() => {\n context.didError = StreamError.RenderTimeout;\n abort();\n }, abortDelay);\n\n // Detect cancel request\n req.on('close', () => {\n context.didError = StreamError.RenderCancel;\n abort();\n });\n}\n\nexport default render;\n"],"names":["async","render","App","handler","config","context","onRouterReady","onShellReady","onResponse","onShellError","onError","getState","abortDelay","req","res","fetchRequest","createFetchRequest","routerContext","query","requestContext","statusCode","handleResponse","SsrManifest","get","injectAssets","isStream","serverContext","response","isServer","basename","router","createStaticRouter","dataRoutes","write","bind","Logger","getLogger","abortTimer","data","args","isString","html","Buffer","from","toString","modifiedHtml","appProps","pipe","abort","renderToPipeableStream","React","createElement","ServerProvider","server","StaticRouterProvider","hydrate","writeResponse","onAllReady","clearTimeout","e","htmlError","error","message","status","setHeader","send","err","obtainStreamError","code","didError","info","chalk","red","StreamError","RenderAborted","RenderTimeout","RenderCancel","includes","dim","setTimeout","on"],"mappings":"ueAyEAA,eAAeC,GACbC,IAAEA,EAAGC,QAAEA,GACPC,EACAC,GACAC,cACEA,EAAaC,aACbA,EAAYC,WACZA,EAAUC,aACVA,EAAYC,QACZA,EAAOC,SACPA,EAAQC,WACRA,EAAa,OAGf,MAAMC,IAAEA,EAAGC,IAAEA,GAAQT,EACfU,EAAeC,EAAmBH,GAExCR,EAAQY,oBAAuBd,EAAQe,MAAMH,EAAc,CACzDI,eAAgBd,IAMlB,MAAMe,EAAaC,EAAeP,EAAKT,EAAQY,eAE/C,IAAKG,EACH,OAGFE,EAAYC,IAAInB,GAAQoB,aAAanB,GAErC,MAAMoB,SAAEA,GAAW,SAAgBnB,IAAgB,CAAED,cAAe,GAEpEA,EAAQoB,SAAWA,EACnBpB,EAAQqB,cAAgB,CACtBC,SAAU,KACVC,UAAU,EACVC,SAAUxB,EAAQY,eAAeY,UAGnC,MAAMC,EAASC,EAAmB5B,EAAQ6B,WAAY3B,EAAQY,eACxDgB,EAAQnB,EAAImB,MAAMC,KAAKpB,GACvBqB,EAAS/B,EAAOgC,YACtB,IAAIC,EAMJvB,EAAImB,MAAQ,CAACK,KAA8BC,KACzC,MAAMC,EAA2B,iBAATF,EAClBG,EAAOD,EAAWF,EAAOI,OAAOC,KAAKL,GAAMM,WAC3CC,EAAerC,IAAa,CAAEH,UAASoC,SAE7C,OAAII,EAEKZ,EAAMO,EAAWK,EAAeH,OAAOC,KAAKE,MAAkBN,GAIhEN,EAAMK,KAASC,EAAgB,EAGxC,MAAMb,cAAEA,EAAaT,cAAEA,EAAa6B,SAAEA,GAAazC,GAE7C0C,KAAEA,EAAIC,MAAEA,GAAUC,EACtBC,EAACC,cAAAC,EAAe,CAAA/C,QAASqB,GACvBwB,EAACC,cAAAjD,GAAImD,OAAQ,IAAKP,EAAUjC,QAC1BqC,EAAAC,cAACG,EAAqB,CAAAxB,OAAQA,EAAQzB,QAASY,EAAesC,SAAS,MAG3E,CACEhD,eACOkB,GAIL+B,EAAcnD,EAAS,CACrB0C,OACA3B,aACAb,eACAI,YAEH,EACD8C,aACEC,aAAarB,GAETZ,GAIJ+B,EAAcnD,EAAS,CACrB0C,OACA3B,aACAb,eACAI,YAEH,EACDF,aAAakD,GACX,MAAMC,EACJnD,IAAe,CAAEJ,UAASwD,MAAOF,KACjC,2CAA2CA,EAAEG,cAE/ChD,EAAIiD,OAAO,KACXjD,EAAIkD,UAAU,eAAgB,aAC9BlD,EAAImD,KAAKL,EACV,EACDlD,QAAQwD,GACNR,aAAarB,GAEb,MAAMwB,EAAQM,EAAkBD,IAC1BE,KAAEA,EAAIN,QAAEA,GAAYD,GACpBQ,SAAEA,GAAahE,EAErBA,EAAQgE,SAAWA,GAAYD,EAE/B1D,IAAU,CAAEL,UAASwD,UACrB1B,EAAOmC,KAAKC,EAAMC,IAAI,uBAAuBJ,MAG3C,CAACK,EAAYC,cAAeD,EAAYE,cAAeF,EAAYG,cAAcC,SAC/ET,GAGFjC,EAAOmC,KAAKC,EAAMO,IAAIhB,IAKxB3B,EAAO0B,MAAMK,EACd,IAKL7B,EAAa0C,YAAW,KACtB1E,EAAQgE,SAAWI,EAAYE,cAC/B3B,GAAO,GACNpC,GAGHC,EAAImE,GAAG,SAAS,KACd3E,EAAQgE,SAAWI,EAAYG,aAC/B5B,GAAO,GAEX"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lomray/vite-ssr-boost",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Vite plugin for create awesome SSR or SPA applications on React.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"chalk": "^5.3.0",
|
|
37
37
|
"commander": "^12.1.0",
|
|
38
|
-
"compression": "^1.7.
|
|
39
|
-
"express": "^4.21.
|
|
38
|
+
"compression": "^1.7.5",
|
|
39
|
+
"express": "^4.21.1",
|
|
40
40
|
"hoist-non-react-statics": "^3.3.2",
|
|
41
41
|
"json5": "^2.2.3"
|
|
42
42
|
},
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@types/react-dom": "^18.3.0",
|
|
56
56
|
"@types/sinon": "^17.0.3",
|
|
57
57
|
"@types/sinon-chai": "^4.0.0",
|
|
58
|
-
"@vitest/coverage-v8": "^2.1.
|
|
58
|
+
"@vitest/coverage-v8": "^2.1.6",
|
|
59
59
|
"@zerollup/ts-transform-paths": "^1.7.18",
|
|
60
60
|
"chai": "^5.1.1",
|
|
61
61
|
"eslint": "^8.57.0",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"sinon": "^19.0.2",
|
|
74
74
|
"sinon-chai": "^4.0.0",
|
|
75
75
|
"typescript": "^5.3.3",
|
|
76
|
-
"vitest": "^2.1.
|
|
76
|
+
"vitest": "^2.1.6"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
79
|
"@babel/generator": ">=7.23.0",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@babel/traverse": ">=7.23.0",
|
|
82
82
|
"@types/express": ">=4.17.21",
|
|
83
83
|
"react-dom": ">=18.2.0",
|
|
84
|
-
"react-router
|
|
84
|
+
"react-router": "^7.0.1",
|
|
85
85
|
"vite": ">=5"
|
|
86
86
|
},
|
|
87
87
|
"bin": {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { Socket } from 'node:net';
|
|
3
|
-
import {
|
|
4
|
-
import { RouteObject } from 'react-router-dom';
|
|
3
|
+
import { RouteObject, RouterState } from 'react-router';
|
|
5
4
|
import { Alias, ModuleNode } from 'vite';
|
|
6
5
|
import { IRequestContext } from "../node/render.js";
|
|
7
6
|
import { TRoutesTree } from "./parse-routes.js";
|
|
@@ -165,14 +164,14 @@ declare class SsrManifest {
|
|
|
165
164
|
/**
|
|
166
165
|
* Get route assets
|
|
167
166
|
*/
|
|
168
|
-
protected getAssets(routes?:
|
|
167
|
+
protected getAssets(routes?: RouterState['matches']): IAsset[];
|
|
169
168
|
/**
|
|
170
169
|
* Get development route assets
|
|
171
170
|
*/
|
|
172
171
|
/**
|
|
173
172
|
* Get development route assets
|
|
174
173
|
*/
|
|
175
|
-
protected getAssetsDev(routes?:
|
|
174
|
+
protected getAssetsDev(routes?: RouterState['matches']): IAsset[];
|
|
176
175
|
/**
|
|
177
176
|
* Get module assets
|
|
178
177
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-manifest.js","sources":["../../src/services/ssr-manifest.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { Socket } from 'node:net';\nimport path from 'node:path';\nimport type { AgnosticDataRouteMatch } from '@remix-run/router/dist/utils';\nimport chalk from 'chalk';\nimport type { RouteObject } from 'react-router-dom';\nimport type { Alias, ModuleNode } from 'vite';\nimport type { IAsyncRoute } from '@helpers/import-route';\nimport type { IRequestContext } from '@node/render';\nimport type { TRoutesTree } from '@services/parse-routes';\nimport ParseRoutes from '@services/parse-routes';\nimport PathNormalize from '@services/path-normalize';\nimport PrepareServer from '@services/prepare-server';\nimport ServerConfig from '@services/server-config';\n\ninterface ISsrManifestParams {\n buildDir?: string;\n viteAliases?: Alias[];\n basename?: string;\n}\n\ninterface IManifest {\n [path: string]: {\n assets: string[];\n css: string[];\n file: string;\n isEntry?: boolean;\n imports: string[];\n };\n}\n\nenum AssetType {\n style = 'style',\n script = 'script',\n image = 'image',\n font = 'font',\n}\n\ninterface IAsset {\n type: AssetType;\n url: string;\n weight: number;\n isNested: boolean;\n isPreload: boolean;\n content?: string;\n}\n\ntype TAssets = { [id: string]: IAsset };\n\nconst CRLF = '\\r\\n';\n\n/**\n * Working with SSR Manifest file\n */\nclass SsrManifest {\n /**\n * Singleton\n */\n protected static instance: SsrManifest | null = null;\n\n /**\n * Server config\n */\n protected readonly config: ServerConfig;\n\n /**\n * Path normalize service\n */\n protected readonly pathNormalize: PathNormalize;\n\n /**\n * Project root path\n */\n protected readonly root: string;\n\n /**\n * Build dir\n */\n protected readonly buildDir?: string;\n\n /**\n * Client manifest file name\n */\n protected readonly manifestName = 'manifest.json';\n\n /**\n * Assets manifest file name\n */\n protected readonly assetsManifest = 'assets-manifest.json';\n\n /**\n * Vite resolve aliases\n */\n protected readonly viteAliases?: Alias[];\n\n /**\n * Vite base\n */\n protected readonly basename?: string;\n\n /**\n * Loaded assets manifest file\n */\n protected routesAssets: Record<string, IAsset[]> | null = null;\n\n /**\n * @constructor\n */\n protected constructor(\n config: ServerConfig,\n { buildDir, viteAliases, basename }: ISsrManifestParams = {},\n ) {\n this.config = config;\n this.root = config.getParams().root;\n this.buildDir = buildDir;\n this.viteAliases = viteAliases ?? config.getVite()?.config?.resolve.alias;\n this.pathNormalize = new PathNormalize(config, viteAliases);\n this.basename = basename;\n }\n\n /**\n * Get singleton instance\n */\n public static get(config: ServerConfig, params: ISsrManifestParams = {}): SsrManifest {\n if (SsrManifest.instance === null) {\n SsrManifest.instance = new SsrManifest(config, params);\n }\n\n return SsrManifest.instance;\n }\n\n /**\n * Get output dir\n */\n protected getOutDir() {\n return path.resolve(this.root, this.buildDir || '');\n }\n\n /**\n * Get assets manifest file name\n */\n protected getAssetsManifestFile(): string {\n return `${this.getOutDir()}/server/${this.assetsManifest}`;\n }\n\n /**\n * Load client ssr manifest\n */\n protected loadClientManifest(): IManifest {\n const clientManifestDir = path.resolve(this.root, `${this.buildDir || ''}/client/.vite`);\n const clientSsrManifest = `${clientManifestDir}/${this.manifestName}`;\n\n if (!fs.existsSync(clientSsrManifest)) {\n return {};\n }\n\n const result = JSON.parse(\n fs.readFileSync(clientSsrManifest, { encoding: 'utf-8' }),\n ) as IManifest;\n\n fs.rmSync(clientSsrManifest);\n\n // try to remove empty .vite dir\n if (fs.readdirSync(clientManifestDir).length === 0) {\n fs.rmSync(clientManifestDir, { recursive: true });\n }\n\n return result;\n }\n\n /**\n * Load assets manifest\n */\n protected loadAssetsManifest(): Record<string, IAsset[]> {\n if (this.routesAssets !== null) {\n return this.routesAssets;\n }\n\n const manifestFile = this.getAssetsManifestFile();\n\n if (!fs.existsSync(manifestFile)) {\n return {};\n }\n\n this.routesAssets = JSON.parse(fs.readFileSync(manifestFile, { encoding: 'utf-8' })) as Record<\n string,\n IAsset[]\n >;\n\n return this.routesAssets;\n }\n\n /**\n * Recursive walk routes and return id's with route import path\n */\n protected async getAsyncRoutesIds(\n routes: RouteObject[],\n index?: string,\n ): Promise<Record<string, string | undefined>> {\n const result: Record<string, string | undefined> = {};\n\n // reason: await + array index\n // eslint-disable-next-line @typescript-eslint/no-for-in-array\n for (const routeIndex in routes) {\n const route = routes[routeIndex];\n const routeId = [index, routeIndex].filter(Boolean).join('-');\n\n if (route.lazy) {\n try {\n const resolvedRoute: IAsyncRoute = await route.lazy();\n\n result[routeId] = this.pathNormalize.getAppPath(resolvedRoute?.pathId);\n } catch (e) {\n console.error(chalk.red('Failed to load route:'), route.path, e);\n }\n } else if (route.children) {\n Object.assign(result, await this.getAsyncRoutesIds(route.children, routeId));\n }\n }\n\n return result;\n }\n\n /**\n * Same as 'getAsyncRoutesIds' but for routes tree from 'ParseRoutes'\n */\n protected getRoutesTreeIds(\n routes: TRoutesTree[],\n index?: string,\n ): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n\n routes.forEach((route, routeIndex) => {\n const routeId = [index, String(routeIndex)].filter(Boolean).join('-');\n\n if (route.import) {\n result[routeId] = this.pathNormalize.getAppPath(route.import);\n }\n\n if (route.children.length > 0) {\n Object.assign(result, this.getRoutesTreeIds(route.children, routeId));\n }\n });\n\n return result;\n }\n\n /**\n * Sort assets\n */\n protected sortAssets(assets: IAsset[]): IAsset[] {\n return assets.sort((a, b) =>\n a.weight === b.weight ? Number(a.isNested) - Number(b.isNested) : a.weight - b.weight,\n );\n }\n\n /**\n * Get recursive module assets\n */\n protected getRouteAssets(\n manifest: IManifest,\n module: IManifest[string],\n isNested = false,\n ): Record<string, IAsset> {\n const rootAssets = [...(module?.assets ?? []), ...(module?.css ?? []), module?.file];\n\n const assets = rootAssets.reduce(\n (res, asset) => {\n if (asset) {\n const type = this.getAssetType(asset);\n const isEntry = module.isEntry && module.file === asset;\n\n // keep only js,css,image,fonts files\n if (type) {\n res[asset] = {\n url: path.posix.normalize(`${this.basename}/${asset}`),\n weight: isEntry ? 1.9 : this.getAssetWeight(asset),\n type,\n isNested,\n isPreload: !isEntry,\n };\n }\n }\n\n return res;\n },\n {} as Record<string, IAsset>,\n );\n\n // nested assets\n if (module?.imports?.length) {\n module.imports.forEach((nestedAsset) => {\n const nestedModule = manifest[nestedAsset];\n\n if (nestedModule) {\n Object.assign(assets, this.getRouteAssets(manifest, nestedModule, true));\n }\n });\n }\n\n return assets;\n }\n\n /**\n * Build routes manifest file\n */\n public async buildRoutesManifest(isNodeParsing: boolean): Promise<void> {\n const prepareServer = PrepareServer.init(\n ServerConfig.init({ isProd: true }, { root: this.getOutDir() }),\n );\n const manifest = this.loadClientManifest();\n let routesPaths: Record<string, string | undefined>;\n\n if (isNodeParsing) {\n const { routes } = await prepareServer.loadEntrypoint(false);\n\n routesPaths = await this.getAsyncRoutesIds(routes as RouteObject[]);\n } else {\n const routesService = new ParseRoutes(this.config, this.viteAliases);\n\n routesPaths = this.getRoutesTreeIds(routesService.parse());\n }\n\n const postfixes = this.pathNormalize.getImportPostfix();\n const result: Record<string, IAsset[]> = {};\n\n // find route assets\n Object.entries(routesPaths).forEach(([routeId, routePath]) => {\n const routePostfix = postfixes.find((postfix) => {\n const filePath = `${routePath}${postfix}`;\n\n return manifest[filePath] !== undefined;\n });\n const routeFile = `${routePath}${routePostfix || ''}`;\n const routeMeta = manifest[routeFile];\n\n result[routeId] = this.sortAssets(Object.values(this.getRouteAssets(manifest, routeMeta)));\n });\n\n fs.writeFileSync(this.getAssetsManifestFile(), JSON.stringify(result, null, 2), {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Get route assets\n */\n protected getAssets(routes?: AgnosticDataRouteMatch[]): IAsset[] {\n if (this.config.getVite()) {\n return this.getAssetsDev(routes);\n }\n\n const routeIds = routes?.map(({ route }) => route.id).filter(Boolean) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n const routesAssets = this.loadAssetsManifest();\n\n return this.sortAssets(\n routeIds\n .map((routeId) => routesAssets[routeId])\n .flat()\n .filter(Boolean),\n );\n }\n\n /**\n * Get development route assets\n */\n protected getAssetsDev(routes?: AgnosticDataRouteMatch[]): IAsset[] {\n const routeIds =\n (routes\n ?.map(({ route }) => this.pathNormalize.getAppPath((route as IAsyncRoute)?.pathId, true))\n .filter(Boolean) as string[]) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n let assets: TAssets = {};\n const postfixes = this.pathNormalize.getImportPostfix();\n const rootId = path.resolve(\n this.root,\n this.config.getPluginConfig()?.clientFile ?? 'client.ts',\n );\n\n [rootId, ...routeIds].forEach((moduleId) => {\n for (const ext of postfixes) {\n const module = this.config.getVite()?.moduleGraph.getModuleById(`${moduleId}${ext}`);\n\n if (module) {\n assets = { ...assets, ...this.getModuleAssets(module) };\n break;\n }\n }\n });\n\n return Object.values(assets);\n }\n\n /**\n * Get module assets\n */\n protected getModuleAssets(module?: ModuleNode, skipModules: Set<string> = new Set()): TAssets {\n if (!module?.clientImportedModules.size || skipModules.has(module.file!)) {\n return {};\n }\n\n let assets: TAssets = {};\n\n skipModules.add(module.file!);\n\n module.clientImportedModules.forEach((subModule) => {\n const { file, clientImportedModules, transformResult } = subModule;\n const ext = file?.split('.').at(-1);\n\n if (file && ext && ['css', 'scss'].includes(ext)) {\n // @TODO investigate better method?\n const code = transformResult?.code.match(/__vite__css\\s+=\\s+\"(?<css>.+)\"/)?.groups?.css;\n\n if (code) {\n try {\n assets[file] = {\n type: AssetType.style,\n url: file,\n weight: this.getAssetWeight(file),\n content: (JSON.parse(`{\"style\": \"${code}\"}`) as { style: string }).style,\n isNested: Boolean(skipModules.size),\n isPreload: false,\n };\n } catch (e) {\n console.warn(chalk.yellowBright('Failed to parse style: ', file));\n }\n }\n } else if (clientImportedModules.size) {\n assets = {\n ...assets,\n ...this.getModuleAssets(subModule, skipModules),\n };\n }\n });\n\n return assets;\n }\n\n /**\n * Get asset weight\n */\n protected getAssetWeight(asset: string): number {\n const type = this.getAssetType(asset);\n\n switch (type) {\n case AssetType.style:\n return 1;\n\n case AssetType.script:\n return 2;\n\n default:\n return 3;\n }\n }\n\n /**\n * Get asset type\n */\n protected getAssetType(asset: string): AssetType | null {\n const ext = asset.split('.').at(-1)?.toLowerCase();\n\n switch (ext) {\n case 'css':\n case 'scss':\n return AssetType.style;\n\n case 'js':\n return AssetType.script;\n\n case 'svg':\n case 'jpg':\n case 'jpeg':\n case 'png':\n case 'webp':\n case 'gif':\n case 'ico':\n return AssetType.image;\n\n case 'ttf':\n case 'otf':\n case 'woff':\n case 'woff2':\n return AssetType.font;\n\n default:\n return null;\n }\n }\n\n /**\n * Write 103 Early Hits header\n */\n public writeEarlyHits(assets: IAsset[], socket: Socket): void {\n socket.write(`HTTP/1.1 103 Early Hints${CRLF}`);\n assets.forEach(({ type, url }) => {\n if (!type || !['style', 'script'].includes(type)) {\n return;\n }\n\n socket.write(`Link: <${url}>; rel=preload; as=${type}${CRLF}`);\n });\n socket.write(CRLF);\n }\n\n /**\n * Inject route assets to head html\n */\n public injectAssets({ routerContext, html, res, hasEarlyHints = false }: IRequestContext): void {\n const assets = this.getAssets(routerContext?.matches);\n const htmlAssets = assets\n .map(({ type, url, isPreload, content = '' }) => {\n switch (type) {\n case AssetType.style:\n return this.config.getVite()\n ? `<style data-vite-dev-id=\"${url}\">${content}</style>`\n : `<link rel=\"stylesheet\" href=\"${url}\">`;\n\n case AssetType.script:\n return isPreload\n ? this.config.isModulePreload\n ? // can reduce lighthouse performance\n `<link rel=\"modulepreload\" as=\"script\" crossorigin href=\"${url}\">`\n : null\n : `<script async type=\"module\" crossorigin src=\"${url}\"></script>`;\n }\n\n return null;\n })\n .filter(Boolean);\n\n html.header = html.header.replace('</head>', `${htmlAssets.join('\\n')}</head>`);\n\n if (hasEarlyHints && htmlAssets.length && res.socket) {\n this.writeEarlyHits(assets, res.socket);\n }\n }\n}\n\nexport default SsrManifest;\n"],"names":["AssetType","CRLF","SsrManifest","static","config","pathNormalize","root","buildDir","manifestName","assetsManifest","viteAliases","basename","routesAssets","constructor","this","getParams","getVite","resolve","alias","PathNormalize","params","instance","getOutDir","path","getAssetsManifestFile","loadClientManifest","clientManifestDir","clientSsrManifest","fs","existsSync","result","JSON","parse","readFileSync","encoding","rmSync","readdirSync","length","recursive","loadAssetsManifest","manifestFile","async","routes","index","routeIndex","route","routeId","filter","Boolean","join","lazy","resolvedRoute","getAppPath","pathId","e","console","error","chalk","red","children","Object","assign","getAsyncRoutesIds","getRoutesTreeIds","forEach","String","import","sortAssets","assets","sort","a","b","weight","Number","isNested","getRouteAssets","manifest","module","css","file","reduce","res","asset","type","getAssetType","isEntry","url","posix","normalize","getAssetWeight","isPreload","imports","nestedAsset","nestedModule","isNodeParsing","prepareServer","PrepareServer","init","ServerConfig","isProd","routesPaths","loadEntrypoint","routesService","ParseRoutes","postfixes","getImportPostfix","entries","routePath","routePostfix","find","postfix","undefined","routeMeta","values","writeFileSync","stringify","getAssets","getAssetsDev","routeIds","map","id","flat","getPluginConfig","clientFile","moduleId","ext","moduleGraph","getModuleById","getModuleAssets","skipModules","Set","clientImportedModules","size","has","add","subModule","transformResult","split","at","includes","code","match","groups","style","content","warn","yellowBright","script","toLowerCase","image","font","writeEarlyHits","socket","write","injectAssets","routerContext","html","hasEarlyHints","matches","htmlAssets","isModulePreload","header","replace"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ssr-manifest.js","sources":["../../src/services/ssr-manifest.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { Socket } from 'node:net';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport type { RouteObject, RouterState } from 'react-router';\nimport type { Alias, ModuleNode } from 'vite';\nimport type { IAsyncRoute } from '@helpers/import-route';\nimport type { IRequestContext } from '@node/render';\nimport type { TRoutesTree } from '@services/parse-routes';\nimport ParseRoutes from '@services/parse-routes';\nimport PathNormalize from '@services/path-normalize';\nimport PrepareServer from '@services/prepare-server';\nimport ServerConfig from '@services/server-config';\n\ninterface ISsrManifestParams {\n buildDir?: string;\n viteAliases?: Alias[];\n basename?: string;\n}\n\ninterface IManifest {\n [path: string]: {\n assets: string[];\n css: string[];\n file: string;\n isEntry?: boolean;\n imports: string[];\n };\n}\n\nenum AssetType {\n style = 'style',\n script = 'script',\n image = 'image',\n font = 'font',\n}\n\ninterface IAsset {\n type: AssetType;\n url: string;\n weight: number;\n isNested: boolean;\n isPreload: boolean;\n content?: string;\n}\n\ntype TAssets = { [id: string]: IAsset };\n\nconst CRLF = '\\r\\n';\n\n/**\n * Working with SSR Manifest file\n */\nclass SsrManifest {\n /**\n * Singleton\n */\n protected static instance: SsrManifest | null = null;\n\n /**\n * Server config\n */\n protected readonly config: ServerConfig;\n\n /**\n * Path normalize service\n */\n protected readonly pathNormalize: PathNormalize;\n\n /**\n * Project root path\n */\n protected readonly root: string;\n\n /**\n * Build dir\n */\n protected readonly buildDir?: string;\n\n /**\n * Client manifest file name\n */\n protected readonly manifestName = 'manifest.json';\n\n /**\n * Assets manifest file name\n */\n protected readonly assetsManifest = 'assets-manifest.json';\n\n /**\n * Vite resolve aliases\n */\n protected readonly viteAliases?: Alias[];\n\n /**\n * Vite base\n */\n protected readonly basename?: string;\n\n /**\n * Loaded assets manifest file\n */\n protected routesAssets: Record<string, IAsset[]> | null = null;\n\n /**\n * @constructor\n */\n protected constructor(\n config: ServerConfig,\n { buildDir, viteAliases, basename }: ISsrManifestParams = {},\n ) {\n this.config = config;\n this.root = config.getParams().root;\n this.buildDir = buildDir;\n this.viteAliases = viteAliases ?? config.getVite()?.config?.resolve.alias;\n this.pathNormalize = new PathNormalize(config, viteAliases);\n this.basename = basename;\n }\n\n /**\n * Get singleton instance\n */\n public static get(config: ServerConfig, params: ISsrManifestParams = {}): SsrManifest {\n if (SsrManifest.instance === null) {\n SsrManifest.instance = new SsrManifest(config, params);\n }\n\n return SsrManifest.instance;\n }\n\n /**\n * Get output dir\n */\n protected getOutDir() {\n return path.resolve(this.root, this.buildDir || '');\n }\n\n /**\n * Get assets manifest file name\n */\n protected getAssetsManifestFile(): string {\n return `${this.getOutDir()}/server/${this.assetsManifest}`;\n }\n\n /**\n * Load client ssr manifest\n */\n protected loadClientManifest(): IManifest {\n const clientManifestDir = path.resolve(this.root, `${this.buildDir || ''}/client/.vite`);\n const clientSsrManifest = `${clientManifestDir}/${this.manifestName}`;\n\n if (!fs.existsSync(clientSsrManifest)) {\n return {};\n }\n\n const result = JSON.parse(\n fs.readFileSync(clientSsrManifest, { encoding: 'utf-8' }),\n ) as IManifest;\n\n fs.rmSync(clientSsrManifest);\n\n // try to remove empty .vite dir\n if (fs.readdirSync(clientManifestDir).length === 0) {\n fs.rmSync(clientManifestDir, { recursive: true });\n }\n\n return result;\n }\n\n /**\n * Load assets manifest\n */\n protected loadAssetsManifest(): Record<string, IAsset[]> {\n if (this.routesAssets !== null) {\n return this.routesAssets;\n }\n\n const manifestFile = this.getAssetsManifestFile();\n\n if (!fs.existsSync(manifestFile)) {\n return {};\n }\n\n this.routesAssets = JSON.parse(fs.readFileSync(manifestFile, { encoding: 'utf-8' })) as Record<\n string,\n IAsset[]\n >;\n\n return this.routesAssets;\n }\n\n /**\n * Recursive walk routes and return id's with route import path\n */\n protected async getAsyncRoutesIds(\n routes: RouteObject[],\n index?: string,\n ): Promise<Record<string, string | undefined>> {\n const result: Record<string, string | undefined> = {};\n\n // reason: await + array index\n // eslint-disable-next-line @typescript-eslint/no-for-in-array\n for (const routeIndex in routes) {\n const route = routes[routeIndex];\n const routeId = [index, routeIndex].filter(Boolean).join('-');\n\n if (route.lazy) {\n try {\n const resolvedRoute: IAsyncRoute = await route.lazy();\n\n result[routeId] = this.pathNormalize.getAppPath(resolvedRoute?.pathId);\n } catch (e) {\n console.error(chalk.red('Failed to load route:'), route.path, e);\n }\n } else if (route.children) {\n Object.assign(result, await this.getAsyncRoutesIds(route.children, routeId));\n }\n }\n\n return result;\n }\n\n /**\n * Same as 'getAsyncRoutesIds' but for routes tree from 'ParseRoutes'\n */\n protected getRoutesTreeIds(\n routes: TRoutesTree[],\n index?: string,\n ): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n\n routes.forEach((route, routeIndex) => {\n const routeId = [index, String(routeIndex)].filter(Boolean).join('-');\n\n if (route.import) {\n result[routeId] = this.pathNormalize.getAppPath(route.import);\n }\n\n if (route.children.length > 0) {\n Object.assign(result, this.getRoutesTreeIds(route.children, routeId));\n }\n });\n\n return result;\n }\n\n /**\n * Sort assets\n */\n protected sortAssets(assets: IAsset[]): IAsset[] {\n return assets.sort((a, b) =>\n a.weight === b.weight ? Number(a.isNested) - Number(b.isNested) : a.weight - b.weight,\n );\n }\n\n /**\n * Get recursive module assets\n */\n protected getRouteAssets(\n manifest: IManifest,\n module: IManifest[string],\n isNested = false,\n ): Record<string, IAsset> {\n const rootAssets = [...(module?.assets ?? []), ...(module?.css ?? []), module?.file];\n\n const assets = rootAssets.reduce(\n (res, asset) => {\n if (asset) {\n const type = this.getAssetType(asset);\n const isEntry = module.isEntry && module.file === asset;\n\n // keep only js,css,image,fonts files\n if (type) {\n res[asset] = {\n url: path.posix.normalize(`${this.basename}/${asset}`),\n weight: isEntry ? 1.9 : this.getAssetWeight(asset),\n type,\n isNested,\n isPreload: !isEntry,\n };\n }\n }\n\n return res;\n },\n {} as Record<string, IAsset>,\n );\n\n // nested assets\n if (module?.imports?.length) {\n module.imports.forEach((nestedAsset) => {\n const nestedModule = manifest[nestedAsset];\n\n if (nestedModule) {\n Object.assign(assets, this.getRouteAssets(manifest, nestedModule, true));\n }\n });\n }\n\n return assets;\n }\n\n /**\n * Build routes manifest file\n */\n public async buildRoutesManifest(isNodeParsing: boolean): Promise<void> {\n const prepareServer = PrepareServer.init(\n ServerConfig.init({ isProd: true }, { root: this.getOutDir() }),\n );\n const manifest = this.loadClientManifest();\n let routesPaths: Record<string, string | undefined>;\n\n if (isNodeParsing) {\n const { routes } = await prepareServer.loadEntrypoint(false);\n\n routesPaths = await this.getAsyncRoutesIds(routes as RouteObject[]);\n } else {\n const routesService = new ParseRoutes(this.config, this.viteAliases);\n\n routesPaths = this.getRoutesTreeIds(routesService.parse());\n }\n\n const postfixes = this.pathNormalize.getImportPostfix();\n const result: Record<string, IAsset[]> = {};\n\n // find route assets\n Object.entries(routesPaths).forEach(([routeId, routePath]) => {\n const routePostfix = postfixes.find((postfix) => {\n const filePath = `${routePath}${postfix}`;\n\n return manifest[filePath] !== undefined;\n });\n const routeFile = `${routePath}${routePostfix || ''}`;\n const routeMeta = manifest[routeFile];\n\n result[routeId] = this.sortAssets(Object.values(this.getRouteAssets(manifest, routeMeta)));\n });\n\n fs.writeFileSync(this.getAssetsManifestFile(), JSON.stringify(result, null, 2), {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Get route assets\n */\n protected getAssets(routes?: RouterState['matches']): IAsset[] {\n if (this.config.getVite()) {\n return this.getAssetsDev(routes);\n }\n\n const routeIds = routes?.map(({ route }) => route.id).filter(Boolean) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n const routesAssets = this.loadAssetsManifest();\n\n return this.sortAssets(\n routeIds\n .map((routeId) => routesAssets[routeId])\n .flat()\n .filter(Boolean),\n );\n }\n\n /**\n * Get development route assets\n */\n protected getAssetsDev(routes?: RouterState['matches']): IAsset[] {\n const routeIds =\n (routes\n ?.map(({ route }) => this.pathNormalize.getAppPath((route as IAsyncRoute)?.pathId, true))\n .filter(Boolean) as string[]) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n let assets: TAssets = {};\n const postfixes = this.pathNormalize.getImportPostfix();\n const rootId = path.resolve(\n this.root,\n this.config.getPluginConfig()?.clientFile ?? 'client.ts',\n );\n\n [rootId, ...routeIds].forEach((moduleId) => {\n for (const ext of postfixes) {\n const module = this.config.getVite()?.moduleGraph.getModuleById(`${moduleId}${ext}`);\n\n if (module) {\n assets = { ...assets, ...this.getModuleAssets(module) };\n break;\n }\n }\n });\n\n return Object.values(assets);\n }\n\n /**\n * Get module assets\n */\n protected getModuleAssets(module?: ModuleNode, skipModules: Set<string> = new Set()): TAssets {\n if (!module?.clientImportedModules.size || skipModules.has(module.file!)) {\n return {};\n }\n\n let assets: TAssets = {};\n\n skipModules.add(module.file!);\n\n module.clientImportedModules.forEach((subModule) => {\n const { file, clientImportedModules, transformResult } = subModule;\n const ext = file?.split('.').at(-1);\n\n if (file && ext && ['css', 'scss'].includes(ext)) {\n // @TODO investigate better method?\n const code = transformResult?.code.match(/__vite__css\\s+=\\s+\"(?<css>.+)\"/)?.groups?.css;\n\n if (code) {\n try {\n assets[file] = {\n type: AssetType.style,\n url: file,\n weight: this.getAssetWeight(file),\n content: (JSON.parse(`{\"style\": \"${code}\"}`) as { style: string }).style,\n isNested: Boolean(skipModules.size),\n isPreload: false,\n };\n } catch (e) {\n console.warn(chalk.yellowBright('Failed to parse style: ', file));\n }\n }\n } else if (clientImportedModules.size) {\n assets = {\n ...assets,\n ...this.getModuleAssets(subModule, skipModules),\n };\n }\n });\n\n return assets;\n }\n\n /**\n * Get asset weight\n */\n protected getAssetWeight(asset: string): number {\n const type = this.getAssetType(asset);\n\n switch (type) {\n case AssetType.style:\n return 1;\n\n case AssetType.script:\n return 2;\n\n default:\n return 3;\n }\n }\n\n /**\n * Get asset type\n */\n protected getAssetType(asset: string): AssetType | null {\n const ext = asset.split('.').at(-1)?.toLowerCase();\n\n switch (ext) {\n case 'css':\n case 'scss':\n return AssetType.style;\n\n case 'js':\n return AssetType.script;\n\n case 'svg':\n case 'jpg':\n case 'jpeg':\n case 'png':\n case 'webp':\n case 'gif':\n case 'ico':\n return AssetType.image;\n\n case 'ttf':\n case 'otf':\n case 'woff':\n case 'woff2':\n return AssetType.font;\n\n default:\n return null;\n }\n }\n\n /**\n * Write 103 Early Hits header\n */\n public writeEarlyHits(assets: IAsset[], socket: Socket): void {\n socket.write(`HTTP/1.1 103 Early Hints${CRLF}`);\n assets.forEach(({ type, url }) => {\n if (!type || !['style', 'script'].includes(type)) {\n return;\n }\n\n socket.write(`Link: <${url}>; rel=preload; as=${type}${CRLF}`);\n });\n socket.write(CRLF);\n }\n\n /**\n * Inject route assets to head html\n */\n public injectAssets({ routerContext, html, res, hasEarlyHints = false }: IRequestContext): void {\n const assets = this.getAssets(routerContext?.matches);\n const htmlAssets = assets\n .map(({ type, url, isPreload, content = '' }) => {\n switch (type) {\n case AssetType.style:\n return this.config.getVite()\n ? `<style data-vite-dev-id=\"${url}\">${content}</style>`\n : `<link rel=\"stylesheet\" href=\"${url}\">`;\n\n case AssetType.script:\n return isPreload\n ? this.config.isModulePreload\n ? // can reduce lighthouse performance\n `<link rel=\"modulepreload\" as=\"script\" crossorigin href=\"${url}\">`\n : null\n : `<script async type=\"module\" crossorigin src=\"${url}\"></script>`;\n }\n\n return null;\n })\n .filter(Boolean);\n\n html.header = html.header.replace('</head>', `${htmlAssets.join('\\n')}</head>`);\n\n if (hasEarlyHints && htmlAssets.length && res.socket) {\n this.writeEarlyHits(assets, res.socket);\n }\n }\n}\n\nexport default SsrManifest;\n"],"names":["AssetType","CRLF","SsrManifest","static","config","pathNormalize","root","buildDir","manifestName","assetsManifest","viteAliases","basename","routesAssets","constructor","this","getParams","getVite","resolve","alias","PathNormalize","params","instance","getOutDir","path","getAssetsManifestFile","loadClientManifest","clientManifestDir","clientSsrManifest","fs","existsSync","result","JSON","parse","readFileSync","encoding","rmSync","readdirSync","length","recursive","loadAssetsManifest","manifestFile","async","routes","index","routeIndex","route","routeId","filter","Boolean","join","lazy","resolvedRoute","getAppPath","pathId","e","console","error","chalk","red","children","Object","assign","getAsyncRoutesIds","getRoutesTreeIds","forEach","String","import","sortAssets","assets","sort","a","b","weight","Number","isNested","getRouteAssets","manifest","module","css","file","reduce","res","asset","type","getAssetType","isEntry","url","posix","normalize","getAssetWeight","isPreload","imports","nestedAsset","nestedModule","isNodeParsing","prepareServer","PrepareServer","init","ServerConfig","isProd","routesPaths","loadEntrypoint","routesService","ParseRoutes","postfixes","getImportPostfix","entries","routePath","routePostfix","find","postfix","undefined","routeMeta","values","writeFileSync","stringify","getAssets","getAssetsDev","routeIds","map","id","flat","getPluginConfig","clientFile","moduleId","ext","moduleGraph","getModuleById","getModuleAssets","skipModules","Set","clientImportedModules","size","has","add","subModule","transformResult","split","at","includes","code","match","groups","style","content","warn","yellowBright","script","toLowerCase","image","font","writeEarlyHits","socket","write","injectAssets","routerContext","html","hasEarlyHints","matches","htmlAssets","isModulePreload","header","replace"],"mappings":"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"}
|