@lomray/vite-ssr-boost 7.0.0-beta.2 → 7.1.0-beta.1

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 CHANGED
@@ -1,21 +1,22 @@
1
- <h1 align='center'>Vite SSR BOOST</h1>
2
- <h4 align='center'>Super easy framework based on Vite for create awesome SSR or SPA applications on React and React Router.</h4>
1
+ # Vite SSR BOOST
3
2
 
4
3
  <p align="center">
5
4
  <img src="https://raw.githubusercontent.com/Lomray-Software/vite-ssr-boost/prod/logo.png" alt="Vite SSR BOOST logo" width="250" height="250">
6
5
  </p>
7
6
 
8
- ### Key features:
7
+ SSR and SPA toolkit for React Router apps on top of Vite.
9
8
 
10
- - Develop ⚡charged⚡ server side applications with React streaming 💨 support.
11
- - Unlocks Suspense for server side applications.
9
+ It is built for applications that want to keep Vite and React Router visible, but still need the practical runtime layer for SSR, SPA fallback, streaming, custom entrypoints and deployment-oriented builds.
10
+
11
+ ## Why use it
12
+
13
+ - SSR and SPA from one route tree
14
+ - React Router on both client and server
15
+ - stream rendering with request lifecycle hooks
16
+ - a Vite-native plugin and CLI flow
12
17
  - Switch between SPA and SSR in 1 second.
