@lomray/vite-ssr-boost 3.3.3 → 3.3.4-beta.2
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/README.md +145 -2
- package/components/navigate.js +1 -1
- package/components/navigate.js.map +1 -1
- package/context/server.d.ts +3 -2
- package/context/server.js.map +1 -1
- package/node/entry.d.ts +5 -3
- package/node/entry.js +1 -1
- package/node/entry.js.map +1 -1
- package/node/render.js +1 -1
- package/node/render.js.map +1 -1
- package/node/server.js +1 -1
- package/node/server.js.map +1 -1
- package/package.json +1 -1
- package/services/build.js +1 -1
- package/services/build.js.map +1 -1
- package/services/prepare-server.js +1 -1
- package/services/prepare-server.js.map +1 -1
- package/services/ssr-manifest.d.ts +6 -1
- package/services/ssr-manifest.js +1 -1
- package/services/ssr-manifest.js.map +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- Very easy to migrate, very easy to use.
|
|
16
16
|
- All the power of [vite](https://vitejs.dev/)⚡
|
|
17
17
|
- All the power of [react-router](https://reactrouter.com/)🛣
|
|
18
|
+
- Easy-peasy develop for [Capacitor](https://capacitorjs.com/)
|
|
18
19
|
|
|
19
20
|
<p align="center">
|
|
20
21
|
<img src="https://sonarcloud.io/api/project_badges/measure?project=vite-ssr-boost&metric=reliability_rating" alt="reliability">
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
- [Useful imports](#useful-imports)
|
|
36
37
|
- [CLI](#cli)
|
|
37
38
|
- [Warning](#warning)
|
|
39
|
+
- [Use Cases](#use-cases)
|
|
38
40
|
- [Bugs and feature requests](#bugs-and-feature-requests)
|
|
39
41
|
- [License](#license)
|
|
40
42
|
|
|
@@ -92,9 +94,22 @@ import routes from './routes';
|
|
|
92
94
|
|
|
93
95
|
void entryClient(App, routes, {
|
|
94
96
|
/**
|
|
95
|
-
* Client configuration
|
|
97
|
+
* (optional). Client configuration
|
|
96
98
|
*/
|
|
97
|
-
init: () => {}
|
|
99
|
+
init: () => {},
|
|
100
|
+
/**
|
|
101
|
+
* (optional). Configure router options
|
|
102
|
+
* @see createBrowserRouter second arg
|
|
103
|
+
*/
|
|
104
|
+
routerOptions: {},
|
|
105
|
+
/**
|
|
106
|
+
* (optional). Customization create router function, e.g. use from sentry one
|
|
107
|
+
*/
|
|
108
|
+
createRouter: createBrowserRouter,
|
|
109
|
+
/**
|
|
110
|
+
* (optional). Change root id
|
|
111
|
+
*/
|
|
112
|
+
rootId: 'root',
|
|
98
113
|
});
|
|
99
114
|
```
|
|
100
115
|
|
|
@@ -122,6 +137,10 @@ export default entryServer(App, routes, {
|
|
|
122
137
|
* E.g. use for configure express middlewares
|
|
123
138
|
*/
|
|
124
139
|
onServerCreated: () => {},
|
|
140
|
+
/**
|
|
141
|
+
* (optional). Called once after express server started.
|
|
142
|
+
*/
|
|
143
|
+
onServerStarted: () => {},
|
|
125
144
|
/**
|
|
126
145
|
* (optional). Called on each incoming request.
|
|
127
146
|
* E.g. configure request state, create state manager etc.
|
|
@@ -142,12 +161,44 @@ export default entryServer(App, routes, {
|
|
|
142
161
|
* E.g. here you can add some payload like custom state (any manager state) to response.
|
|
143
162
|
*/
|
|
144
163
|
onResponse: () => {},
|
|
164
|
+
/**
|
|
165
|
+
* (optional). Stream error callback. Catch stream errors.
|
|
166
|
+
*/
|
|
167
|
+
onError: () => {},
|
|
145
168
|
/**
|
|
146
169
|
* (optional). Called when application shell or all html (depends on stream option) is ready to send on client.
|
|
147
170
|
* E.g. here you can send any context or state to client.
|
|
148
171
|
*/
|
|
149
172
|
getState: () => {},
|
|
150
173
|
}),
|
|
174
|
+
/**
|
|
175
|
+
* (optional). Customize production log handler
|
|
176
|
+
* @see @lomray/vite-ssr-boost/services/logger
|
|
177
|
+
*/
|
|
178
|
+
loggerProd: new Logger(),
|
|
179
|
+
/**
|
|
180
|
+
* (optional). Customize development log handler
|
|
181
|
+
* @see @lomray/vite-ssr-boost/services/logger
|
|
182
|
+
*/
|
|
183
|
+
loggerDev: new Logger(),
|
|
184
|
+
/**
|
|
185
|
+
* (optional). Configure pre-builded middlewares
|
|
186
|
+
*/
|
|
187
|
+
middlewares: {
|
|
188
|
+
/**
|
|
189
|
+
* @see CompressionOptions from 'compression' package
|
|
190
|
+
*/
|
|
191
|
+
compression: {},
|
|
192
|
+
/**
|
|
193
|
+
* @see ServeStaticOptions from 'serve-static' package
|
|
194
|
+
*/
|
|
195
|
+
expressStatic: {},
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* (optional). Configure router static handler options
|
|
199
|
+
* @see createStaticHandler second arg
|
|
200
|
+
*/
|
|
201
|
+
routerOptions: {},
|
|
151
202
|
});
|
|
152
203
|
```
|
|
153
204
|
|
|
@@ -198,6 +249,19 @@ SsrBoost({
|
|
|
198
249
|
* Add tsconfig aliases to vite config aliases
|
|
199
250
|
*/
|
|
200
251
|
tsconfigAliases: true, // default: true
|
|
252
|
+
/**
|
|
253
|
+
* Path contains routes declaration files (need to detect route files).
|
|
254
|
+
*/
|
|
255
|
+
routesPath: undefined, //default: undefined
|
|
256
|
+
/**
|
|
257
|
+
* Create additional SPA entrypoint: index-spa.html
|
|
258
|
+
* Can be used for service worker: createHandlerBoundToURL("index-spa.html")
|
|
259
|
+
*/
|
|
260
|
+
spaIndex: false, // default: false
|
|
261
|
+
/**
|
|
262
|
+
* Additional entry points for build
|
|
263
|
+
*/
|
|
264
|
+
entrypoint: [], // default: undefined
|
|
201
265
|
})
|
|
202
266
|
```
|
|
203
267
|
|
|
@@ -268,6 +332,85 @@ const routes: RouteObject[] = [
|
|
|
268
332
|
}
|
|
269
333
|
];
|
|
270
334
|
```
|
|
335
|
+
## Use Cases
|
|
336
|
+
|
|
337
|
+
### Change `basename`
|
|
338
|
+
```typescript jsx
|
|
339
|
+
/**
|
|
340
|
+
* Configure client
|
|
341
|
+
*/
|
|
342
|
+
void entryClient(App, routes, {
|
|
343
|
+
routerOptions: {
|
|
344
|
+
basename: '/custom',
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
```typescript jsx
|
|
349
|
+
/**
|
|
350
|
+
* Configure server
|
|
351
|
+
*/
|
|
352
|
+
export default entryServer(App, routes, {
|
|
353
|
+
routerOptions: {
|
|
354
|
+
basename: '/custom',
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Change `base` for static assets
|
|
360
|
+
```typescript
|
|
361
|
+
// https://vitejs.dev/config/
|
|
362
|
+
export default defineConfig({
|
|
363
|
+
base: '/static',
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
```typescript jsx
|
|
368
|
+
/**
|
|
369
|
+
* Configure server
|
|
370
|
+
* NOTE: 'basename' should be equal to 'base' from vite config
|
|
371
|
+
*/
|
|
372
|
+
export default entryServer(App, routes, {
|
|
373
|
+
middlewares: {
|
|
374
|
+
expressStatic: {
|
|
375
|
+
basename: '/static',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
### [Capacitor](https://capacitorjs.com/) additional endpoint
|
|
383
|
+
```typescript
|
|
384
|
+
// https://vitejs.dev/config/
|
|
385
|
+
export default defineConfig({
|
|
386
|
+
plugins: [
|
|
387
|
+
SsrBoost({
|
|
388
|
+
entrypoint: [
|
|
389
|
+
{
|
|
390
|
+
name: 'mobile',
|
|
391
|
+
type: 'spa',
|
|
392
|
+
clientFile: './src/mobile.tsx',
|
|
393
|
+
buildOptions: '--mode mobile',
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
}),
|
|
397
|
+
react(),
|
|
398
|
+
],
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
```typescript jsx
|
|
402
|
+
const AppMobile: FC = (props) => {
|
|
403
|
+
// some mobile logic
|
|
404
|
+
|
|
405
|
+
return <App {...props} />;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* src/mobile.tsx
|
|
410
|
+
*/
|
|
411
|
+
void entryClient(AppMobile, routes, {});
|
|
412
|
+
```
|
|
413
|
+
|
|
271
414
|
|
|
272
415
|
## Bugs and feature requests
|
|
273
416
|
|
package/components/navigate.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"react";import{Navigate as t}from"react-router-dom";import{useServerContext as r}from"../context/server.js";const o=({to:o,status:s=301,...a})=>{const n=r();if(!n.isServer)return e.createElement(t,{to:o,...a});if(n){const
|
|
1
|
+
import e from"react";import{Navigate as t}from"react-router-dom";import{useServerContext as r}from"../context/server.js";const o=({to:o,status:s=301,...a})=>{const n=r();if(!n.isServer)return e.createElement(t,{to:o,...a});if(n){const{basename:e}=n,t="string"==typeof o?[e,o].filter(Boolean).join(""):[e,o.pathname,o.search,o.hash].filter(Boolean).join("");n.response=new Response("",{status:s,headers:new Headers({Location:t})})}return null};export{o 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-dom';\nimport { Navigate as DefaultNavigate } from 'react-router-dom';\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 location =\n typeof to === 'string'
|
|
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-dom';\nimport { Navigate as DefaultNavigate } from 'react-router-dom';\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 =\n typeof to === 'string'\n ? [basename, to].filter(Boolean).join('')\n : [basename, to.pathname, to.search, to.hash].filter(Boolean).join('');\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","filter","Boolean","join","pathname","search","hash","response","Response","headers","Headers","Location"],"mappings":"yHAgBA,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,EACU,iBAAPV,EACH,CAACS,EAAUT,GAAIW,OAAOC,SAASC,KAAK,IACpC,CAACJ,EAAUT,EAAGc,SAAUd,EAAGe,OAAQf,EAAGgB,MAAML,OAAOC,SAASC,KAAK,IAEvEV,EAAQc,SAAW,IAAIC,SAAS,GAAI,CAAEjB,SAAQkB,QAAS,IAAIC,QAAQ,CAAEC,SAAUX,KAChF,CAED,OAAO,IAAI"}
|
package/context/server.d.ts
CHANGED
|
@@ -4,13 +4,14 @@ import { FC, PropsWithChildren } from "react";
|
|
|
4
4
|
interface IServerContext {
|
|
5
5
|
response: Response | null;
|
|
6
6
|
isServer: boolean;
|
|
7
|
+
basename?: string;
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
10
|
* Server application context
|
|
10
11
|
*/
|
|
11
12
|
declare const ServerContext: React.Context<IServerContext>;
|
|
12
13
|
interface IServerProvider {
|
|
13
|
-
context:
|
|
14
|
+
context: IServerContext;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Server application context provider
|
|
@@ -18,4 +19,4 @@ interface IServerProvider {
|
|
|
18
19
|
*/
|
|
19
20
|
declare const ServerProvider: FC<PropsWithChildren<IServerProvider>>;
|
|
20
21
|
declare const useServerContext: () => IServerContext;
|
|
21
|
-
export { IServerContext,
|
|
22
|
+
export { IServerContext, ServerContext, ServerProvider, useServerContext };
|
package/context/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../../src/context/server.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport React, { useContext } from 'react';\n\nexport interface IServerContext {\n response: Response | null;\n isServer: boolean;\n}\n\nconst initState = {\n response: null,\n isServer: false,\n};\n\n/**\n * Server application context\n */\nconst ServerContext = React.createContext<IServerContext>(initState);\n\
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../../src/context/server.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport React, { useContext } from 'react';\n\nexport interface IServerContext {\n response: Response | null;\n isServer: boolean;\n basename?: string;\n}\n\nconst initState = {\n response: null,\n isServer: false,\n};\n\n/**\n * Server application context\n */\nconst ServerContext = React.createContext<IServerContext>(initState);\n\ninterface IServerProvider {\n context: IServerContext;\n}\n\n/**\n * Server application context provider\n * @constructor\n */\nconst ServerProvider: FC<PropsWithChildren<IServerProvider>> = ({ children, context }) => (\n <ServerContext.Provider value={context} children={children} />\n);\n\nconst useServerContext = (): IServerContext => useContext(ServerContext);\n\nexport { ServerContext, ServerProvider, useServerContext };\n"],"names":["ServerContext","React","createContext","response","isServer","ServerProvider","children","context","createElement","Provider","value","useServerContext","useContext"],"mappings":"sCASA,MAQMA,EAAgBC,EAAMC,cARV,CAChBC,SAAU,KACVC,UAAU,IAgBNC,EAAyD,EAAGC,WAAUC,aAC1EN,EAAAO,cAACR,EAAcS,SAAS,CAAAC,MAAOH,EAASD,SAAUA,IAG9CK,EAAmB,IAAsBC,EAAWZ"}
|
package/node/entry.d.ts
CHANGED
|
@@ -36,7 +36,9 @@ interface IPrepareRenderOut<TAppProps = Record<string, any>> {
|
|
|
36
36
|
loggerDev?: Logger;
|
|
37
37
|
middlewares?: {
|
|
38
38
|
compression?: CompressionOptions | false;
|
|
39
|
-
expressStatic?: ServeStaticOptions
|
|
39
|
+
expressStatic?: (ServeStaticOptions & {
|
|
40
|
+
basename?: string;
|
|
41
|
+
}) | false;
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
interface IAppServerProps<T = Record<string, any>> {
|
|
@@ -51,10 +53,10 @@ interface IEntryServerOptions<TAppProps = Record<string, any>> {
|
|
|
51
53
|
loggerProd?: IPrepareRenderOut['loggerProd'];
|
|
52
54
|
loggerDev?: IPrepareRenderOut['loggerDev'];
|
|
53
55
|
middlewares?: IPrepareRenderOut['middlewares'];
|
|
54
|
-
|
|
56
|
+
routerOptions?: Parameters<typeof createStaticHandler>[1];
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
59
|
* Render server side application
|
|
58
60
|
*/
|
|
59
|
-
declare function entry<TAppProps>(App: TApp<TAppProps>, routes: TRouteObject[], { init,
|
|
61
|
+
declare function entry<TAppProps>(App: TApp<TAppProps>, routes: TRouteObject[], { init, routerOptions, ...rest }?: IEntryServerOptions<TAppProps>): IPrepareRenderOut<TAppProps>;
|
|
60
62
|
export { entry as default, IInitServerRequestOut, IEntrypointOptions, IPrepareRenderOut, IAppServerProps, TApp, IEntryServerOptions };
|
package/node/entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createStaticHandler as r}from"react-router-dom/server.mjs";import
|
|
1
|
+
import{createStaticHandler as r}from"react-router-dom/server.mjs";import e from"./render.js";function t(t,n,{init:o,routerOptions:i,...s}={}){const u=r(n,i);return{render:e.bind(null,{handler:u,App:t}),init:o,routes:n,...s}}export{t 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-dom';\nimport { createStaticHandler } from 'react-router-dom/server.mjs';\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 expressStatic?: ServeStaticOptions | 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
|
|
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-dom';\nimport { createStaticHandler } from 'react-router-dom/server.mjs';\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":"6FAqEA,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.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-dom/server.mjs";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:v}=h,
|
|
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-dom/server.mjs";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:v}=h,b=c(j);h.routerContext=await u.query(b,{requestContext:h});const w=m(v,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=v.write.bind(v),B=f.getLogger();let $;v.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>`;v.status(500),v.setHeader("content-type","text/html"),v.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 type { StaticHandler } from '@remix-run/router';\nimport chalk from 'chalk';\nimport type { Request, Response as ExpressResponse } from 'express';\nimport React from 'react';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport type { StaticHandlerContext } from 'react-router-dom/server';\nimport { createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.mjs';\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 = {
|
|
1
|
+
{"version":3,"file":"render.js","sources":["../../src/node/render.tsx"],"sourcesContent":["import type { StaticHandler } from '@remix-run/router';\nimport chalk from 'chalk';\nimport type { Request, Response as ExpressResponse } from 'express';\nimport React from 'react';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport type { StaticHandlerContext } from 'react-router-dom/server';\nimport { createStaticRouter, StaticRouterProvider } from 'react-router-dom/server.mjs';\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":"sfA0EAA,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/node/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:http";import r from"node:https";import t from"path";import s from"compression";import o from"express";import i from"../helpers/print-server-info.js";import a from"../services/prepare-server.js";import n from"../services/server-api.js";async function p(p){const
|
|
1
|
+
import e from"node:http";import r from"node:https";import t from"path";import s from"compression";import o from"express";import i from"../helpers/print-server-info.js";import a from"../services/prepare-server.js";import n from"../services/server-api.js";async function p(p){const m=o().disable("x-powered-by"),c=new n;p.setApp(m);const d=a.init(p,c);if(!p.isProd){const e=await(await import("vite")).createServer({server:{middlewareMode:!0,watch:{usePolling:!0,interval:100}},appType:"custom",mode:p.mode});m.use(e.middlewares),p.setVite(e)}const{isSPA:l}=p.getParams();if(l||await d.onAppCreated(),p.isProd){const{root:e,publicDir:r}=p.getParams(),{compression:i,expressStatic:a}=d.getMiddlewaresConfig();if(i&&m.use(s(i)),l||m.use(((e,r,t)=>{"/index.html"!==e.url||c.hasAccessIndexHtml()||(e.url="/index-not-found.html"),t()})),a){const{basename:s,...i}=a;m.use(s,o.static(t.resolve(`${e}/${r}`),{...i,index:!!l&&void 0}))}}return l?m.use("*",((e,r,t)=>{(async()=>{try{const t=(await d.loadHtml(e)).join("");r.send(t)}catch(e){p.getLogger().error(`Failed to handle request: ${e?.message}`,{error:e}),t()}})()})):m.use("*",((e,r,t)=>{(async()=>{try{const[{render:s,onRequest:o,...i},a]=await Promise.all([d.loadEntrypoint(),d.loadHtml(e)]),{appProps:n,hasEarlyHints:m,shouldSkip:c}=await(o?.(e,r))??{},[l,f]=a;if(c)return t();const u={req:e,res:r,hasEarlyHints:m,appProps:n??{},html:{header:l,footer:f}};await s(p,u,i)}catch(e){p.getLogger().error(`Failed to handle request: ${e?.message}`,{error:e}),t()}})()})),{run:({version:t,isPrintInfo:s=!0}={})=>{const{port:o,host:a}=p.getParams(),n=Boolean(p.getVite()?.config?.server?.https);p.isHost&&!p.isProd&&(p.getVite().config.server.host=a);const l=(n?r.createServer(p.getVite().config.server.https,m):e.createServer(m)).listen(o,a,(()=>{d.onServerStarted?.(m,c,l),s&&i(p,{version:t,server:l})}));return l},app:m}}export{p as default};
|
|
2
2
|
//# sourceMappingURL=server.js.map
|
package/node/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../../src/node/server.ts"],"sourcesContent":["import http from 'node:http';\nimport https from 'node:https';\nimport type { Server } from 'node:net';\nimport path from 'path';\nimport compression from 'compression';\nimport type { Express } from 'express';\nimport express from 'express';\nimport printServerInfo from '@helpers/print-server-info';\nimport type { IRequestContext } from '@node/render';\nimport PrepareServer from '@services/prepare-server';\nimport ServerApi from '@services/server-api';\nimport type ServerConfig from '@services/server-config';\n\nexport interface ICreateServerOut {\n run: (options?: { version?: string; isPrintInfo?: boolean }) => Server;\n app: Express;\n}\n\n/**\n * Create SSR server\n */\nasync function createServer(config: ServerConfig): Promise<ICreateServerOut> {\n const app = express().disable('x-powered-by');\n const serverApi = new ServerApi();\n\n config.setApp(app);\n\n const prepareServer = PrepareServer.init(config, serverApi);\n\n if (!config.isProd) {\n // Create Vite server in middleware mode and configure the app type as\n // 'custom', disabling Vite's own HTML serving logic so parent server\n // can take control\n const vite = await (\n await import('vite')\n ).createServer({\n server: {\n middlewareMode: true,\n watch: {\n // During tests, we edit the files too fast and sometimes chokidar\n // misses change events, so enforce polling for consistency\n usePolling: true,\n interval: 100,\n },\n },\n appType: 'custom',\n mode: config.mode,\n });\n\n // Use vite's connect instance as middleware\n app.use(vite.middlewares);\n\n config.setVite(vite);\n }\n\n const { isSPA } = config.getParams();\n\n if (!isSPA) {\n await prepareServer.onAppCreated();\n }\n\n if (config.isProd) {\n const { root, publicDir } = config.getParams();\n const { compression: compressionConfig, expressStatic } = prepareServer.getMiddlewaresConfig();\n\n if (compressionConfig) {\n app.use(compression(compressionConfig));\n }\n\n if (!isSPA) {\n // ignore index.html file in SSR mode\n app.use((req, _, next) => {\n if (req.url === '/index.html' && !serverApi.hasAccessIndexHtml()) {\n req.url = '/index-not-found.html';\n }\n\n next();\n });\n }\n\n if (expressStatic) {\n app.use(\n express.static(path.resolve(`${root}/${publicDir}`), {\n ...
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../../src/node/server.ts"],"sourcesContent":["import http from 'node:http';\nimport https from 'node:https';\nimport type { Server } from 'node:net';\nimport path from 'path';\nimport compression from 'compression';\nimport type { Express } from 'express';\nimport express from 'express';\nimport printServerInfo from '@helpers/print-server-info';\nimport type { IRequestContext } from '@node/render';\nimport PrepareServer from '@services/prepare-server';\nimport ServerApi from '@services/server-api';\nimport type ServerConfig from '@services/server-config';\n\nexport interface ICreateServerOut {\n run: (options?: { version?: string; isPrintInfo?: boolean }) => Server;\n app: Express;\n}\n\n/**\n * Create SSR server\n */\nasync function createServer(config: ServerConfig): Promise<ICreateServerOut> {\n const app = express().disable('x-powered-by');\n const serverApi = new ServerApi();\n\n config.setApp(app);\n\n const prepareServer = PrepareServer.init(config, serverApi);\n\n if (!config.isProd) {\n // Create Vite server in middleware mode and configure the app type as\n // 'custom', disabling Vite's own HTML serving logic so parent server\n // can take control\n const vite = await (\n await import('vite')\n ).createServer({\n server: {\n middlewareMode: true,\n watch: {\n // During tests, we edit the files too fast and sometimes chokidar\n // misses change events, so enforce polling for consistency\n usePolling: true,\n interval: 100,\n },\n },\n appType: 'custom',\n mode: config.mode,\n });\n\n // Use vite's connect instance as middleware\n app.use(vite.middlewares);\n\n config.setVite(vite);\n }\n\n const { isSPA } = config.getParams();\n\n if (!isSPA) {\n await prepareServer.onAppCreated();\n }\n\n if (config.isProd) {\n const { root, publicDir } = config.getParams();\n const { compression: compressionConfig, expressStatic } = prepareServer.getMiddlewaresConfig();\n\n if (compressionConfig) {\n app.use(compression(compressionConfig));\n }\n\n if (!isSPA) {\n // ignore index.html file in SSR mode\n app.use((req, _, next) => {\n if (req.url === '/index.html' && !serverApi.hasAccessIndexHtml()) {\n req.url = '/index-not-found.html';\n }\n\n next();\n });\n }\n\n if (expressStatic) {\n const { basename, ...expressStaticOpts } = expressStatic;\n\n app.use(\n basename!,\n express.static(path.resolve(`${root}/${publicDir}`), {\n ...expressStaticOpts,\n index: isSPA ? undefined : false,\n }),\n );\n }\n }\n\n // SSR mode\n if (!isSPA) {\n app.use('*', (req, res, next) => {\n void (async () => {\n try {\n const [{ render, onRequest, ...renderParams }, clientHtml] = await Promise.all([\n prepareServer.loadEntrypoint(),\n prepareServer.loadHtml(req),\n ]);\n const { appProps, hasEarlyHints, shouldSkip } = (await onRequest?.(req, res)) ?? {};\n const [header, footer] = clientHtml;\n\n if (shouldSkip) {\n return next();\n }\n\n const context: IRequestContext = {\n req,\n res,\n hasEarlyHints,\n appProps: appProps ?? {},\n html: { header, footer },\n };\n\n await render(config, context, renderParams);\n } catch (e) {\n config\n .getLogger()\n .error(`Failed to handle request: ${(e as Error)?.message}`, { error: e as Error });\n next();\n }\n })();\n });\n } else {\n // SPA mode, redirect any request to index.html\n app.use('*', (req, res, next) => {\n void (async () => {\n try {\n const html = (await prepareServer.loadHtml(req)).join('');\n\n res.send(html);\n } catch (e) {\n config\n .getLogger()\n .error(`Failed to handle request: ${(e as Error)?.message}`, { error: e as Error });\n next();\n }\n })();\n });\n }\n\n return {\n run: ({ version, isPrintInfo = true } = {}): Server => {\n const { port, host } = config.getParams();\n const isHTTPS = Boolean(config.getVite()?.config?.server?.https);\n\n // update resolved host for print network link\n if (config.isHost && !config.isProd) {\n config.getVite()!.config.server.host = host;\n }\n\n const server = (\n isHTTPS\n ? https.createServer(config.getVite()!.config.server.https!, app)\n : http.createServer(app)\n ).listen(port, host, () => {\n void prepareServer.onServerStarted?.(app, serverApi, server);\n\n if (!isPrintInfo) {\n return;\n }\n\n void printServerInfo(config, { version, server });\n });\n\n return server;\n },\n app,\n };\n}\n\nexport default createServer;\n"],"names":["async","createServer","config","app","express","disable","serverApi","ServerApi","setApp","prepareServer","PrepareServer","init","isProd","vite","import","server","middlewareMode","watch","usePolling","interval","appType","mode","use","middlewares","setVite","isSPA","getParams","onAppCreated","root","publicDir","compression","compressionConfig","expressStatic","getMiddlewaresConfig","req","_","next","url","hasAccessIndexHtml","basename","expressStaticOpts","static","path","resolve","index","undefined","res","html","loadHtml","join","send","e","getLogger","error","message","render","onRequest","renderParams","clientHtml","Promise","all","loadEntrypoint","appProps","hasEarlyHints","shouldSkip","header","footer","context","run","version","isPrintInfo","port","host","isHTTPS","Boolean","getVite","https","isHost","http","listen","onServerStarted","printServerInfo"],"mappings":"8PAqBAA,eAAeC,EAAaC,GAC1B,MAAMC,EAAMC,IAAUC,QAAQ,gBACxBC,EAAY,IAAIC,EAEtBL,EAAOM,OAAOL,GAEd,MAAMM,EAAgBC,EAAcC,KAAKT,EAAQI,GAEjD,IAAKJ,EAAOU,OAAQ,CAIlB,MAAMC,cACEC,OAAO,SACbb,aAAa,CACbc,OAAQ,CACNC,gBAAgB,EAChBC,MAAO,CAGLC,YAAY,EACZC,SAAU,MAGdC,QAAS,SACTC,KAAMnB,EAAOmB,OAIflB,EAAImB,IAAIT,EAAKU,aAEbrB,EAAOsB,QAAQX,EAChB,CAED,MAAMY,MAAEA,GAAUvB,EAAOwB,YAMzB,GAJKD,SACGhB,EAAckB,eAGlBzB,EAAOU,OAAQ,CACjB,MAAMgB,KAAEA,EAAIC,UAAEA,GAAc3B,EAAOwB,aAC3BI,YAAaC,EAAiBC,cAAEA,GAAkBvB,EAAcwB,uBAiBxE,GAfIF,GACF5B,EAAImB,IAAIQ,EAAYC,IAGjBN,GAEHtB,EAAImB,KAAI,CAACY,EAAKC,EAAGC,KACC,gBAAZF,EAAIG,KAA0B/B,EAAUgC,uBAC1CJ,EAAIG,IAAM,yBAGZD,GAAM,IAINJ,EAAe,CACjB,MAAMO,SAAEA,KAAaC,GAAsBR,EAE3C7B,EAAImB,IACFiB,EACAnC,EAAQqC,OAAOC,EAAKC,QAAQ,GAAGf,KAAQC,KAAc,IAChDW,EACHI,QAAOnB,QAAQoB,IAGpB,CACF,CAqDD,OAlDKpB,EAkCHtB,EAAImB,IAAI,KAAK,CAACY,EAAKY,EAAKV,KACjB,WACH,IACE,MAAMW,SAActC,EAAcuC,SAASd,IAAMe,KAAK,IAEtDH,EAAII,KAAKH,EACV,CAAC,MAAOI,GACPjD,EACGkD,YACAC,MAAM,6BAA8BF,GAAaG,UAAW,CAAED,MAAOF,IACxEf,GACD,CACF,EAXI,EAWD,IA7CNjC,EAAImB,IAAI,KAAK,CAACY,EAAKY,EAAKV,KACjB,WACH,IACE,OAAOmB,OAAEA,EAAMC,UAAEA,KAAcC,GAAgBC,SAAoBC,QAAQC,IAAI,CAC7EnD,EAAcoD,iBACdpD,EAAcuC,SAASd,MAEnB4B,SAAEA,EAAQC,cAAEA,EAAaC,WAAEA,SAAsBR,IAAYtB,EAAKY,KAAS,IAC1EmB,EAAQC,GAAUR,EAEzB,GAAIM,EACF,OAAO5B,IAGT,MAAM+B,EAA2B,CAC/BjC,MACAY,MACAiB,gBACAD,SAAUA,GAAY,CAAE,EACxBf,KAAM,CAAEkB,SAAQC,iBAGZX,EAAOrD,EAAQiE,EAASV,EAC/B,CAAC,MAAON,GACPjD,EACGkD,YACAC,MAAM,6BAA8BF,GAAaG,UAAW,CAAED,MAAOF,IACxEf,GACD,CACF,EA5BI,EA4BD,IAoBD,CACLgC,IAAK,EAAGC,UAASC,eAAc,GAAS,CAAA,KACtC,MAAMC,KAAEA,EAAIC,KAAEA,GAAStE,EAAOwB,YACxB+C,EAAUC,QAAQxE,EAAOyE,WAAWzE,QAAQa,QAAQ6D,OAGtD1E,EAAO2E,SAAW3E,EAAOU,SAC3BV,EAAOyE,UAAWzE,OAAOa,OAAOyD,KAAOA,GAGzC,MAAMzD,GACJ0D,EACIG,EAAM3E,aAAaC,EAAOyE,UAAWzE,OAAOa,OAAO6D,MAAQzE,GAC3D2E,EAAK7E,aAAaE,IACtB4E,OAAOR,EAAMC,GAAM,KACd/D,EAAcuE,kBAAkB7E,EAAKG,EAAWS,GAEhDuD,GAIAW,EAAgB/E,EAAQ,CAAEmE,UAAStD,UAAS,IAGnD,OAAOA,CAAM,EAEfZ,MAEJ"}
|
package/package.json
CHANGED
package/services/build.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import i from"node:child_process";import s from"node:fs";import t from"node:path";import e from"chalk";import{resolveConfig as o}from"vite";import n from"../cli/helpers/vite-reset-cache.js";import r from"../helpers/create-focus-only.js";import{createDevMarker as l}from"../helpers/dev-marker.js";import a from"../helpers/plugin-config.js";import c from"../helpers/process-stop.js";import{readMeta as u,removeMeta as d}from"../helpers/ssr-meta.js";import h from"./server-config.js";import p from"./ssr-manifest.js";class f{isProd;nodeEnv;buildDir;viteConfig;pluginConfig;params={mode:"",clientOptions:"",serverOptions:"",focusOnly:"app",isWatch:!1,isUnlockRobots:!1,isEject:!1,isServerless:!1,isNoWarnings:!1};abortController=null;runningBuild=[];hasPreviewModeExitListener=!1;constructor(i){this.params={...this.params,...i}}async makeConfig(){const{mode:i}=this.params;this.viteConfig=await o({},"build",i,"production"),this.pluginConfig=a(this.viteConfig),this.buildDir=t.resolve(this.viteConfig.root,this.viteConfig.build.outDir),this.nodeEnv=process.env.NODE_ENV||"production",this.isProd="production"===this.nodeEnv}clearBuildFolder(){s.existsSync(this.buildDir)&&s.rmSync(this.buildDir,{recursive:!0})}getIsProd(){return this.isProd}getNodeEnv(){return this.nodeEnv}getRunningBuildNames(){return this.runningBuild.map((({name:i})=>i))}promisifyProcess(i,s=!1){const t=new Promise(((t,e)=>{i.on("exit",(i=>{t(i)})),i.on("close",(i=>{t(i)})),i.on("error",(i=>{e(i)})),s&&i.stderr?.on("data",(i=>{const s=Buffer.from(i).toString();(s.includes("warning")||s.includes("WARNING"))&&t(1)}))}));return i.stdout?.pipe(process.stdout),i.stderr?.pipe(process.stderr),{promise:t,command:i}}async buildManifest(){console.info(e.blue(`Building routes manifest file: ${this.pluginConfig.routesParsing}`));const i="node"===this.pluginConfig.routesParsing,s=h.init({isProd:this.isProd,mode:this.params.mode},{root:this.viteConfig.root,clientFile:this.pluginConfig.clientFile});await p.get(s,{buildDir:this.viteConfig.build.outDir,viteAliases:this.viteConfig.resolve.alias}).buildRoutesManifest(i),i&&this.cleanupClientRoutes()}cleanupClientRoutes(){const{routeFiles:i}=u(this.buildDir),t=new Set(Object.values(i??[]));t.size&&t.forEach((i=>{const t=`${this.buildDir}/client/${i}`;try{const i=s.readFileSync(t,{encoding:"utf-8"}).replace(/(lazy:.*?\((.*?)\)),\s?".*?"\)/g,"$1)");s.writeFileSync(t,i)}catch(i){console.log(`Failed cleanup client route ${t}:`,i)}}))}unlockRobots(){const i=`${this.buildDir}/client/robots.txt`;if(!s.existsSync(i))return void console.warn(`Failed to unlock robots.txt, file not exist: ${i}`);const t=s.readFileSync(i,{encoding:"utf-8"}).replace(/Disallow: \/$/m,"Allow: /");s.writeFileSync(i,t,{encoding:"utf-8"}),console.info(e.blue("\nrobots.txt unlocked."))}eject(){const i=`${this.buildDir}/server/start.js`;s.writeFileSync(i,"import runProd from '@lomray/vite-ssr-boost/cli/run-prod.js';\n\nconst VERSION = process.env.VERSION || \"1.0.0\";\nconst PORT = process.env.PORT || 3000;\nconst IS_HOST = process.env.IS_HOST || \"0\";\nconst ONLY_CLIENT = process.env.ONLY_CLIENT || \"0\";\n\nawait runProd({\n version: VERSION,\n isHost: IS_HOST === '1',\n isPrintInfo: true,\n port: PORT,\n onlyClient: ONLY_CLIENT === '1',\n });\n",{encoding:"utf-8"})}createServerless(){const i=`${this.buildDir}/server/serverless.js`;s.writeFileSync(i,"import runServerless from '@lomray/vite-ssr-boost/cli/run-serverless.js';\n\nexport default await runServerless({ version: process.env.VERSION || \"1.0.0\" });\n",{encoding:"utf-8"})}async spawnBuild(s,t,e={}){const{mode:o,isNoWarnings:n}=this.params,{focusOnly:l=this.params.focusOnly,shouldWait:a=!1,env:c={}}=e,u=o&&!t.includes("--mode")?`--mode ${o}`:"",d=this.promisifyProcess(i.spawn(`vite build ${t} ${u} --emptyOutDir`,{signal:this.abortController.signal,stdio:[process.stdin,"pipe","pipe"],shell:!0,env:{...process.env,...c,FORCE_COLOR:"2",SSR_BOOST_IS_SSR:r(l).isOnlyClient()?"0":"1",SSR_BOOST_ACTION:global.viteBoostAction}}),n);this.runningBuild.push({name:s,buildProcess:d}),a&&await this.waitLastBuild()}async waitLastBuild(){const i=this.runningBuild.at(-1);if(!i)return;const s=await i.buildProcess.promise;c(s,!0)}runPreviewMode(){this.hasPreviewModeExitListener||(process.on("exit",(()=>{this.abortController.abort()})),this.hasPreviewModeExitListener=!0);const{onFinish:i}=this.params;let s=this.runningBuild.length;const t=e=>{Buffer.from(e).toString().includes("built in")&&(s-=1,s||(this.runningBuild.forEach((({buildProcess:i})=>{i.command.stdout?.removeListener("data",t)})),l(this.isProd,this.viteConfig),i?.()))};this.runningBuild.forEach((({buildProcess:i})=>{i.command.stdout?.on("data",t)}))}async build(){await this.makeConfig(),await n(),this.clearBuildFolder();const{clientOptions:i,serverOptions:s,onFinish:t,isWatch:e,focusOnly:o,isEject:a,isServerless:c,isUnlockRobots:u}=this.params,{outDir:h}=this.viteConfig.build,p=r(o);this.abortController=new AbortController,this.runningBuild=[],p.isClient()&&await this.spawnBuild("client",`${i} --outDir ${h}/client`,{shouldWait:!e}),p.isServer()&&(await this.spawnBuild("server",`${s} --outDir ${h}/server --ssr ${this.pluginConfig.serverFile}`,{shouldWait:!e}),e||(await this.buildManifest(),a&&this.eject(),c&&this.createServerless()));const{entrypoint:f}=this.pluginConfig;if(f?.length&&p.isEntrypoint())for(const{name:i,type:s,serverFile:t,buildOptions:o=""}of f){const n=t&&"ssr"===s?`--ssr ${t}`:"";await this.spawnBuild(i,`${o} ${n} --outDir ${h}/${i}`,{shouldWait:!e,focusOnly:"ssr"===s?"server":"client",env:{SSR_BOOST_CUSTOM_ENTRYPOINT_BUILD_NAME:i}})}e?this.runPreviewMode():(u&&this.unlockRobots(),l(this.isProd,this.viteConfig),d(this.buildDir),t?.())}}export{f as default};
|
|
1
|
+
import i from"node:child_process";import s from"node:fs";import t from"node:path";import e from"chalk";import{resolveConfig as o}from"vite";import n from"../cli/helpers/vite-reset-cache.js";import r from"../helpers/create-focus-only.js";import{createDevMarker as l}from"../helpers/dev-marker.js";import a from"../helpers/plugin-config.js";import c from"../helpers/process-stop.js";import{readMeta as u,removeMeta as d}from"../helpers/ssr-meta.js";import h from"./server-config.js";import p from"./ssr-manifest.js";class f{isProd;nodeEnv;buildDir;viteConfig;pluginConfig;params={mode:"",clientOptions:"",serverOptions:"",focusOnly:"app",isWatch:!1,isUnlockRobots:!1,isEject:!1,isServerless:!1,isNoWarnings:!1};abortController=null;runningBuild=[];hasPreviewModeExitListener=!1;constructor(i){this.params={...this.params,...i}}async makeConfig(){const{mode:i}=this.params;this.viteConfig=await o({},"build",i,"production"),this.pluginConfig=a(this.viteConfig),this.buildDir=t.resolve(this.viteConfig.root,this.viteConfig.build.outDir),this.nodeEnv=process.env.NODE_ENV||"production",this.isProd="production"===this.nodeEnv}clearBuildFolder(){s.existsSync(this.buildDir)&&s.rmSync(this.buildDir,{recursive:!0})}getIsProd(){return this.isProd}getNodeEnv(){return this.nodeEnv}getRunningBuildNames(){return this.runningBuild.map((({name:i})=>i))}promisifyProcess(i,s=!1){const t=new Promise(((t,e)=>{i.on("exit",(i=>{t(i)})),i.on("close",(i=>{t(i)})),i.on("error",(i=>{e(i)})),s&&i.stderr?.on("data",(i=>{const s=Buffer.from(i).toString();(s.includes("warning")||s.includes("WARNING"))&&t(1)}))}));return i.stdout?.pipe(process.stdout),i.stderr?.pipe(process.stderr),{promise:t,command:i}}async buildManifest(){console.info(e.blue(`Building routes manifest file: ${this.pluginConfig.routesParsing}`));const i="node"===this.pluginConfig.routesParsing,s=h.init({isProd:this.isProd,mode:this.params.mode},{root:this.viteConfig.root,clientFile:this.pluginConfig.clientFile});await p.get(s,{buildDir:this.viteConfig.build.outDir,viteAliases:this.viteConfig.resolve.alias,basename:this.viteConfig.base}).buildRoutesManifest(i),i&&this.cleanupClientRoutes()}cleanupClientRoutes(){const{routeFiles:i}=u(this.buildDir),t=new Set(Object.values(i??[]));t.size&&t.forEach((i=>{const t=`${this.buildDir}/client/${i}`;try{const i=s.readFileSync(t,{encoding:"utf-8"}).replace(/(lazy:.*?\((.*?)\)),\s?".*?"\)/g,"$1)");s.writeFileSync(t,i)}catch(i){console.log(`Failed cleanup client route ${t}:`,i)}}))}unlockRobots(){const i=`${this.buildDir}/client/robots.txt`;if(!s.existsSync(i))return void console.warn(`Failed to unlock robots.txt, file not exist: ${i}`);const t=s.readFileSync(i,{encoding:"utf-8"}).replace(/Disallow: \/$/m,"Allow: /");s.writeFileSync(i,t,{encoding:"utf-8"}),console.info(e.blue("\nrobots.txt unlocked."))}eject(){const i=`${this.buildDir}/server/start.js`;s.writeFileSync(i,"import runProd from '@lomray/vite-ssr-boost/cli/run-prod.js';\n\nconst VERSION = process.env.VERSION || \"1.0.0\";\nconst PORT = process.env.PORT || 3000;\nconst IS_HOST = process.env.IS_HOST || \"0\";\nconst ONLY_CLIENT = process.env.ONLY_CLIENT || \"0\";\n\nawait runProd({\n version: VERSION,\n isHost: IS_HOST === '1',\n isPrintInfo: true,\n port: PORT,\n onlyClient: ONLY_CLIENT === '1',\n });\n",{encoding:"utf-8"})}createServerless(){const i=`${this.buildDir}/server/serverless.js`;s.writeFileSync(i,"import runServerless from '@lomray/vite-ssr-boost/cli/run-serverless.js';\n\nexport default await runServerless({ version: process.env.VERSION || \"1.0.0\" });\n",{encoding:"utf-8"})}async spawnBuild(s,t,e={}){const{mode:o,isNoWarnings:n}=this.params,{focusOnly:l=this.params.focusOnly,shouldWait:a=!1,env:c={}}=e,u=o&&!t.includes("--mode")?`--mode ${o}`:"",d=this.promisifyProcess(i.spawn(`vite build ${t} ${u} --emptyOutDir`,{signal:this.abortController.signal,stdio:[process.stdin,"pipe","pipe"],shell:!0,env:{...process.env,...c,FORCE_COLOR:"2",SSR_BOOST_IS_SSR:r(l).isOnlyClient()?"0":"1",SSR_BOOST_ACTION:global.viteBoostAction}}),n);this.runningBuild.push({name:s,buildProcess:d}),a&&await this.waitLastBuild()}async waitLastBuild(){const i=this.runningBuild.at(-1);if(!i)return;const s=await i.buildProcess.promise;c(s,!0)}runPreviewMode(){this.hasPreviewModeExitListener||(process.on("exit",(()=>{this.abortController.abort()})),this.hasPreviewModeExitListener=!0);const{onFinish:i}=this.params;let s=this.runningBuild.length;const t=e=>{Buffer.from(e).toString().includes("built in")&&(s-=1,s||(this.runningBuild.forEach((({buildProcess:i})=>{i.command.stdout?.removeListener("data",t)})),l(this.isProd,this.viteConfig),i?.()))};this.runningBuild.forEach((({buildProcess:i})=>{i.command.stdout?.on("data",t)}))}async build(){await this.makeConfig(),await n(),this.clearBuildFolder();const{clientOptions:i,serverOptions:s,onFinish:t,isWatch:e,focusOnly:o,isEject:a,isServerless:c,isUnlockRobots:u}=this.params,{outDir:h}=this.viteConfig.build,p=r(o);this.abortController=new AbortController,this.runningBuild=[],p.isClient()&&await this.spawnBuild("client",`${i} --outDir ${h}/client`,{shouldWait:!e}),p.isServer()&&(await this.spawnBuild("server",`${s} --outDir ${h}/server --ssr ${this.pluginConfig.serverFile}`,{shouldWait:!e}),e||(await this.buildManifest(),a&&this.eject(),c&&this.createServerless()));const{entrypoint:f}=this.pluginConfig;if(f?.length&&p.isEntrypoint())for(const{name:i,type:s,serverFile:t,buildOptions:o=""}of f){const n=t&&"ssr"===s?`--ssr ${t}`:"";await this.spawnBuild(i,`${o} ${n} --outDir ${h}/${i}`,{shouldWait:!e,focusOnly:"ssr"===s?"server":"client",env:{SSR_BOOST_CUSTOM_ENTRYPOINT_BUILD_NAME:i}})}e?this.runPreviewMode():(u&&this.unlockRobots(),l(this.isProd,this.viteConfig),d(this.buildDir),t?.())}}export{f as default};
|
|
2
2
|
//# sourceMappingURL=build.js.map
|
package/services/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sources":["../../src/services/build.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport type { ResolvedConfig } from 'vite';\nimport { resolveConfig } from 'vite';\nimport viteResetCache from '@cli/helpers/vite-reset-cache';\nimport createFocusOnly from '@helpers/create-focus-only';\nimport { createDevMarker } from '@helpers/dev-marker';\nimport type { IPluginConfig } from '@helpers/plugin-config';\nimport getPluginConfig from '@helpers/plugin-config';\nimport processStop from '@helpers/process-stop';\nimport { readMeta, removeMeta } from '@helpers/ssr-meta';\nimport ServerConfig from '@services/server-config';\nimport SsrManifest from '@services/ssr-manifest';\n\nexport interface IBuildParams {\n mode: string;\n onFinish?: () => void;\n clientOptions?: string;\n serverOptions?: string;\n focusOnly?: 'all' | 'app' | 'client' | 'server' | 'entrypoint';\n isWatch?: boolean;\n isUnlockRobots?: boolean;\n isEject?: boolean;\n isServerless?: boolean;\n isNoWarnings?: boolean;\n}\n\ninterface IBuildProcess {\n promise: Promise<number | null | string>;\n command: childProcess.ChildProcess;\n}\n\ninterface ISpawnBuildParams {\n shouldWait?: boolean;\n focusOnly?: IBuildParams['focusOnly'];\n env?: Record<string, string>;\n}\n\nexport interface IBuildEntrypoint {\n // entrypoint name\n name: string;\n type: 'spa' | 'ssr';\n // custom index file, default: indexFile from plugin config\n indexFile?: string;\n // custom entry file for replace in indexFile, default: undefined (do nothing)\n clientFile?: string;\n // custom server file, indexFile and clientFile will be ignored\n serverFile?: string;\n // additional options for vite build command\n buildOptions?: string;\n}\n\n/**\n * Build service\n */\nclass Build {\n /**\n * Is production build\n */\n protected isProd: boolean;\n\n /**\n * Node environment\n */\n protected nodeEnv: string;\n\n /**\n * Build folder\n */\n protected buildDir: string;\n\n /**\n * Vite config\n */\n protected viteConfig: ResolvedConfig;\n\n /**\n * Plugin config\n */\n protected pluginConfig: IPluginConfig;\n\n /**\n * Build params\n */\n protected params: IBuildParams = {\n mode: '',\n clientOptions: '',\n serverOptions: '',\n focusOnly: 'app',\n isWatch: false,\n isUnlockRobots: false,\n isEject: false,\n isServerless: false,\n isNoWarnings: false,\n };\n\n /**\n * Abort controller for builds\n */\n protected abortController: AbortController | null = null;\n\n /**\n * Running builds\n */\n protected runningBuild: { name: string; buildProcess: IBuildProcess }[] = [];\n\n /**\n * Listener for preview has attached\n */\n protected hasPreviewModeExitListener = false;\n\n /**\n * @constructor\n */\n public constructor(params: IBuildParams) {\n this.params = { ...this.params, ...params };\n }\n\n /**\n * Make config\n */\n protected async makeConfig(): Promise<void> {\n const { mode } = this.params;\n\n this.viteConfig = await resolveConfig({}, 'build', mode, 'production');\n this.pluginConfig = getPluginConfig(this.viteConfig);\n this.buildDir = path.resolve(this.viteConfig.root, this.viteConfig.build.outDir);\n this.nodeEnv = process.env.NODE_ENV || 'production';\n this.isProd = this.nodeEnv === 'production';\n }\n\n /**\n * Clear build folder\n */\n public clearBuildFolder(): void {\n // clear build folder\n if (fs.existsSync(this.buildDir)) {\n fs.rmSync(this.buildDir, { recursive: true });\n }\n }\n\n /**\n * Return is prod indicator value\n */\n public getIsProd(): boolean {\n return this.isProd;\n }\n\n /**\n * Return node env value\n */\n public getNodeEnv(): string {\n return this.nodeEnv;\n }\n\n /**\n * Return build names\n */\n public getRunningBuildNames(): string[] {\n return this.runningBuild.map(({ name }) => name);\n }\n\n /**\n * Promisify spawn process\n */\n protected promisifyProcess(\n command: childProcess.ChildProcess,\n isRejectWarnings = false,\n ): IBuildProcess {\n const promise = new Promise<number | null | string>((resolve, reject): void => {\n command.on('exit', (code) => {\n resolve(code);\n });\n\n command.on('close', (code: number): void => {\n resolve(code);\n });\n\n command.on('error', (message: string): void => {\n reject(message);\n });\n\n if (isRejectWarnings) {\n command.stderr?.on('data', (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('warning') || msg.includes('WARNING')) {\n resolve(1);\n }\n });\n }\n });\n\n command.stdout?.pipe(process.stdout);\n command.stderr?.pipe(process.stderr);\n\n return { promise, command };\n }\n\n /**\n * Build assets manifest file\n */\n protected async buildManifest(): Promise<void> {\n console.info(chalk.blue(`Building routes manifest file: ${this.pluginConfig.routesParsing}`));\n\n const isNodeParsing = this.pluginConfig.routesParsing === 'node';\n const serverConfig = ServerConfig.init(\n { isProd: this.isProd, mode: this.params.mode },\n { root: this.viteConfig.root, clientFile: this.pluginConfig.clientFile },\n );\n\n await SsrManifest.get(serverConfig, {\n buildDir: this.viteConfig.build.outDir,\n viteAliases: this.viteConfig.resolve.alias,\n }).buildRoutesManifest(isNodeParsing);\n\n if (isNodeParsing) {\n this.cleanupClientRoutes();\n }\n }\n\n /**\n * Remove pathId from client route files\n */\n private cleanupClientRoutes(): void {\n const { routeFiles } = readMeta(this.buildDir);\n const files = new Set(Object.values(routeFiles ?? []));\n\n if (!files.size) {\n return;\n }\n\n files.forEach((file) => {\n const filepath = `${this.buildDir}/client/${file}`;\n\n try {\n const result = fs\n .readFileSync(filepath, { encoding: 'utf-8' })\n .replace(/(lazy:.*?\\((.*?)\\)),\\s?\".*?\"\\)/g, '$1)');\n\n fs.writeFileSync(filepath, result);\n } catch (e) {\n console.log(`Failed cleanup client route ${filepath}:`, e);\n }\n });\n }\n\n /**\n * Change general directive Disallow to Allow in robots.txt.\n */\n protected unlockRobots(): void {\n const robotsFile = `${this.buildDir}/client/robots.txt`;\n\n if (!fs.existsSync(robotsFile)) {\n console.warn(`Failed to unlock robots.txt, file not exist: ${robotsFile}`);\n\n return;\n }\n\n const data = fs\n .readFileSync(robotsFile, { encoding: 'utf-8' })\n .replace(/Disallow: \\/$/m, 'Allow: /');\n\n fs.writeFileSync(robotsFile, data, { encoding: 'utf-8' });\n\n console.info(chalk.blue('\\nrobots.txt unlocked.'));\n }\n\n /**\n * Eject cli to run app via node\n */\n protected eject(): void {\n const entrypoint = `${this.buildDir}/server/start.js`;\n const script =\n \"import runProd from '@lomray/vite-ssr-boost/cli/run-prod.js';\\n\\n\" +\n 'const VERSION = process.env.VERSION || \"1.0.0\";\\n' +\n 'const PORT = process.env.PORT || 3000;\\n' +\n 'const IS_HOST = process.env.IS_HOST || \"0\";\\n' +\n 'const ONLY_CLIENT = process.env.ONLY_CLIENT || \"0\";\\n\\n' +\n `await runProd({\n version: VERSION,\n isHost: IS_HOST === '1',\n isPrintInfo: true,\n port: PORT,\n onlyClient: ONLY_CLIENT === '1',\n });\\n`;\n\n fs.writeFileSync(entrypoint, script, {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Create serverless entrypoint\n */\n protected createServerless(): void {\n const entrypoint = `${this.buildDir}/server/serverless.js`;\n const script =\n \"import runServerless from '@lomray/vite-ssr-boost/cli/run-serverless.js';\\n\\n\" +\n `export default await runServerless({ version: process.env.VERSION || \"1.0.0\" });\\n`;\n\n fs.writeFileSync(entrypoint, script, {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Build specified entrypoint\n */\n protected async spawnBuild(\n name: string,\n buildOptions: string,\n params: ISpawnBuildParams = {},\n ): Promise<void> {\n const { mode, isNoWarnings } = this.params;\n const { focusOnly = this.params.focusOnly, shouldWait = false, env = {} } = params;\n const modeOpt = mode && !buildOptions.includes('--mode') ? `--mode ${mode}` : '';\n\n const buildProcess = this.promisifyProcess(\n childProcess.spawn(`vite build ${buildOptions} ${modeOpt} --emptyOutDir`, {\n signal: this.abortController!.signal,\n stdio: [process.stdin, 'pipe', 'pipe'],\n shell: true,\n env: {\n ...process.env,\n ...env,\n FORCE_COLOR: '2',\n SSR_BOOST_IS_SSR: createFocusOnly(focusOnly).isOnlyClient() ? '0' : '1',\n SSR_BOOST_ACTION: global.viteBoostAction,\n },\n }),\n isNoWarnings,\n );\n\n this.runningBuild.push({ name, buildProcess });\n\n if (!shouldWait) {\n return;\n }\n\n await this.waitLastBuild();\n }\n\n /**\n * Wait latest build and stop process in case error\n */\n protected async waitLastBuild(): Promise<void> {\n const latestProcess = this.runningBuild.at(-1);\n\n if (!latestProcess) {\n return;\n }\n\n const exitCode = await latestProcess.buildProcess.promise;\n\n processStop(exitCode, true);\n }\n\n /**\n * Run preview mode\n */\n protected runPreviewMode(): void {\n if (!this.hasPreviewModeExitListener) {\n process.on('exit', () => {\n this.abortController!.abort();\n });\n\n this.hasPreviewModeExitListener = true;\n }\n\n const { onFinish } = this.params;\n let buildCount = this.runningBuild.length;\n\n /**\n * Detect finished builds for process\n */\n const listener = (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('built in')) {\n buildCount -= 1;\n\n if (!buildCount) {\n this.runningBuild.forEach(({ buildProcess }) => {\n buildProcess.command.stdout?.removeListener('data', listener);\n });\n createDevMarker(this.isProd, this.viteConfig);\n onFinish?.();\n }\n }\n };\n\n /**\n * Listen output for call onFinish\n */\n this.runningBuild.forEach(({ buildProcess }) => {\n buildProcess.command.stdout?.on('data', listener);\n });\n }\n\n /**\n * Run app build\n */\n public async build(): Promise<void> {\n await this.makeConfig();\n // this is required step - build with different env may cause problems\n await viteResetCache();\n this.clearBuildFolder();\n\n const {\n clientOptions,\n serverOptions,\n onFinish,\n isWatch,\n focusOnly,\n isEject,\n isServerless,\n isUnlockRobots,\n } = this.params;\n const { outDir } = this.viteConfig.build;\n const focus = createFocusOnly(focusOnly);\n\n this.abortController = new AbortController();\n this.runningBuild = [];\n\n if (focus.isClient()) {\n /**\n * Build client\n */\n await this.spawnBuild('client', `${clientOptions} --outDir ${outDir}/client`, {\n shouldWait: !isWatch,\n });\n }\n\n /**\n * Build server\n */\n if (focus.isServer()) {\n await this.spawnBuild(\n 'server',\n `${serverOptions} --outDir ${outDir}/server --ssr ${this.pluginConfig.serverFile}`,\n {\n shouldWait: !isWatch,\n },\n );\n\n if (!isWatch) {\n await this.buildManifest();\n\n if (isEject) {\n this.eject();\n }\n\n if (isServerless) {\n this.createServerless();\n }\n }\n }\n\n /**\n * Build additional entrypoint\n */\n const { entrypoint } = this.pluginConfig;\n\n if (entrypoint?.length && focus.isEntrypoint()) {\n for (const { name, type, serverFile, buildOptions = '' } of entrypoint) {\n const cliOptions = serverFile && type === 'ssr' ? `--ssr ${serverFile}` : '';\n\n await this.spawnBuild(name, `${buildOptions} ${cliOptions} --outDir ${outDir}/${name}`, {\n shouldWait: !isWatch,\n focusOnly: type === 'ssr' ? 'server' : 'client',\n env: {\n SSR_BOOST_CUSTOM_ENTRYPOINT_BUILD_NAME: name,\n },\n });\n }\n }\n\n /**\n * Preview mode\n */\n if (isWatch) {\n this.runPreviewMode();\n\n return;\n }\n\n if (isUnlockRobots) {\n this.unlockRobots();\n }\n\n createDevMarker(this.isProd, this.viteConfig);\n removeMeta(this.buildDir);\n onFinish?.();\n }\n}\n\nexport default Build;\n"],"names":["Build","isProd","nodeEnv","buildDir","viteConfig","pluginConfig","params","mode","clientOptions","serverOptions","focusOnly","isWatch","isUnlockRobots","isEject","isServerless","isNoWarnings","abortController","runningBuild","hasPreviewModeExitListener","constructor","this","async","resolveConfig","getPluginConfig","path","resolve","root","build","outDir","process","env","NODE_ENV","clearBuildFolder","fs","existsSync","rmSync","recursive","getIsProd","getNodeEnv","getRunningBuildNames","map","name","promisifyProcess","command","isRejectWarnings","promise","Promise","reject","on","code","message","stderr","buff","msg","Buffer","from","toString","includes","stdout","pipe","console","info","chalk","blue","routesParsing","isNodeParsing","serverConfig","ServerConfig","init","clientFile","SsrManifest","get","viteAliases","alias","buildRoutesManifest","cleanupClientRoutes","routeFiles","readMeta","files","Set","Object","values","size","forEach","file","filepath","result","readFileSync","encoding","replace","writeFileSync","e","log","unlockRobots","robotsFile","warn","data","eject","entrypoint","createServerless","buildOptions","shouldWait","modeOpt","buildProcess","childProcess","spawn","signal","stdio","stdin","shell","FORCE_COLOR","SSR_BOOST_IS_SSR","createFocusOnly","isOnlyClient","SSR_BOOST_ACTION","global","viteBoostAction","push","waitLastBuild","latestProcess","at","exitCode","processStop","runPreviewMode","abort","onFinish","buildCount","length","listener","removeListener","createDevMarker","makeConfig","viteResetCache","focus","AbortController","isClient","spawnBuild","isServer","serverFile","buildManifest","isEntrypoint","type","cliOptions","SSR_BOOST_CUSTOM_ENTRYPOINT_BUILD_NAME","removeMeta"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build.js","sources":["../../src/services/build.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport type { ResolvedConfig } from 'vite';\nimport { resolveConfig } from 'vite';\nimport viteResetCache from '@cli/helpers/vite-reset-cache';\nimport createFocusOnly from '@helpers/create-focus-only';\nimport { createDevMarker } from '@helpers/dev-marker';\nimport type { IPluginConfig } from '@helpers/plugin-config';\nimport getPluginConfig from '@helpers/plugin-config';\nimport processStop from '@helpers/process-stop';\nimport { readMeta, removeMeta } from '@helpers/ssr-meta';\nimport ServerConfig from '@services/server-config';\nimport SsrManifest from '@services/ssr-manifest';\n\nexport interface IBuildParams {\n mode: string;\n onFinish?: () => void;\n clientOptions?: string;\n serverOptions?: string;\n focusOnly?: 'all' | 'app' | 'client' | 'server' | 'entrypoint';\n isWatch?: boolean;\n isUnlockRobots?: boolean;\n isEject?: boolean;\n isServerless?: boolean;\n isNoWarnings?: boolean;\n}\n\ninterface IBuildProcess {\n promise: Promise<number | null | string>;\n command: childProcess.ChildProcess;\n}\n\ninterface ISpawnBuildParams {\n shouldWait?: boolean;\n focusOnly?: IBuildParams['focusOnly'];\n env?: Record<string, string>;\n}\n\nexport interface IBuildEntrypoint {\n // entrypoint name\n name: string;\n type: 'spa' | 'ssr';\n // custom index file, default: indexFile from plugin config\n indexFile?: string;\n // custom entry file for replace in indexFile, default: undefined (do nothing)\n clientFile?: string;\n // custom server file, indexFile and clientFile will be ignored\n serverFile?: string;\n // additional options for vite build command\n buildOptions?: string;\n}\n\n/**\n * Build service\n */\nclass Build {\n /**\n * Is production build\n */\n protected isProd: boolean;\n\n /**\n * Node environment\n */\n protected nodeEnv: string;\n\n /**\n * Build folder\n */\n protected buildDir: string;\n\n /**\n * Vite config\n */\n protected viteConfig: ResolvedConfig;\n\n /**\n * Plugin config\n */\n protected pluginConfig: IPluginConfig;\n\n /**\n * Build params\n */\n protected params: IBuildParams = {\n mode: '',\n clientOptions: '',\n serverOptions: '',\n focusOnly: 'app',\n isWatch: false,\n isUnlockRobots: false,\n isEject: false,\n isServerless: false,\n isNoWarnings: false,\n };\n\n /**\n * Abort controller for builds\n */\n protected abortController: AbortController | null = null;\n\n /**\n * Running builds\n */\n protected runningBuild: { name: string; buildProcess: IBuildProcess }[] = [];\n\n /**\n * Listener for preview has attached\n */\n protected hasPreviewModeExitListener = false;\n\n /**\n * @constructor\n */\n public constructor(params: IBuildParams) {\n this.params = { ...this.params, ...params };\n }\n\n /**\n * Make config\n */\n protected async makeConfig(): Promise<void> {\n const { mode } = this.params;\n\n this.viteConfig = await resolveConfig({}, 'build', mode, 'production');\n this.pluginConfig = getPluginConfig(this.viteConfig);\n this.buildDir = path.resolve(this.viteConfig.root, this.viteConfig.build.outDir);\n this.nodeEnv = process.env.NODE_ENV || 'production';\n this.isProd = this.nodeEnv === 'production';\n }\n\n /**\n * Clear build folder\n */\n public clearBuildFolder(): void {\n // clear build folder\n if (fs.existsSync(this.buildDir)) {\n fs.rmSync(this.buildDir, { recursive: true });\n }\n }\n\n /**\n * Return is prod indicator value\n */\n public getIsProd(): boolean {\n return this.isProd;\n }\n\n /**\n * Return node env value\n */\n public getNodeEnv(): string {\n return this.nodeEnv;\n }\n\n /**\n * Return build names\n */\n public getRunningBuildNames(): string[] {\n return this.runningBuild.map(({ name }) => name);\n }\n\n /**\n * Promisify spawn process\n */\n protected promisifyProcess(\n command: childProcess.ChildProcess,\n isRejectWarnings = false,\n ): IBuildProcess {\n const promise = new Promise<number | null | string>((resolve, reject): void => {\n command.on('exit', (code) => {\n resolve(code);\n });\n\n command.on('close', (code: number): void => {\n resolve(code);\n });\n\n command.on('error', (message: string): void => {\n reject(message);\n });\n\n if (isRejectWarnings) {\n command.stderr?.on('data', (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('warning') || msg.includes('WARNING')) {\n resolve(1);\n }\n });\n }\n });\n\n command.stdout?.pipe(process.stdout);\n command.stderr?.pipe(process.stderr);\n\n return { promise, command };\n }\n\n /**\n * Build assets manifest file\n */\n protected async buildManifest(): Promise<void> {\n console.info(chalk.blue(`Building routes manifest file: ${this.pluginConfig.routesParsing}`));\n\n const isNodeParsing = this.pluginConfig.routesParsing === 'node';\n const serverConfig = ServerConfig.init(\n { isProd: this.isProd, mode: this.params.mode },\n { root: this.viteConfig.root, clientFile: this.pluginConfig.clientFile },\n );\n\n await SsrManifest.get(serverConfig, {\n buildDir: this.viteConfig.build.outDir,\n viteAliases: this.viteConfig.resolve.alias,\n basename: this.viteConfig.base,\n }).buildRoutesManifest(isNodeParsing);\n\n if (isNodeParsing) {\n this.cleanupClientRoutes();\n }\n }\n\n /**\n * Remove pathId from client route files\n */\n private cleanupClientRoutes(): void {\n const { routeFiles } = readMeta(this.buildDir);\n const files = new Set(Object.values(routeFiles ?? []));\n\n if (!files.size) {\n return;\n }\n\n files.forEach((file) => {\n const filepath = `${this.buildDir}/client/${file}`;\n\n try {\n const result = fs\n .readFileSync(filepath, { encoding: 'utf-8' })\n .replace(/(lazy:.*?\\((.*?)\\)),\\s?\".*?\"\\)/g, '$1)');\n\n fs.writeFileSync(filepath, result);\n } catch (e) {\n console.log(`Failed cleanup client route ${filepath}:`, e);\n }\n });\n }\n\n /**\n * Change general directive Disallow to Allow in robots.txt.\n */\n protected unlockRobots(): void {\n const robotsFile = `${this.buildDir}/client/robots.txt`;\n\n if (!fs.existsSync(robotsFile)) {\n console.warn(`Failed to unlock robots.txt, file not exist: ${robotsFile}`);\n\n return;\n }\n\n const data = fs\n .readFileSync(robotsFile, { encoding: 'utf-8' })\n .replace(/Disallow: \\/$/m, 'Allow: /');\n\n fs.writeFileSync(robotsFile, data, { encoding: 'utf-8' });\n\n console.info(chalk.blue('\\nrobots.txt unlocked.'));\n }\n\n /**\n * Eject cli to run app via node\n */\n protected eject(): void {\n const entrypoint = `${this.buildDir}/server/start.js`;\n const script =\n \"import runProd from '@lomray/vite-ssr-boost/cli/run-prod.js';\\n\\n\" +\n 'const VERSION = process.env.VERSION || \"1.0.0\";\\n' +\n 'const PORT = process.env.PORT || 3000;\\n' +\n 'const IS_HOST = process.env.IS_HOST || \"0\";\\n' +\n 'const ONLY_CLIENT = process.env.ONLY_CLIENT || \"0\";\\n\\n' +\n `await runProd({\n version: VERSION,\n isHost: IS_HOST === '1',\n isPrintInfo: true,\n port: PORT,\n onlyClient: ONLY_CLIENT === '1',\n });\\n`;\n\n fs.writeFileSync(entrypoint, script, {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Create serverless entrypoint\n */\n protected createServerless(): void {\n const entrypoint = `${this.buildDir}/server/serverless.js`;\n const script =\n \"import runServerless from '@lomray/vite-ssr-boost/cli/run-serverless.js';\\n\\n\" +\n `export default await runServerless({ version: process.env.VERSION || \"1.0.0\" });\\n`;\n\n fs.writeFileSync(entrypoint, script, {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Build specified entrypoint\n */\n protected async spawnBuild(\n name: string,\n buildOptions: string,\n params: ISpawnBuildParams = {},\n ): Promise<void> {\n const { mode, isNoWarnings } = this.params;\n const { focusOnly = this.params.focusOnly, shouldWait = false, env = {} } = params;\n const modeOpt = mode && !buildOptions.includes('--mode') ? `--mode ${mode}` : '';\n\n const buildProcess = this.promisifyProcess(\n childProcess.spawn(`vite build ${buildOptions} ${modeOpt} --emptyOutDir`, {\n signal: this.abortController!.signal,\n stdio: [process.stdin, 'pipe', 'pipe'],\n shell: true,\n env: {\n ...process.env,\n ...env,\n FORCE_COLOR: '2',\n SSR_BOOST_IS_SSR: createFocusOnly(focusOnly).isOnlyClient() ? '0' : '1',\n SSR_BOOST_ACTION: global.viteBoostAction,\n },\n }),\n isNoWarnings,\n );\n\n this.runningBuild.push({ name, buildProcess });\n\n if (!shouldWait) {\n return;\n }\n\n await this.waitLastBuild();\n }\n\n /**\n * Wait latest build and stop process in case error\n */\n protected async waitLastBuild(): Promise<void> {\n const latestProcess = this.runningBuild.at(-1);\n\n if (!latestProcess) {\n return;\n }\n\n const exitCode = await latestProcess.buildProcess.promise;\n\n processStop(exitCode, true);\n }\n\n /**\n * Run preview mode\n */\n protected runPreviewMode(): void {\n if (!this.hasPreviewModeExitListener) {\n process.on('exit', () => {\n this.abortController!.abort();\n });\n\n this.hasPreviewModeExitListener = true;\n }\n\n const { onFinish } = this.params;\n let buildCount = this.runningBuild.length;\n\n /**\n * Detect finished builds for process\n */\n const listener = (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('built in')) {\n buildCount -= 1;\n\n if (!buildCount) {\n this.runningBuild.forEach(({ buildProcess }) => {\n buildProcess.command.stdout?.removeListener('data', listener);\n });\n createDevMarker(this.isProd, this.viteConfig);\n onFinish?.();\n }\n }\n };\n\n /**\n * Listen output for call onFinish\n */\n this.runningBuild.forEach(({ buildProcess }) => {\n buildProcess.command.stdout?.on('data', listener);\n });\n }\n\n /**\n * Run app build\n */\n public async build(): Promise<void> {\n await this.makeConfig();\n // this is required step - build with different env may cause problems\n await viteResetCache();\n this.clearBuildFolder();\n\n const {\n clientOptions,\n serverOptions,\n onFinish,\n isWatch,\n focusOnly,\n isEject,\n isServerless,\n isUnlockRobots,\n } = this.params;\n const { outDir } = this.viteConfig.build;\n const focus = createFocusOnly(focusOnly);\n\n this.abortController = new AbortController();\n this.runningBuild = [];\n\n if (focus.isClient()) {\n /**\n * Build client\n */\n await this.spawnBuild('client', `${clientOptions} --outDir ${outDir}/client`, {\n shouldWait: !isWatch,\n });\n }\n\n /**\n * Build server\n */\n if (focus.isServer()) {\n await this.spawnBuild(\n 'server',\n `${serverOptions} --outDir ${outDir}/server --ssr ${this.pluginConfig.serverFile}`,\n {\n shouldWait: !isWatch,\n },\n );\n\n if (!isWatch) {\n await this.buildManifest();\n\n if (isEject) {\n this.eject();\n }\n\n if (isServerless) {\n this.createServerless();\n }\n }\n }\n\n /**\n * Build additional entrypoint\n */\n const { entrypoint } = this.pluginConfig;\n\n if (entrypoint?.length && focus.isEntrypoint()) {\n for (const { name, type, serverFile, buildOptions = '' } of entrypoint) {\n const cliOptions = serverFile && type === 'ssr' ? `--ssr ${serverFile}` : '';\n\n await this.spawnBuild(name, `${buildOptions} ${cliOptions} --outDir ${outDir}/${name}`, {\n shouldWait: !isWatch,\n focusOnly: type === 'ssr' ? 'server' : 'client',\n env: {\n SSR_BOOST_CUSTOM_ENTRYPOINT_BUILD_NAME: name,\n },\n });\n }\n }\n\n /**\n * Preview mode\n */\n if (isWatch) {\n this.runPreviewMode();\n\n return;\n }\n\n if (isUnlockRobots) {\n this.unlockRobots();\n }\n\n createDevMarker(this.isProd, this.viteConfig);\n removeMeta(this.buildDir);\n onFinish?.();\n }\n}\n\nexport default Build;\n"],"names":["Build","isProd","nodeEnv","buildDir","viteConfig","pluginConfig","params","mode","clientOptions","serverOptions","focusOnly","isWatch","isUnlockRobots","isEject","isServerless","isNoWarnings","abortController","runningBuild","hasPreviewModeExitListener","constructor","this","async","resolveConfig","getPluginConfig","path","resolve","root","build","outDir","process","env","NODE_ENV","clearBuildFolder","fs","existsSync","rmSync","recursive","getIsProd","getNodeEnv","getRunningBuildNames","map","name","promisifyProcess","command","isRejectWarnings","promise","Promise","reject","on","code","message","stderr","buff","msg","Buffer","from","toString","includes","stdout","pipe","console","info","chalk","blue","routesParsing","isNodeParsing","serverConfig","ServerConfig","init","clientFile","SsrManifest","get","viteAliases","alias","basename","base","buildRoutesManifest","cleanupClientRoutes","routeFiles","readMeta","files","Set","Object","values","size","forEach","file","filepath","result","readFileSync","encoding","replace","writeFileSync","e","log","unlockRobots","robotsFile","warn","data","eject","entrypoint","createServerless","buildOptions","shouldWait","modeOpt","buildProcess","childProcess","spawn","signal","stdio","stdin","shell","FORCE_COLOR","SSR_BOOST_IS_SSR","createFocusOnly","isOnlyClient","SSR_BOOST_ACTION","global","viteBoostAction","push","waitLastBuild","latestProcess","at","exitCode","processStop","runPreviewMode","abort","onFinish","buildCount","length","listener","removeListener","createDevMarker","makeConfig","viteResetCache","focus","AbortController","isClient","spawnBuild","isServer","serverFile","buildManifest","isEntrypoint","type","cliOptions","SSR_BOOST_CUSTOM_ENTRYPOINT_BUILD_NAME","removeMeta"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"fs";import t from"node:path";import r from"node:process";import{pathToFileURL as i}from"node:url";import o from"chalk";import s from"./server-api.js";class n{config;serverApi;entrypoint;onServerCreated;onServerStarted;html;middlewaresConfigs;constructor(e,t){this.config=e,this.serverApi=t??new s}static init(e,t){return new n(e,t)}async loadEntrypoint(e=!0){if(this.entrypoint&&this.config.isProd)return this.entrypoint;const{root:s,isProd:n,serverFile:a}=this.config.getParams(),l=t.resolve(`${s}/${a}`);let d;try{d=n?(await import(i(l).toString())).default:(await this.config.getVite().ssrLoadModule(l,{fixStacktrace:!0})).default}catch(e){if(e instanceof Error&&e.message.includes("Cannot find module")&&e.message.includes("/build/"))return this.config.getLogger().error(o.red(`Before starting the server, you need to create a build: ${o.yellow("ssr-boost build")} or provide path to build dir: ${o.yellow("ssr-boost start --build-dir build")}`)),r.exit(1);throw e}!e&&d.init&&delete d.init;const{render:c,init:g,routes:h,abortDelay:f,loggerProd:p,loggerDev:m,middlewares:u}=d;this.middlewaresConfigs=u,p&&n?this.config.setLogger(p):m&&!n&&this.config.setLogger(m);const{onServerCreated:y,onServerStarted:v,...w}=await(g?.({config:this.config}))??{};return this.entrypoint={render:c,routes:h,abortDelay:f,...w},this.onServerCreated=y,this.onServerStarted=v,this.entrypoint}async loadHtml(r){const{isProd:i,root:o,indexFile:s,clientFile:n}=this.config.getParams();this.html&&i||(this.html=e.readFileSync(t.resolve(`${o}/${s}`),"utf-8"));let a=this.html;if(!i){const e=t.posix.normalize(`${this.config.getVite()?.config.base}/${n}`);a=(await this.config.getVite().transformIndexHtml(r.originalUrl,this.html,s)).replace(new RegExp(`<script[^>]*?\\bsrc=["']/?${e}["'][^>]*?\\sasync\\b`,"g"),(e=>e.replace(/\sasync\b/,"")))}return a.split("\x3c!--ssr-outlet--\x3e")}async onAppCreated(){return await this.loadEntrypoint(),await(this.onServerCreated?.(this.config.getApp(),this.serverApi)),this}getMiddlewaresConfig(){const{compression:e,expressStatic:t}=this.middlewaresConfigs??{};return{compression:!1!==e&&{...e??{}},expressStatic:!1!==t&&{...t??{},basename:t?.basename??"/"}}}}export{n as default};
|
|
2
2
|
//# sourceMappingURL=prepare-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepare-server.js","sources":["../../src/services/prepare-server.ts"],"sourcesContent":["import fs from 'fs';\nimport process from 'node:process';\nimport { pathToFileURL } from 'node:url';\nimport
|
|
1
|
+
{"version":3,"file":"prepare-server.js","sources":["../../src/services/prepare-server.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'node:path';\nimport process from 'node:process';\nimport { pathToFileURL } from 'node:url';\nimport chalk from 'chalk';\nimport type { Request } from 'express';\nimport type { TRouteObject } from '@interfaces/route-object';\nimport type { IEntrypointOptions, IPrepareRenderOut } from '@node/entry';\nimport type { TRender } from '@node/render';\nimport ServerApi from '@services/server-api';\nimport type ServerConfig from '@services/server-config';\n\ninterface IPrepareServerEntrypointLoadOut<TAppProps = Record<string, any>> {\n render: TRender;\n routes: TRouteObject[];\n abortDelay?: number;\n onRequest?: IEntrypointOptions<TAppProps>['onRequest'];\n onRouterReady?: IEntrypointOptions<TAppProps>['onRouterReady'];\n onShellReady?: IEntrypointOptions<TAppProps>['onShellReady'];\n onShellError?: IEntrypointOptions<TAppProps>['onShellError'];\n onResponse?: IEntrypointOptions<TAppProps>['onResponse'];\n onError?: IEntrypointOptions<TAppProps>['onError'];\n getState?: IEntrypointOptions<TAppProps>['getState'];\n}\n\n/**\n * Load server entrypoint and template\n * DEV MODE: refresh entrypoint and template\n */\nclass PrepareServer {\n /**\n * Server configuration\n */\n protected readonly config: ServerConfig;\n\n /**\n * Server API\n */\n protected readonly serverApi: ServerApi;\n\n /**\n * Entrypoint resolved params\n */\n protected entrypoint?: IPrepareServerEntrypointLoadOut;\n\n /**\n * Hook which calls after express server created\n */\n protected onServerCreated?: IEntrypointOptions['onServerCreated'];\n\n /**\n * Hook which calls after express server started\n */\n public onServerStarted?: IEntrypointOptions['onServerStarted'];\n\n /**\n * Html shell\n */\n protected html: string;\n\n /**\n * Middlewares configs\n */\n protected middlewaresConfigs?: IPrepareRenderOut['middlewares'];\n\n /**\n * @constructor\n */\n protected constructor(config: ServerConfig, serverApi?: ServerApi) {\n this.config = config;\n this.serverApi = serverApi ?? new ServerApi();\n }\n\n /**\n * Init service\n */\n public static init(config: ServerConfig, serverApi?: ServerApi): PrepareServer {\n return new PrepareServer(config, serverApi);\n }\n\n /**\n * Resolve and return entrypoint params\n */\n public async loadEntrypoint(shouldInit = true): Promise<IPrepareServerEntrypointLoadOut> {\n // load server entrypoint each time only in development mode (for fast refresh)\n if (this.entrypoint && this.config.isProd) {\n return this.entrypoint;\n }\n\n const { root, isProd, serverFile } = this.config.getParams();\n const entrypointPath = path.resolve(`${root}/${serverFile}`);\n\n let resolvedEntrypoint: IPrepareRenderOut;\n\n try {\n if (!isProd) {\n resolvedEntrypoint = (\n await this.config.getVite()!.ssrLoadModule(entrypointPath, {\n fixStacktrace: true,\n })\n ).default as IPrepareRenderOut;\n } else {\n resolvedEntrypoint = (\n (await import(pathToFileURL(entrypointPath).toString())) as { default: IPrepareRenderOut }\n ).default;\n }\n } catch (e) {\n if (\n e instanceof Error &&\n e.message.includes('Cannot find module') &&\n e.message.includes('/build/')\n ) {\n this.config\n .getLogger()\n .error(\n chalk.red(\n `Before starting the server, you need to create a build: ${chalk.yellow(\n 'ssr-boost build',\n )} or provide path to build dir: ${chalk.yellow(\n 'ssr-boost start --build-dir build',\n )}`,\n ),\n );\n\n return process.exit(1);\n }\n\n throw e;\n }\n\n if (!shouldInit && resolvedEntrypoint.init) {\n delete resolvedEntrypoint.init;\n }\n\n const { render, init, routes, abortDelay, loggerProd, loggerDev, middlewares } =\n resolvedEntrypoint;\n\n this.middlewaresConfigs = middlewares;\n\n if (loggerProd && isProd) {\n this.config.setLogger(loggerProd);\n } else if (loggerDev && !isProd) {\n this.config.setLogger(loggerDev);\n }\n\n const { onServerCreated, onServerStarted, ...renderParams } =\n (await init?.({\n config: this.config,\n })) ?? {};\n\n this.entrypoint = {\n render,\n routes,\n abortDelay,\n ...renderParams,\n };\n this.onServerCreated = onServerCreated;\n this.onServerStarted = onServerStarted;\n\n return this.entrypoint;\n }\n\n /**\n * Load and return html shell\n */\n public async loadHtml(req: Request): Promise<[string, string]> {\n const { isProd, root, indexFile, clientFile } = this.config.getParams();\n\n if (!this.html || !isProd) {\n this.html = fs.readFileSync(path.resolve(`${root}/${indexFile}`), 'utf-8');\n }\n\n let modifiedHtml = this.html;\n\n if (!isProd) {\n const clientFileEntry = path.posix.normalize(\n `${this.config.getVite()?.config.base}/${clientFile}`,\n );\n\n // Apply Vite HTML transforms. This injects the Vite HMR client,\n // and also applies HTML transforms from Vite plugins, e.g. global\n // preambles from @vitejs/plugin-react\n modifiedHtml = (\n await this.config.getVite()!.transformIndexHtml(req.originalUrl, this.html, indexFile)\n )\n // remove 'async' attribute from app entrypoint for development\n // it might cause problems with preambles from @vitejs/plugin-react\n .replace(\n new RegExp(`<script[^>]*?\\\\bsrc=[\"']/?${clientFileEntry}[\"'][^>]*?\\\\sasync\\\\b`, 'g'),\n (match) => match.replace(/\\sasync\\b/, ''),\n );\n }\n\n return modifiedHtml.split('<!--ssr-outlet-->') as [string, string];\n }\n\n /**\n * Run server created hook\n */\n public async onAppCreated(): Promise<PrepareServer> {\n await this.loadEntrypoint();\n await this.onServerCreated?.(this.config.getApp()!, this.serverApi);\n\n return this;\n }\n\n /**\n * Return SSR express middlewares configs\n */\n public getMiddlewaresConfig(): NonNullable<PrepareServer['middlewaresConfigs']> {\n const { compression, expressStatic } = this.middlewaresConfigs ?? {};\n\n return {\n compression:\n compression !== false\n ? {\n ...(compression ?? {}),\n }\n : false,\n expressStatic:\n expressStatic !== false\n ? {\n ...(expressStatic ?? {}),\n basename: expressStatic?.basename ?? '/',\n }\n : false,\n };\n }\n}\n\nexport default PrepareServer;\n"],"names":["PrepareServer","config","serverApi","entrypoint","onServerCreated","onServerStarted","html","middlewaresConfigs","constructor","this","ServerApi","static","async","shouldInit","isProd","root","serverFile","getParams","entrypointPath","path","resolve","resolvedEntrypoint","import","pathToFileURL","toString","default","getVite","ssrLoadModule","fixStacktrace","e","Error","message","includes","getLogger","error","chalk","red","yellow","process","exit","init","render","routes","abortDelay","loggerProd","loggerDev","middlewares","setLogger","renderParams","req","indexFile","clientFile","fs","readFileSync","modifiedHtml","clientFileEntry","posix","normalize","base","transformIndexHtml","originalUrl","replace","RegExp","match","split","loadEntrypoint","getApp","getMiddlewaresConfig","compression","expressStatic","basename"],"mappings":"oKA6BA,MAAMA,EAIeC,OAKAC,UAKTC,WAKAC,gBAKHC,gBAKGC,KAKAC,mBAKVC,YAAsBP,EAAsBC,GAC1CO,KAAKR,OAASA,EACdQ,KAAKP,UAAYA,GAAa,IAAIQ,CACnC,CAKMC,YAAYV,EAAsBC,GACvC,OAAO,IAAIF,EAAcC,EAAQC,EAClC,CAKMU,qBAAqBC,GAAa,GAEvC,GAAIJ,KAAKN,YAAcM,KAAKR,OAAOa,OACjC,OAAOL,KAAKN,WAGd,MAAMY,KAAEA,EAAID,OAAEA,EAAME,WAAEA,GAAeP,KAAKR,OAAOgB,YAC3CC,EAAiBC,EAAKC,QAAQ,GAAGL,KAAQC,KAE/C,IAAIK,EAEJ,IAQIA,EAPGP,SAQMQ,OAAOC,EAAcL,GAAgBM,aAC5CC,eAPMhB,KAAKR,OAAOyB,UAAWC,cAAcT,EAAgB,CACzDU,eAAe,KAEjBH,OAML,CAAC,MAAOI,GACP,GACEA,aAAaC,OACbD,EAAEE,QAAQC,SAAS,uBACnBH,EAAEE,QAAQC,SAAS,WAcnB,OAZAvB,KAAKR,OACFgC,YACAC,MACCC,EAAMC,IACJ,2DAA2DD,EAAME,OAC/D,oDACiCF,EAAME,OACvC,yCAKDC,EAAQC,KAAK,GAGtB,MAAMV,CACP,EAEIhB,GAAcQ,EAAmBmB,aAC7BnB,EAAmBmB,KAG5B,MAAMC,OAAEA,EAAMD,KAAEA,EAAIE,OAAEA,EAAMC,WAAEA,EAAUC,WAAEA,EAAUC,UAAEA,EAASC,YAAEA,GAC/DzB,EAEFZ,KAAKF,mBAAqBuC,EAEtBF,GAAc9B,EAChBL,KAAKR,OAAO8C,UAAUH,GACbC,IAAc/B,GACvBL,KAAKR,OAAO8C,UAAUF,GAGxB,MAAMzC,gBAAEA,EAAeC,gBAAEA,KAAoB2C,SACpCR,IAAO,CACZvC,OAAQQ,KAAKR,WACR,CAAA,EAWT,OATAQ,KAAKN,WAAa,CAChBsC,SACAC,SACAC,gBACGK,GAELvC,KAAKL,gBAAkBA,EACvBK,KAAKJ,gBAAkBA,EAEhBI,KAAKN,UACb,CAKMS,eAAeqC,GACpB,MAAMnC,OAAEA,EAAMC,KAAEA,EAAImC,UAAEA,EAASC,WAAEA,GAAe1C,KAAKR,OAAOgB,YAEvDR,KAAKH,MAASQ,IACjBL,KAAKH,KAAO8C,EAAGC,aAAalC,EAAKC,QAAQ,GAAGL,KAAQmC,KAAc,UAGpE,IAAII,EAAe7C,KAAKH,KAExB,IAAKQ,EAAQ,CACX,MAAMyC,EAAkBpC,EAAKqC,MAAMC,UACjC,GAAGhD,KAAKR,OAAOyB,WAAWzB,OAAOyD,QAAQP,KAM3CG,SACQ7C,KAAKR,OAAOyB,UAAWiC,mBAAmBV,EAAIW,YAAanD,KAAKH,KAAM4C,IAI3EW,QACC,IAAIC,OAAO,6BAA6BP,yBAAwC,MAC/EQ,GAAUA,EAAMF,QAAQ,YAAa,KAE3C,CAED,OAAOP,EAAaU,MAAM,0BAC3B,CAKMpD,qBAIL,aAHMH,KAAKwD,uBACLxD,KAAKL,kBAAkBK,KAAKR,OAAOiE,SAAWzD,KAAKP,YAElDO,IACR,CAKM0D,uBACL,MAAMC,YAAEA,EAAWC,cAAEA,GAAkB5D,KAAKF,oBAAsB,CAAA,EAElE,MAAO,CACL6D,aACkB,IAAhBA,GACI,IACMA,GAAe,CAAA,GAG3BC,eACoB,IAAlBA,GACI,IACMA,GAAiB,CAAA,EACrBC,SAAUD,GAAeC,UAAY,KAIhD"}
|
|
@@ -10,6 +10,7 @@ import ServerConfig from "./server-config.js";
|
|
|
10
10
|
interface ISsrManifestParams {
|
|
11
11
|
buildDir?: string;
|
|
12
12
|
viteAliases?: Alias[];
|
|
13
|
+
basename?: string;
|
|
13
14
|
}
|
|
14
15
|
interface IManifest {
|
|
15
16
|
[path: string]: {
|
|
@@ -73,6 +74,10 @@ declare class SsrManifest {
|
|
|
73
74
|
* Vite resolve aliases
|
|
74
75
|
*/
|
|
75
76
|
protected readonly viteAliases?: Alias[];
|
|
77
|
+
/**
|
|
78
|
+
* Vite base
|
|
79
|
+
*/
|
|
80
|
+
protected readonly basename?: string;
|
|
76
81
|
/**
|
|
77
82
|
* Loaded assets manifest file
|
|
78
83
|
*/
|
|
@@ -83,7 +88,7 @@ declare class SsrManifest {
|
|
|
83
88
|
/**
|
|
84
89
|
* @constructor
|
|
85
90
|
*/
|
|
86
|
-
protected constructor(config: ServerConfig, { buildDir, viteAliases }?: ISsrManifestParams);
|
|
91
|
+
protected constructor(config: ServerConfig, { buildDir, viteAliases, basename }?: ISsrManifestParams);
|
|
87
92
|
/**
|
|
88
93
|
* Get singleton instance
|
|
89
94
|
*/
|
package/services/ssr-manifest.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import t from"node:fs";import e from"node:path";import s from"chalk";import i from"./parse-routes.js";import r from"./path-normalize.js";import o from"./prepare-server.js";import n from"./server-config.js";var a;!function(t){t.style="style",t.script="script",t.image="image",t.font="font"}(a||(a={}));const l="\r\n";class c{static instance=null;config;pathNormalize;root;buildDir;manifestName="manifest.json";assetsManifest="assets-manifest.json";viteAliases;routesAssets=null;constructor(t,{buildDir:e,viteAliases:s}={}){this.config=t,this.root=t.getParams().root,this.buildDir=e,this.viteAliases=s??t.getVite()?.config?.resolve.alias,this.pathNormalize=new r(t,s)}static get(t,e={}){return null===c.instance&&(c.instance=new c(t,e)),c.instance}getOutDir(){return e.resolve(this.root,this.buildDir||"")}getAssetsManifestFile(){return`${this.getOutDir()}/server/${this.assetsManifest}`}loadClientManifest(){const s=e.resolve(this.root,`${this.buildDir||""}/client/.vite`),i=`${s}/${this.manifestName}`;if(!t.existsSync(i))return{};const r=JSON.parse(t.readFileSync(i,{encoding:"utf-8"}));return t.rmSync(i),0===t.readdirSync(s).length&&t.rmSync(s,{recursive:!0}),r}loadAssetsManifest(){if(null!==this.routesAssets)return this.routesAssets;const e=this.getAssetsManifestFile();return t.existsSync(e)?(this.routesAssets=JSON.parse(t.readFileSync(e,{encoding:"utf-8"})),this.routesAssets):{}}async getAsyncRoutesIds(t,e){const i={};for(const r in t){const o=t[r],n=[e,r].filter(Boolean).join("-");if(o.lazy)try{const t=await o.lazy();i[n]=this.pathNormalize.getAppPath(t?.pathId)}catch(t){console.error(s.red("Failed to load route:"),o.path,t)}else o.children&&Object.assign(i,await this.getAsyncRoutesIds(o.children,n))}return i}getRoutesTreeIds(t,e){const s={};return t.forEach(((t,i)=>{const r=[e,String(i)].filter(Boolean).join("-");t.import&&(s[r]=this.pathNormalize.getAppPath(t.import)),t.children.length>0&&Object.assign(s,this.getRoutesTreeIds(t.children,r))})),s}sortAssets(t){return t.sort(((t,e)=>t.weight===e.weight?Number(t.isNested)-Number(e.isNested):t.weight-e.weight))}getRouteAssets(t,
|
|
1
|
+
import t from"node:fs";import e from"node:path";import s from"chalk";import i from"./parse-routes.js";import r from"./path-normalize.js";import o from"./prepare-server.js";import n from"./server-config.js";var a;!function(t){t.style="style",t.script="script",t.image="image",t.font="font"}(a||(a={}));const l="\r\n";class c{static instance=null;config;pathNormalize;root;buildDir;manifestName="manifest.json";assetsManifest="assets-manifest.json";viteAliases;basename;routesAssets=null;constructor(t,{buildDir:e,viteAliases:s,basename:i}={}){this.config=t,this.root=t.getParams().root,this.buildDir=e,this.viteAliases=s??t.getVite()?.config?.resolve.alias,this.pathNormalize=new r(t,s),this.basename=i}static get(t,e={}){return null===c.instance&&(c.instance=new c(t,e)),c.instance}getOutDir(){return e.resolve(this.root,this.buildDir||"")}getAssetsManifestFile(){return`${this.getOutDir()}/server/${this.assetsManifest}`}loadClientManifest(){const s=e.resolve(this.root,`${this.buildDir||""}/client/.vite`),i=`${s}/${this.manifestName}`;if(!t.existsSync(i))return{};const r=JSON.parse(t.readFileSync(i,{encoding:"utf-8"}));return t.rmSync(i),0===t.readdirSync(s).length&&t.rmSync(s,{recursive:!0}),r}loadAssetsManifest(){if(null!==this.routesAssets)return this.routesAssets;const e=this.getAssetsManifestFile();return t.existsSync(e)?(this.routesAssets=JSON.parse(t.readFileSync(e,{encoding:"utf-8"})),this.routesAssets):{}}async getAsyncRoutesIds(t,e){const i={};for(const r in t){const o=t[r],n=[e,r].filter(Boolean).join("-");if(o.lazy)try{const t=await o.lazy();i[n]=this.pathNormalize.getAppPath(t?.pathId)}catch(t){console.error(s.red("Failed to load route:"),o.path,t)}else o.children&&Object.assign(i,await this.getAsyncRoutesIds(o.children,n))}return i}getRoutesTreeIds(t,e){const s={};return t.forEach(((t,i)=>{const r=[e,String(i)].filter(Boolean).join("-");t.import&&(s[r]=this.pathNormalize.getAppPath(t.import)),t.children.length>0&&Object.assign(s,this.getRoutesTreeIds(t.children,r))})),s}sortAssets(t){return t.sort(((t,e)=>t.weight===e.weight?Number(t.isNested)-Number(e.isNested):t.weight-e.weight))}getRouteAssets(t,s,i=!1){const r=[...s?.assets??[],...s?.css??[],s?.file].reduce(((t,r)=>{if(r){const o=this.getAssetType(r),n=s.isEntry&&s.file===r;o&&(t[r]={url:e.posix.normalize(`${this.basename}/${r}`),weight:n?1.9:this.getAssetWeight(r),type:o,isNested:i,isPreload:!n})}return t}),{});return s?.imports?.length&&s.imports.forEach((e=>{const s=t[e];s&&Object.assign(r,this.getRouteAssets(t,s,!0))})),r}async buildRoutesManifest(e){const s=o.init(n.init({isProd:!0},{root:this.getOutDir()})),r=this.loadClientManifest();let a;if(e){const{routes:t}=await s.loadEntrypoint(!1);a=await this.getAsyncRoutesIds(t)}else{const t=new i(this.config,this.viteAliases);a=this.getRoutesTreeIds(t.parse())}const l=this.pathNormalize.getImportPostfix(),c={};Object.entries(a).forEach((([t,e])=>{const s=l.find((t=>void 0!==r[`${e}${t}`])),i=r[`${e}${s||""}`];c[t]=this.sortAssets(Object.values(this.getRouteAssets(r,i)))})),t.writeFileSync(this.getAssetsManifestFile(),JSON.stringify(c,null,2),{encoding:"utf-8"})}getAssets(t){if(this.config.getVite())return this.getAssetsDev(t);const e=t?.map((({route:t})=>t.id)).filter(Boolean)??[];if(!e.length)return[];const s=this.loadAssetsManifest();return this.sortAssets(e.map((t=>s[t])).flat().filter(Boolean))}getAssetsDev(t){const s=t?.map((({route:t})=>this.pathNormalize.getAppPath(t?.pathId,!0))).filter(Boolean)??[];if(!s.length)return[];let i={};const r=this.pathNormalize.getImportPostfix();return[e.resolve(this.root,this.config.getPluginConfig()?.clientFile??"client.ts"),...s].forEach((t=>{for(const e of r){const s=this.config.getVite()?.moduleGraph.getModuleById(`${t}${e}`);if(s){i={...i,...this.getModuleAssets(s)};break}}})),Object.values(i)}getModuleAssets(t,e=new Set){if(!t?.clientImportedModules.size||e.has(t.file))return{};let i={};return e.add(t.file),t.clientImportedModules.forEach((t=>{const{file:r,clientImportedModules:o,transformResult:n}=t,l=r?.split(".").at(-1);if(r&&l&&["css","scss"].includes(l)){const t=n?.code.match(/__vite__css\s+=\s+"(?<css>.+)"/)?.groups?.css;if(t)try{i[r]={type:a.style,url:r,weight:this.getAssetWeight(r),content:JSON.parse(`{"style": "${t}"}`).style,isNested:Boolean(e.size),isPreload:!1}}catch(t){console.warn(s.yellowBright("Failed to parse style: ",r))}}else o.size&&(i={...i,...this.getModuleAssets(t,e)})})),i}getAssetWeight(t){switch(this.getAssetType(t)){case a.style:return 1;case a.script:return 2;default:return 3}}getAssetType(t){const e=t.split(".").at(-1)?.toLowerCase();switch(e){case"css":case"scss":return a.style;case"js":return a.script;case"svg":case"jpg":case"jpeg":case"png":case"webp":case"gif":case"ico":return a.image;case"ttf":case"otf":case"woff":case"woff2":return a.font;default:return null}}writeEarlyHits(t,e){e.write(`HTTP/1.1 103 Early Hints${l}`),t.forEach((({type:t,url:s})=>{t&&["style","script"].includes(t)&&e.write(`Link: <${s}>; rel=preload; as=${t}${l}`)})),e.write(l)}injectAssets({routerContext:t,html:e,res:s,hasEarlyHints:i=!1}){const r=this.getAssets(t?.matches),o=r.map((({type:t,url:e,isPreload:s,content:i=""})=>{switch(t){case a.style:return this.config.getVite()?`<style data-vite-dev-id="${e}">${i}</style>`:`<link rel="stylesheet" href="${e}">`;case a.script:return s?this.config.isModulePreload?`<link rel="modulepreload" as="script" crossorigin href="${e}">`:null:`<script async type="module" crossorigin src="${e}"><\/script>`}return null})).filter(Boolean);e.header=e.header.replace("</head>",`${o.join("\n")}</head>`),i&&o.length&&s.socket&&this.writeEarlyHits(r,s.socket)}}export{c as default};
|
|
2
2
|
//# sourceMappingURL=ssr-manifest.js.map
|
|
@@ -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}\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 * Loaded assets manifest file\n */\n protected routesAssets: Record<string, IAsset[]> | null = null;\n\n /**\n * @constructor\n */\n protected constructor(config: ServerConfig, { buildDir, viteAliases }: ISsrManifestParams = {}) {\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 }\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: `/${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","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","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 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":"8MA+BA,IAAKA,GAAL,SAAKA,GACHA,EAAA,MAAA,QACAA,EAAA,OAAA,SACAA,EAAA,MAAA,QACAA,EAAA,KAAA,MACD,CALD,CAAKA,IAAAA,EAKJ,CAAA,IAaD,MAAMC,EAAO,OAKb,MAAMC,EAIMC,gBAAsC,KAK7BC,OAKAC,cAKAC,KAKAC,SAKAC,aAAe,gBAKfC,eAAiB,uBAKjBC,YAKAC,SAKTC,aAAgD,KAK1DC,YACET,GACAG,SAAEA,EAAQG,YAAEA,EAAWC,SAAEA,GAAiC,IAE1DG,KAAKV,OAASA,EACdU,KAAKR,KAAOF,EAAOW,YAAYT,KAC/BQ,KAAKP,SAAWA,EAChBO,KAAKJ,YAAcA,GAAeN,EAAOY,WAAWZ,QAAQa,QAAQC,MACpEJ,KAAKT,cAAgB,IAAIc,EAAcf,EAAQM,GAC/CI,KAAKH,SAAWA,CACjB,CAKMR,WAAWC,EAAsBgB,EAA6B,IAKnE,OAJ6B,OAAzBlB,EAAYmB,WACdnB,EAAYmB,SAAW,IAAInB,EAAYE,EAAQgB,IAG1ClB,EAAYmB,QACpB,CAKSC,YACR,OAAOC,EAAKN,QAAQH,KAAKR,KAAMQ,KAAKP,UAAY,GACjD,CAKSiB,wBACR,MAAO,GAAGV,KAAKQ,sBAAsBR,KAAKL,gBAC3C,CAKSgB,qBACR,MAAMC,EAAoBH,EAAKN,QAAQH,KAAKR,KAAM,GAAGQ,KAAKP,UAAY,mBAChEoB,EAAoB,GAAGD,KAAqBZ,KAAKN,eAEvD,IAAKoB,EAAGC,WAAWF,GACjB,MAAO,GAGT,MAAMG,EAASC,KAAKC,MAClBJ,EAAGK,aAAaN,EAAmB,CAAEO,SAAU,WAUjD,OAPAN,EAAGO,OAAOR,GAGuC,IAA7CC,EAAGQ,YAAYV,GAAmBW,QACpCT,EAAGO,OAAOT,EAAmB,CAAEY,WAAW,IAGrCR,CACR,CAKSS,qBACR,GAA0B,OAAtBzB,KAAKF,aACP,OAAOE,KAAKF,aAGd,MAAM4B,EAAe1B,KAAKU,wBAE1B,OAAKI,EAAGC,WAAWW,IAInB1B,KAAKF,aAAemB,KAAKC,MAAMJ,EAAGK,aAAaO,EAAc,CAAEN,SAAU,WAKlEpB,KAAKF,cARH,EASV,CAKS6B,wBACRC,EACAC,GAEA,MAAMb,EAA6C,CAAA,EAInD,IAAK,MAAMc,KAAcF,EAAQ,CAC/B,MAAMG,EAAQH,EAAOE,GACfE,EAAU,CAACH,EAAOC,GAAYG,OAAOC,SAASC,KAAK,KAEzD,GAAIJ,EAAMK,KACR,IACE,MAAMC,QAAmCN,EAAMK,OAE/CpB,EAAOgB,GAAWhC,KAAKT,cAAc+C,WAAWD,GAAeE,OAChE,CAAC,MAAOC,GACPC,QAAQC,MAAMC,EAAMC,IAAI,yBAA0Bb,EAAMtB,KAAM+B,EAC/D,MACQT,EAAMc,UACfC,OAAOC,OAAO/B,QAAchB,KAAKgD,kBAAkBjB,EAAMc,SAAUb,GAEtE,CAED,OAAOhB,CACR,CAKSiC,iBACRrB,EACAC,GAEA,MAAMb,EAA6C,CAAA,EAcnD,OAZAY,EAAOsB,SAAQ,CAACnB,EAAOD,KACrB,MAAME,EAAU,CAACH,EAAOsB,OAAOrB,IAAaG,OAAOC,SAASC,KAAK,KAE7DJ,EAAMqB,SACRpC,EAAOgB,GAAWhC,KAAKT,cAAc+C,WAAWP,EAAMqB,SAGpDrB,EAAMc,SAAStB,OAAS,GAC1BuB,OAAOC,OAAO/B,EAAQhB,KAAKiD,iBAAiBlB,EAAMc,SAAUb,GAC7D,IAGIhB,CACR,CAKSqC,WAAWC,GACnB,OAAOA,EAAOC,MAAK,CAACC,EAAGC,IACrBD,EAAEE,SAAWD,EAAEC,OAASC,OAAOH,EAAEI,UAAYD,OAAOF,EAAEG,UAAYJ,EAAEE,OAASD,EAAEC,QAElF,CAKSG,eACRC,EACAC,EACAH,GAAW,GAEX,MAEMN,EAFa,IAAKS,GAAQT,QAAU,MAASS,GAAQC,KAAO,GAAKD,GAAQE,MAErDC,QACxB,CAACC,EAAKC,KACJ,GAAIA,EAAO,CACT,MAAMC,EAAOrE,KAAKsE,aAAaF,GACzBG,EAAUR,EAAOQ,SAAWR,EAAOE,OAASG,EAG9CC,IACFF,EAAIC,GAAS,CACXI,IAAK/D,EAAKgE,MAAMC,UAAU,GAAG1E,KAAKH,YAAYuE,KAC9CV,OAAQa,EAAU,IAAMvE,KAAK2E,eAAeP,GAC5CC,OACAT,WACAgB,WAAYL,GAGjB,CAED,OAAOJ,CAAG,GAEZ,CAA4B,GAc9B,OAVIJ,GAAQc,SAAStD,QACnBwC,EAAOc,QAAQ3B,SAAS4B,IACtB,MAAMC,EAAejB,EAASgB,GAE1BC,GACFjC,OAAOC,OAAOO,EAAQtD,KAAK6D,eAAeC,EAAUiB,GAAc,GACnE,IAIEzB,CACR,CAKM3B,0BAA0BqD,GAC/B,MAAMC,EAAgBC,EAAcC,KAClCC,EAAaD,KAAK,CAAEE,QAAQ,GAAQ,CAAE7F,KAAMQ,KAAKQ,eAE7CsD,EAAW9D,KAAKW,qBACtB,IAAI2E,EAEJ,GAAIN,EAAe,CACjB,MAAMpD,OAAEA,SAAiBqD,EAAcM,gBAAe,GAEtDD,QAAoBtF,KAAKgD,kBAAkBpB,EAC5C,KAAM,CACL,MAAM4D,EAAgB,IAAIC,EAAYzF,KAAKV,OAAQU,KAAKJ,aAExD0F,EAActF,KAAKiD,iBAAiBuC,EAActE,QACnD,CAED,MAAMwE,EAAY1F,KAAKT,cAAcoG,mBAC/B3E,EAAmC,CAAA,EAGzC8B,OAAO8C,QAAQN,GAAapC,SAAQ,EAAElB,EAAS6D,MAC7C,MAAMC,EAAeJ,EAAUK,MAAMC,QAGLC,IAAvBnC,EAFU,GAAG+B,IAAYG,OAK5BE,EAAYpC,EADA,GAAG+B,IAAYC,GAAgB,MAGjD9E,EAAOgB,GAAWhC,KAAKqD,WAAWP,OAAOqD,OAAOnG,KAAK6D,eAAeC,EAAUoC,IAAY,IAG5FpF,EAAGsF,cAAcpG,KAAKU,wBAAyBO,KAAKoF,UAAUrF,EAAQ,KAAM,GAAI,CAC9EI,SAAU,SAEb,CAKSkF,UAAU1E,GAClB,GAAI5B,KAAKV,OAAOY,UACd,OAAOF,KAAKuG,aAAa3E,GAG3B,MAAM4E,EAAW5E,GAAQ6E,KAAI,EAAG1E,WAAYA,EAAM2E,KAAIzE,OAAOC,UAAY,GAEzE,IAAKsE,EAASjF,OACZ,MAAO,GAGT,MAAMzB,EAAeE,KAAKyB,qBAE1B,OAAOzB,KAAKqD,WACVmD,EACGC,KAAKzE,GAAYlC,EAAakC,KAC9B2E,OACA1E,OAAOC,SAEb,CAKSqE,aAAa3E,GACrB,MAAM4E,EACH5E,GACG6E,KAAI,EAAG1E,WAAY/B,KAAKT,cAAc+C,WAAYP,GAAuBQ,QAAQ,KAClFN,OAAOC,UAAyB,GAErC,IAAKsE,EAASjF,OACZ,MAAO,GAGT,IAAI+B,EAAkB,CAAA,EACtB,MAAMoC,EAAY1F,KAAKT,cAAcoG,mBAiBrC,MAXA,CALelF,EAAKN,QAClBH,KAAKR,KACLQ,KAAKV,OAAOsH,mBAAmBC,YAAc,gBAGnCL,GAAUtD,SAAS4D,IAC7B,IAAK,MAAMC,KAAOrB,EAAW,CAC3B,MAAM3B,EAAS/D,KAAKV,OAAOY,WAAW8G,YAAYC,cAAc,GAAGH,IAAWC,KAE9E,GAAIhD,EAAQ,CACVT,EAAS,IAAKA,KAAWtD,KAAKkH,gBAAgBnD,IAC9C,KACD,CACF,KAGIjB,OAAOqD,OAAO7C,EACtB,CAKS4D,gBAAgBnD,EAAqBoD,EAA2B,IAAIC,KAC5E,IAAKrD,GAAQsD,sBAAsBC,MAAQH,EAAYI,IAAIxD,EAAOE,MAChE,MAAO,GAGT,IAAIX,EAAkB,CAAA,EAkCtB,OAhCA6D,EAAYK,IAAIzD,EAAOE,MAEvBF,EAAOsD,sBAAsBnE,SAASuE,IACpC,MAAMxD,KAAEA,EAAIoD,sBAAEA,EAAqBK,gBAAEA,GAAoBD,EACnDV,EAAM9C,GAAM0D,MAAM,KAAKC,IAAI,GAEjC,GAAI3D,GAAQ8C,GAAO,CAAC,MAAO,QAAQc,SAASd,GAAM,CAEhD,MAAMe,EAAOJ,GAAiBI,KAAKC,MAAM,mCAAmCC,QAAQhE,IAEpF,GAAI8D,EACF,IACExE,EAAOW,GAAQ,CACbI,KAAMnF,EAAU+I,MAChBzD,IAAKP,EACLP,OAAQ1D,KAAK2E,eAAeV,GAC5BiE,QAAUjH,KAAKC,MAAM,cAAc4G,OAAgCG,MACnErE,SAAU1B,QAAQiF,EAAYG,MAC9B1C,WAAW,EAEd,CAAC,MAAOpC,GACPC,QAAQ0F,KAAKxF,EAAMyF,aAAa,0BAA2BnE,GAC5D,CAEJ,MAAUoD,EAAsBC,OAC/BhE,EAAS,IACJA,KACAtD,KAAKkH,gBAAgBO,EAAWN,IAEtC,IAGI7D,CACR,CAKSqB,eAAeP,GAGvB,OAFapE,KAAKsE,aAAaF,IAG7B,KAAKlF,EAAU+I,MACb,OAAO,EAET,KAAK/I,EAAUmJ,OACb,OAAO,EAET,QACE,OAAO,EAEZ,CAKS/D,aAAaF,GACrB,MAAM2C,EAAM3C,EAAMuD,MAAM,KAAKC,IAAI,IAAIU,cAErC,OAAQvB,GACN,IAAK,MACL,IAAK,OACH,OAAO7H,EAAU+I,MAEnB,IAAK,KACH,OAAO/I,EAAUmJ,OAEnB,IAAK,MACL,IAAK,MACL,IAAK,OACL,IAAK,MACL,IAAK,OACL,IAAK,MACL,IAAK,MACH,OAAOnJ,EAAUqJ,MAEnB,IAAK,MACL,IAAK,MACL,IAAK,OACL,IAAK,QACH,OAAOrJ,EAAUsJ,KAEnB,QACE,OAAO,KAEZ,CAKMC,eAAenF,EAAkBoF,GACtCA,EAAOC,MAAM,2BAA2BxJ,KACxCmE,EAAOJ,SAAQ,EAAGmB,OAAMG,UACjBH,GAAS,CAAC,QAAS,UAAUwD,SAASxD,IAI3CqE,EAAOC,MAAM,UAAUnE,uBAAyBH,IAAOlF,IAAO,IAEhEuJ,EAAOC,MAAMxJ,EACd,CAKMyJ,cAAaC,cAAEA,EAAaC,KAAEA,EAAI3E,IAAEA,EAAG4E,cAAEA,GAAgB,IAC9D,MAAMzF,EAAStD,KAAKsG,UAAUuC,GAAeG,SACvCC,EAAa3F,EAChBmD,KAAI,EAAGpC,OAAMG,MAAKI,YAAWsD,UAAU,OACtC,OAAQ7D,GACN,KAAKnF,EAAU+I,MACb,OAAOjI,KAAKV,OAAOY,UACf,4BAA4BsE,MAAQ0D,YACpC,gCAAgC1D,MAEtC,KAAKtF,EAAUmJ,OACb,OAAOzD,EACH5E,KAAKV,OAAO4J,gBAEV,2DAA2D1E,MAC3D,KACF,gDAAgDA,gBAGxD,OAAO,IAAI,IAEZvC,OAAOC,SAEV4G,EAAKK,OAASL,EAAKK,OAAOC,QAAQ,UAAW,GAAGH,EAAW9G,KAAK,gBAE5D4G,GAAiBE,EAAW1H,QAAU4C,EAAIuE,QAC5C1I,KAAKyI,eAAenF,EAAQa,EAAIuE,OAEnC"}
|