13
- - Charged CLI out of box.
14
- - Deploy to AWS Amplify/Docker in SPA/SSR mode for 5 sec.
15
- - Very easy to migrate, very easy to use.
16
- - All the power of [vite](https://vitejs.dev/)⚡
17
- - All the power of [react-router](https://reactrouter.com/)🛣
18
- - Easy-peasy develop for [Capacitor](https://capacitorjs.com/)
18
+ - response-aware helpers such as redirects and status codes
19
+ - custom entrypoints for mobile, embedded and service-worker-friendly shells
19
20
 
20
21
  <p align="center">
21
22
  <img src="https://sonarcloud.io/api/project_badges/measure?project=vite-ssr-boost&metric=reliability_rating" alt="reliability">
@@ -28,392 +29,18 @@
28
29
  <img src="https://img.shields.io/npm/v/@lomray/vite-ssr-boost?label=semantic%20release&logo=semantic-release" alt="semantic version">
29
30
  </p>
30
31
 
31
- ## Table of contents
32
- - [Getting started](#getting-started)
33
- - [How to use](#how-to-use)
34
- - [Example](#how-to-use)
35
- - [Plugin options](#plugin-options)
36
- - [Useful imports](#useful-imports)
37
- - [CLI](#cli)
38
- - [Warning](#warning)
39
- - [Use Cases](#use-cases)
40
- - [Bugs and feature requests](#bugs-and-feature-requests)
41
- - [License](#license)
42
-
43
- ## Getting started
44
-
45
- The package is distributed using [npm](https://www.npmjs.com/), the node package manager.
46
-
47
- ```
48
- npm i --save @lomray/vite-ssr-boost
49
- ```
50
-
51
- ## How to use
52
-
53
- **Explore [template](https://github.com/Lomray-Software/vite-template)** to more understand how it works or:
54
-
55
- 1. Add plugin to vite config:
56
- ```typescript
57
- /**
58
- * vite.config.ts
59
- */
60
-
61
- import { defineConfig } from 'vite'
62
- import react from '@vitejs/plugin-react'
63
- /**
64
- * Import plugin
65
- */
66
- import SsrBoost from '@lomray/vite-ssr-boost/plugin';
67
-
68
- // https://vitejs.dev/config/
69
- export default defineConfig({
70
- /**
71
- * Change root not necessary, but more understandable
72
- */
73
- root: 'src',
74
- publicDir: '../public',
75
- build: {
76
- outDir: '../build',
77
- },
78
- /**
79
- * Put here
80
- */
81
- plugins: [SsrBoost(), react()],
82
- });
83
-
84
- ```
85
- 2. Create `client` entrypoint:
86
-
87
- ```typescript jsx
88
- /**
89
- * src/client.tsx
90
- */
91
- import entryClient from '@lomray/vite-ssr-boost/browser/entry';
92
- import App from './App.tsx'
93
- import routes from './routes';
94
-
95
- void entryClient(App, routes, {
96
- /**
97
- * (optional). Client configuration
98
- */
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',
113
- });
114
- ```
115
-
116
- 3. Create `server` entrypoint:
117
-
118
- ```typescript jsx
119
- /**
120
- * src/server.ts
121
- */
122
- import entryServer from '@lomray/vite-ssr-boost/node/entry';
123
- import App from './App';
124
- import routes from './routes';
125
-
126
- export default entryServer(App, routes, {
127
- /**
128
- * Request timeout (If your backend is slow, increase this value)
129
- */
130
- abortDelay: 15000, // default: 15000 (ms)
131
- /**
132
- * Server configuration (optional)
133
- */
134
- init: () => ({
135
- /**
136
- * (optional). Called once after express server creation.
137
- * E.g. use for configure express middlewares
138
- */
139
- onServerCreated: () => {},
140
- /**
141
- * (optional). Called once after express server started.
142
- */
143
- onServerStarted: () => {},
144
- /**
145
- * (optional). Called on each incoming request.
146
- * E.g. configure request state, create state manager etc.
147
- */
148
- onRequest: async () => {},
149
- /**
150
- * (optional). Called when react router and it's context was created.
151
- * E.g. here you can switch stream depends on req.headers, for search crawlers you can disable stream.
152
- */
153
- onRouterReady: () => {},
154
- /**
155
- * (optional). Called when application shell is ready to send on client.
156
- * E.g. here you can modify header or footer.
157
- */
158
- onShellReady: () => {},
159
- /**
160
- * (optional). Called when application shell or suspense resolved and sent to the client.
161
- * E.g. here you can add some payload like custom state (any manager state) to response.
162
- */
163
- onResponse: () => {},
164
- /**
165
- * (optional). Stream error callback. Catch stream errors.
166
- */
167
- onError: () => {},
168
- /**
169
- * (optional). Called when application shell or all html (depends on stream option) is ready to send on client.
170
- * E.g. here you can send any context or state to client.
171
- */
172
- getState: () => {},
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: {},
202
- });
203
- ```
204
-
205
- 4. Replace `package.json` scripts:
206
-
207
- ```json
208
- {
209
- ...
210
- "scripts": {
211
- "develop": "ssr-boost dev",
212
- "build": "ssr-boost build",
213
- "start:ssr": "ssr-boost start",
214
- "start:spa": "ssr-boost start --only-client",
215
- "preview": "ssr-boost preview"
216
- },
217
- ...
218
- }
219
- ```
220
-
221
- 5. Let's do the magic:
222
-
223
- ```shell
224
- npm run develop
225
- ```
226
-
227
- ## Plugin options
228
- ```typescript
229
- import SsrBoost from '@lomray/vite-ssr-boost/plugin';
230
- import type { FCRoute } from '@lomray/vite-ssr-boost/interfaces/fc-route';
32
+ ## Install
231
33
 
232
- /**
233
- * Configuration
234
- */
235
- SsrBoost({
236
- /**
237
- * index.html file path
238
- */
239
- indexFile: 'index.html', // default: index.html
240
- /**
241
- * Server entrypoint file
242
- */
243
- serverFile: 'server.ts', // default: server.ts
244
- /**
245
- * Client entrypoint file
246
- */
247
- clientFile: 'client.ts', // default: client.ts
248
- /**
249
- * Add tsconfig aliases to vite config aliases
250
- */
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
265
- })
34
+ ```bash
35
+ npm i @lomray/vite-ssr-boost
266
36
  ```
267
37
 
268
- ## Useful imports
269
- ```typescript tsx
270
- /**
271
- * Components
272
- */
273
- // Navigate component based on react-router-dom with server-side support
274
- import Navigate from '@lomray/vite-ssr-boost/components/navigate';
275
- // Change server response status
276
- import ResponseStatus from '@lomray/vite-ssr-boost/components/response-status';
277
- // Scroll page to top after navigate
278
- import ScrollToTop from '@lomray/vite-ssr-boost/components/scroll-to-top';
279
- // HOC for wrap component in Suspense
280
- import withSuspense from '@lomray/vite-ssr-boost/components/with-suspense';
281
- // Only client side components
282
- import OnlyClient from '@lomray/vite-ssr-boost/components/only-client';
38
+ ## Documentation
283
39
 
284
- /**
285
- * Helpers
286
- */
287
- // Get server state (e.g. state manager) on client side
288
- import getServerState from '@lomray/vite-ssr-boost/helpers/get-server-state';
289
-
290
- /**
291
- * Interfaces
292
- */
293
- // interfaces for route components
294
- import type { FCRoute, FCCRoute } from '@lomray/vite-ssr-boost/interfaces/fc-route';
295
- // interface for define routes
296
- import type { TRouteObject } from '@lomray/vite-ssr-boost/interfaces/route-object';
297
- ```
298
-
299
- Client side components import example:
300
- ```typescript jsx
301
- <OnlyClient load={() => import('external-package')}>
302
- {(LoadedComponent) => (
303
- <LoadedComponent />
304
- )}
305
- </OnlyClient>
306
- ```
307
-
308
- ## CLI
309
- Explore all commands and options:
310
- ```shell
311
- ssr-boost -h
312
- ```
313
-
314
- ## WARNING
315
- Route imports of the following types are supported:
316
- ```typescript jsx
317
- import { RouteObject } from 'react-router-dom';
318
- import HomePage from './pages/home'; // not lazy imports should be directly in file where it use
319
-
320
- const importPath = './pages/home';
321
-
322
- const routes: RouteObject[] = [
323
- {
324
- path: '/home',
325
- Component: HomePage, // support
326
- element: <AppLayout />, // support
327
- lazy: () => import('./pages/home'), // support
328
- lazy: () => import(importPath), // not support, but you can move logic in separate file and import it with supported case
329
- }
330
- ];
331
- ```
332
- ## Use Cases
333
-
334
- ### Change `basename`
335
- ```typescript jsx
336
- /**
337
- * Configure client
338
- */
339
- void entryClient(App, routes, {
340
- routerOptions: {
341
- basename: '/custom',
342
- },
343
- });
344
- ```
345
- ```typescript jsx
346
- /**
347
- * Configure server
348
- */
349
- export default entryServer(App, routes, {
350
- routerOptions: {
351
- basename: '/custom',
352
- },
353
- });
354
- ```
355
-
356
- ### Change `base` for static assets
357
- ```typescript
358
- // https://vitejs.dev/config/
359
- export default defineConfig({
360
- base: '/static',
361
- });
362
- ```
363
-
364
- ```typescript jsx
365
- /**
366
- * Configure server
367
- * NOTE: 'basename' should be equal to 'base' from vite config
368
- */
369
- export default entryServer(App, routes, {
370
- middlewares: {
371
- expressStatic: {
372
- basename: '/static',
373
- },
374
- },
375
- });
376
- ```
377
-
378
-
379
- ### [Capacitor](https://capacitorjs.com/) additional endpoint
380
- ```typescript
381
- // https://vitejs.dev/config/
382
- export default defineConfig({
383
- plugins: [
384
- SsrBoost({
385
- entrypoint: [
386
- {
387
- name: 'mobile',
388
- type: 'spa',
389
- clientFile: './src/mobile.tsx',
390
- buildOptions: '--mode mobile',
391
- },
392
- ],
393
- }),
394
- react(),
395
- ],
396
- });
397
- ```
398
- ```typescript jsx
399
- const AppMobile: FC = (props) => {
400
- // some mobile logic
401
-
402
- return <App {...props} />;
403
- }
404
-
405
- /**
406
- * src/mobile.tsx
407
- */
408
- void entryClient(AppMobile, routes, {});
409
- ```
410
-
411
-
412
- ## Bugs and feature requests
413
-
414
- Bug or a feature request, [please open a new issue](https://github.com/Lomray-Software/vite-ssr-boost/issues/new).
40
+ Full documentation lives here: [lomray-software.github.io/vite-ssr-boost](https://lomray-software.github.io/vite-ssr-boost/)
415
41
 
416
42
  ## License
43
+
417
44
  Made with 💚
418
45
 
419
46
  Published under [MIT License](./LICENSE).
@@ -1,2 +1,2 @@
1
- import e from"hoist-non-react-statics";import t,{useState as n,useEffect as o}from"react";const r=(r,m)=>{const l=e=>{const[l,c]=n(!1);return o((()=>{c(!0)}),[]),l&&r?t.createElement(r,{...e}):m&&"element"in m?m.element:m&&"Component"in m&&m.Component?t.createElement(m.Component,null):null};return e(l,r),l};export{r as default};
1
+ import e from"hoist-non-react-statics";import t,{useState as n,useEffect as o}from"react";const r=(r,m)=>{const l=e=>{const[l,c]=n(!1);return o((()=>{c(!0)}),[]),l&&r?t.createElement(r,{...e}):m&&"element"in m?m.element:m&&"Component"in m&&m.Component?t.createElement(m.Component,null):null};return r&&e(l,r),l};export{r as default};
2
2
  //# sourceMappingURL=render-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-client.js","sources":["../../src/components/render-client.tsx"],"sourcesContent":["import hoistNonReactStatics from 'hoist-non-react-statics';\nimport type { FC, ReactNode } from 'react';\nimport React, { useEffect, useState } from 'react';\nimport type { FCAny } from '@interfaces/fc';\n\n/**\n * HOC: Render component only on client side\n */\nconst renderClient = <T extends Record<string, any>>(\n Component: FCAny<T> | null | undefined,\n Fallback?: { element: ReactNode } | { Component: FC | null },\n): FC<T> => {\n const Element: FC<T> = (props) => {\n const [shouldRender, setShouldRender] = useState(false);\n\n useEffect(() => {\n setShouldRender(true);\n }, []);\n\n if (shouldRender && Component) {\n return <Component {...props} />;\n }\n\n if (Fallback && 'element' in Fallback) {\n return Fallback.element;\n }\n\n if (Fallback && 'Component' in Fallback && Fallback.Component) {\n return <Fallback.Component />;\n }\n\n return null;\n };\n\n hoistNonReactStatics(Element, Component);\n\n return Element;\n};\n\nexport default renderClient;\n"],"names":["renderClient","Component","Fallback","Element","props","shouldRender","setShouldRender","useState","useEffect","React","createElement","element","hoistNonReactStatics"],"mappings":"0FAQA,MAAMA,EAAe,CACnBC,EACAC,KAEA,MAAMC,EAAkBC,IACtB,MAAOC,EAAcC,GAAmBC,GAAS,GAMjD,OAJAC,GAAU,KACRF,GAAgB,EAAK,GACpB,IAECD,GAAgBJ,EACXQ,EAAAC,cAACT,EAAS,IAAKG,IAGpBF,GAAY,YAAaA,EACpBA,EAASS,QAGdT,GAAY,cAAeA,GAAYA,EAASD,UAC3CQ,EAAAC,cAACR,EAASD,gBAGZ,IAAI,EAKb,OAFAW,EAAqBT,EAASF,GAEvBE,CAAO"}
1
+ {"version":3,"file":"render-client.js","sources":["../../src/components/render-client.tsx"],"sourcesContent":["import hoistNonReactStatics from 'hoist-non-react-statics';\nimport type { FC, ReactNode } from 'react';\nimport React, { useEffect, useState } from 'react';\nimport type { FCAny } from '@interfaces/fc';\n\n/**\n * HOC: Render component only on client side\n */\nconst renderClient = <T extends Record<string, any>>(\n Component: FCAny<T> | null | undefined,\n Fallback?: { element: ReactNode } | { Component: FC | null },\n): FC<T> => {\n const Element: FC<T> = (props) => {\n const [shouldRender, setShouldRender] = useState(false);\n\n useEffect(() => {\n setShouldRender(true);\n }, []);\n\n if (shouldRender && Component) {\n return <Component {...props} />;\n }\n\n if (Fallback && 'element' in Fallback) {\n return Fallback.element;\n }\n\n if (Fallback && 'Component' in Fallback && Fallback.Component) {\n return <Fallback.Component />;\n }\n\n return null;\n };\n\n if (Component) {\n hoistNonReactStatics(Element, Component);\n }\n\n return Element;\n};\n\nexport default renderClient;\n"],"names":["renderClient","Component","Fallback","Element","props","shouldRender","setShouldRender","useState","useEffect","React","createElement","element","hoistNonReactStatics"],"mappings":"0FAQA,MAAMA,EAAe,CACnBC,EACAC,KAEA,MAAMC,EAAkBC,IACtB,MAAOC,EAAcC,GAAmBC,GAAS,GAMjD,OAJAC,GAAU,KACRF,GAAgB,EAAK,GACpB,IAECD,GAAgBJ,EACXQ,EAAAC,cAACT,EAAS,IAAKG,IAGpBF,GAAY,YAAaA,EACpBA,EAASS,QAGdT,GAAY,cAAeA,GAAYA,EAASD,UAC3CQ,EAAAC,cAACR,EAASD,gBAGZ,IAAI,EAOb,OAJIA,GACFW,EAAqBT,EAASF,GAGzBE,CAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sources":["../../src/node/render.tsx"],"sourcesContent":["import chalk from 'chalk';\nimport type { Request, Response as ExpressResponse } from 'express';\nimport React from 'react';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport type { StaticHandlerContext, StaticHandler } from 'react-router';\nimport { createStaticRouter, StaticRouterProvider } from 'react-router';\nimport StreamError from '@constants/stream-error';\nimport type { IServerContext } from '@context/server';\nimport { ServerProvider } from '@context/server';\nimport handleResponse from '@helpers/handle-response';\nimport type { IObtainStreamErrorOut } from '@helpers/obtain-stream-error';\nimport obtainStreamError from '@helpers/obtain-stream-error';\nimport createFetchRequest from '@node/create-fetch-request';\nimport type { TApp } from '@node/entry';\nimport writeResponse from '@node/write-response';\nimport type ServerConfig from '@services/server-config';\nimport SsrManifest from '@services/ssr-manifest';\n\nexport interface IRequestContext<TAppProps = Record<any, any>> {\n req: Request;\n res: ExpressResponse;\n appProps: NonNullable<TAppProps>;\n html: { header: string; footer: string };\n routerContext?: StaticHandlerContext;\n serverContext?: IServerContext;\n isStream?: boolean;\n hasEarlyHints?: boolean;\n didError?: StreamError;\n}\n\nexport type TRender<TAppProps = Record<any, any>> = (\n config: ServerConfig,\n context: IRequestContext<TAppProps>,\n options: IRenderOptions,\n) => Promise<void>;\n\nexport interface IRenderParams<TAppProps = Record<string, any>> {\n App: TApp<TAppProps>;\n handler: StaticHandler;\n}\n\nexport interface IRenderOptions<TAppProps = Record<string, any>> {\n abortDelay?: number;\n onRouterReady?: (params: {\n context: IRequestContext<TAppProps>;\n }) => Promise<IRouterReadyOut> | IRouterReadyOut;\n onShellReady?: (params: { context: IRequestContext<TAppProps> }) => IShellReadyOut;\n onShellError?: (params: {\n context: IRequestContext<TAppProps>;\n error: Error;\n }) => string | undefined | void; // return html or undefined\n onError?: (params: { context: IRequestContext<TAppProps>; error: IObtainStreamErrorOut }) => void;\n onResponse?: (params: {\n context: IRequestContext<TAppProps>;\n html: string;\n }) => string | undefined | void;\n getState?: (params: {\n context: IRequestContext<TAppProps>;\n }) => Record<string, Record<string, any>> | undefined | void;\n}\n\nexport interface IRouterReadyOut {\n isStream?: boolean;\n}\n\nexport interface IShellReadyOut {\n header?: string;\n footer?: string;\n}\n\n/**\n * Render application\n */\nasync function render(\n { App, handler }: IRenderParams, // @see entry (bind)\n config: ServerConfig,\n context: IRequestContext,\n {\n onRouterReady,\n onShellReady,\n onResponse,\n onShellError,\n onError,\n getState,\n abortDelay = 15000,\n }: IRenderOptions,\n): Promise<void> {\n const { req, res } = context;\n const fetchRequest = createFetchRequest(req);\n\n context.routerContext = (await handler.query(fetchRequest, {\n requestContext: context,\n })) as StaticHandlerContext;\n\n /**\n * Handle response from page loader, router context can be Response\n */\n const statusCode = handleResponse(res, context.routerContext);\n\n if (!statusCode) {\n return;\n }\n\n SsrManifest.get(config).injectAssets(context);\n\n const { isStream = true } = (await onRouterReady?.({ context })) ?? {};\n\n context.isStream = isStream;\n context.serverContext = {\n response: null,\n isServer: true,\n basename: context.routerContext?.basename,\n };\n\n const router = createStaticRouter(handler.dataRoutes, context.routerContext);\n const write = res.write.bind(res) as ExpressResponse['write'];\n const Logger = config.getLogger();\n let abortTimer: NodeJS.Timeout | undefined = undefined;\n\n /**\n * Listen response and stream to add possibility modify html on fly\n * E.g. listen stream and append some data\n */\n res.write = (data: string | Uint8Array, ...args): boolean => {\n const isString = typeof data === 'string';\n const html = isString ? data : Buffer.from(data).toString();\n const modifiedHtml = onResponse?.({ context, html });\n\n if (modifiedHtml) {\n // @ts-ignore\n return write(isString ? modifiedHtml : Buffer.from(modifiedHtml), ...args) as boolean;\n }\n\n // @ts-ignore\n return write(data, ...args) as boolean;\n };\n\n const { serverContext, routerContext, appProps } = context;\n\n const { pipe, abort } = renderToPipeableStream(\n <ServerProvider context={serverContext}>\n <App server={{ ...appProps, req }}>\n <StaticRouterProvider router={router} context={routerContext} hydrate={false} />\n </App>\n </ServerProvider>,\n {\n onShellReady(): void {\n if (!isStream) {\n return;\n }\n\n writeResponse(context, {\n pipe,\n statusCode,\n onShellReady,\n getState,\n });\n },\n onAllReady(): void {\n clearTimeout(abortTimer);\n\n if (isStream) {\n return;\n }\n\n writeResponse(context, {\n pipe,\n statusCode,\n onShellReady,\n getState,\n });\n },\n onShellError(e: Error): void {\n const htmlError =\n onShellError?.({ context, error: e }) ||\n `<!doctype html><p>Something went wrong: ${e.message}</p>`;\n\n res.status(500);\n res.setHeader('content-type', 'text/html');\n res.send(htmlError);\n },\n onError(err): void {\n clearTimeout(abortTimer);\n\n const error = obtainStreamError(err);\n const { code, message } = error;\n const { didError } = context;\n\n context.didError = didError ?? code;\n\n onError?.({ context, error });\n Logger.info(chalk.red(`Stream error. Code: ${code}`));\n\n if (\n [StreamError.RenderAborted, StreamError.RenderTimeout, StreamError.RenderCancel].includes(\n code,\n )\n ) {\n Logger.info(chalk.dim(message));\n\n return;\n }\n\n Logger.error(err as string);\n },\n },\n );\n\n // Abandon and switch to client rendering if enough time passes.\n abortTimer = setTimeout(() => {\n context.didError = StreamError.RenderTimeout;\n abort();\n }, abortDelay);\n\n // Detect cancel request\n req.on('close', () => {\n context.didError = StreamError.RenderCancel;\n abort();\n });\n}\n\nexport default render;\n"],"names":["async","render","App","handler","config","context","onRouterReady","onShellReady","onResponse","onShellError","onError","getState","abortDelay","req","res","fetchRequest","createFetchRequest","routerContext","query","requestContext","statusCode","handleResponse","SsrManifest","get","injectAssets","isStream","serverContext","response","isServer","basename","router","createStaticRouter","dataRoutes","write","bind","Logger","getLogger","abortTimer","data","args","isString","html","Buffer","from","toString","modifiedHtml","appProps","pipe","abort","renderToPipeableStream","React","createElement","ServerProvider","server","StaticRouterProvider","hydrate","writeResponse","onAllReady","clearTimeout","e","htmlError","error","message","status","setHeader","send","err","obtainStreamError","code","didError","info","chalk","red","StreamError","RenderAborted","RenderTimeout","RenderCancel","includes","dim","setTimeout","on"],"mappings":"ueAyEAA,eAAeC,GACbC,IAAEA,EAAGC,QAAEA,GACPC,EACAC,GACAC,cACEA,EAAaC,aACbA,EAAYC,WACZA,EAAUC,aACVA,EAAYC,QACZA,EAAOC,SACPA,EAAQC,WACRA,EAAa,OAGf,MAAMC,IAAEA,EAAGC,IAAEA,GAAQT,EACfU,EAAeC,EAAmBH,GAExCR,EAAQY,oBAAuBd,EAAQe,MAAMH,EAAc,CACzDI,eAAgBd,IAMlB,MAAMe,EAAaC,EAAeP,EAAKT,EAAQY,eAE/C,IAAKG,EACH,OAGFE,EAAYC,IAAInB,GAAQoB,aAAanB,GAErC,MAAMoB,SAAEA,GAAW,SAAgBnB,IAAgB,CAAED,cAAe,CAAA,EAEpEA,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,EAAAC,cAACC,EAAc,CAAC/C,QAASqB,GACvBwB,EAAAC,cAACjD,GAAImD,OAAQ,IAAKP,EAAUjC,QAC1BqC,EAAAC,cAACG,EAAoB,CAACxB,OAAQA,EAAQzB,QAASY,EAAesC,SAAS,MAG3E,CACEhD,eACOkB,GAIL+B,EAAcnD,EAAS,CACrB0C,OACA3B,aACAb,eACAI,YAEJ,EACA8C,aACEC,aAAarB,GAETZ,GAIJ+B,EAAcnD,EAAS,CACrB0C,OACA3B,aACAb,eACAI,YAEJ,EACAF,aAAakD,GACX,MAAMC,EACJnD,IAAe,CAAEJ,UAASwD,MAAOF,KACjC,2CAA2CA,EAAEG,cAE/ChD,EAAIiD,OAAO,KACXjD,EAAIkD,UAAU,eAAgB,aAC9BlD,EAAImD,KAAKL,EACX,EACAlD,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,EACf,IAKJ7B,EAAa0C,YAAW,KACtB1E,EAAQgE,SAAWI,EAAYE,cAC/B3B,GAAO,GACNpC,GAGHC,EAAImE,GAAG,SAAS,KACd3E,EAAQgE,SAAWI,EAAYG,aAC/B5B,GAAO,GAEX"}
1
+ {"version":3,"file":"render.js","sources":["../../src/node/render.tsx"],"sourcesContent":["import chalk from 'chalk';\nimport type { Request, Response as ExpressResponse } from 'express';\nimport React from 'react';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport type { StaticHandlerContext, StaticHandler } from 'react-router';\nimport { createStaticRouter, StaticRouterProvider } from 'react-router';\nimport StreamError from '@constants/stream-error';\nimport type { IServerContext } from '@context/server';\nimport { ServerProvider } from '@context/server';\nimport handleResponse from '@helpers/handle-response';\nimport type { IObtainStreamErrorOut } from '@helpers/obtain-stream-error';\nimport obtainStreamError from '@helpers/obtain-stream-error';\nimport createFetchRequest from '@node/create-fetch-request';\nimport type { TApp } from '@node/entry';\nimport writeResponse from '@node/write-response';\nimport type ServerConfig from '@services/server-config';\nimport SsrManifest from '@services/ssr-manifest';\n\nexport interface IRequestContext<TAppProps = Record<any, any>> {\n req: Request;\n res: ExpressResponse;\n appProps: NonNullable<TAppProps>;\n html: { header: string; footer: string };\n routerContext?: StaticHandlerContext;\n serverContext?: IServerContext;\n isStream?: boolean;\n hasEarlyHints?: boolean;\n didError?: StreamError;\n}\n\nexport type TRender<TAppProps = Record<any, any>> = (\n config: ServerConfig,\n context: IRequestContext<TAppProps>,\n options: IRenderOptions,\n) => Promise<void>;\n\nexport interface IRenderParams<TAppProps = Record<string, any>> {\n App: TApp<TAppProps>;\n handler: StaticHandler;\n}\n\nexport interface IRenderOptions<TAppProps = Record<string, any>> {\n abortDelay?: number;\n onRouterReady?: (params: {\n context: IRequestContext<TAppProps>;\n }) => Promise<IRouterReadyOut> | IRouterReadyOut;\n onShellReady?: (params: { context: IRequestContext<TAppProps> }) => IShellReadyOut;\n onShellError?: (params: {\n context: IRequestContext<TAppProps>;\n error: Error;\n }) => string | undefined | void; // return html or undefined\n onError?: (params: { context: IRequestContext<TAppProps>; error: IObtainStreamErrorOut }) => void;\n onResponse?: (params: {\n context: IRequestContext<TAppProps>;\n html: string;\n }) => string | undefined | void;\n getState?: (params: {\n context: IRequestContext<TAppProps>;\n }) => Record<string, Record<string, any>> | undefined | void;\n}\n\nexport interface IRouterReadyOut {\n isStream?: boolean;\n}\n\nexport interface IShellReadyOut {\n header?: string;\n footer?: string;\n}\n\n/**\n * Render application\n */\nasync function render(\n { App, handler }: IRenderParams, // @see entry (bind)\n config: ServerConfig,\n context: IRequestContext,\n {\n onRouterReady,\n onShellReady,\n onResponse,\n onShellError,\n onError,\n getState,\n abortDelay = 15000,\n }: IRenderOptions,\n): Promise<void> {\n const { req, res } = context;\n const fetchRequest = createFetchRequest(req);\n\n context.routerContext = (await handler.query(fetchRequest, {\n requestContext: context,\n })) as StaticHandlerContext;\n\n /**\n * Handle response from page loader, router context can be Response\n */\n const statusCode = handleResponse(res, context.routerContext);\n\n if (!statusCode) {\n return;\n }\n\n SsrManifest.get(config).injectAssets(context);\n\n const { isStream = true } = (await onRouterReady?.({ context })) ?? {};\n\n context.isStream = isStream;\n context.serverContext = {\n response: null,\n isServer: true,\n basename: context.routerContext?.basename,\n };\n\n const router = createStaticRouter(handler.dataRoutes, context.routerContext);\n const write = res.write.bind(res) as (...args: unknown[]) => boolean;\n const Logger = config.getLogger();\n let abortTimer: NodeJS.Timeout | 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 return write(isString ? modifiedHtml : Buffer.from(modifiedHtml), ...args);\n }\n\n return write(data, ...args);\n };\n\n const { serverContext, routerContext, appProps } = context;\n\n const { pipe, abort } = renderToPipeableStream(\n <ServerProvider context={serverContext}>\n <App server={{ ...appProps, req }}>\n <StaticRouterProvider router={router} context={routerContext} hydrate={false} />\n </App>\n </ServerProvider>,\n {\n onShellReady(): void {\n if (!isStream) {\n return;\n }\n\n writeResponse(context, {\n pipe,\n statusCode,\n onShellReady,\n getState,\n });\n },\n onAllReady(): void {\n clearTimeout(abortTimer);\n\n if (isStream) {\n return;\n }\n\n writeResponse(context, {\n pipe,\n statusCode,\n onShellReady,\n getState,\n });\n },\n onShellError(e: Error): void {\n const htmlError =\n onShellError?.({ context, error: e }) ||\n `<!doctype html><p>Something went wrong: ${e.message}</p>`;\n\n res.status(500);\n res.setHeader('content-type', 'text/html');\n res.send(htmlError);\n },\n onError(err): void {\n clearTimeout(abortTimer);\n\n const error = obtainStreamError(err);\n const { code, message } = error;\n const { didError } = context;\n\n context.didError = didError ?? code;\n\n onError?.({ context, error });\n Logger.info(chalk.red(`Stream error. Code: ${code}`));\n\n if (\n [StreamError.RenderAborted, StreamError.RenderTimeout, StreamError.RenderCancel].includes(\n code,\n )\n ) {\n Logger.info(chalk.dim(message));\n\n return;\n }\n\n Logger.error(err as string);\n },\n },\n );\n\n // Abandon and switch to client rendering if enough time passes.\n abortTimer = setTimeout(() => {\n context.didError = StreamError.RenderTimeout;\n abort();\n }, abortDelay);\n\n // Detect cancel request\n req.on('close', () => {\n context.didError = StreamError.RenderCancel;\n abort();\n });\n}\n\nexport default render;\n"],"names":["async","render","App","handler","config","context","onRouterReady","onShellReady","onResponse","onShellError","onError","getState","abortDelay","req","res","fetchRequest","createFetchRequest","routerContext","query","requestContext","statusCode","handleResponse","SsrManifest","get","injectAssets","isStream","serverContext","response","isServer","basename","router","createStaticRouter","dataRoutes","write","bind","Logger","getLogger","abortTimer","data","args","isString","html","Buffer","from","toString","modifiedHtml","appProps","pipe","abort","renderToPipeableStream","React","createElement","ServerProvider","server","StaticRouterProvider","hydrate","writeResponse","onAllReady","clearTimeout","e","htmlError","error","message","status","setHeader","send","err","obtainStreamError","code","didError","info","chalk","red","StreamError","RenderAborted","RenderTimeout","RenderCancel","includes","dim","setTimeout","on"],"mappings":"ueAyEAA,eAAeC,GACbC,IAAEA,EAAGC,QAAEA,GACPC,EACAC,GACAC,cACEA,EAAaC,aACbA,EAAYC,WACZA,EAAUC,aACVA,EAAYC,QACZA,EAAOC,SACPA,EAAQC,WACRA,EAAa,OAGf,MAAMC,IAAEA,EAAGC,IAAEA,GAAQT,EACfU,EAAeC,EAAmBH,GAExCR,EAAQY,oBAAuBd,EAAQe,MAAMH,EAAc,CACzDI,eAAgBd,IAMlB,MAAMe,EAAaC,EAAeP,EAAKT,EAAQY,eAE/C,IAAKG,EACH,OAGFE,EAAYC,IAAInB,GAAQoB,aAAanB,GAErC,MAAMoB,SAAEA,GAAW,SAAgBnB,IAAgB,CAAED,cAAe,CAAA,EAEpEA,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,EACKZ,EAAMO,EAAWK,EAAeH,OAAOC,KAAKE,MAAkBN,GAGhEN,EAAMK,KAASC,EAAK,EAG7B,MAAMb,cAAEA,EAAaT,cAAEA,EAAa6B,SAAEA,GAAazC,GAE7C0C,KAAEA,EAAIC,MAAEA,GAAUC,EACtBC,EAAAC,cAACC,EAAc,CAAC/C,QAASqB,GACvBwB,EAAAC,cAACjD,GAAImD,OAAQ,IAAKP,EAAUjC,QAC1BqC,EAAAC,cAACG,EAAoB,CAACxB,OAAQA,EAAQzB,QAASY,EAAesC,SAAS,MAG3E,CACEhD,eACOkB,GAIL+B,EAAcnD,EAAS,CACrB0C,OACA3B,aACAb,eACAI,YAEJ,EACA8C,aACEC,aAAarB,GAETZ,GAIJ+B,EAAcnD,EAAS,CACrB0C,OACA3B,aACAb,eACAI,YAEJ,EACAF,aAAakD,GACX,MAAMC,EACJnD,IAAe,CAAEJ,UAASwD,MAAOF,KACjC,2CAA2CA,EAAEG,cAE/ChD,EAAIiD,OAAO,KACXjD,EAAIkD,UAAU,eAAgB,aAC9BlD,EAAImD,KAAKL,EACX,EACAlD,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,EACf,IAKJ7B,EAAa0C,YAAW,KACtB1E,EAAQgE,SAAWI,EAAYE,cAC/B3B,GAAO,GACNpC,GAGHC,EAAImE,GAAG,SAAS,KACd3E,EAAQgE,SAAWI,EAAYG,aAC/B5B,GAAO,GAEX"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lomray/vite-ssr-boost",
3
- "version": "7.0.0-beta.2",
3
+ "version": "7.1.0-beta.1",
4
4
  "description": "Vite plugin for create awesome SSR or SPA applications on React.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -26,55 +26,55 @@
26
26
  "scripts": {
27
27
  "build": "rollup -c",
28
28
  "build:watch": "rollup -c -w",
29
+ "docs:dev": "vitepress dev docs",
30
+ "docs:build": "vitepress build docs",
31
+ "docs:preview": "vitepress preview docs",
29
32
  "release": "npm run build && cd lib && npm publish",
30
- "lint:check": "eslint \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
31
- "lint:format": "eslint --fix \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
33
+ "lint:check": "eslint . --max-warnings=0",
34
+ "lint:format": "eslint . --fix",
32
35
  "ts:check": "tsc --project ./tsconfig.json --skipLibCheck --noemit",
33
36
  "test": "vitest run",
34
37
  "test:coverage": "vitest run --coverage"
35
38
  },
36
39
  "dependencies": {
37
40
  "chalk": "^5.6.2",
38
- "commander": "^14.0.2",
41
+ "commander": "^14.0.3",
39
42
  "compression": "^1.8.1",
40
- "express": "^5.1.0",
43
+ "express": "^5.2.1",
41
44
  "hoist-non-react-statics": "^3.3.2",
42
45
  "json5": "^2.2.3"
43
46
  },
44
47
  "devDependencies": {
45
- "@commitlint/cli": "^20.1.0",
46
- "@commitlint/config-conventional": "^20.0.0",
47
- "@lomray/eslint-config-react": "^5.0.6",
48
- "@lomray/prettier-config": "^2.0.1",
49
- "@rollup/plugin-terser": "^0.4.4",
50
- "@testing-library/react": "^16.3.0",
51
- "@types/babel__generator": "^7.6.8",
52
- "@types/babel__traverse": "^7.20.6",
53
- "@types/chai": "^5.2.3",
48
+ "@commitlint/cli": "^20.5.0",
49
+ "@commitlint/config-conventional": "^20.5.0",
50
+ "@lomray/eslint-config-react": "^6.0.3",
51
+ "@lomray/prettier-config": "^2.1.0",
52
+ "@rollup/plugin-terser": "^1.0.0",
53
+ "@testing-library/react": "^16.3.2",
54
+ "@types/babel__generator": "^7.27.0",
55
+ "@types/babel__traverse": "^7.28.0",
54
56
  "@types/compression": "^1.8.1",
55
57
  "@types/hoist-non-react-statics": "^3.3.7",
56
- "@types/react-dom": "^18.3.7",
57
- "@types/sinon": "^17.0.4",
58
- "@types/sinon-chai": "^4.0.0",
59
- "@vitest/coverage-v8": "^4.0.5",
58
+ "@types/react-dom": "^19.2.3",
59
+ "@types/sinon": "^21.0.0",
60
+ "@vitest/coverage-v8": "^4.1.0",
60
61
  "@zerollup/ts-transform-paths": "^1.7.18",
61
- "chai": "^6.2.0",
62
- "eslint": "^8.57.1",
62
+ "eslint": "^9.39.4",
63
63
  "husky": "^9.1.7",
64
- "jsdom": "^27.0.1",
65
- "lint-staged": "^16.2.6",
66
- "prettier": "^3.6.2",
67
- "rollup": "^4.52.5",
64
+ "jsdom": "^29.0.1",
65
+ "lint-staged": "^16.4.0",
66
+ "prettier": "^3.8.1",
67
+ "rollup": "^4.59.1",
68
68
  "rollup-plugin-copy": "^3.5.0",
69
69
  "rollup-plugin-folder-input": "^1.0.1",
70
70
  "rollup-plugin-peer-deps-external": "^2.2.4",
71
71
  "rollup-plugin-preserve-shebangs": "^0.2.0",
72
72
  "rollup-plugin-ts": "^3.4.5",
73
- "semantic-release": "^25.0.1",
74
- "sinon": "^21.0.0",
75
- "sinon-chai": "^4.0.1",
73
+ "semantic-release": "^25.0.3",
74
+ "sinon": "^21.0.3",
76
75
  "typescript": "^5.3.3",
77
- "vitest": "^4.0.5"
76
+ "vitepress": "^1.6.4",
77
+ "vitest": "^4.1.0"
78
78
  },
79
79
  "peerDependencies": {
80
80
  "@babel/generator": ">=7.23.0",
@@ -1,2 +1,2 @@
1
- import e from"fs";import{resolve as t}from"node:path";import r from"path";import n from"@babel/generator";import*as a from"@babel/parser";import o from"@babel/traverse";import{isObjectProperty as i,isIdentifier as l,isBooleanLiteral as s,isJSXElement as p,isFunction as u,isArrayExpression as c,objectProperty as m,identifier as f,stringLiteral as d,isJSXIdentifier as h,isObjectExpression as y}from"@babel/types";import v from"../constants/plugin-name.js";import g from"./path-normalize.js";const E=n.default??n,x=o.default??o;class I{pathNormalize;config;constructor(e,t){this.config=e,this.pathNormalize=new g(e,t)}parse(){const{clientFile:e,root:r}=this.config.getParams(),n=t(r,e),a=this.findRoutesEntrypoint(n);if(!a?.routesPath)throw new Error(`Unable to find routes file import in ${e}`);const{routesPath:o,exportName:i}=a,l=this.resolveFilename(o,n);return this.recursiveBuildRoutesTree(l,i)}parseFile(t){try{const r=e.readFileSync(t,"utf-8");return a.parse(r,{sourceType:"module",plugins:["typescript","jsx"]})}catch(e){return null}}getImportPath(e,t){let r=null,n=null;return x(e,{ImportDeclaration(e){const a=e.node;a.specifiers.forEach((e=>{e.local.name===t&&(n="ImportDefaultSpecifier"===e.type?null:t,r=a.source.value)}))}}),{routesPath:r,exportName:n}}findRoutesDefinition(e,t){let r=t;if(x(e,{ExportNamedDeclaration({node:e}){!e.declaration&&e.specifiers.length>0&&e.specifiers.forEach((e=>{const n=e.exported.name;null===t&&"ExportSpecifier"===e.type?"default"===e.local.name&&(r=n):n===t&&(r=e.local.name)}))},ExportDefaultDeclaration({node:e}){null===t&&("Identifier"===e.declaration.type?r=e.declaration.name:"VariableDeclaration"===e.declaration.type&&(r=e.declaration.declarations[0].id.name))}}),r){let t=null;return x(e,{VariableDeclaration({node:e}){e.declarations.forEach((n=>{n.id.name===r&&(t=e)}))}}),t}return null}findRoutesEntrypoint(e){const t=this.parseFile(e);let r=null;return t?(x(t,{CallExpression({node:e}){"entryClient"===e.callee.name&&e.arguments.length>=2&&"Identifier"===e.arguments[1].type&&(r=e.arguments[1].name)}}),this.getImportPath(t,r)):r}resolveFilename(e,t){let n=e;(e.startsWith("./")||e.startsWith("../"))&&t&&(n=r.resolve(r.dirname(t),e));const a=this.pathNormalize.getAppPath(n,!0);return this.pathNormalize.findAppFile(a)}parseRoutesArray(e,t,r){const n=[];return e.forEach(((e,a)=>{if("ObjectExpression"===e.type){const o={index:a,import:"",children:[]};e.properties.forEach((e=>{const n=e;if("children"===n.key.name&&"ArrayExpression"===n.value.type&&(o.children=this.parseRoutesArray(n.value.elements,t,r)),"lazy"===n.key.name&&"ArrowFunctionExpression"===n.value.type){const e=n.value.body;if("CallExpression"===e.type&&"Import"===e.callee.type){const[t]=e.arguments;"StringLiteral"===t.type&&(o.import=t.value)}}if("Component"===n.key.name&&"Identifier"===n.value.type){const e=n.value.name,{path:r}=t[e]??{};r&&(o.import=r)}if("element"===n.key.name&&"JSXElement"===n.value.type){const e=n.value?.openingElement?.name?.name,{path:r}=t[e]??{};r&&(o.import=r)}if("children"===n.key.name&&"Identifier"===n.value.type){const e=n.value.name,{path:a,isDefault:i}=t[e]??{};if(a){const t=this.resolveFilename(a,r);t&&(o.children=this.recursiveBuildRoutesTree(t,i?null:e))}}})),(o.import||o.children.length>0)&&n.push(o)}})),n}static parseImportsMap(e){const t={};return x(e,{ImportDeclaration(e){const r=e.node;r.specifiers.forEach((e=>{t[e.local.name]={path:r.source.value,isDefault:"ImportDefaultSpecifier"===e.type}}))}}),t}recursiveBuildRoutesTree(e,t=null){if(!e)return[];const r=this.parseFile(e);if(!r)return[];const n=this.findRoutesDefinition(r,t),a=[];if(!n)return a;const o=I.parseImportsMap(r),i=n.declarations[0].init?.elements;return a.push(...this.parseRoutesArray(i,o,e)),a}static processRouteFileCode(e,t,r,n){e.node.properties.forEach((a=>{if(i(a)&&l(a.key)){if("lazy"===a.key.name&&"ArrowFunctionExpression"===a.value.type){const t=a.value.body,o=e.node.properties.find((e=>i(e)&&l(e.key)&&"onlyClient"===e.key.name));if(a.value={type:"CallExpression",callee:{type:"Identifier",name:"n"},arguments:i(o)&&(s(o.value)||p(o.value)||u(o.value)||l(o.value))?[a.value,o.value]:[a.value]},o&&(e.node.properties=e.node.properties.filter((e=>e!==o))),n(),"CallExpression"===t.type&&"Import"===t.callee.type){const[n]=t.arguments,o=e.findParent?.((e=>c(e.node)));if(o&&"StringLiteral"===n.type&&n.value&&r){const t=m(f("pathId"),d(n.value));e.node.properties.splice(e.node.properties.indexOf(a)+1,0,t)}}}if("element"===a.key.name||"Component"===a.key.name){let n="";p(a.value)&&h(a.value.openingElement.name)?n=a.value.openingElement.name.name:l(a.value)&&(n=a.value.name);const o=e.findParent?.((e=>c(e.node))),i=t[n]?.path;if(o&&i&&r){const t=m(f("pathId"),d(i));e.node.properties.splice(e.node.properties.indexOf(a)+1,0,t)}}"children"===a.key.name&&c(a.value)&&a.value.elements.forEach((e=>{y(e)&&I.processRouteFileCode({node:e},t,r,n)}))}}))}static handleRoutes(e,t){if(!e)return e;const r=a.parse(e,{sourceType:"module",plugins:["typescript","jsx"]});if(!r)return e;const n=I.parseImportsMap(r);let o=!1;return x(r,{ObjectExpression(e){I.processRouteFileCode(e,n,t,(()=>{o=!0}))}}),o&&r.program.body.unshift({type:"ImportDeclaration",specifiers:[{type:"ImportDefaultSpecifier",local:{type:"Identifier",name:"n"}}],source:{type:"StringLiteral",value:`${v}/helpers/import-route`}}),E(r,{retainLines:!0}).code}}export{I as default};
1
+ import e from"fs";import{resolve as t}from"node:path";import r from"path";import n from"@babel/generator";import*as a from"@babel/parser";import o from"@babel/traverse";import{isObjectProperty as i,isIdentifier as l,isBooleanLiteral as s,isJSXElement as p,isFunction as u,isArrayExpression as c,objectProperty as m,identifier as f,stringLiteral as d,isJSXIdentifier as h,isObjectExpression as y}from"@babel/types";import v from"../constants/plugin-name.js";import g from"./path-normalize.js";const E=n.default??n,x=o.default??o;class I{pathNormalize;config;constructor(e,t){this.config=e,this.pathNormalize=new g(e,t)}parse(){const{clientFile:e,root:r}=this.config.getParams(),n=t(r,e),a=this.findRoutesEntrypoint(n);if(!a?.routesPath)throw new Error(`Unable to find routes file import in ${e}`);const{routesPath:o,exportName:i}=a,l=this.resolveFilename(o,n);return this.recursiveBuildRoutesTree(l,i)}parseFile(t){try{const r=e.readFileSync(t,"utf-8");return a.parse(r,{sourceType:"module",plugins:["typescript","jsx"]})}catch{return null}}getImportPath(e,t){let r=null,n=null;return x(e,{ImportDeclaration(e){const a=e.node;a.specifiers.forEach((e=>{e.local.name===t&&(n="ImportDefaultSpecifier"===e.type?null:t,r=a.source.value)}))}}),{routesPath:r,exportName:n}}findRoutesDefinition(e,t){let r=t;if(x(e,{ExportNamedDeclaration({node:e}){!e.declaration&&e.specifiers.length>0&&e.specifiers.forEach((e=>{const n=e.exported.name;null===t&&"ExportSpecifier"===e.type?"default"===e.local.name&&(r=n):n===t&&(r=e.local.name)}))},ExportDefaultDeclaration({node:e}){null===t&&("Identifier"===e.declaration.type?r=e.declaration.name:"VariableDeclaration"===e.declaration.type&&(r=e.declaration.declarations[0].id.name))}}),r){let t=null;return x(e,{VariableDeclaration({node:e}){e.declarations.forEach((n=>{n.id.name===r&&(t=e)}))}}),t}return null}findRoutesEntrypoint(e){const t=this.parseFile(e);let r=null;return t?(x(t,{CallExpression({node:e}){"entryClient"===e.callee.name&&e.arguments.length>=2&&"Identifier"===e.arguments[1].type&&(r=e.arguments[1].name)}}),this.getImportPath(t,r)):r}resolveFilename(e,t){let n=e;(e.startsWith("./")||e.startsWith("../"))&&t&&(n=r.resolve(r.dirname(t),e));const a=this.pathNormalize.getAppPath(n,!0);return this.pathNormalize.findAppFile(a)}parseRoutesArray(e,t,r){const n=[];return e.forEach(((e,a)=>{if("ObjectExpression"===e.type){const o={index:a,import:"",children:[]};e.properties.forEach((e=>{const n=e;if("children"===n.key.name&&"ArrayExpression"===n.value.type&&(o.children=this.parseRoutesArray(n.value.elements,t,r)),"lazy"===n.key.name&&"ArrowFunctionExpression"===n.value.type){const e=n.value.body;if("CallExpression"===e.type&&"Import"===e.callee.type){const[t]=e.arguments;"StringLiteral"===t.type&&(o.import=t.value)}}if("Component"===n.key.name&&"Identifier"===n.value.type){const e=n.value.name,{path:r}=t[e]??{};r&&(o.import=r)}if("element"===n.key.name&&"JSXElement"===n.value.type){const e=n.value?.openingElement?.name?.name,{path:r}=t[e]??{};r&&(o.import=r)}if("children"===n.key.name&&"Identifier"===n.value.type){const e=n.value.name,{path:a,isDefault:i}=t[e]??{};if(a){const t=this.resolveFilename(a,r);t&&(o.children=this.recursiveBuildRoutesTree(t,i?null:e))}}})),(o.import||o.children.length>0)&&n.push(o)}})),n}static parseImportsMap(e){const t={};return x(e,{ImportDeclaration(e){const r=e.node;r.specifiers.forEach((e=>{t[e.local.name]={path:r.source.value,isDefault:"ImportDefaultSpecifier"===e.type}}))}}),t}recursiveBuildRoutesTree(e,t=null){if(!e)return[];const r=this.parseFile(e);if(!r)return[];const n=this.findRoutesDefinition(r,t),a=[];if(!n)return a;const o=I.parseImportsMap(r),i=n.declarations[0].init?.elements;return a.push(...this.parseRoutesArray(i,o,e)),a}static processRouteFileCode(e,t,r,n){e.node.properties.forEach((a=>{if(i(a)&&l(a.key)){if("lazy"===a.key.name&&"ArrowFunctionExpression"===a.value.type){const t=a.value.body,o=e.node.properties.find((e=>i(e)&&l(e.key)&&"onlyClient"===e.key.name));if(a.value={type:"CallExpression",callee:{type:"Identifier",name:"n"},arguments:i(o)&&(s(o.value)||p(o.value)||u(o.value)||l(o.value))?[a.value,o.value]:[a.value]},o&&(e.node.properties=e.node.properties.filter((e=>e!==o))),n(),"CallExpression"===t.type&&"Import"===t.callee.type){const[n]=t.arguments,o=e.findParent?.((e=>c(e.node)));if(o&&"StringLiteral"===n.type&&n.value&&r){const t=m(f("pathId"),d(n.value));e.node.properties.splice(e.node.properties.indexOf(a)+1,0,t)}}}if("element"===a.key.name||"Component"===a.key.name){let n="";p(a.value)&&h(a.value.openingElement.name)?n=a.value.openingElement.name.name:l(a.value)&&(n=a.value.name);const o=e.findParent?.((e=>c(e.node))),i=t[n]?.path;if(o&&i&&r){const t=m(f("pathId"),d(i));e.node.properties.splice(e.node.properties.indexOf(a)+1,0,t)}}"children"===a.key.name&&c(a.value)&&a.value.elements.forEach((e=>{y(e)&&I.processRouteFileCode({node:e},t,r,n)}))}}))}static handleRoutes(e,t){if(!e)return e;const r=a.parse(e,{sourceType:"module",plugins:["typescript","jsx"]});if(!r)return e;const n=I.parseImportsMap(r);let o=!1;return x(r,{ObjectExpression(e){I.processRouteFileCode(e,n,t,(()=>{o=!0}))}}),o&&r.program.body.unshift({type:"ImportDeclaration",specifiers:[{type:"ImportDefaultSpecifier",local:{type:"Identifier",name:"n"}}],source:{type:"StringLiteral",value:`${v}/helpers/import-route`}}),E(r,{retainLines:!0}).code}}export{I as default};
2
2
  //# sourceMappingURL=parse-routes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-routes.js","sources":["../../src/services/parse-routes.ts"],"sourcesContent":["import fs from 'fs';\nimport { resolve } from 'node:path';\nimport path from 'path';\nimport babelGenerate from '@babel/generator';\nimport type * as GenerateTypes from '@babel/generator';\nimport * as parser from '@babel/parser';\nimport type { ParseResult } from '@babel/parser';\nimport babelTraverse from '@babel/traverse';\nimport type * as TraverseTypes from '@babel/traverse';\nimport type {\n CallExpression,\n File as BabelFile,\n VariableDeclaration,\n ObjectExpression,\n} from '@babel/types';\nimport {\n isObjectProperty,\n isIdentifier,\n identifier,\n isBooleanLiteral,\n stringLiteral,\n isFunction,\n objectProperty,\n isArrayExpression,\n isJSXElement,\n isJSXIdentifier,\n isObjectExpression,\n} from '@babel/types';\nimport type { Alias } from 'vite';\nimport PLUGIN_NAME from '@constants/plugin-name';\nimport PathNormalize from '@services/path-normalize';\nimport type ServerConfig from '@services/server-config';\n//\n// @ts-expect-error known import problem\nconst generate = (babelGenerate.default ?? babelGenerate) as (typeof GenerateTypes)['default'];\n// @ts-expect-error known import problem\nconst traverse = (babelTraverse.default ?? babelTraverse) as (typeof TraverseTypes)['default'];\n\ninterface IPathImport {\n routesPath: string | null;\n exportName: string | null;\n}\n\ninterface IMapImports {\n [name: string]: {\n path: string;\n isDefault: boolean; // is default import?\n };\n}\n\nexport type TRoutesTree = {\n index: number;\n import: string;\n children: TRoutesTree[];\n};\n\n/**\n * Parse react router routes array\n */\nclass ParseRoutes {\n /**\n * Path normalize service\n */\n protected readonly pathNormalize: PathNormalize;\n\n /**\n * Server config\n */\n protected readonly config: ServerConfig;\n\n /**\n * @constructor\n */\n constructor(config: ServerConfig, viteAliases?: Alias[]) {\n this.config = config;\n this.pathNormalize = new PathNormalize(config, viteAliases);\n }\n\n /**\n * Parse routes\n */\n public parse(): TRoutesTree[] {\n const { clientFile, root } = this.config.getParams();\n\n const clientEntrypoint = resolve(root, clientFile);\n const routesEntrypoint = this.findRoutesEntrypoint(clientEntrypoint);\n\n if (!routesEntrypoint?.routesPath) {\n throw new Error(`Unable to find routes file import in ${clientFile}`);\n }\n\n const { routesPath, exportName } = routesEntrypoint;\n const routeFilepath = this.resolveFilename(routesPath, clientEntrypoint);\n\n return this.recursiveBuildRoutesTree(routeFilepath, exportName);\n }\n\n /**\n * Parse file and return ast\n */\n private parseFile(filename: string): ParseResult<BabelFile> | null {\n try {\n const code = fs.readFileSync(filename, 'utf-8');\n\n return parser.parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n });\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Find route import filepath\n */\n private getImportPath(\n ast: ParseResult<BabelFile>,\n importName: string | null,\n ): IPathImport | null {\n let routesPath: string | null = null;\n let exportName: string | null = null;\n\n traverse(ast, {\n ImportDeclaration(nodePath) {\n const importNode = nodePath.node;\n\n importNode.specifiers.forEach((specifier) => {\n if (specifier.local.name === importName) {\n exportName = specifier.type === 'ImportDefaultSpecifier' ? null : importName;\n routesPath = importNode.source.value;\n }\n });\n },\n });\n\n return {\n routesPath,\n exportName,\n };\n }\n\n /**\n * Find routes array inside code\n */\n private findRoutesDefinition(\n ast: ParseResult<BabelFile>,\n exportName: string | null,\n ): null | VariableDeclaration {\n let exportNameResolved = exportName;\n\n // noinspection JSUnusedGlobalSymbols\n traverse(ast, {\n ExportNamedDeclaration({ node }) {\n if (!node.declaration && node.specifiers.length > 0) {\n node.specifiers.forEach((specifier) => {\n // @ts-expect-error missing in types\n const exportedName = specifier.exported.name as string;\n\n if (exportName === null && specifier.type === 'ExportSpecifier') {\n if (specifier.local.name === 'default') {\n exportNameResolved = exportedName;\n }\n } else if (exportedName === exportName) {\n // @ts-expect-error missing in types\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n exportNameResolved = specifier.local.name as string;\n }\n });\n }\n },\n ExportDefaultDeclaration({ node }) {\n if (exportName === null) {\n if (node.declaration.type === 'Identifier') {\n exportNameResolved = node.declaration.name;\n // @ts-expect-error missing in types\n } else if (node.declaration.type === 'VariableDeclaration') {\n // @ts-expect-error missing in types\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n exportNameResolved = node.declaration.declarations[0].id.name as string;\n }\n }\n },\n });\n\n if (exportNameResolved) {\n let variableNode: VariableDeclaration | null = null;\n\n traverse(ast, {\n VariableDeclaration({ node }) {\n node.declarations.forEach((declaration) => {\n // @ts-expect-error missing in types\n if (declaration.id.name === exportNameResolved) {\n variableNode = node;\n }\n });\n },\n });\n\n return variableNode;\n }\n\n return null;\n }\n\n /**\n * Entrypoint routes file\n */\n private findRoutesEntrypoint(clientEntrypoint: string): IPathImport | null {\n const ast = this.parseFile(clientEntrypoint);\n\n let routesVariable: string | null = null;\n\n if (!ast) {\n return routesVariable;\n }\n\n traverse(ast, {\n CallExpression({ node }) {\n if (\n // @ts-expect-error missing in types\n node.callee.name === 'entryClient' &&\n node.arguments.length >= 2 &&\n node.arguments[1].type === 'Identifier'\n ) {\n routesVariable = node.arguments[1].name;\n }\n },\n });\n\n return this.getImportPath(ast, routesVariable);\n }\n\n /**\n * Resolve route filename import\n */\n private resolveFilename(filename: string, relativeFile?: string): string | null {\n let resolvedFilename = filename;\n\n if ((filename.startsWith('./') || filename.startsWith('../')) && relativeFile) {\n resolvedFilename = path.resolve(path.dirname(relativeFile), filename);\n }\n\n const filepath = this.pathNormalize.getAppPath(resolvedFilename, true);\n\n return this.pathNormalize.findAppFile(filepath!);\n }\n\n /**\n * Parse ast array routes objects\n */\n private parseRoutesArray(\n elements: TraverseTypes.Node[],\n importsMap: IMapImports,\n relativeFile: string,\n ): TRoutesTree[] {\n const results: TRoutesTree[] = [];\n\n elements.forEach((node, index) => {\n if (node.type === 'ObjectExpression') {\n const routeInfo: TRoutesTree = { index, import: '', children: [] };\n\n node.properties.forEach((prop) => {\n const objectProp = prop as {\n key: { name: string };\n value: { type: string; elements: TraverseTypes.Node[] };\n };\n\n if (objectProp.key.name === 'children' && objectProp.value.type === 'ArrayExpression') {\n routeInfo.children = this.parseRoutesArray(\n objectProp.value.elements,\n importsMap,\n relativeFile,\n );\n }\n\n // async routes\n if (\n objectProp.key.name === 'lazy' &&\n objectProp.value.type === 'ArrowFunctionExpression'\n ) {\n // @ts-expect-error incorrect types\n const importCall = objectProp.value.body as CallExpression;\n\n if (importCall.type === 'CallExpression' && importCall.callee.type === 'Import') {\n const [importArg] = importCall.arguments;\n\n if (importArg.type === 'StringLiteral') {\n routeInfo.import = importArg.value;\n }\n }\n }\n\n // static routes: Component\n if (objectProp.key.name === 'Component' && objectProp.value.type === 'Identifier') {\n // @ts-expect-error incorrect types\n const importName = objectProp.value.name as string;\n const { path: importPath } = importsMap[importName] ?? {};\n\n if (importPath) {\n routeInfo.import = importPath;\n }\n }\n\n // static routes: element\n if (objectProp.key.name === 'element' && objectProp.value.type === 'JSXElement') {\n // @ts-expect-error incorrect types\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const importName = objectProp.value?.openingElement?.name?.name as string;\n const { path: importPath } = importsMap[importName] ?? {};\n\n if (importPath) {\n routeInfo.import = importPath;\n }\n }\n\n if (objectProp.key.name === 'children' && objectProp.value.type === 'Identifier') {\n // @ts-expect-error incorrect types\n const importName = objectProp.value.name as string;\n const { path: importPath, isDefault } = importsMap[importName] ?? {};\n\n if (importPath) {\n const childrenFilePath = this.resolveFilename(importPath, relativeFile);\n\n if (childrenFilePath) {\n routeInfo.children = this.recursiveBuildRoutesTree(\n childrenFilePath,\n isDefault ? null : importName,\n );\n }\n }\n }\n });\n\n if (routeInfo.import || routeInfo.children.length > 0) {\n results.push(routeInfo);\n }\n }\n });\n\n return results;\n }\n\n /**\n * Parse imports map from ast\n */\n private static parseImportsMap(ast: ParseResult<BabelFile>): IMapImports {\n const importsMap: IMapImports = {};\n\n traverse(ast, {\n ImportDeclaration(nodePath) {\n const importNode = nodePath.node;\n\n importNode.specifiers.forEach((specifier) => {\n importsMap[specifier.local.name] = {\n path: importNode.source.value,\n isDefault: specifier.type === 'ImportDefaultSpecifier',\n };\n });\n },\n });\n\n return importsMap;\n }\n\n /**\n * Recursive build routes tree with dynamic imports\n */\n private recursiveBuildRoutesTree(\n filename: string | null,\n exportName: string | null = null,\n ): TRoutesTree[] {\n if (!filename) {\n return [];\n }\n\n const ast = this.parseFile(filename);\n\n if (!ast) {\n return [];\n }\n\n const routesNode = this.findRoutesDefinition(ast, exportName);\n const results: TRoutesTree[] = [];\n\n if (!routesNode) {\n return results;\n }\n\n const importsMap = ParseRoutes.parseImportsMap(ast);\n\n // @ts-expect-error missing types\n const elements = routesNode.declarations[0].init?.elements as TraverseTypes.Node[];\n\n results.push(...this.parseRoutesArray(elements, importsMap, filename));\n\n return results;\n }\n\n /**\n * Add pathId to static routes\n */\n private static processRouteFileCode(\n nodePath: TraverseTypes.NodePath<ObjectExpression>,\n importsMap: IMapImports,\n shouldAddPathId: boolean,\n addImportRouteWrapper: () => void,\n ): void {\n nodePath.node.properties.forEach((property) => {\n if (isObjectProperty(property) && isIdentifier(property.key)) {\n // async routes\n if (property.key.name === 'lazy' && property.value.type === 'ArrowFunctionExpression') {\n const importCall = property.value.body as CallExpression;\n const onlyClientProp = nodePath.node.properties.find(\n (p) => isObjectProperty(p) && isIdentifier(p.key) && p.key.name === 'onlyClient',\n );\n\n /**\n * Wrap lazy import with:\n * @see importRoute\n */\n property.value = {\n type: 'CallExpression',\n callee: {\n type: 'Identifier',\n name: 'n',\n },\n arguments:\n isObjectProperty(onlyClientProp) &&\n (isBooleanLiteral(onlyClientProp.value) ||\n isJSXElement(onlyClientProp.value) ||\n isFunction(onlyClientProp.value) ||\n isIdentifier(onlyClientProp.value))\n ? [property.value, onlyClientProp.value]\n : [property.value],\n };\n\n if (onlyClientProp) {\n nodePath.node.properties = nodePath.node.properties.filter((p) => p !== onlyClientProp);\n }\n\n addImportRouteWrapper();\n\n if (importCall.type === 'CallExpression' && importCall.callee.type === 'Import') {\n const [importArg] = importCall.arguments;\n // current object has part of array (inside array)\n const parent = nodePath.findParent?.((p) => isArrayExpression(p.node));\n\n if (\n parent &&\n importArg.type === 'StringLiteral' &&\n importArg.value &&\n shouldAddPathId\n ) {\n const pathIdProperty = objectProperty(\n identifier('pathId'),\n stringLiteral(importArg.value),\n );\n\n // Insert the pathId property right after the element property\n nodePath.node.properties.splice(\n nodePath.node.properties.indexOf(property) + 1,\n 0,\n pathIdProperty,\n );\n }\n }\n }\n\n if (property.key.name === 'element' || property.key.name === 'Component') {\n let componentName = '';\n\n if (isJSXElement(property.value) && isJSXIdentifier(property.value.openingElement.name)) {\n componentName = property.value.openingElement.name.name;\n } else if (isIdentifier(property.value)) {\n componentName = property.value.name;\n }\n\n // current object has part of array (inside array)\n const parent = nodePath.findParent?.((p) => isArrayExpression(p.node));\n const importName = importsMap[componentName]?.path;\n\n if (parent && importName && shouldAddPathId) {\n const pathIdProperty = objectProperty(identifier('pathId'), stringLiteral(importName));\n\n // Insert the pathId property right after the element property\n nodePath.node.properties.splice(\n nodePath.node.properties.indexOf(property) + 1,\n 0,\n pathIdProperty,\n );\n }\n }\n\n if (property.key.name === 'children' && isArrayExpression(property.value)) {\n // Process each object in the children array recursively\n property.value.elements.forEach((element) => {\n if (isObjectExpression(element)) {\n ParseRoutes.processRouteFileCode(\n { node: element } as TraverseTypes.NodePath<ObjectExpression>,\n importsMap,\n shouldAddPathId,\n addImportRouteWrapper,\n );\n }\n });\n }\n }\n });\n }\n\n /**\n * Inject pathId to sync/async routes\n * Add async wrapper (see import-routes)\n */\n public static handleRoutes(code: string, shouldAddPathId: boolean): string {\n if (!code) {\n return code;\n }\n\n const ast = parser.parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n });\n\n if (!ast) {\n return code;\n }\n\n const importsMap = ParseRoutes.parseImportsMap(ast);\n let shouldAddRoutesImport = false;\n\n traverse(ast, {\n ObjectExpression(nodePath): void {\n ParseRoutes.processRouteFileCode(nodePath, importsMap, shouldAddPathId, () => {\n shouldAddRoutesImport = true;\n });\n },\n });\n\n if (shouldAddRoutesImport) {\n ast.program.body.unshift({\n type: 'ImportDeclaration',\n specifiers: [\n {\n type: 'ImportDefaultSpecifier',\n local: {\n type: 'Identifier',\n name: 'n',\n },\n },\n ],\n source: {\n type: 'StringLiteral',\n value: `${PLUGIN_NAME}/helpers/import-route`,\n },\n });\n }\n\n return generate(ast, {\n retainLines: true,\n }).code;\n }\n}\n\nexport default ParseRoutes;\n"],"names":["generate","babelGenerate","default","traverse","babelTraverse","ParseRoutes","pathNormalize","config","constructor","viteAliases","this","PathNormalize","parse","clientFile","root","getParams","clientEntrypoint","resolve","routesEntrypoint","findRoutesEntrypoint","routesPath","Error","exportName","routeFilepath","resolveFilename","recursiveBuildRoutesTree","parseFile","filename","code","fs","readFileSync","parser","sourceType","plugins","e","getImportPath","ast","importName","ImportDeclaration","nodePath","importNode","node","specifiers","forEach","specifier","local","name","type","source","value","findRoutesDefinition","exportNameResolved","ExportNamedDeclaration","declaration","length","exportedName","exported","ExportDefaultDeclaration","declarations","id","variableNode","VariableDeclaration","routesVariable","CallExpression","callee","arguments","relativeFile","resolvedFilename","startsWith","path","dirname","filepath","getAppPath","findAppFile","parseRoutesArray","elements","importsMap","results","index","routeInfo","import","children","properties","prop","objectProp","key","importCall","body","importArg","importPath","openingElement","isDefault","childrenFilePath","push","static","routesNode","parseImportsMap","init","shouldAddPathId","addImportRouteWrapper","property","isObjectProperty","isIdentifier","onlyClientProp","find","p","isBooleanLiteral","isJSXElement","isFunction","filter","parent","findParent","isArrayExpression","pathIdProperty","objectProperty","identifier","stringLiteral","splice","indexOf","componentName","isJSXIdentifier","element","isObjectExpression","processRouteFileCode","shouldAddRoutesImport","ObjectExpression","program","unshift","PLUGIN_NAME","retainLines"],"mappings":"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"}
1
+ {"version":3,"file":"parse-routes.js","sources":["../../src/services/parse-routes.ts"],"sourcesContent":["import fs from 'fs';\nimport { resolve } from 'node:path';\nimport path from 'path';\nimport babelGenerate from '@babel/generator';\nimport type * as GenerateTypes from '@babel/generator';\nimport * as parser from '@babel/parser';\nimport type { ParseResult } from '@babel/parser';\nimport babelTraverse from '@babel/traverse';\nimport type * as TraverseTypes from '@babel/traverse';\nimport type {\n CallExpression,\n File as BabelFile,\n VariableDeclaration,\n ObjectExpression,\n} from '@babel/types';\nimport {\n isObjectProperty,\n isIdentifier,\n identifier,\n isBooleanLiteral,\n stringLiteral,\n isFunction,\n objectProperty,\n isArrayExpression,\n isJSXElement,\n isJSXIdentifier,\n isObjectExpression,\n} from '@babel/types';\nimport type { Alias } from 'vite';\nimport PLUGIN_NAME from '@constants/plugin-name';\nimport PathNormalize from '@services/path-normalize';\nimport type ServerConfig from '@services/server-config';\n//\n// @ts-expect-error known import problem\nconst generate = (babelGenerate.default ?? babelGenerate) as (typeof GenerateTypes)['default'];\n// @ts-expect-error known import problem\nconst traverse = (babelTraverse.default ?? babelTraverse) as (typeof TraverseTypes)['default'];\n\ninterface IPathImport {\n routesPath: string | null;\n exportName: string | null;\n}\n\ninterface IMapImports {\n [name: string]: {\n path: string;\n isDefault: boolean; // is default import?\n };\n}\n\nexport type TRoutesTree = {\n index: number;\n import: string;\n children: TRoutesTree[];\n};\n\n/**\n * Parse react router routes array\n */\nclass ParseRoutes {\n /**\n * Path normalize service\n */\n protected readonly pathNormalize: PathNormalize;\n\n /**\n * Server config\n */\n protected readonly config: ServerConfig;\n\n /**\n * @constructor\n */\n constructor(config: ServerConfig, viteAliases?: Alias[]) {\n this.config = config;\n this.pathNormalize = new PathNormalize(config, viteAliases);\n }\n\n /**\n * Parse routes\n */\n public parse(): TRoutesTree[] {\n const { clientFile, root } = this.config.getParams();\n\n const clientEntrypoint = resolve(root, clientFile);\n const routesEntrypoint = this.findRoutesEntrypoint(clientEntrypoint);\n\n if (!routesEntrypoint?.routesPath) {\n throw new Error(`Unable to find routes file import in ${clientFile}`);\n }\n\n const { routesPath, exportName } = routesEntrypoint;\n const routeFilepath = this.resolveFilename(routesPath, clientEntrypoint);\n\n return this.recursiveBuildRoutesTree(routeFilepath, exportName);\n }\n\n /**\n * Parse file and return ast\n */\n private parseFile(filename: string): ParseResult<BabelFile> | null {\n try {\n const code = fs.readFileSync(filename, 'utf-8');\n\n return parser.parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n });\n } catch {\n return null;\n }\n }\n\n /**\n * Find route import filepath\n */\n private getImportPath(\n ast: ParseResult<BabelFile>,\n importName: string | null,\n ): IPathImport | null {\n let routesPath: string | null = null;\n let exportName: string | null = null;\n\n traverse(ast, {\n ImportDeclaration(nodePath) {\n const importNode = nodePath.node;\n\n importNode.specifiers.forEach((specifier) => {\n if (specifier.local.name === importName) {\n exportName = specifier.type === 'ImportDefaultSpecifier' ? null : importName;\n routesPath = importNode.source.value;\n }\n });\n },\n });\n\n return {\n routesPath,\n exportName,\n };\n }\n\n /**\n * Find routes array inside code\n */\n private findRoutesDefinition(\n ast: ParseResult<BabelFile>,\n exportName: string | null,\n ): null | VariableDeclaration {\n let exportNameResolved = exportName;\n\n // noinspection JSUnusedGlobalSymbols\n traverse(ast, {\n ExportNamedDeclaration({ node }) {\n if (!node.declaration && node.specifiers.length > 0) {\n node.specifiers.forEach((specifier) => {\n // @ts-expect-error missing in types\n const exportedName = specifier.exported.name as string;\n\n if (exportName === null && specifier.type === 'ExportSpecifier') {\n if (specifier.local.name === 'default') {\n exportNameResolved = exportedName;\n }\n } else if (exportedName === exportName) {\n // @ts-expect-error missing in types\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n exportNameResolved = specifier.local.name as string;\n }\n });\n }\n },\n ExportDefaultDeclaration({ node }) {\n if (exportName === null) {\n if (node.declaration.type === 'Identifier') {\n exportNameResolved = node.declaration.name;\n // @ts-expect-error missing in types\n } else if (node.declaration.type === 'VariableDeclaration') {\n // @ts-expect-error missing in types\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n exportNameResolved = node.declaration.declarations[0].id.name as string;\n }\n }\n },\n });\n\n if (exportNameResolved) {\n let variableNode: VariableDeclaration | null = null;\n\n traverse(ast, {\n VariableDeclaration({ node }) {\n node.declarations.forEach((declaration) => {\n // @ts-expect-error missing in types\n if (declaration.id.name === exportNameResolved) {\n variableNode = node;\n }\n });\n },\n });\n\n return variableNode;\n }\n\n return null;\n }\n\n /**\n * Entrypoint routes file\n */\n private findRoutesEntrypoint(clientEntrypoint: string): IPathImport | null {\n const ast = this.parseFile(clientEntrypoint);\n\n let routesVariable: string | null = null;\n\n if (!ast) {\n return routesVariable;\n }\n\n traverse(ast, {\n CallExpression({ node }) {\n if (\n // @ts-expect-error missing in types\n node.callee.name === 'entryClient' &&\n node.arguments.length >= 2 &&\n node.arguments[1].type === 'Identifier'\n ) {\n routesVariable = node.arguments[1].name;\n }\n },\n });\n\n return this.getImportPath(ast, routesVariable);\n }\n\n /**\n * Resolve route filename import\n */\n private resolveFilename(filename: string, relativeFile?: string): string | null {\n let resolvedFilename = filename;\n\n if ((filename.startsWith('./') || filename.startsWith('../')) && relativeFile) {\n resolvedFilename = path.resolve(path.dirname(relativeFile), filename);\n }\n\n const filepath = this.pathNormalize.getAppPath(resolvedFilename, true);\n\n return this.pathNormalize.findAppFile(filepath!);\n }\n\n /**\n * Parse ast array routes objects\n */\n private parseRoutesArray(\n elements: TraverseTypes.Node[],\n importsMap: IMapImports,\n relativeFile: string,\n ): TRoutesTree[] {\n const results: TRoutesTree[] = [];\n\n elements.forEach((node, index) => {\n if (node.type === 'ObjectExpression') {\n const routeInfo: TRoutesTree = { index, import: '', children: [] };\n\n node.properties.forEach((prop) => {\n const objectProp = prop as {\n key: { name: string };\n value: { type: string; elements: TraverseTypes.Node[] };\n };\n\n if (objectProp.key.name === 'children' && objectProp.value.type === 'ArrayExpression') {\n routeInfo.children = this.parseRoutesArray(\n objectProp.value.elements,\n importsMap,\n relativeFile,\n );\n }\n\n // async routes\n if (\n objectProp.key.name === 'lazy' &&\n objectProp.value.type === 'ArrowFunctionExpression'\n ) {\n // @ts-expect-error incorrect types\n const importCall = objectProp.value.body as CallExpression;\n\n if (importCall.type === 'CallExpression' && importCall.callee.type === 'Import') {\n const [importArg] = importCall.arguments;\n\n if (importArg.type === 'StringLiteral') {\n routeInfo.import = importArg.value;\n }\n }\n }\n\n // static routes: Component\n if (objectProp.key.name === 'Component' && objectProp.value.type === 'Identifier') {\n // @ts-expect-error incorrect types\n const importName = objectProp.value.name as string;\n const { path: importPath } = importsMap[importName] ?? {};\n\n if (importPath) {\n routeInfo.import = importPath;\n }\n }\n\n // static routes: element\n if (objectProp.key.name === 'element' && objectProp.value.type === 'JSXElement') {\n // @ts-expect-error incorrect types\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const importName = objectProp.value?.openingElement?.name?.name as string;\n const { path: importPath } = importsMap[importName] ?? {};\n\n if (importPath) {\n routeInfo.import = importPath;\n }\n }\n\n if (objectProp.key.name === 'children' && objectProp.value.type === 'Identifier') {\n // @ts-expect-error incorrect types\n const importName = objectProp.value.name as string;\n const { path: importPath, isDefault } = importsMap[importName] ?? {};\n\n if (importPath) {\n const childrenFilePath = this.resolveFilename(importPath, relativeFile);\n\n if (childrenFilePath) {\n routeInfo.children = this.recursiveBuildRoutesTree(\n childrenFilePath,\n isDefault ? null : importName,\n );\n }\n }\n }\n });\n\n if (routeInfo.import || routeInfo.children.length > 0) {\n results.push(routeInfo);\n }\n }\n });\n\n return results;\n }\n\n /**\n * Parse imports map from ast\n */\n private static parseImportsMap(ast: ParseResult<BabelFile>): IMapImports {\n const importsMap: IMapImports = {};\n\n traverse(ast, {\n ImportDeclaration(nodePath) {\n const importNode = nodePath.node;\n\n importNode.specifiers.forEach((specifier) => {\n importsMap[specifier.local.name] = {\n path: importNode.source.value,\n isDefault: specifier.type === 'ImportDefaultSpecifier',\n };\n });\n },\n });\n\n return importsMap;\n }\n\n /**\n * Recursive build routes tree with dynamic imports\n */\n private recursiveBuildRoutesTree(\n filename: string | null,\n exportName: string | null = null,\n ): TRoutesTree[] {\n if (!filename) {\n return [];\n }\n\n const ast = this.parseFile(filename);\n\n if (!ast) {\n return [];\n }\n\n const routesNode = this.findRoutesDefinition(ast, exportName);\n const results: TRoutesTree[] = [];\n\n if (!routesNode) {\n return results;\n }\n\n const importsMap = ParseRoutes.parseImportsMap(ast);\n\n // @ts-expect-error missing types\n const elements = routesNode.declarations[0].init?.elements as TraverseTypes.Node[];\n\n results.push(...this.parseRoutesArray(elements, importsMap, filename));\n\n return results;\n }\n\n /**\n * Add pathId to static routes\n */\n private static processRouteFileCode(\n nodePath: TraverseTypes.NodePath<ObjectExpression>,\n importsMap: IMapImports,\n shouldAddPathId: boolean,\n addImportRouteWrapper: () => void,\n ): void {\n nodePath.node.properties.forEach((property) => {\n if (isObjectProperty(property) && isIdentifier(property.key)) {\n // async routes\n if (property.key.name === 'lazy' && property.value.type === 'ArrowFunctionExpression') {\n const importCall = property.value.body as CallExpression;\n const onlyClientProp = nodePath.node.properties.find(\n (p) => isObjectProperty(p) && isIdentifier(p.key) && p.key.name === 'onlyClient',\n );\n\n /**\n * Wrap lazy import with:\n * @see importRoute\n */\n property.value = {\n type: 'CallExpression',\n callee: {\n type: 'Identifier',\n name: 'n',\n },\n arguments:\n isObjectProperty(onlyClientProp) &&\n (isBooleanLiteral(onlyClientProp.value) ||\n isJSXElement(onlyClientProp.value) ||\n isFunction(onlyClientProp.value) ||\n isIdentifier(onlyClientProp.value))\n ? [property.value, onlyClientProp.value]\n : [property.value],\n };\n\n if (onlyClientProp) {\n nodePath.node.properties = nodePath.node.properties.filter((p) => p !== onlyClientProp);\n }\n\n addImportRouteWrapper();\n\n if (importCall.type === 'CallExpression' && importCall.callee.type === 'Import') {\n const [importArg] = importCall.arguments;\n // current object has part of array (inside array)\n const parent = nodePath.findParent?.((p) => isArrayExpression(p.node));\n\n if (\n parent &&\n importArg.type === 'StringLiteral' &&\n importArg.value &&\n shouldAddPathId\n ) {\n const pathIdProperty = objectProperty(\n identifier('pathId'),\n stringLiteral(importArg.value),\n );\n\n // Insert the pathId property right after the element property\n nodePath.node.properties.splice(\n nodePath.node.properties.indexOf(property) + 1,\n 0,\n pathIdProperty,\n );\n }\n }\n }\n\n if (property.key.name === 'element' || property.key.name === 'Component') {\n let componentName = '';\n\n if (isJSXElement(property.value) && isJSXIdentifier(property.value.openingElement.name)) {\n componentName = property.value.openingElement.name.name;\n } else if (isIdentifier(property.value)) {\n componentName = property.value.name;\n }\n\n // current object has part of array (inside array)\n const parent = nodePath.findParent?.((p) => isArrayExpression(p.node));\n const importName = importsMap[componentName]?.path;\n\n if (parent && importName && shouldAddPathId) {\n const pathIdProperty = objectProperty(identifier('pathId'), stringLiteral(importName));\n\n // Insert the pathId property right after the element property\n nodePath.node.properties.splice(\n nodePath.node.properties.indexOf(property) + 1,\n 0,\n pathIdProperty,\n );\n }\n }\n\n if (property.key.name === 'children' && isArrayExpression(property.value)) {\n // Process each object in the children array recursively\n property.value.elements.forEach((element) => {\n if (isObjectExpression(element)) {\n ParseRoutes.processRouteFileCode(\n { node: element } as TraverseTypes.NodePath<ObjectExpression>,\n importsMap,\n shouldAddPathId,\n addImportRouteWrapper,\n );\n }\n });\n }\n }\n });\n }\n\n /**\n * Inject pathId to sync/async routes\n * Add async wrapper (see import-routes)\n */\n public static handleRoutes(code: string, shouldAddPathId: boolean): string {\n if (!code) {\n return code;\n }\n\n const ast = parser.parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n });\n\n if (!ast) {\n return code;\n }\n\n const importsMap = ParseRoutes.parseImportsMap(ast);\n let shouldAddRoutesImport = false;\n\n traverse(ast, {\n ObjectExpression(nodePath): void {\n ParseRoutes.processRouteFileCode(nodePath, importsMap, shouldAddPathId, () => {\n shouldAddRoutesImport = true;\n });\n },\n });\n\n if (shouldAddRoutesImport) {\n ast.program.body.unshift({\n type: 'ImportDeclaration',\n specifiers: [\n {\n type: 'ImportDefaultSpecifier',\n local: {\n type: 'Identifier',\n name: 'n',\n },\n },\n ],\n source: {\n type: 'StringLiteral',\n value: `${PLUGIN_NAME}/helpers/import-route`,\n },\n });\n }\n\n return generate(ast, {\n retainLines: true,\n }).code;\n }\n}\n\nexport default ParseRoutes;\n"],"names":["generate","babelGenerate","default","traverse","babelTraverse","ParseRoutes","pathNormalize","config","constructor","viteAliases","this","PathNormalize","parse","clientFile","root","getParams","clientEntrypoint","resolve","routesEntrypoint","findRoutesEntrypoint","routesPath","Error","exportName","routeFilepath","resolveFilename","recursiveBuildRoutesTree","parseFile","filename","code","fs","readFileSync","parser","sourceType","plugins","getImportPath","ast","importName","ImportDeclaration","nodePath","importNode","node","specifiers","forEach","specifier","local","name","type","source","value","findRoutesDefinition","exportNameResolved","ExportNamedDeclaration","declaration","length","exportedName","exported","ExportDefaultDeclaration","declarations","id","variableNode","VariableDeclaration","routesVariable","CallExpression","callee","arguments","relativeFile","resolvedFilename","startsWith","path","dirname","filepath","getAppPath","findAppFile","parseRoutesArray","elements","importsMap","results","index","routeInfo","import","children","properties","prop","objectProp","key","importCall","body","importArg","importPath","openingElement","isDefault","childrenFilePath","push","static","routesNode","parseImportsMap","init","shouldAddPathId","addImportRouteWrapper","property","isObjectProperty","isIdentifier","onlyClientProp","find","p","isBooleanLiteral","isJSXElement","isFunction","filter","parent","findParent","isArrayExpression","pathIdProperty","objectProperty","identifier","stringLiteral","splice","indexOf","componentName","isJSXIdentifier","element","isObjectExpression","processRouteFileCode","shouldAddRoutesImport","ObjectExpression","program","unshift","PLUGIN_NAME","retainLines"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import e from"node:fs";import t from"node:path";import s from"chalk";import i from"./parse-routes.js";import r from"./path-normalize.js";var n;!function(e){e.style="style",e.script="script",e.image="image",e.font="font"}(n||(n={}));const o="\r\n";class a{static instance=null;config;pathNormalize;root;buildDir;manifestName="manifest.json";assetsManifest="assets-manifest.json";viteAliases;basename;renderBuiltUrl;routesAssets=null;constructor(e,{buildDir:t,viteAliases:s,basename:i,renderBuiltUrl:n}={}){this.config=e,this.root=e.getParams().root,this.buildDir=t,this.viteAliases=s??e.getVite()?.config?.resolve.alias,this.pathNormalize=new r(e,s),this.basename=i,this.renderBuiltUrl=n}static get(e,t={}){return null===a.instance&&(a.instance=new a(e,t)),a.instance}getOutDir(){return t.resolve(this.root,this.buildDir||"")}getAssetsManifestFile(){return`${this.getOutDir()}/server/${this.assetsManifest}`}loadClientManifest(){const s=t.resolve(this.root,`${this.buildDir||""}/client/.vite`),i=`${s}/${this.manifestName}`;if(!e.existsSync(i))return{};const r=JSON.parse(e.readFileSync(i,{encoding:"utf-8"}));return e.rmSync(i),0===e.readdirSync(s).length&&e.rmSync(s,{recursive:!0}),r}loadAssetsManifest(){if(null!==this.routesAssets)return this.routesAssets;const t=this.getAssetsManifestFile();return e.existsSync(t)?(this.routesAssets=JSON.parse(e.readFileSync(t,{encoding:"utf-8"})),this.routesAssets):{}}getRoutesTreeIds(e,t){const s={};return e.forEach(((e,i)=>{const r=[t,String(i)].filter(Boolean).join("-");e.import&&(s[r]=this.pathNormalize.getAppPath(e.import)),e.children.length>0&&Object.assign(s,this.getRoutesTreeIds(e.children,r))})),s}sortAssets(e){return e.sort(((e,t)=>e.weight===t.weight?Number(e.isNested)-Number(t.isNested):e.weight-t.weight))}getRouteAssets(e,s,i=!1){const r=[...s?.assets??[],...s?.css??[],s?.file].reduce(((e,r)=>{if(r){const n=this.getAssetType(r),o=s.isEntry&&s.file===r;if(n){const s=t.posix.normalize(`${this.basename}/${r}`),a=this.renderBuiltUrl?.(s,{type:"asset",ssr:!0,hostId:"",hostType:s.split(".").at(-1)?.toLowerCase()});e[r]={url:"string"==typeof a?a:s,weight:o?1.9:this.getAssetWeight(r),type:n,isNested:i,isPreload:!o}}}return e}),{});return s?.imports?.length&&s.imports.forEach((t=>{const s=e[t];s&&Object.assign(r,this.getRouteAssets(e,s,!0))})),r}buildRoutesManifest(){const t=this.loadClientManifest(),s=new i(this.config,this.viteAliases),r=this.getRoutesTreeIds(s.parse()),n=this.pathNormalize.getImportPostfix(),o={};Object.entries(r).forEach((([e,s])=>{const i=n.find((e=>void 0!==t[`${s}${e}`])),r=t[`${s}${i||""}`];o[e]=this.sortAssets(Object.values(this.getRouteAssets(t,r)))})),e.writeFileSync(this.getAssetsManifestFile(),JSON.stringify(o,null,2),{encoding:"utf-8"})}getAssets(e){if(this.config.getVite())return this.getAssetsDev(e);const t=e?.map((({route:e})=>e.id)).filter(Boolean)??[];if(!t.length)return[];const s=this.loadAssetsManifest();return this.sortAssets(t.map((e=>s[e])).flat().filter(Boolean))}getAssetsDev(e){const s=e?.map((({route:e})=>this.pathNormalize.getAppPath(e?.pathId,!0))).filter(Boolean)??[];if(!s.length)return[];let i={};const r=this.pathNormalize.getImportPostfix();return[t.resolve(this.root,this.config.getPluginConfig()?.clientFile??"client.ts"),...s].forEach((e=>{for(const t of r){const s=this.config.getVite()?.moduleGraph.getModuleById(`${e}${t}`);if(s){i={...i,...this.getModuleAssets(s)};break}}})),Object.values(i)}getModuleAssets(e,t=new Set){if(!e?.clientImportedModules.size||t.has(e.file))return{};let i={};return t.add(e.file),e.clientImportedModules.forEach((e=>{const{file:r,clientImportedModules:o,transformResult:a}=e,l=r?.split(".").at(-1);if(r&&l&&["css","scss"].includes(l)){const e=a?.code.match(/__vite__css\s+=\s+"(?<css>.+)"/)?.groups?.css;if(e)try{i[r]={type:n.style,url:r,weight:this.getAssetWeight(r),content:JSON.parse(`{"style": "${e}"}`).style,isNested:Boolean(t.size),isPreload:!1}}catch(e){console.warn(s.yellowBright("Failed to parse style: ",r))}}else o.size&&(i={...i,...this.getModuleAssets(e,t)})})),i}getAssetWeight(e){switch(this.getAssetType(e)){case n.style:return 1;case n.script:return 2;default:return 3}}getAssetType(e){const t=e.split(".").at(-1)?.toLowerCase();switch(t){case"css":case"scss":return n.style;case"js":return n.script;case"svg":case"jpg":case"jpeg":case"png":case"webp":case"gif":case"ico":return n.image;case"ttf":case"otf":case"woff":case"woff2":return n.font;default:return null}}writeEarlyHits(e,t){t.write(`HTTP/1.1 103 Early Hints${o}`),e.forEach((({type:e,url:s})=>{e&&["style","script"].includes(e)&&t.write(`Link: <${s}>; rel=preload; as=${e}${o}`)})),t.write(o)}injectAssets({routerContext:e,html:t,res:s,hasEarlyHints:i=!1}){const r=this.getAssets(e?.matches),o=r.map((({type:e,url:t,isPreload:s,content:i=""})=>{switch(e){case n.style:return this.config.getVite()?`<style data-vite-dev-id="${t}">${i}</style>`:`<link rel="stylesheet" href="${t}">`;case n.script:return s?this.config.isModulePreload?`<link rel="modulepreload" as="script" crossorigin href="${t}">`:null:`<script async type="module" crossorigin src="${t}"><\/script>`}return null})).filter(Boolean);t.header=t.header.replace("</head>",`${o.join("\n")}</head>`),i&&o.length&&s.socket&&this.writeEarlyHits(r,s.socket)}}export{a as default};
1
+ import e from"node:fs";import t from"node:path";import s from"chalk";import i from"./parse-routes.js";import r from"./path-normalize.js";var n;!function(e){e.style="style",e.script="script",e.image="image",e.font="font"}(n||(n={}));const o="\r\n";class a{static instance=null;config;pathNormalize;root;buildDir;manifestName="manifest.json";assetsManifest="assets-manifest.json";viteAliases;basename;renderBuiltUrl;routesAssets=null;constructor(e,{buildDir:t,viteAliases:s,basename:i,renderBuiltUrl:n}={}){this.config=e,this.root=e.getParams().root,this.buildDir=t,this.viteAliases=s??e.getVite()?.config?.resolve.alias,this.pathNormalize=new r(e,s),this.basename=i,this.renderBuiltUrl=n}static get(e,t={}){return null===a.instance&&(a.instance=new a(e,t)),a.instance}getOutDir(){return t.resolve(this.root,this.buildDir||"")}getAssetsManifestFile(){return`${this.getOutDir()}/server/${this.assetsManifest}`}loadClientManifest(){const s=t.resolve(this.root,`${this.buildDir||""}/client/.vite`),i=`${s}/${this.manifestName}`;if(!e.existsSync(i))return{};const r=JSON.parse(e.readFileSync(i,{encoding:"utf-8"}));return e.rmSync(i),0===e.readdirSync(s).length&&e.rmSync(s,{recursive:!0}),r}loadAssetsManifest(){if(null!==this.routesAssets)return this.routesAssets;const t=this.getAssetsManifestFile();return e.existsSync(t)?(this.routesAssets=JSON.parse(e.readFileSync(t,{encoding:"utf-8"})),this.routesAssets):{}}getRoutesTreeIds(e,t){const s={};return e.forEach(((e,i)=>{const r=[t,String(i)].filter(Boolean).join("-");e.import&&(s[r]=this.pathNormalize.getAppPath(e.import)),e.children.length>0&&Object.assign(s,this.getRoutesTreeIds(e.children,r))})),s}sortAssets(e){return e.sort(((e,t)=>e.weight===t.weight?Number(e.isNested)-Number(t.isNested):e.weight-t.weight))}getRouteAssets(e,s,i=!1){const r=[...s?.assets??[],...s?.css??[],s?.file].reduce(((e,r)=>{if(r){const n=this.getAssetType(r),o=s.isEntry&&s.file===r;if(n){const s=t.posix.normalize(`${this.basename}/${r}`),a=this.renderBuiltUrl?.(s,{type:"asset",ssr:!0,hostId:"",hostType:s.split(".").at(-1)?.toLowerCase()});e[r]={url:"string"==typeof a?a:s,weight:o?1.9:this.getAssetWeight(r),type:n,isNested:i,isPreload:!o}}}return e}),{});return s?.imports?.length&&s.imports.forEach((t=>{const s=e[t];s&&Object.assign(r,this.getRouteAssets(e,s,!0))})),r}buildRoutesManifest(){const t=this.loadClientManifest(),s=new i(this.config,this.viteAliases),r=this.getRoutesTreeIds(s.parse()),n=this.pathNormalize.getImportPostfix(),o={};Object.entries(r).forEach((([e,s])=>{const i=n.find((e=>void 0!==t[`${s}${e}`])),r=t[`${s}${i||""}`];o[e]=this.sortAssets(Object.values(this.getRouteAssets(t,r)))})),e.writeFileSync(this.getAssetsManifestFile(),JSON.stringify(o,null,2),{encoding:"utf-8"})}getAssets(e){if(this.config.getVite())return this.getAssetsDev(e);const t=e?.map((({route:e})=>e.id)).filter(Boolean)??[];if(!t.length)return[];const s=this.loadAssetsManifest();return this.sortAssets(t.map((e=>s[e])).flat().filter(Boolean))}getAssetsDev(e){const s=e?.map((({route:e})=>this.pathNormalize.getAppPath(e?.pathId,!0))).filter(Boolean)??[];if(!s.length)return[];let i={};const r=this.pathNormalize.getImportPostfix();return[t.resolve(this.root,this.config.getPluginConfig()?.clientFile??"client.ts"),...s].forEach((e=>{for(const t of r){const s=this.config.getVite()?.moduleGraph.getModuleById(`${e}${t}`);if(s){i={...i,...this.getModuleAssets(s)};break}}})),Object.values(i)}getModuleAssets(e,t=new Set){if(!e?.clientImportedModules.size||t.has(e.file))return{};let i={};return t.add(e.file),e.clientImportedModules.forEach((e=>{const{file:r,clientImportedModules:o,transformResult:a}=e,l=r?.split(".").at(-1);if(r&&l&&["css","scss"].includes(l)){const e=a?.code.match(/__vite__css\s+=\s+"(?<css>.+)"/)?.groups?.css;if(e)try{i[r]={type:n.style,url:r,weight:this.getAssetWeight(r),content:JSON.parse(`{"style": "${e}"}`).style,isNested:Boolean(t.size),isPreload:!1}}catch{console.warn(s.yellowBright("Failed to parse style: ",r))}}else o.size&&(i={...i,...this.getModuleAssets(e,t)})})),i}getAssetWeight(e){switch(this.getAssetType(e)){case n.style:return 1;case n.script:return 2;default:return 3}}getAssetType(e){const t=e.split(".").at(-1)?.toLowerCase();switch(t){case"css":case"scss":return n.style;case"js":return n.script;case"svg":case"jpg":case"jpeg":case"png":case"webp":case"gif":case"ico":return n.image;case"ttf":case"otf":case"woff":case"woff2":return n.font;default:return null}}writeEarlyHits(e,t){t.write(`HTTP/1.1 103 Early Hints${o}`),e.forEach((({type:e,url:s})=>{e&&["style","script"].includes(e)&&t.write(`Link: <${s}>; rel=preload; as=${e}${o}`)})),t.write(o)}injectAssets({routerContext:e,html:t,res:s,hasEarlyHints:i=!1}){const r=this.getAssets(e?.matches),o=r.map((({type:e,url:t,isPreload:s,content:i=""})=>{switch(e){case n.style:return this.config.getVite()?`<style data-vite-dev-id="${t}">${i}</style>`:`<link rel="stylesheet" href="${t}">`;case n.script:return s?this.config.isModulePreload?`<link rel="modulepreload" as="script" crossorigin href="${t}">`:null:`<script async type="module" crossorigin src="${t}"><\/script>`}return null})).filter(Boolean);t.header=t.header.replace("</head>",`${o.join("\n")}</head>`),i&&o.length&&s.socket&&this.writeEarlyHits(r,s.socket)}}export{a 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 chalk from 'chalk';\nimport type { RouterState } from 'react-router';\nimport type { Alias, ModuleNode, RenderBuiltAssetUrl } 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 type ServerConfig from '@services/server-config';\n\ninterface ISsrManifestParams {\n buildDir?: string;\n viteAliases?: Alias[];\n basename?: string;\n renderBuiltUrl?: RenderBuiltAssetUrl;\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 * Vite renderBuiltUrl config func\n */\n protected readonly renderBuiltUrl?: RenderBuiltAssetUrl;\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, renderBuiltUrl }: 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 this.renderBuiltUrl = renderBuiltUrl;\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 * 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 const filename = path.posix.normalize(`${this.basename}/${asset}`);\n const modifiedFilename = this.renderBuiltUrl?.(filename, {\n type: 'asset',\n ssr: true,\n hostId: '',\n hostType: filename.split('.').at(-1)?.toLowerCase() as 'js',\n });\n\n res[asset] = {\n url: typeof modifiedFilename === 'string' ? modifiedFilename : filename,\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 buildRoutesManifest(): void {\n const manifest = this.loadClientManifest();\n const routesService = new ParseRoutes(this.config, this.viteAliases);\n const routesPaths = this.getRoutesTreeIds(routesService.parse());\n\n const postfixes = this.pathNormalize.getImportPostfix();\n const result: Record<string, IAsset[]> = {};\n\n // find route assets\n Object.entries(routesPaths).forEach(([routeId, routePath]) => {\n const routePostfix = postfixes.find((postfix) => {\n const filePath = `${routePath}${postfix}`;\n\n return manifest[filePath] !== undefined;\n });\n const routeFile = `${routePath}${routePostfix || ''}`;\n const routeMeta = manifest[routeFile];\n\n result[routeId] = this.sortAssets(Object.values(this.getRouteAssets(manifest, routeMeta)));\n });\n\n fs.writeFileSync(this.getAssetsManifestFile(), JSON.stringify(result, null, 2), {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Get route assets\n */\n protected getAssets(routes?: RouterState['matches']): IAsset[] {\n if (this.config.getVite()) {\n return this.getAssetsDev(routes);\n }\n\n const routeIds = routes?.map(({ route }) => route.id).filter(Boolean) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n const routesAssets = this.loadAssetsManifest();\n\n return this.sortAssets(\n routeIds\n .map((routeId) => routesAssets[routeId])\n .flat()\n .filter(Boolean),\n );\n }\n\n /**\n * Get development route assets\n */\n protected getAssetsDev(routes?: RouterState['matches']): IAsset[] {\n const routeIds =\n (routes\n ?.map(({ route }) => this.pathNormalize.getAppPath((route as IAsyncRoute)?.pathId, true))\n .filter(Boolean) as string[]) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n let assets: TAssets = {};\n const postfixes = this.pathNormalize.getImportPostfix();\n const rootId = path.resolve(\n this.root,\n this.config.getPluginConfig()?.clientFile ?? 'client.ts',\n );\n\n [rootId, ...routeIds].forEach((moduleId) => {\n for (const ext of postfixes) {\n const module = this.config.getVite()?.moduleGraph.getModuleById(`${moduleId}${ext}`);\n\n if (module) {\n assets = { ...assets, ...this.getModuleAssets(module) };\n break;\n }\n }\n });\n\n return Object.values(assets);\n }\n\n /**\n * Get module assets\n */\n protected getModuleAssets(module?: ModuleNode, skipModules: Set<string> = new Set()): TAssets {\n if (!module?.clientImportedModules.size || skipModules.has(module.file!)) {\n return {};\n }\n\n let assets: TAssets = {};\n\n skipModules.add(module.file!);\n\n module.clientImportedModules.forEach((subModule) => {\n const { file, clientImportedModules, transformResult } = subModule;\n const ext = file?.split('.').at(-1);\n\n if (file && ext && ['css', 'scss'].includes(ext)) {\n // @TODO investigate better method?\n const code = transformResult?.code.match(/__vite__css\\s+=\\s+\"(?<css>.+)\"/)?.groups?.css;\n\n if (code) {\n try {\n assets[file] = {\n type: AssetType.style,\n url: file,\n weight: this.getAssetWeight(file),\n content: (JSON.parse(`{\"style\": \"${code}\"}`) as { style: string }).style,\n isNested: Boolean(skipModules.size),\n isPreload: false,\n };\n } catch (e) {\n console.warn(chalk.yellowBright('Failed to parse style: ', file));\n }\n }\n } else if (clientImportedModules.size) {\n assets = {\n ...assets,\n ...this.getModuleAssets(subModule, skipModules),\n };\n }\n });\n\n return assets;\n }\n\n /**\n * Get asset weight\n */\n protected getAssetWeight(asset: string): number {\n const type = this.getAssetType(asset);\n\n switch (type) {\n case AssetType.style:\n return 1;\n\n case AssetType.script:\n return 2;\n\n default:\n return 3;\n }\n }\n\n /**\n * Get asset type\n */\n protected getAssetType(asset: string): AssetType | null {\n const ext = asset.split('.').at(-1)?.toLowerCase();\n\n switch (ext) {\n case 'css':\n case 'scss':\n return AssetType.style;\n\n case 'js':\n return AssetType.script;\n\n case 'svg':\n case 'jpg':\n case 'jpeg':\n case 'png':\n case 'webp':\n case 'gif':\n case 'ico':\n return AssetType.image;\n\n case 'ttf':\n case 'otf':\n case 'woff':\n case 'woff2':\n return AssetType.font;\n\n default:\n return null;\n }\n }\n\n /**\n * Write 103 Early Hits header\n */\n public writeEarlyHits(assets: IAsset[], socket: Socket): void {\n socket.write(`HTTP/1.1 103 Early Hints${CRLF}`);\n assets.forEach(({ type, url }) => {\n if (!type || !['style', 'script'].includes(type)) {\n return;\n }\n\n socket.write(`Link: <${url}>; rel=preload; as=${type}${CRLF}`);\n });\n socket.write(CRLF);\n }\n\n /**\n * Inject route assets to head html\n */\n public injectAssets({ routerContext, html, res, hasEarlyHints = false }: IRequestContext): void {\n const assets = this.getAssets(routerContext?.matches);\n const htmlAssets = assets\n .map(({ type, url, isPreload, content = '' }) => {\n switch (type) {\n case AssetType.style:\n return this.config.getVite()\n ? `<style data-vite-dev-id=\"${url}\">${content}</style>`\n : `<link rel=\"stylesheet\" href=\"${url}\">`;\n\n case AssetType.script:\n return isPreload\n ? this.config.isModulePreload\n ? // can reduce lighthouse performance\n `<link rel=\"modulepreload\" as=\"script\" crossorigin href=\"${url}\">`\n : null\n : `<script async type=\"module\" crossorigin src=\"${url}\"></script>`;\n }\n\n return null;\n })\n .filter(Boolean);\n\n html.header = html.header.replace('</head>', `${htmlAssets.join('\\n')}</head>`);\n\n if (hasEarlyHints && htmlAssets.length && res.socket) {\n this.writeEarlyHits(assets, res.socket);\n }\n }\n}\n\nexport default SsrManifest;\n"],"names":["AssetType","CRLF","SsrManifest","static","config","pathNormalize","root","buildDir","manifestName","assetsManifest","viteAliases","basename","renderBuiltUrl","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","getRoutesTreeIds","routes","index","forEach","route","routeIndex","routeId","String","filter","Boolean","join","import","getAppPath","children","Object","assign","sortAssets","assets","sort","a","b","weight","Number","isNested","getRouteAssets","manifest","module","css","file","reduce","res","asset","type","getAssetType","isEntry","filename","posix","normalize","modifiedFilename","ssr","hostId","hostType","split","at","toLowerCase","url","getAssetWeight","isPreload","imports","nestedAsset","nestedModule","buildRoutesManifest","routesService","ParseRoutes","routesPaths","postfixes","getImportPostfix","entries","routePath","routePostfix","find","postfix","undefined","routeMeta","values","writeFileSync","stringify","getAssets","getAssetsDev","routeIds","map","id","flat","pathId","getPluginConfig","clientFile","moduleId","ext","moduleGraph","getModuleById","getModuleAssets","skipModules","Set","clientImportedModules","size","has","add","subModule","transformResult","includes","code","match","groups","style","content","e","console","warn","chalk","yellowBright","script","image","font","writeEarlyHits","socket","write","injectAssets","routerContext","html","hasEarlyHints","matches","htmlAssets","isModulePreload","header","replace"],"mappings":"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"}
1
+ {"version":3,"file":"ssr-manifest.js","sources":["../../src/services/ssr-manifest.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { Socket } from 'node:net';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport type { RouterState } from 'react-router';\nimport type { Alias, ModuleNode, RenderBuiltAssetUrl } 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 type ServerConfig from '@services/server-config';\n\ninterface ISsrManifestParams {\n buildDir?: string;\n viteAliases?: Alias[];\n basename?: string;\n renderBuiltUrl?: RenderBuiltAssetUrl;\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 * Vite renderBuiltUrl config func\n */\n protected readonly renderBuiltUrl?: RenderBuiltAssetUrl;\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, renderBuiltUrl }: 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 this.renderBuiltUrl = renderBuiltUrl;\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 * 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 const filename = path.posix.normalize(`${this.basename}/${asset}`);\n const modifiedFilename = this.renderBuiltUrl?.(filename, {\n type: 'asset',\n ssr: true,\n hostId: '',\n hostType: filename.split('.').at(-1)?.toLowerCase() as 'js',\n });\n\n res[asset] = {\n url: typeof modifiedFilename === 'string' ? modifiedFilename : filename,\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 buildRoutesManifest(): void {\n const manifest = this.loadClientManifest();\n const routesService = new ParseRoutes(this.config, this.viteAliases);\n const routesPaths = this.getRoutesTreeIds(routesService.parse());\n\n const postfixes = this.pathNormalize.getImportPostfix();\n const result: Record<string, IAsset[]> = {};\n\n // find route assets\n Object.entries(routesPaths).forEach(([routeId, routePath]) => {\n const routePostfix = postfixes.find((postfix) => {\n const filePath = `${routePath}${postfix}`;\n\n return manifest[filePath] !== undefined;\n });\n const routeFile = `${routePath}${routePostfix || ''}`;\n const routeMeta = manifest[routeFile];\n\n result[routeId] = this.sortAssets(Object.values(this.getRouteAssets(manifest, routeMeta)));\n });\n\n fs.writeFileSync(this.getAssetsManifestFile(), JSON.stringify(result, null, 2), {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Get route assets\n */\n protected getAssets(routes?: RouterState['matches']): IAsset[] {\n if (this.config.getVite()) {\n return this.getAssetsDev(routes);\n }\n\n const routeIds = routes?.map(({ route }) => route.id).filter(Boolean) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n const routesAssets = this.loadAssetsManifest();\n\n return this.sortAssets(\n routeIds\n .map((routeId) => routesAssets[routeId])\n .flat()\n .filter(Boolean),\n );\n }\n\n /**\n * Get development route assets\n */\n protected getAssetsDev(routes?: RouterState['matches']): IAsset[] {\n const routeIds =\n (routes\n ?.map(({ route }) => this.pathNormalize.getAppPath((route as IAsyncRoute)?.pathId, true))\n .filter(Boolean) as string[]) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n let assets: TAssets = {};\n const postfixes = this.pathNormalize.getImportPostfix();\n const rootId = path.resolve(\n this.root,\n this.config.getPluginConfig()?.clientFile ?? 'client.ts',\n );\n\n [rootId, ...routeIds].forEach((moduleId) => {\n for (const ext of postfixes) {\n const module = this.config.getVite()?.moduleGraph.getModuleById(`${moduleId}${ext}`);\n\n if (module) {\n assets = { ...assets, ...this.getModuleAssets(module) };\n break;\n }\n }\n });\n\n return Object.values(assets);\n }\n\n /**\n * Get module assets\n */\n protected getModuleAssets(module?: ModuleNode, skipModules: Set<string> = new Set()): TAssets {\n if (!module?.clientImportedModules.size || skipModules.has(module.file!)) {\n return {};\n }\n\n let assets: TAssets = {};\n\n skipModules.add(module.file!);\n\n module.clientImportedModules.forEach((subModule) => {\n const { file, clientImportedModules, transformResult } = subModule;\n const ext = file?.split('.').at(-1);\n\n if (file && ext && ['css', 'scss'].includes(ext)) {\n // @TODO investigate better method?\n const code = transformResult?.code.match(/__vite__css\\s+=\\s+\"(?<css>.+)\"/)?.groups?.css;\n\n if (code) {\n try {\n assets[file] = {\n type: AssetType.style,\n url: file,\n weight: this.getAssetWeight(file),\n content: (JSON.parse(`{\"style\": \"${code}\"}`) as { style: string }).style,\n isNested: Boolean(skipModules.size),\n isPreload: false,\n };\n } catch {\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","renderBuiltUrl","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","getRoutesTreeIds","routes","index","forEach","route","routeIndex","routeId","String","filter","Boolean","join","import","getAppPath","children","Object","assign","sortAssets","assets","sort","a","b","weight","Number","isNested","getRouteAssets","manifest","module","css","file","reduce","res","asset","type","getAssetType","isEntry","filename","posix","normalize","modifiedFilename","ssr","hostId","hostType","split","at","toLowerCase","url","getAssetWeight","isPreload","imports","nestedAsset","nestedModule","buildRoutesManifest","routesService","ParseRoutes","routesPaths","postfixes","getImportPostfix","entries","routePath","routePostfix","find","postfix","undefined","routeMeta","values","writeFileSync","stringify","getAssets","getAssetsDev","routeIds","map","id","flat","pathId","getPluginConfig","clientFile","moduleId","ext","moduleGraph","getModuleById","getModuleAssets","skipModules","Set","clientImportedModules","size","has","add","subModule","transformResult","includes","code","match","groups","style","content","console","warn","chalk","yellowBright","script","image","font","writeEarlyHits","socket","write","injectAssets","routerContext","html","hasEarlyHints","matches","htmlAssets","isModulePreload","header","replace"],"mappings":"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"}