@lomray/vite-ssr-boost 2.3.7 → 2.4.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/build.js +1 -1
- package/cli/build.js.map +1 -1
- package/constants/stream-error.d.ts +1 -0
- package/constants/stream-error.js +1 -1
- package/constants/stream-error.js.map +1 -1
- package/helpers/obtain-stream-error.js +1 -1
- package/helpers/obtain-stream-error.js.map +1 -1
- package/helpers/ssr-meta.d.ts +18 -0
- package/helpers/ssr-meta.js +2 -0
- package/helpers/ssr-meta.js.map +1 -0
- package/package.json +15 -12
- package/plugin.d.ts +1 -0
- package/plugin.js +1 -1
- package/plugin.js.map +1 -1
- package/plugins/make-aliases.d.ts +1 -1
- package/plugins/make-aliases.js.map +1 -1
- package/plugins/normalize-route.d.ts +2 -1
- package/plugins/normalize-route.js +1 -1
- package/plugins/normalize-route.js.map +1 -1
- package/services/build.d.ts +7 -0
- package/services/build.js +1 -1
- package/services/build.js.map +1 -1
- package/services/parse-routes.d.ts +85 -0
- package/services/parse-routes.js +2 -0
- package/services/parse-routes.js.map +1 -0
- package/services/path-normalize.d.ts +51 -0
- package/services/path-normalize.js +2 -0
- package/services/path-normalize.js.map +1 -0
- package/services/server-config.d.ts +1 -0
- package/services/server-config.js +1 -1
- package/services/server-config.js.map +1 -1
- package/services/ssr-manifest.d.ts +15 -23
- package/services/ssr-manifest.js +1 -1
- package/services/ssr-manifest.js.map +1 -1
package/cli/build.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:child_process";import{performance as o}from"node:perf_hooks";import i from"chalk";import s from"./helpers/vite-reset-cache.js";import t from"../constants/cli-name.js";import{createDevMarker as r}from"../helpers/dev-marker.js";import n from"../helpers/process-stop.js";import l from"../services/build.js";async function
|
|
1
|
+
import e from"node:child_process";import{performance as o}from"node:perf_hooks";import i from"chalk";import s from"./helpers/vite-reset-cache.js";import t from"../constants/cli-name.js";import{createDevMarker as r}from"../helpers/dev-marker.js";import n from"../helpers/process-stop.js";import{removeMeta as l}from"../helpers/ssr-meta.js";import a from"../services/build.js";async function m({onFinish:m,mode:p="",clientOptions:c="",serverOptions:d="",isOnlyClient:f=!1,isWatch:u=!1,isUnlockRobots:v=!1,isEject:O=!1,isServerless:S=!1,isNoWarnings:h=!1}){const $=o.now(),b=new a({mode:p}),_=["client"],g=new AbortController,w=p?`--mode ${p}`:"";await b.makeConfig(),await s(),b.clearBuildFolder();const C=b.promisifyProcess(e.spawn(`vite build ${c} --emptyOutDir --outDir ${b.outDir}/client ${w}`,{signal:g.signal,stdio:[process.stdin,"pipe","pipe"],shell:!0,env:{...process.env,FORCE_COLOR:"2",SSR_BOOST_IS_SSR:f?"0":"1",SSR_BOOST_ACTION:global.viteBoostAction}}),h);if(!u){const e=await C.promise;n(e,!0)}let R=null;if(!f){if(R=b.promisifyProcess(e.spawn(`vite build ${d} --emptyOutDir --outDir ${b.outDir}/server --ssr ${b.serverFile} ${w}`,{signal:g.signal,stdio:[process.stdin,"pipe","pipe"],shell:!0,env:{...process.env,FORCE_COLOR:"2",SSR_BOOST_IS_SSR:f?"0":"1",SSR_BOOST_ACTION:global.viteBoostAction}}),h),!u){const e=await R.promise;n(e,!0),await b.buildManifest(),O&&b.eject(),S&&b.createServerless()}_.push("server")}if(u){process.on("exit",(()=>{g.abort()}));let e=f?1:2;const o=i=>{Buffer.from(i).toString().includes("built in")&&(e-=1,e||(C.command.stdout?.removeListener("data",o),R?.command.stdout?.removeListener("data",o),r(b.isProd,b.viteConfig),m?.()))};return C.command.stdout?.on("data",o),void R?.command.stdout?.on("data",o)}v&&b.unlockRobots(),r(b.isProd,b.viteConfig),l(b.buildDir),m?.();const B=Math.ceil(o.now()-$),j=B>1e3?(B/1e3).toFixed(2):B,D=B>1e3?"s":"ms",y=i.dim(`${i.yellowBright(_.join(","))} built in ${i.reset(i.bold(j))} ${D}`);console.info(`\n ${i.green(`${i.bold(t.toUpperCase())}`)} ${y} ${b.isProd?"":i.redBright(`NODE_ENV=${b.nodeEnv}`)}\n`)}export{m as default};
|
|
2
2
|
//# sourceMappingURL=build.js.map
|
package/cli/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sources":["../../src/cli/build.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport { performance } from 'node:perf_hooks';\nimport chalk from 'chalk';\nimport viteResetCache from '@cli/helpers/vite-reset-cache';\nimport cliName from '@constants/cli-name';\nimport { createDevMarker } from '@helpers/dev-marker';\nimport processStop from '@helpers/process-stop';\nimport Build from '@services/build';\n\ninterface IBuildParams {\n onFinish?: () => void;\n mode?: string;\n clientOptions?: string;\n serverOptions?: string;\n isOnlyClient?: boolean;\n isWatch?: boolean;\n isUnlockRobots?: boolean;\n isEject?: boolean;\n isServerless?: boolean;\n isNoWarnings?: boolean;\n}\n\n/**\n * Build production application\n */\nasync function build({\n onFinish,\n mode = '',\n clientOptions = '',\n serverOptions = '',\n isOnlyClient = false,\n isWatch = false,\n isUnlockRobots = false,\n isEject = false,\n isServerless = false,\n isNoWarnings = false,\n}: IBuildParams): Promise<void> {\n const perfStart = performance.now();\n const buildService = new Build({ mode });\n const types = ['client'];\n const controller = new AbortController();\n const modeOpt = mode ? `--mode ${mode}` : '';\n\n await buildService.makeConfig();\n\n // this is required step - build with different env may cause problems\n await viteResetCache();\n buildService.clearBuildFolder();\n\n /**\n * Build client\n */\n const clientProcess = buildService.promisifyProcess(\n childProcess.spawn(\n `vite build ${clientOptions} --emptyOutDir --outDir ${buildService.outDir}/client ${modeOpt}`,\n {\n signal: controller.signal,\n stdio: [process.stdin, 'pipe', 'pipe'],\n shell: true,\n env: {\n ...process.env,\n FORCE_COLOR: '2',\n SSR_BOOST_IS_SSR: isOnlyClient ? '0' : '1',\n SSR_BOOST_ACTION: global.viteBoostAction,\n },\n },\n ),\n isNoWarnings,\n );\n\n if (!isWatch) {\n const exitCode = await clientProcess.promise;\n\n processStop(exitCode, true);\n }\n\n let serverProcess: ReturnType<Build['promisifyProcess']> | null = null;\n\n /**\n * Build server\n */\n if (!isOnlyClient) {\n serverProcess = buildService.promisifyProcess(\n childProcess.spawn(\n `vite build ${serverOptions} --emptyOutDir --outDir ${buildService.outDir}/server --ssr ${buildService.serverFile} ${modeOpt}`,\n {\n signal: controller.signal,\n stdio: [process.stdin, 'pipe', 'pipe'],\n shell: true,\n env: {\n ...process.env,\n FORCE_COLOR: '2',\n SSR_BOOST_IS_SSR: isOnlyClient ? '0' : '1',\n SSR_BOOST_ACTION: global.viteBoostAction,\n },\n },\n ),\n isNoWarnings,\n );\n\n if (!isWatch) {\n const exitCode = await serverProcess.promise;\n\n processStop(exitCode, true);\n await buildService.buildManifest();\n\n if (isEject) {\n buildService.eject();\n }\n\n if (isServerless) {\n buildService.createServerless();\n }\n }\n\n types.push('server');\n }\n\n /**\n * Preview mode\n */\n if (isWatch) {\n process.on('exit', () => {\n controller.abort();\n });\n\n let buildCount = isOnlyClient ? 1 : 2;\n const listener = (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('built in')) {\n buildCount -= 1;\n\n if (!buildCount) {\n clientProcess.command.stdout?.removeListener('data', listener);\n serverProcess?.command.stdout?.removeListener('data', listener);\n createDevMarker(buildService.isProd, buildService.viteConfig);\n onFinish?.();\n }\n }\n };\n\n /**\n * Listen output for call onFinish\n */\n clientProcess.command.stdout?.on('data', listener);\n serverProcess?.command.stdout?.on('data', listener);\n\n return;\n }\n\n if (isUnlockRobots) {\n buildService.unlockRobots();\n }\n\n createDevMarker(buildService.isProd, buildService.viteConfig);\n onFinish?.();\n\n const durationMs = Math.ceil(performance.now() - perfStart);\n const duration = durationMs > 1000 ? (durationMs / 1000).toFixed(2) : durationMs;\n const units = durationMs > 1000 ? 's' : 'ms';\n\n const buildDurationString = chalk.dim(\n `${chalk.yellowBright(types.join(','))} built in ${chalk.reset(chalk.bold(duration))} ${units}`,\n );\n\n console.info(\n `\\n ${chalk.green(`${chalk.bold(cliName.toUpperCase())}`)} ${buildDurationString} ${\n buildService.isProd ? '' : chalk.redBright(`NODE_ENV=${buildService.nodeEnv}`)\n }\\n`,\n );\n}\n\nexport default build;\n"],"names":["async","build","onFinish","mode","clientOptions","serverOptions","isOnlyClient","isWatch","isUnlockRobots","isEject","isServerless","isNoWarnings","perfStart","performance","now","buildService","Build","types","controller","AbortController","modeOpt","makeConfig","viteResetCache","clearBuildFolder","clientProcess","promisifyProcess","childProcess","spawn","outDir","signal","stdio","process","stdin","shell","env","FORCE_COLOR","SSR_BOOST_IS_SSR","SSR_BOOST_ACTION","global","viteBoostAction","exitCode","promise","processStop","serverProcess","serverFile","buildManifest","eject","createServerless","push","on","abort","buildCount","listener","buff","Buffer","from","toString","includes","command","stdout","removeListener","createDevMarker","isProd","viteConfig","unlockRobots","durationMs","Math","ceil","duration","toFixed","units","buildDurationString","chalk","dim","yellowBright","join","reset","bold","console","info","green","cliName","toUpperCase","redBright","nodeEnv"],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.js","sources":["../../src/cli/build.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport { performance } from 'node:perf_hooks';\nimport chalk from 'chalk';\nimport viteResetCache from '@cli/helpers/vite-reset-cache';\nimport cliName from '@constants/cli-name';\nimport { createDevMarker } from '@helpers/dev-marker';\nimport processStop from '@helpers/process-stop';\nimport { removeMeta } from '@helpers/ssr-meta';\nimport Build from '@services/build';\n\ninterface IBuildParams {\n onFinish?: () => void;\n mode?: string;\n clientOptions?: string;\n serverOptions?: string;\n isOnlyClient?: boolean;\n isWatch?: boolean;\n isUnlockRobots?: boolean;\n isEject?: boolean;\n isServerless?: boolean;\n isNoWarnings?: boolean;\n}\n\n/**\n * Build production application\n */\nasync function build({\n onFinish,\n mode = '',\n clientOptions = '',\n serverOptions = '',\n isOnlyClient = false,\n isWatch = false,\n isUnlockRobots = false,\n isEject = false,\n isServerless = false,\n isNoWarnings = false,\n}: IBuildParams): Promise<void> {\n const perfStart = performance.now();\n const buildService = new Build({ mode });\n const types = ['client'];\n const controller = new AbortController();\n const modeOpt = mode ? `--mode ${mode}` : '';\n\n await buildService.makeConfig();\n\n // this is required step - build with different env may cause problems\n await viteResetCache();\n buildService.clearBuildFolder();\n\n /**\n * Build client\n */\n const clientProcess = buildService.promisifyProcess(\n childProcess.spawn(\n `vite build ${clientOptions} --emptyOutDir --outDir ${buildService.outDir}/client ${modeOpt}`,\n {\n signal: controller.signal,\n stdio: [process.stdin, 'pipe', 'pipe'],\n shell: true,\n env: {\n ...process.env,\n FORCE_COLOR: '2',\n SSR_BOOST_IS_SSR: isOnlyClient ? '0' : '1',\n SSR_BOOST_ACTION: global.viteBoostAction,\n },\n },\n ),\n isNoWarnings,\n );\n\n if (!isWatch) {\n const exitCode = await clientProcess.promise;\n\n processStop(exitCode, true);\n }\n\n let serverProcess: ReturnType<Build['promisifyProcess']> | null = null;\n\n /**\n * Build server\n */\n if (!isOnlyClient) {\n serverProcess = buildService.promisifyProcess(\n childProcess.spawn(\n `vite build ${serverOptions} --emptyOutDir --outDir ${buildService.outDir}/server --ssr ${buildService.serverFile} ${modeOpt}`,\n {\n signal: controller.signal,\n stdio: [process.stdin, 'pipe', 'pipe'],\n shell: true,\n env: {\n ...process.env,\n FORCE_COLOR: '2',\n SSR_BOOST_IS_SSR: isOnlyClient ? '0' : '1',\n SSR_BOOST_ACTION: global.viteBoostAction,\n },\n },\n ),\n isNoWarnings,\n );\n\n if (!isWatch) {\n const exitCode = await serverProcess.promise;\n\n processStop(exitCode, true);\n await buildService.buildManifest();\n\n if (isEject) {\n buildService.eject();\n }\n\n if (isServerless) {\n buildService.createServerless();\n }\n }\n\n types.push('server');\n }\n\n /**\n * Preview mode\n */\n if (isWatch) {\n process.on('exit', () => {\n controller.abort();\n });\n\n let buildCount = isOnlyClient ? 1 : 2;\n const listener = (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('built in')) {\n buildCount -= 1;\n\n if (!buildCount) {\n clientProcess.command.stdout?.removeListener('data', listener);\n serverProcess?.command.stdout?.removeListener('data', listener);\n createDevMarker(buildService.isProd, buildService.viteConfig);\n onFinish?.();\n }\n }\n };\n\n /**\n * Listen output for call onFinish\n */\n clientProcess.command.stdout?.on('data', listener);\n serverProcess?.command.stdout?.on('data', listener);\n\n return;\n }\n\n if (isUnlockRobots) {\n buildService.unlockRobots();\n }\n\n createDevMarker(buildService.isProd, buildService.viteConfig);\n removeMeta(buildService.buildDir);\n onFinish?.();\n\n const durationMs = Math.ceil(performance.now() - perfStart);\n const duration = durationMs > 1000 ? (durationMs / 1000).toFixed(2) : durationMs;\n const units = durationMs > 1000 ? 's' : 'ms';\n\n const buildDurationString = chalk.dim(\n `${chalk.yellowBright(types.join(','))} built in ${chalk.reset(chalk.bold(duration))} ${units}`,\n );\n\n console.info(\n `\\n ${chalk.green(`${chalk.bold(cliName.toUpperCase())}`)} ${buildDurationString} ${\n buildService.isProd ? '' : chalk.redBright(`NODE_ENV=${buildService.nodeEnv}`)\n }\\n`,\n );\n}\n\nexport default build;\n"],"names":["async","build","onFinish","mode","clientOptions","serverOptions","isOnlyClient","isWatch","isUnlockRobots","isEject","isServerless","isNoWarnings","perfStart","performance","now","buildService","Build","types","controller","AbortController","modeOpt","makeConfig","viteResetCache","clearBuildFolder","clientProcess","promisifyProcess","childProcess","spawn","outDir","signal","stdio","process","stdin","shell","env","FORCE_COLOR","SSR_BOOST_IS_SSR","SSR_BOOST_ACTION","global","viteBoostAction","exitCode","promise","processStop","serverProcess","serverFile","buildManifest","eject","createServerless","push","on","abort","buildCount","listener","buff","Buffer","from","toString","includes","command","stdout","removeListener","createDevMarker","isProd","viteConfig","unlockRobots","removeMeta","buildDir","durationMs","Math","ceil","duration","toFixed","units","buildDurationString","chalk","dim","yellowBright","join","reset","bold","console","info","green","cliName","toUpperCase","redBright","nodeEnv"],"mappings":"uXA0BAA,eAAeC,GAAMC,SACnBA,EAAQC,KACRA,EAAO,GAAEC,cACTA,EAAgB,GAAEC,cAClBA,EAAgB,GAAEC,aAClBA,GAAe,EAAKC,QACpBA,GAAU,EAAKC,eACfA,GAAiB,EAAKC,QACtBA,GAAU,EAAKC,aACfA,GAAe,EAAKC,aACpBA,GAAe,IAEf,MAAMC,EAAYC,EAAYC,MACxBC,EAAe,IAAIC,EAAM,CAAEb,SAC3Bc,EAAQ,CAAC,UACTC,EAAa,IAAIC,gBACjBC,EAAUjB,EAAO,UAAUA,IAAS,SAEpCY,EAAaM,mBAGbC,IACNP,EAAaQ,mBAKb,MAAMC,EAAgBT,EAAaU,iBACjCC,EAAaC,MACX,cAAcvB,4BAAwCW,EAAaa,iBAAiBR,IACpF,CACES,OAAQX,EAAWW,OACnBC,MAAO,CAACC,QAAQC,MAAO,OAAQ,QAC/BC,OAAO,EACPC,IAAK,IACAH,QAAQG,IACXC,YAAa,IACbC,iBAAkB9B,EAAe,IAAM,IACvC+B,iBAAkBC,OAAOC,mBAI/B5B,GAGF,IAAKJ,EAAS,CACZ,MAAMiC,QAAiBhB,EAAciB,QAErCC,EAAYF,GAAU,EACvB,CAED,IAAIG,EAA8D,KAKlE,IAAKrC,EAAc,CAmBjB,GAlBAqC,EAAgB5B,EAAaU,iBAC3BC,EAAaC,MACX,cAActB,4BAAwCU,EAAaa,uBAAuBb,EAAa6B,cAAcxB,IACrH,CACES,OAAQX,EAAWW,OACnBC,MAAO,CAACC,QAAQC,MAAO,OAAQ,QAC/BC,OAAO,EACPC,IAAK,IACAH,QAAQG,IACXC,YAAa,IACbC,iBAAkB9B,EAAe,IAAM,IACvC+B,iBAAkBC,OAAOC,mBAI/B5B,IAGGJ,EAAS,CACZ,MAAMiC,QAAiBG,EAAcF,QAErCC,EAAYF,GAAU,SAChBzB,EAAa8B,gBAEfpC,GACFM,EAAa+B,QAGXpC,GACFK,EAAagC,kBAEhB,CAED9B,EAAM+B,KAAK,SACZ,CAKD,GAAIzC,EAAS,CACXwB,QAAQkB,GAAG,QAAQ,KACjB/B,EAAWgC,OAAO,IAGpB,IAAIC,EAAa7C,EAAe,EAAI,EACpC,MAAM8C,EAAYC,IACJC,OAAOC,KAAKF,GAAMG,WAEtBC,SAAS,cACfN,GAAc,EAETA,IACH3B,EAAckC,QAAQC,QAAQC,eAAe,OAAQR,GACrDT,GAAee,QAAQC,QAAQC,eAAe,OAAQR,GACtDS,EAAgB9C,EAAa+C,OAAQ/C,EAAagD,YAClD7D,OAEH,EASH,OAHAsB,EAAckC,QAAQC,QAAQV,GAAG,OAAQG,QACzCT,GAAee,QAAQC,QAAQV,GAAG,OAAQG,EAG3C,CAEG5C,GACFO,EAAaiD,eAGfH,EAAgB9C,EAAa+C,OAAQ/C,EAAagD,YAClDE,EAAWlD,EAAamD,UACxBhE,MAEA,MAAMiE,EAAaC,KAAKC,KAAKxD,EAAYC,MAAQF,GAC3C0D,EAAWH,EAAa,KAAQA,EAAa,KAAMI,QAAQ,GAAKJ,EAChEK,EAAQL,EAAa,IAAO,IAAM,KAElCM,EAAsBC,EAAMC,IAChC,GAAGD,EAAME,aAAa3D,EAAM4D,KAAK,kBAAkBH,EAAMI,MAAMJ,EAAMK,KAAKT,OAAcE,KAG1FQ,QAAQC,KACN,OAAOP,EAAMQ,MAAM,GAAGR,EAAMK,KAAKI,EAAQC,sBAAsBX,KAC7D1D,EAAa+C,OAAS,GAAKY,EAAMW,UAAU,YAAYtE,EAAauE,eAG1E"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e;!function(e){e.RenderAborted="aborted",e.RenderTimeout="timeout",e.RenderCancel="cancel",e.Unknown="unknown"}(e||(e={}));var n=e;export{n as default};
|
|
1
|
+
var e;!function(e){e.RenderAborted="aborted",e.RenderTimeout="timeout",e.RenderCancel="cancel",e.RenderSkip="skip",e.Unknown="unknown"}(e||(e={}));var n=e;export{n as default};
|
|
2
2
|
//# sourceMappingURL=stream-error.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-error.js","sources":["../../src/constants/stream-error.ts"],"sourcesContent":["/**\n * React stream errors\n */\nenum StreamError {\n RenderAborted = 'aborted',\n RenderTimeout = 'timeout',\n RenderCancel = 'cancel',\n Unknown = 'unknown',\n}\n\nexport default StreamError;\n"],"names":["StreamError","StreamError$1"],"mappings":"AAGA,IAAKA,GAAL,SAAKA,GACHA,EAAA,cAAA,UACAA,EAAA,cAAA,UACAA,EAAA,aAAA,SACAA,EAAA,QAAA,SACD,
|
|
1
|
+
{"version":3,"file":"stream-error.js","sources":["../../src/constants/stream-error.ts"],"sourcesContent":["/**\n * React stream errors\n */\nenum StreamError {\n RenderAborted = 'aborted',\n RenderTimeout = 'timeout',\n RenderCancel = 'cancel',\n RenderSkip = 'skip',\n Unknown = 'unknown',\n}\n\nexport default StreamError;\n"],"names":["StreamError","StreamError$1"],"mappings":"AAGA,IAAKA,GAAL,SAAKA,GACHA,EAAA,cAAA,UACAA,EAAA,cAAA,UACAA,EAAA,aAAA,SACAA,EAAA,WAAA,OACAA,EAAA,QAAA,SACD,CAND,CAAKA,IAAAA,EAMJ,CAAA,IAED,IAAAC,EAAeD"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"../constants/stream-error.js";const r=r=>{const o=(r?.message??"Unknown.").replace("Error: ","");return"The render was aborted by the server without a reason."===o?{code:e.RenderAborted,message:o,original:r}:{code:e.Unknown,message:o||"Unknown error",original:r}};export{r as default};
|
|
1
|
+
import e from"../constants/stream-error.js";const r=r=>{const o=(r?.message??"Unknown.").replace("Error: ","");return"The render was aborted by the server without a reason."===o?{code:e.RenderAborted,message:o,original:r}:"The destination stream closed early."===o?{code:e.RenderSkip,message:o,original:r}:{code:e.Unknown,message:o||"Unknown error",original:r}};export{r as default};
|
|
2
2
|
//# sourceMappingURL=obtain-stream-error.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"obtain-stream-error.js","sources":["../../src/helpers/obtain-stream-error.ts"],"sourcesContent":["import StreamError from '@constants/stream-error';\n\nexport interface IObtainStreamErrorOut {\n code: StreamError;\n message: string;\n original: unknown;\n}\n\n/**\n * Get react stream error\n */\nconst obtainStreamError = (err: unknown): IObtainStreamErrorOut => {\n const message = ((err as Error)?.message ?? 'Unknown.').replace('Error: ', '');\n\n if (message === 'The render was aborted by the server without a reason.') {\n return {\n code: StreamError.RenderAborted,\n message,\n original: err,\n };\n }\n\n return {\n code: StreamError.Unknown,\n message: message || 'Unknown error',\n original: err,\n };\n};\n\nexport default obtainStreamError;\n"],"names":["obtainStreamError","err","message","replace","code","StreamError","RenderAborted","original","Unknown"],"mappings":"4CAWA,MAAMA,EAAqBC,IACzB,MAAMC,GAAYD,GAAeC,SAAW,YAAYC,QAAQ,UAAW,IAE3E,MAAgB,2DAAZD,EACK,CACLE,KAAMC,EAAYC,cAClBJ,UACAK,SAAUN,GAIP,CACLG,KAAMC,
|
|
1
|
+
{"version":3,"file":"obtain-stream-error.js","sources":["../../src/helpers/obtain-stream-error.ts"],"sourcesContent":["import StreamError from '@constants/stream-error';\n\nexport interface IObtainStreamErrorOut {\n code: StreamError;\n message: string;\n original: unknown;\n}\n\n/**\n * Get react stream error\n */\nconst obtainStreamError = (err: unknown): IObtainStreamErrorOut => {\n const message = ((err as Error)?.message ?? 'Unknown.').replace('Error: ', '');\n\n if (message === 'The render was aborted by the server without a reason.') {\n return {\n code: StreamError.RenderAborted,\n message,\n original: err,\n };\n } else if (message === 'The destination stream closed early.') {\n return {\n code: StreamError.RenderSkip,\n message,\n original: err,\n };\n }\n\n return {\n code: StreamError.Unknown,\n message: message || 'Unknown error',\n original: err,\n };\n};\n\nexport default obtainStreamError;\n"],"names":["obtainStreamError","err","message","replace","code","StreamError","RenderAborted","original","RenderSkip","Unknown"],"mappings":"4CAWA,MAAMA,EAAqBC,IACzB,MAAMC,GAAYD,GAAeC,SAAW,YAAYC,QAAQ,UAAW,IAE3E,MAAgB,2DAAZD,EACK,CACLE,KAAMC,EAAYC,cAClBJ,UACAK,SAAUN,GAES,yCAAZC,EACF,CACLE,KAAMC,EAAYG,WAClBN,UACAK,SAAUN,GAIP,CACLG,KAAMC,EAAYI,QAClBP,QAASA,GAAW,gBACpBK,SAAUN,EACX"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface ISsrMetadata {
|
|
2
|
+
routeFiles?: {
|
|
3
|
+
[originalFileName: string]: string;
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Write build metadata
|
|
8
|
+
*/
|
|
9
|
+
declare const writeMeta: (buildDir: string, data: ISsrMetadata) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Read build metadata
|
|
12
|
+
*/
|
|
13
|
+
declare const readMeta: (buildDir: string) => ISsrMetadata;
|
|
14
|
+
/**
|
|
15
|
+
* Remove metadata file
|
|
16
|
+
*/
|
|
17
|
+
declare const removeMeta: (buildDir: string) => void;
|
|
18
|
+
export { writeMeta, readMeta, removeMeta };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import n from"node:fs";const t=n=>`${n}/meta.json`,r=(r,e)=>{const o=c(r);n.writeFileSync(t(r),JSON.stringify({...o,...e},null,2),{encoding:"utf-8"})},c=r=>{const c=t(r);try{return JSON.parse(n.readFileSync(c,{encoding:"utf-8"}))}catch(n){}return{}},e=r=>{try{n.unlinkSync(t(r))}catch(n){}};export{c as readMeta,e as removeMeta,r as writeMeta};
|
|
2
|
+
//# sourceMappingURL=ssr-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-meta.js","sources":["../../src/helpers/ssr-meta.ts"],"sourcesContent":["import fs from 'node:fs';\n\ninterface ISsrMetadata {\n routeFiles?: {\n // original => generated file\n [originalFileName: string]: string;\n };\n}\n\n/**\n * Return meta file path\n */\nconst getMetaFilepath = (buildDir: string): string => `${buildDir}/meta.json`;\n\n/**\n * Write build metadata\n */\nconst writeMeta = (buildDir: string, data: ISsrMetadata): void => {\n const meta = readMeta(buildDir);\n\n fs.writeFileSync(getMetaFilepath(buildDir), JSON.stringify({ ...meta, ...data }, null, 2), {\n encoding: 'utf-8',\n });\n};\n\n/**\n * Read build metadata\n */\nconst readMeta = (buildDir: string): ISsrMetadata => {\n const metaFile = getMetaFilepath(buildDir);\n\n try {\n return JSON.parse(fs.readFileSync(metaFile, { encoding: 'utf-8' })) as ISsrMetadata;\n } catch (e) {\n // ignore, file not exist\n }\n\n return {};\n};\n\n/**\n * Remove metadata file\n */\nconst removeMeta = (buildDir: string): void => {\n try {\n fs.unlinkSync(getMetaFilepath(buildDir));\n } catch (e) {\n // ignore\n }\n};\n\nexport { writeMeta, readMeta, removeMeta };\n"],"names":["getMetaFilepath","buildDir","writeMeta","data","meta","readMeta","fs","writeFileSync","JSON","stringify","encoding","metaFile","parse","readFileSync","e","removeMeta","unlinkSync"],"mappings":"uBAYA,MAAMA,EAAmBC,GAA6B,GAAGA,cAKnDC,EAAY,CAACD,EAAkBE,KACnC,MAAMC,EAAOC,EAASJ,GAEtBK,EAAGC,cAAcP,EAAgBC,GAAWO,KAAKC,UAAU,IAAKL,KAASD,GAAQ,KAAM,GAAI,CACzFO,SAAU,SACV,EAMEL,EAAYJ,IAChB,MAAMU,EAAWX,EAAgBC,GAEjC,IACE,OAAOO,KAAKI,MAAMN,EAAGO,aAAaF,EAAU,CAAED,SAAU,UACzD,CAAC,MAAOI,GAER,CAED,MAAO,EAAE,EAMLC,EAAcd,IAClB,IACEK,EAAGU,WAAWhB,EAAgBC,GAC/B,CAAC,MAAOa,GAER"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lomray/vite-ssr-boost",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0-beta.2",
|
|
4
4
|
"description": "Vite plugin for create awesome SSR or SPA applications on React.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -34,46 +34,49 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"chalk": "^5.3.0",
|
|
37
|
-
"commander": "^12.
|
|
37
|
+
"commander": "^12.1.0",
|
|
38
38
|
"compression": "^1.7.4",
|
|
39
39
|
"express": "^4.19.2",
|
|
40
40
|
"hoist-non-react-statics": "^3.3.2",
|
|
41
41
|
"json5": "^2.2.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@commitlint/cli": "^19.
|
|
44
|
+
"@commitlint/cli": "^19.3.0",
|
|
45
45
|
"@commitlint/config-conventional": "^19.2.2",
|
|
46
46
|
"@lomray/eslint-config-react": "^5.0.6",
|
|
47
47
|
"@lomray/prettier-config": "^2.0.1",
|
|
48
48
|
"@rollup/plugin-terser": "^0.4.4",
|
|
49
|
-
"@testing-library/react": "^15.0.
|
|
50
|
-
"@types/
|
|
49
|
+
"@testing-library/react": "^15.0.7",
|
|
50
|
+
"@types/babel__traverse": "^7.20.6",
|
|
51
|
+
"@types/chai": "^4.3.16",
|
|
51
52
|
"@types/compression": "^1.7.5",
|
|
52
53
|
"@types/hoist-non-react-statics": "^3.3.5",
|
|
53
|
-
"@types/react-dom": "^18.
|
|
54
|
+
"@types/react-dom": "^18.3.0",
|
|
54
55
|
"@types/sinon": "^17.0.3",
|
|
55
56
|
"@types/sinon-chai": "^3.2.12",
|
|
56
|
-
"@vitest/coverage-v8": "^1.
|
|
57
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
57
58
|
"@zerollup/ts-transform-paths": "^1.7.18",
|
|
58
59
|
"chai": "^4.4.1",
|
|
59
60
|
"eslint": "^8.57.0",
|
|
60
61
|
"husky": "^9.0.11",
|
|
61
62
|
"jsdom": "^24.0.0",
|
|
62
|
-
"lint-staged": "^15.2.
|
|
63
|
+
"lint-staged": "^15.2.4",
|
|
63
64
|
"prettier": "^3.2.5",
|
|
64
|
-
"rollup": "^4.
|
|
65
|
+
"rollup": "^4.18.0",
|
|
65
66
|
"rollup-plugin-copy": "^3.5.0",
|
|
66
67
|
"rollup-plugin-folder-input": "^1.0.1",
|
|
67
68
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
68
69
|
"rollup-plugin-preserve-shebangs": "^0.2.0",
|
|
69
70
|
"rollup-plugin-ts": "^3.4.5",
|
|
70
|
-
"semantic-release": "^23.
|
|
71
|
-
"sinon": "^17.0.
|
|
71
|
+
"semantic-release": "^23.1.1",
|
|
72
|
+
"sinon": "^17.0.2",
|
|
72
73
|
"sinon-chai": "^3.7.0",
|
|
73
74
|
"typescript": "^5.3.3",
|
|
74
|
-
"vitest": "^1.
|
|
75
|
+
"vitest": "^1.6.0"
|
|
75
76
|
},
|
|
76
77
|
"peerDependencies": {
|
|
78
|
+
"@babel/parser": ">=7.23.0",
|
|
79
|
+
"@babel/traverse": ">=7.23.0",
|
|
77
80
|
"@types/express": ">=4.17.21",
|
|
78
81
|
"react-dom": ">=18.2.0",
|
|
79
82
|
"react-router-dom": ">=6.12.1",
|
package/plugin.d.ts
CHANGED
package/plugin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"node:path";import i from"./constants/cli-actions.js";import s from"./constants/plugin-name.js";import o from"./plugins/make-aliases.js";import n from"./plugins/normalize-route.js";const t={indexFile:"index.html",serverFile:"server.ts",clientFile:"client.ts",routesParsing:"babel",tsconfigAliases:!0};function r(r={}){const a=new URL(import.meta.url),l=global.viteBoostAction||process.env.SSR_BOOST_ACTION,p={...t,...r},u="1"===process.env.SSR_BOOST_IS_SSR||l===i.dev,m=l===i.build,c=[{name:s,enforce:"pre",pluginOptions:{...p,pluginPath:e.dirname(a.pathname),action:l,isDev:l===i.dev},config:(e,{isSsrBuild:i})=>(e.define={...e.define??{},__IS_SSR__:u},e.build={...e.build??{}},i?{...e,...m?{appType:"custom"}:{},publicDir:!1}:(u&&m&&(e.build.manifest=!0),e))}],{tsconfigAliases:d,routesPath:f,routesParsing:S}=p;return d&&c.push(o("boolean"==typeof d?void 0:d)),c.push(n({isSSR:u,isBuild:m,routesPath:f,isNodeParsing:"node"===S})),c}export{r as default};
|
|
2
2
|
//# sourceMappingURL=plugin.js.map
|
package/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["../src/plugin.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Plugin } from 'vite';\nimport CliActions from '@constants/cli-actions';\nimport type { ICliContext } from '@constants/cli-context';\nimport PLUGIN_NAME from '@constants/plugin-name';\nimport type { IPluginOptions as IMakeAliasesPluginOptions } from '@plugins/make-aliases';\nimport ViteMakeAliasesPlugin from '@plugins/make-aliases';\nimport ViteNormalizeRouterPlugin from '@plugins/normalize-route';\n\nexport interface IPluginOptions {\n // default: index.html\n indexFile?: string;\n // default: server.ts\n serverFile?: string;\n // default: client.ts\n clientFile?: string;\n // Path contains routes declaration files (need to detect route files). default: undefined, e.g.: /routes/\n routesPath?: string;\n // Read aliases from tsconfig\n tsconfigAliases?: boolean | IMakeAliasesPluginOptions;\n customShortcuts?: {\n key: string;\n description: string;\n action: (cliContext: ICliContext) => Promise<void> | void;\n isOnlyDev?: boolean;\n }[];\n}\n\nconst defaultOptions: IPluginOptions = {\n indexFile: 'index.html',\n serverFile: 'server.ts',\n clientFile: 'client.ts',\n tsconfigAliases: true,\n};\n\n/**\n * Init plugin\n * @constructor\n */\nfunction ViteSsrBoostPlugin(options: IPluginOptions = {}): Plugin[] {\n const dirInfo = new URL(import.meta.url);\n const action = (global.viteBoostAction || process.env.SSR_BOOST_ACTION) as CliActions;\n const mergedOptions: IPluginOptions = { ...defaultOptions, ...options };\n const isSSR = process.env.SSR_BOOST_IS_SSR === '1' || action === CliActions.dev;\n const isBuild = action === CliActions.build;\n\n const plugins: Plugin[] = [\n {\n name: PLUGIN_NAME,\n enforce: 'pre',\n // @ts-ignore save custom options\n pluginOptions: {\n ...mergedOptions,\n pluginPath: path.dirname(dirInfo.pathname),\n action,\n isDev: action === CliActions.dev,\n },\n\n config(config, { isSsrBuild }) {\n config.define = {\n ...(config.define ?? {}),\n __IS_SSR__: isSSR,\n };\n\n config.build = {\n ...(config.build ?? {}),\n };\n\n if (!isSsrBuild) {\n if (isSSR && isBuild) {\n config.build.manifest = true;\n }\n\n return config;\n }\n\n return {\n ...config,\n ...(isBuild ? { appType: 'custom' } : {}),\n publicDir: false,\n };\n },\n },\n ];\n\n const { tsconfigAliases, routesPath } = mergedOptions;\n\n if (tsconfigAliases) {\n plugins.push(\n ViteMakeAliasesPlugin(typeof tsconfigAliases === 'boolean' ? undefined : tsconfigAliases),\n );\n }\n\n plugins.push(ViteNormalizeRouterPlugin({
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../src/plugin.ts"],"sourcesContent":["import path from 'node:path';\nimport type { Plugin } from 'vite';\nimport CliActions from '@constants/cli-actions';\nimport type { ICliContext } from '@constants/cli-context';\nimport PLUGIN_NAME from '@constants/plugin-name';\nimport type { IPluginOptions as IMakeAliasesPluginOptions } from '@plugins/make-aliases';\nimport ViteMakeAliasesPlugin from '@plugins/make-aliases';\nimport ViteNormalizeRouterPlugin from '@plugins/normalize-route';\n\nexport interface IPluginOptions {\n // default: index.html\n indexFile?: string;\n // default: server.ts\n serverFile?: string;\n // default: client.ts\n clientFile?: string;\n // Path contains routes declaration files (need to detect route files). default: undefined, e.g.: /routes/\n routesPath?: string;\n // how parse routes\n // node - import routes file directly and walk through\n // babel - use babel travers to walk through and avoid import routes file\n // default: babel\n routesParsing?: 'node' | 'babel';\n // Read aliases from tsconfig\n tsconfigAliases?: boolean | IMakeAliasesPluginOptions;\n customShortcuts?: {\n key: string;\n description: string;\n action: (cliContext: ICliContext) => Promise<void> | void;\n isOnlyDev?: boolean;\n }[];\n}\n\nconst defaultOptions: IPluginOptions = {\n indexFile: 'index.html',\n serverFile: 'server.ts',\n clientFile: 'client.ts',\n routesParsing: 'babel',\n tsconfigAliases: true,\n};\n\n/**\n * Init plugin\n * @constructor\n */\nfunction ViteSsrBoostPlugin(options: IPluginOptions = {}): Plugin[] {\n const dirInfo = new URL(import.meta.url);\n const action = (global.viteBoostAction || process.env.SSR_BOOST_ACTION) as CliActions;\n const mergedOptions: IPluginOptions = { ...defaultOptions, ...options };\n const isSSR = process.env.SSR_BOOST_IS_SSR === '1' || action === CliActions.dev;\n const isBuild = action === CliActions.build;\n\n const plugins: Plugin[] = [\n {\n name: PLUGIN_NAME,\n enforce: 'pre',\n // @ts-ignore save custom options\n pluginOptions: {\n ...mergedOptions,\n pluginPath: path.dirname(dirInfo.pathname),\n action,\n isDev: action === CliActions.dev,\n },\n\n config(config, { isSsrBuild }) {\n config.define = {\n ...(config.define ?? {}),\n __IS_SSR__: isSSR,\n };\n\n config.build = {\n ...(config.build ?? {}),\n };\n\n if (!isSsrBuild) {\n if (isSSR && isBuild) {\n config.build.manifest = true;\n }\n\n return config;\n }\n\n return {\n ...config,\n ...(isBuild ? { appType: 'custom' } : {}),\n publicDir: false,\n };\n },\n },\n ];\n\n const { tsconfigAliases, routesPath, routesParsing } = mergedOptions;\n\n if (tsconfigAliases) {\n plugins.push(\n ViteMakeAliasesPlugin(typeof tsconfigAliases === 'boolean' ? undefined : tsconfigAliases),\n );\n }\n\n plugins.push(\n ViteNormalizeRouterPlugin({\n isSSR,\n isBuild,\n routesPath,\n isNodeParsing: routesParsing === 'node',\n }),\n );\n\n return plugins;\n}\n\nexport default ViteSsrBoostPlugin;\n"],"names":["defaultOptions","indexFile","serverFile","clientFile","routesParsing","tsconfigAliases","ViteSsrBoostPlugin","options","dirInfo","URL","url","action","global","viteBoostAction","process","env","SSR_BOOST_ACTION","mergedOptions","isSSR","SSR_BOOST_IS_SSR","CliActions","dev","isBuild","build","plugins","name","PLUGIN_NAME","enforce","pluginOptions","pluginPath","path","dirname","pathname","isDev","config","isSsrBuild","define","__IS_SSR__","appType","publicDir","manifest","routesPath","push","ViteMakeAliasesPlugin","undefined","ViteNormalizeRouterPlugin","isNodeParsing"],"mappings":"kMAiCA,MAAMA,EAAiC,CACrCC,UAAW,aACXC,WAAY,YACZC,WAAY,YACZC,cAAe,QACfC,iBAAiB,GAOnB,SAASC,EAAmBC,EAA0B,IACpD,MAAMC,EAAU,IAAIC,gBAAgBC,KAC9BC,EAAUC,OAAOC,iBAAmBC,QAAQC,IAAIC,iBAChDC,EAAgC,IAAKjB,KAAmBO,GACxDW,EAAyC,MAAjCJ,QAAQC,IAAII,kBAA4BR,IAAWS,EAAWC,IACtEC,EAAUX,IAAWS,EAAWG,MAEhCC,EAAoB,CACxB,CACEC,KAAMC,EACNC,QAAS,MAETC,cAAe,IACVX,EACHY,WAAYC,EAAKC,QAAQvB,EAAQwB,UACjCrB,SACAsB,MAAOtB,IAAWS,EAAWC,KAG/Ba,OAAM,CAACA,GAAQC,WAAEA,MACfD,EAAOE,OAAS,IACVF,EAAOE,QAAU,CAAE,EACvBC,WAAYnB,GAGdgB,EAAOX,MAAQ,IACTW,EAAOX,OAAS,CAAE,GAGnBY,EAQE,IACFD,KACCZ,EAAU,CAAEgB,QAAS,UAAa,CAAE,EACxCC,WAAW,IAVPrB,GAASI,IACXY,EAAOX,MAAMiB,UAAW,GAGnBN,OAYT7B,gBAAEA,EAAeoC,WAAEA,EAAUrC,cAAEA,GAAkBa,EAiBvD,OAfIZ,GACFmB,EAAQkB,KACNC,EAAiD,kBAApBtC,OAAgCuC,EAAYvC,IAI7EmB,EAAQkB,KACNG,EAA0B,CACxB3B,QACAI,UACAmB,aACAK,cAAiC,SAAlB1C,KAIZoB,CACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-aliases.js","sources":["../../src/plugins/make-aliases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\nimport JSON5 from 'json5';\nimport type { Plugin } from 'vite';\n// import without aliases for use in vitest.config.ts\nimport PLUGIN_NAME from '../constants/plugin-name';\nimport ViteAliases from '../helpers/vite-aliases';\n\nexport interface IPluginOptions {\n root?: string; // default: cwd()\n tsconfig?: string; // default: tsconfig.json\n}\n\nconst pluginName = `${PLUGIN_NAME}-make-aliases`;\nconst cleanupAlias = (str: string): string => str.replace('/*', '');\n\n/**\n * Read tsconfig file and set vite aliases\n * @see
|
|
1
|
+
{"version":3,"file":"make-aliases.js","sources":["../../src/plugins/make-aliases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\nimport JSON5 from 'json5';\nimport type { Plugin } from 'vite';\n// import without aliases for use in vitest.config.ts\nimport PLUGIN_NAME from '../constants/plugin-name';\nimport ViteAliases from '../helpers/vite-aliases';\n\nexport interface IPluginOptions {\n root?: string; // default: cwd()\n tsconfig?: string; // default: tsconfig.json\n}\n\nconst pluginName = `${PLUGIN_NAME}-make-aliases`;\nconst cleanupAlias = (str: string): string => str.replace('/*', '');\n\n/**\n * Read tsconfig file and set vite aliases\n * @see PathNormalize.getAliases\n * @constructor\n */\nfunction ViteMakeAliasesPlugin(options: IPluginOptions = {}): Plugin {\n const { root, tsconfig } = options;\n const projectRoot = root ?? process.cwd();\n const tsconfigPath = path.resolve(projectRoot, tsconfig ?? 'tsconfig.json');\n const aliases: [string, string][] = [];\n\n if (!fs.existsSync(tsconfigPath)) {\n console.error(`${pluginName}: tsconfig not exist in \"${tsconfigPath}\"`);\n } else {\n const tsJson = JSON5.parse<{ compilerOptions?: { paths: Record<string, string[]> } }>(\n fs.readFileSync(tsconfigPath, { encoding: 'utf-8' }),\n );\n const paths = tsJson?.compilerOptions?.paths ?? {};\n\n Object.entries(paths).forEach(([alias, aliasPaths]) => {\n aliases.push([cleanupAlias(alias), cleanupAlias(aliasPaths[0])]);\n });\n }\n\n return {\n name: pluginName,\n config(config) {\n if (aliases.length) {\n const resolveConfig = config.resolve ?? {};\n const defaultAliases = resolveConfig.alias ?? [];\n const normalizedAliases = Array.isArray(defaultAliases)\n ? defaultAliases\n : Object.entries(defaultAliases).map(([find, val]) => ({\n find,\n replacement: val as string,\n }));\n\n normalizedAliases.push(...ViteAliases(aliases, `${projectRoot}/${config?.root ?? ''}`));\n\n config.resolve = {\n ...resolveConfig,\n alias: normalizedAliases,\n };\n }\n\n return config;\n },\n };\n}\n\nexport default ViteMakeAliasesPlugin;\n"],"names":["pluginName","PLUGIN_NAME","cleanupAlias","str","replace","ViteMakeAliasesPlugin","options","root","tsconfig","projectRoot","process","cwd","tsconfigPath","path","resolve","aliases","fs","existsSync","tsJson","JSON5","parse","readFileSync","encoding","paths","compilerOptions","Object","entries","forEach","alias","aliasPaths","push","console","error","name","config","length","resolveConfig","defaultAliases","normalizedAliases","Array","isArray","map","find","val","replacement","ViteAliases"],"mappings":"sLAcA,MAAMA,EAAa,GAAGC,iBAChBC,EAAgBC,GAAwBA,EAAIC,QAAQ,KAAM,IAOhE,SAASC,EAAsBC,EAA0B,IACvD,MAAMC,KAAEA,EAAIC,SAAEA,GAAaF,EACrBG,EAAcF,GAAQG,EAAQC,MAC9BC,EAAeC,EAAKC,QAAQL,EAAaD,GAAY,iBACrDO,EAA8B,GAEpC,GAAKC,EAAGC,WAAWL,GAEZ,CACL,MAAMM,EAASC,EAAMC,MACnBJ,EAAGK,aAAaT,EAAc,CAAEU,SAAU,WAEtCC,EAAQL,GAAQM,iBAAiBD,OAAS,CAAA,EAEhDE,OAAOC,QAAQH,GAAOI,SAAQ,EAAEC,EAAOC,MACrCd,EAAQe,KAAK,CAAC5B,EAAa0B,GAAQ1B,EAAa2B,EAAW,KAAK,GAEnE,MAVCE,QAAQC,MAAM,GAAGhC,6BAAsCY,MAYzD,MAAO,CACLqB,KAAMjC,EACNkC,OAAOA,GACL,GAAInB,EAAQoB,OAAQ,CAClB,MAAMC,EAAgBF,EAAOpB,SAAW,GAClCuB,EAAiBD,EAAcR,OAAS,GACxCU,EAAoBC,MAAMC,QAAQH,GACpCA,EACAZ,OAAOC,QAAQW,GAAgBI,KAAI,EAAEC,EAAMC,MAAU,CACnDD,OACAE,YAAaD,MAGnBL,EAAkBR,QAAQe,EAAY9B,EAAS,GAAGN,KAAeyB,GAAQ3B,MAAQ,OAEjF2B,EAAOpB,QAAU,IACZsB,EACHR,MAAOU,EAEV,CAED,OAAOJ,CACR,EAEL"}
|
|
@@ -3,6 +3,7 @@ interface IPluginOptions {
|
|
|
3
3
|
isSSR?: boolean;
|
|
4
4
|
isBuild?: boolean;
|
|
5
5
|
routesPath?: string;
|
|
6
|
+
isNodeParsing?: boolean;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Add possibility to export route components like FCRoute or FCCRoute
|
|
@@ -10,7 +11,7 @@ interface IPluginOptions {
|
|
|
10
11
|
* USAGE: { path: '/', lazy: () => import('./pages/home') }
|
|
11
12
|
* @see FCRoute
|
|
12
13
|
* @see FCCRoute
|
|
13
|
-
* @see SsrManifest.
|
|
14
|
+
* @see SsrManifest.getAsyncRoutesIds
|
|
14
15
|
* @see importRoute
|
|
15
16
|
* @constructor
|
|
16
17
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{extname as e}from"node:path";import
|
|
1
|
+
import{extname as e,resolve as r}from"node:path";import t from"../constants/plugin-name.js";import o from"../helpers/is-route-file.js";import{writeMeta as s}from"../helpers/ssr-meta.js";const n=(e,r=!1)=>{if(r)return e;const t=[...e.matchAll(/import\s+(\w+)\sfrom\s+['"]([^'"]+)['"]/g)].reduce(((e,[,r,t])=>({[r]:t,...e})),{});return e.replace(/(.*?(?:Component|element):[\s<]*(\w+)[^,}]*),*/gs,((e,r,o)=>{const s=t?.[o];return s?`${r},pathId: '${s}',`:e}))},i=(e,r)=>{const o=e.replace(/(lazy)(:\s*)(\(\)\s*=>\s*import\(([^)]+)\))/gs,r?"lazy$2n($3,$4)":"lazy$2n($3)");return e!==o?`import n from '${t}/helpers/import-route';${o}`:e};function l(l={}){const{routesPath:u,isNodeParsing:m=!1,isSSR:c=!1,isBuild:p=!1}=l,a=new Map,d={root:"",buildDir:""};return{name:`${t}-normalize-route`,enforce:"pre",transform(r,t){const[s]=e(t).split("?"),l=!u||t.includes(u);if(!t.includes("node_modules")&&[".js",".mjs",".ts",".tsx"].includes(s)&&l&&o(r))return a.set(t,""),{code:i(n(r,p),c&&(m||p)),map:{mappings:""}}},...m?{config(e,{isSsrBuild:r}){r||(d.root=e.root,d.buildDir=e.build.outDir)},generateBundle(e,r){for(const[e,t]of Object.entries(r))"chunk"===t.type&&Object.entries(t.modules).forEach((([r])=>{a.has(r)&&a.set(r,e)}))},writeBundle(){if(!d.root)return;const[e]=r(d.root,d.buildDir).split("/client");s(e,{routeFiles:Object.fromEntries(a)})}}:{}}}export{l as default};
|
|
2
2
|
//# sourceMappingURL=normalize-route.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-route.js","sources":["../../src/plugins/normalize-route.ts"],"sourcesContent":["import { extname } from 'node:path';\nimport type { Plugin } from 'vite';\nimport PLUGIN_NAME from '@constants/plugin-name';\nimport isRoutesFile from '@helpers/is-route-file';\n\nexport interface IPluginOptions {\n isSSR?: boolean;\n isBuild?: boolean;\n routesPath?: string;\n}\n\n/**\n * Add pathId to route (where pathId - import string)\n * NOTE: only for dev mode\n */\nconst normalizeSyncRoutes = (code: string, isBuild = false): string => {\n if (isBuild) {\n return code;\n }\n\n const imports: Record<string, string> = [\n ...code.matchAll(/import\\s+(\\w+)\\sfrom\\s+['\"]([^'\"]+)['\"]/g),\n ].reduce(\n (res, [, key, value]) => ({\n [key]: value,\n ...res,\n }),\n {},\n );\n\n return code.replace(\n /(.*?(?:Component|element):[\\s<]*(\\w+)[^,}]*),*/gs,\n (fullMatch, before: string, routeName: string) => {\n const importPath = imports?.[routeName];\n\n if (!importPath) {\n return fullMatch;\n }\n\n return `${before},pathId: '${importPath}',`;\n },\n );\n};\n\n/**\n * Add normalize wrapper to lazy imports for client build\n */\nconst normalizeAsyncRoutes = (code: string,
|
|
1
|
+
{"version":3,"file":"normalize-route.js","sources":["../../src/plugins/normalize-route.ts"],"sourcesContent":["import { extname, resolve } from 'node:path';\nimport type { Plugin } from 'vite';\nimport PLUGIN_NAME from '@constants/plugin-name';\nimport isRoutesFile from '@helpers/is-route-file';\nimport { writeMeta } from '@helpers/ssr-meta';\n\nexport interface IPluginOptions {\n isSSR?: boolean;\n isBuild?: boolean;\n routesPath?: string;\n isNodeParsing?: boolean;\n}\n\n/**\n * Add pathId to route (where pathId - import string)\n * NOTE: only for dev mode\n */\nconst normalizeSyncRoutes = (code: string, isBuild = false): string => {\n if (isBuild) {\n return code;\n }\n\n const imports: Record<string, string> = [\n ...code.matchAll(/import\\s+(\\w+)\\sfrom\\s+['\"]([^'\"]+)['\"]/g),\n ].reduce(\n (res, [, key, value]) => ({\n [key]: value,\n ...res,\n }),\n {},\n );\n\n return code.replace(\n /(.*?(?:Component|element):[\\s<]*(\\w+)[^,}]*),*/gs,\n (fullMatch, before: string, routeName: string) => {\n const importPath = imports?.[routeName];\n\n if (!importPath) {\n return fullMatch;\n }\n\n return `${before},pathId: '${importPath}',`;\n },\n );\n};\n\n/**\n * Add normalize wrapper to lazy imports for client build\n */\nconst normalizeAsyncRoutes = (code: string, hasPathId: boolean): string => {\n const modifiedCode = code.replace(\n /(lazy)(:\\s*)(\\(\\)\\s*=>\\s*import\\(([^)]+)\\))/gs,\n hasPathId ? 'lazy$2n($3,$4)' : 'lazy$2n($3)',\n );\n\n if (code !== modifiedCode) {\n return `import n from '${PLUGIN_NAME}/helpers/import-route';${modifiedCode}`;\n }\n\n return code;\n};\n\n/**\n * Add possibility to export route components like FCRoute or FCCRoute\n * Add route path for generating manifest\n * USAGE: { path: '/', lazy: () => import('./pages/home') }\n * @see FCRoute\n * @see FCCRoute\n * @see SsrManifest.getAsyncRoutesIds\n * @see importRoute\n * @constructor\n */\nfunction ViteNormalizeRouterPlugin(options: IPluginOptions = {}): Plugin {\n const { routesPath, isNodeParsing = false, isSSR = false, isBuild = false } = options;\n const routeFiles = new Map<string, string>();\n const cfg = { root: '', buildDir: '' };\n\n return {\n name: `${PLUGIN_NAME}-normalize-route`,\n enforce: 'pre',\n transform(code, id) {\n const [extName] = extname(id).split('?');\n const isRoutesPath = !routesPath || id.includes(routesPath);\n\n if (\n id.includes('node_modules') ||\n !['.js', '.mjs', '.ts', '.tsx'].includes(extName) ||\n !isRoutesPath ||\n !isRoutesFile(code)\n ) {\n return;\n }\n\n routeFiles.set(id, '');\n\n return {\n // always add pathId to routes for development\n code: normalizeAsyncRoutes(\n normalizeSyncRoutes(code, isBuild),\n isSSR && (isNodeParsing || isBuild),\n ),\n map: { mappings: '' },\n };\n },\n ...(isNodeParsing\n ? {\n /**\n * Get build path\n */\n config(config, { isSsrBuild }): void {\n if (isSsrBuild) {\n return;\n }\n\n cfg.root = config.root!;\n cfg.buildDir = config.build!.outDir!;\n },\n /**\n * Get transformed route files\n */\n generateBundle(_, bundle) {\n for (const [fileName, chunk] of Object.entries(bundle)) {\n if (chunk.type === 'chunk') {\n Object.entries(chunk.modules).forEach(([modulePath]) => {\n if (routeFiles.has(modulePath)) {\n routeFiles.set(modulePath, fileName);\n }\n });\n }\n }\n },\n /**\n * Save metadata on for client build\n * @see config hook\n */\n writeBundle(): void {\n if (!cfg.root) {\n return;\n }\n\n const [buildDir] = resolve(cfg.root, cfg.buildDir).split('/client');\n\n writeMeta(buildDir, { routeFiles: Object.fromEntries(routeFiles) });\n },\n }\n : {}),\n };\n}\n\nexport default ViteNormalizeRouterPlugin;\n"],"names":["normalizeSyncRoutes","code","isBuild","imports","matchAll","reduce","res","key","value","replace","fullMatch","before","routeName","importPath","normalizeAsyncRoutes","hasPathId","modifiedCode","PLUGIN_NAME","ViteNormalizeRouterPlugin","options","routesPath","isNodeParsing","isSSR","routeFiles","Map","cfg","root","buildDir","name","enforce","transform","id","extName","extname","split","isRoutesPath","includes","isRoutesFile","set","map","mappings","config","isSsrBuild","build","outDir","generateBundle","_","bundle","fileName","chunk","Object","entries","type","modules","forEach","modulePath","has","writeBundle","resolve","writeMeta","fromEntries"],"mappings":"0LAiBA,MAAMA,EAAsB,CAACC,EAAcC,GAAU,KACnD,GAAIA,EACF,OAAOD,EAGT,MAAME,EAAkC,IACnCF,EAAKG,SAAS,6CACjBC,QACA,CAACC,GAAK,CAAGC,EAAKC,MAAY,CACxBD,CAACA,GAAMC,KACJF,KAEL,CAAE,GAGJ,OAAOL,EAAKQ,QACV,oDACA,CAACC,EAAWC,EAAgBC,KAC1B,MAAMC,EAAaV,IAAUS,GAE7B,OAAKC,EAIE,GAAGF,cAAmBE,MAHpBH,CAGkC,GAE9C,EAMGI,EAAuB,CAACb,EAAcc,KAC1C,MAAMC,EAAef,EAAKQ,QACxB,gDACAM,EAAY,iBAAmB,eAGjC,OAAId,IAASe,EACJ,kBAAkBC,2BAAqCD,IAGzDf,CAAI,EAab,SAASiB,EAA0BC,EAA0B,IAC3D,MAAMC,WAAEA,EAAUC,cAAEA,GAAgB,EAAKC,MAAEA,GAAQ,EAAKpB,QAAEA,GAAU,GAAUiB,EACxEI,EAAa,IAAIC,IACjBC,EAAM,CAAEC,KAAM,GAAIC,SAAU,IAElC,MAAO,CACLC,KAAM,GAAGX,oBACTY,QAAS,MACTC,UAAU7B,EAAM8B,GACd,MAAOC,GAAWC,EAAQF,GAAIG,MAAM,KAC9BC,GAAgBf,GAAcW,EAAGK,SAAShB,GAEhD,IACEW,EAAGK,SAAS,iBACX,CAAC,MAAO,OAAQ,MAAO,QAAQA,SAASJ,IACxCG,GACAE,EAAapC,GAOhB,OAFAsB,EAAWe,IAAIP,EAAI,IAEZ,CAEL9B,KAAMa,EACJd,EAAoBC,EAAMC,GAC1BoB,IAAUD,GAAiBnB,IAE7BqC,IAAK,CAAEC,SAAU,IAEpB,KACGnB,EACA,CAIEoB,OAAOA,GAAQC,WAAEA,IACXA,IAIJjB,EAAIC,KAAOe,EAAOf,KAClBD,EAAIE,SAAWc,EAAOE,MAAOC,OAC9B,EAIDC,eAAeC,EAAGC,GAChB,IAAK,MAAOC,EAAUC,KAAUC,OAAOC,QAAQJ,GAC1B,UAAfE,EAAMG,MACRF,OAAOC,QAAQF,EAAMI,SAASC,SAAQ,EAAEC,MAClChC,EAAWiC,IAAID,IACjBhC,EAAWe,IAAIiB,EAAYP,EAC5B,GAIR,EAKDS,cACE,IAAKhC,EAAIC,KACP,OAGF,MAAOC,GAAY+B,EAAQjC,EAAIC,KAAMD,EAAIE,UAAUO,MAAM,WAEzDyB,EAAUhC,EAAU,CAAEJ,WAAY2B,OAAOU,YAAYrC,IACtD,GAEH,CAAE,EAEV"}
|
package/services/build.d.ts
CHANGED
|
@@ -79,6 +79,13 @@ declare class Build {
|
|
|
79
79
|
* Build assets manifest file
|
|
80
80
|
*/
|
|
81
81
|
buildManifest(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Remove pathId from client route files
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Remove pathId from client route files
|
|
87
|
+
*/
|
|
88
|
+
private cleanupClientRoutes;
|
|
82
89
|
/**
|
|
83
90
|
* Change general directive Disallow to Allow in robots.txt.
|
|
84
91
|
*/
|
package/services/build.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import i from"node:fs";import
|
|
1
|
+
import i from"node:fs";import e from"node:path";import s from"chalk";import{resolveConfig as o}from"vite";import t from"../helpers/plugin-config.js";import{readMeta as r}from"../helpers/ssr-meta.js";import n from"./server-config.js";import l from"./ssr-manifest.js";class c{isProd;nodeEnv;buildDir;outDir;serverFile;viteConfig;pluginConfig;params;constructor(i){this.params=i}async makeConfig(){const{mode:i}=this.params;this.viteConfig=await o({},"build",i,"production"),this.pluginConfig=t(this.viteConfig),this.buildDir=e.resolve(this.viteConfig.root,this.viteConfig.build.outDir),this.outDir=this.viteConfig.build.outDir,this.serverFile=this.pluginConfig.serverFile,this.nodeEnv=process.env.NODE_ENV||"production",this.isProd="production"===this.nodeEnv}clearBuildFolder(){i.existsSync(this.buildDir)&&i.rmSync(this.buildDir,{recursive:!0})}promisifyProcess(i,e=!1){const s=new Promise(((s,o)=>{i.on("exit",(i=>{s(i)})),i.on("close",(i=>{s(i)})),i.on("error",(i=>{o(i)})),e&&i.stderr?.on("data",(i=>{const e=Buffer.from(i).toString();(e.includes("warning")||e.includes("WARNING"))&&s(1)}))}));return i.stdout?.pipe(process.stdout),i.stderr?.pipe(process.stderr),{promise:s,command:i}}async buildManifest(){console.info(s.blue(`Building routes manifest file: ${this.pluginConfig.routesParsing}`));const i="node"===this.pluginConfig.routesParsing,e=n.init({isProd:this.isProd,mode:this.params.mode},{root:this.viteConfig.root,clientFile:this.pluginConfig.clientFile});await l.get(e,{buildDir:this.viteConfig.build.outDir,viteAliases:this.viteConfig.resolve.alias}).buildRoutesManifest(i),i&&this.cleanupClientRoutes()}cleanupClientRoutes(){const{routeFiles:e}=r(this.buildDir),s=new Set(Object.values(e??[]));s.size&&s.forEach((e=>{const s=`${this.buildDir}/client/${e}`;try{const e=i.readFileSync(s,{encoding:"utf-8"}).replace(/(lazy:.*?\((.*?)\)),\s?".*?"\)/g,"$1)");i.writeFileSync(s,e)}catch(i){console.log(`Failed cleanup client route ${s}:`,i)}}))}unlockRobots(){const e=`${this.buildDir}/client/robots.txt`;if(!i.existsSync(e))return void console.warn(`Failed to unlock robots.txt, file not exist: ${e}`);const o=i.readFileSync(e,{encoding:"utf-8"}).replace(/Disallow: \/$/m,"Allow: /");i.writeFileSync(e,o,{encoding:"utf-8"}),console.info(s.blue("\nrobots.txt unlocked."))}eject(){const e=`${this.buildDir}/server/start.js`;i.writeFileSync(e,"import runProd from '@lomray/vite-ssr-boost/cli/run-prod.js';\n\nconst VERSION = process.env.VERSION || \"1.0.0\";\nconst PORT = process.env.PORT || 3000;\nconst IS_HOST = process.env.IS_HOST || \"0\";\nconst ONLY_CLIENT = process.env.ONLY_CLIENT || \"0\";\n\nawait runProd({\n version: VERSION,\n isHost: IS_HOST === '1',\n isPrintInfo: true,\n port: PORT,\n onlyClient: ONLY_CLIENT === '1',\n });\n",{encoding:"utf-8"})}createServerless(){const e=`${this.buildDir}/server/serverless.js`;i.writeFileSync(e,"import runServerless from '@lomray/vite-ssr-boost/cli/run-serverless.js';\n\nexport default await runServerless({ version: process.env.VERSION || \"1.0.0\" });\n",{encoding:"utf-8"})}}export{c as default};
|
|
2
2
|
//# sourceMappingURL=build.js.map
|
package/services/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sources":["../../src/services/build.ts"],"sourcesContent":["import type childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport type { ResolvedConfig } from 'vite';\nimport { resolveConfig } from 'vite';\nimport type { IPluginConfig } from '@helpers/plugin-config';\nimport getPluginConfig from '@helpers/plugin-config';\nimport ServerConfig from '@services/server-config';\nimport SsrManifest from '@services/ssr-manifest';\n\ninterface IBuildParams {\n mode: string;\n}\n\n/**\n * Build service\n */\nclass Build {\n /**\n * Is production build\n */\n public isProd: boolean;\n\n /**\n * Node environment\n */\n public nodeEnv: string;\n\n /**\n * Build folder\n */\n public buildDir: string;\n\n /**\n * Relative build dir\n */\n public outDir: string;\n\n /**\n * Server file\n */\n public serverFile: string;\n\n /**\n * Vite config\n */\n public viteConfig: ResolvedConfig;\n\n /**\n * Plugin config\n */\n public pluginConfig: IPluginConfig;\n\n /**\n * Build params\n */\n protected params: IBuildParams;\n\n /**\n * @constructor\n */\n constructor(params: IBuildParams) {\n this.params = params;\n }\n\n /**\n * Make config\n */\n public async makeConfig(): Promise<void> {\n const { mode } = this.params;\n\n this.viteConfig = await resolveConfig({}, 'build', mode, 'production');\n this.pluginConfig = getPluginConfig(this.viteConfig);\n this.buildDir = path.resolve(this.viteConfig.root, this.viteConfig.build.outDir);\n this.outDir = this.viteConfig.build.outDir;\n this.serverFile = this.pluginConfig.serverFile;\n this.nodeEnv = process.env.NODE_ENV || 'production';\n this.isProd = this.nodeEnv === 'production';\n }\n\n /**\n * Clear build folder\n */\n public clearBuildFolder(): void {\n // clear build folder\n if (fs.existsSync(this.buildDir)) {\n fs.rmSync(this.buildDir, { recursive: true });\n }\n }\n\n /**\n * Promisify spawn process\n */\n public promisifyProcess(\n command: childProcess.ChildProcess,\n isRejectWarnings = false,\n ): { promise: Promise<number | null | string>; command: childProcess.ChildProcess } {\n const promise = new Promise<number | null | string>((resolve, reject) => {\n command.on('exit', (code) => {\n resolve(code);\n });\n\n command.on('close', (code: number): void => {\n resolve(code);\n });\n\n command.on('error', (message: string): void => {\n reject(message);\n });\n\n if (isRejectWarnings) {\n command.stderr?.on('data', (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('warning') || msg.includes('WARNING')) {\n resolve(1);\n }\n });\n }\n });\n\n command.stdout?.pipe(process.stdout);\n command.stderr?.pipe(process.stderr);\n\n return { promise, command };\n }\n\n /**\n * Build assets manifest file\n */\n public async buildManifest(): Promise<void> {\n console.
|
|
1
|
+
{"version":3,"file":"build.js","sources":["../../src/services/build.ts"],"sourcesContent":["import type childProcess from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport type { ResolvedConfig } from 'vite';\nimport { resolveConfig } from 'vite';\nimport type { IPluginConfig } from '@helpers/plugin-config';\nimport getPluginConfig from '@helpers/plugin-config';\nimport { readMeta } from '@helpers/ssr-meta';\nimport ServerConfig from '@services/server-config';\nimport SsrManifest from '@services/ssr-manifest';\n\ninterface IBuildParams {\n mode: string;\n}\n\n/**\n * Build service\n */\nclass Build {\n /**\n * Is production build\n */\n public isProd: boolean;\n\n /**\n * Node environment\n */\n public nodeEnv: string;\n\n /**\n * Build folder\n */\n public buildDir: string;\n\n /**\n * Relative build dir\n */\n public outDir: string;\n\n /**\n * Server file\n */\n public serverFile: string;\n\n /**\n * Vite config\n */\n public viteConfig: ResolvedConfig;\n\n /**\n * Plugin config\n */\n public pluginConfig: IPluginConfig;\n\n /**\n * Build params\n */\n protected params: IBuildParams;\n\n /**\n * @constructor\n */\n constructor(params: IBuildParams) {\n this.params = params;\n }\n\n /**\n * Make config\n */\n public async makeConfig(): Promise<void> {\n const { mode } = this.params;\n\n this.viteConfig = await resolveConfig({}, 'build', mode, 'production');\n this.pluginConfig = getPluginConfig(this.viteConfig);\n this.buildDir = path.resolve(this.viteConfig.root, this.viteConfig.build.outDir);\n this.outDir = this.viteConfig.build.outDir;\n this.serverFile = this.pluginConfig.serverFile;\n this.nodeEnv = process.env.NODE_ENV || 'production';\n this.isProd = this.nodeEnv === 'production';\n }\n\n /**\n * Clear build folder\n */\n public clearBuildFolder(): void {\n // clear build folder\n if (fs.existsSync(this.buildDir)) {\n fs.rmSync(this.buildDir, { recursive: true });\n }\n }\n\n /**\n * Promisify spawn process\n */\n public promisifyProcess(\n command: childProcess.ChildProcess,\n isRejectWarnings = false,\n ): { promise: Promise<number | null | string>; command: childProcess.ChildProcess } {\n const promise = new Promise<number | null | string>((resolve, reject) => {\n command.on('exit', (code) => {\n resolve(code);\n });\n\n command.on('close', (code: number): void => {\n resolve(code);\n });\n\n command.on('error', (message: string): void => {\n reject(message);\n });\n\n if (isRejectWarnings) {\n command.stderr?.on('data', (buff: Uint8Array): void => {\n const msg = Buffer.from(buff).toString();\n\n if (msg.includes('warning') || msg.includes('WARNING')) {\n resolve(1);\n }\n });\n }\n });\n\n command.stdout?.pipe(process.stdout);\n command.stderr?.pipe(process.stderr);\n\n return { promise, command };\n }\n\n /**\n * Build assets manifest file\n */\n public async buildManifest(): Promise<void> {\n console.info(chalk.blue(`Building routes manifest file: ${this.pluginConfig.routesParsing}`));\n\n const isNodeParsing = this.pluginConfig.routesParsing === 'node';\n const serverConfig = ServerConfig.init(\n { isProd: this.isProd, mode: this.params.mode },\n { root: this.viteConfig.root, clientFile: this.pluginConfig.clientFile },\n );\n\n await SsrManifest.get(serverConfig, {\n buildDir: this.viteConfig.build.outDir,\n viteAliases: this.viteConfig.resolve.alias,\n }).buildRoutesManifest(isNodeParsing);\n\n if (isNodeParsing) {\n this.cleanupClientRoutes();\n }\n }\n\n /**\n * Remove pathId from client route files\n */\n private cleanupClientRoutes(): void {\n const { routeFiles } = readMeta(this.buildDir);\n const files = new Set(Object.values(routeFiles ?? []));\n\n if (!files.size) {\n return;\n }\n\n files.forEach((file) => {\n const filepath = `${this.buildDir}/client/${file}`;\n\n try {\n const result = fs\n .readFileSync(filepath, { encoding: 'utf-8' })\n .replace(/(lazy:.*?\\((.*?)\\)),\\s?\".*?\"\\)/g, '$1)');\n\n fs.writeFileSync(filepath, result);\n } catch (e) {\n console.log(`Failed cleanup client route ${filepath}:`, e);\n }\n });\n }\n\n /**\n * Change general directive Disallow to Allow in robots.txt.\n */\n public unlockRobots(): void {\n const robotsFile = `${this.buildDir}/client/robots.txt`;\n\n if (!fs.existsSync(robotsFile)) {\n console.warn(`Failed to unlock robots.txt, file not exist: ${robotsFile}`);\n\n return;\n }\n\n const data = fs\n .readFileSync(robotsFile, { encoding: 'utf-8' })\n .replace(/Disallow: \\/$/m, 'Allow: /');\n\n fs.writeFileSync(robotsFile, data, { encoding: 'utf-8' });\n\n console.info(chalk.blue('\\nrobots.txt unlocked.'));\n }\n\n /**\n * Eject cli to run app via node\n */\n public eject(): void {\n const entrypoint = `${this.buildDir}/server/start.js`;\n const script =\n \"import runProd from '@lomray/vite-ssr-boost/cli/run-prod.js';\\n\\n\" +\n 'const VERSION = process.env.VERSION || \"1.0.0\";\\n' +\n 'const PORT = process.env.PORT || 3000;\\n' +\n 'const IS_HOST = process.env.IS_HOST || \"0\";\\n' +\n 'const ONLY_CLIENT = process.env.ONLY_CLIENT || \"0\";\\n\\n' +\n `await runProd({\n version: VERSION,\n isHost: IS_HOST === '1',\n isPrintInfo: true,\n port: PORT,\n onlyClient: ONLY_CLIENT === '1',\n });\\n`;\n\n fs.writeFileSync(entrypoint, script, {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Create serverless entrypoint\n */\n public createServerless(): void {\n const entrypoint = `${this.buildDir}/server/serverless.js`;\n const script =\n \"import runServerless from '@lomray/vite-ssr-boost/cli/run-serverless.js';\\n\\n\" +\n `export default await runServerless({ version: process.env.VERSION || \"1.0.0\" });\\n`;\n\n fs.writeFileSync(entrypoint, script, {\n encoding: 'utf-8',\n });\n }\n}\n\nexport default Build;\n"],"names":["Build","isProd","nodeEnv","buildDir","outDir","serverFile","viteConfig","pluginConfig","params","constructor","this","async","mode","resolveConfig","getPluginConfig","path","resolve","root","build","process","env","NODE_ENV","clearBuildFolder","fs","existsSync","rmSync","recursive","promisifyProcess","command","isRejectWarnings","promise","Promise","reject","on","code","message","stderr","buff","msg","Buffer","from","toString","includes","stdout","pipe","console","info","chalk","blue","routesParsing","isNodeParsing","serverConfig","ServerConfig","init","clientFile","SsrManifest","get","viteAliases","alias","buildRoutesManifest","cleanupClientRoutes","routeFiles","readMeta","files","Set","Object","values","size","forEach","file","filepath","result","readFileSync","encoding","replace","writeFileSync","e","log","unlockRobots","robotsFile","warn","data","eject","entrypoint","createServerless"],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Alias } from 'vite';
|
|
2
|
+
import PathNormalize from "./path-normalize.js";
|
|
3
|
+
import ServerConfig from "./server-config.js";
|
|
4
|
+
type TRoutesTree = {
|
|
5
|
+
index: number;
|
|
6
|
+
import: string;
|
|
7
|
+
children: TRoutesTree[];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Parse react router routes array
|
|
11
|
+
*/
|
|
12
|
+
declare class ParseRoutes {
|
|
13
|
+
/**
|
|
14
|
+
* Path normalize service
|
|
15
|
+
*/
|
|
16
|
+
protected readonly pathNormalize: PathNormalize;
|
|
17
|
+
/**
|
|
18
|
+
* Server config
|
|
19
|
+
*/
|
|
20
|
+
protected readonly config: ServerConfig;
|
|
21
|
+
/**
|
|
22
|
+
* @constructor
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* @constructor
|
|
26
|
+
*/
|
|
27
|
+
constructor(config: ServerConfig, viteAliases?: Alias[]);
|
|
28
|
+
/**
|
|
29
|
+
* Parse routes
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Parse routes
|
|
33
|
+
*/
|
|
34
|
+
parse(): TRoutesTree[];
|
|
35
|
+
/**
|
|
36
|
+
* Parse file and return ast
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Parse file and return ast
|
|
40
|
+
*/
|
|
41
|
+
private parseFile;
|
|
42
|
+
/**
|
|
43
|
+
* Find route import filepath
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Find route import filepath
|
|
47
|
+
*/
|
|
48
|
+
private getImportPath;
|
|
49
|
+
/**
|
|
50
|
+
* Find routes array inside code
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Find routes array inside code
|
|
54
|
+
*/
|
|
55
|
+
private findRoutesDefinition;
|
|
56
|
+
/**
|
|
57
|
+
* Entrypoint routes file
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Entrypoint routes file
|
|
61
|
+
*/
|
|
62
|
+
private findRoutesEntrypoint;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve route filename import
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* Resolve route filename import
|
|
68
|
+
*/
|
|
69
|
+
private resolveFilename;
|
|
70
|
+
/**
|
|
71
|
+
* Parse ast array routes objects
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* Parse ast array routes objects
|
|
75
|
+
*/
|
|
76
|
+
private parseRoutesArray;
|
|
77
|
+
/**
|
|
78
|
+
* Recursive build routes tree with dynamic imports
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Recursive build routes tree with dynamic imports
|
|
82
|
+
*/
|
|
83
|
+
private recursiveBuildRoutesTree;
|
|
84
|
+
}
|
|
85
|
+
export { ParseRoutes as default, TRoutesTree };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"fs";import{resolve as t}from"node:path";import r from"path";import*as n from"@babel/parser";import a from"@babel/traverse";import i from"./path-normalize.js";const o=a.default??a;class l{pathNormalize;config;constructor(e,t){this.config=e,this.pathNormalize=new i(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:i,exportName:o}=a,l=this.resolveFilename(i);return this.recursiveBuildRoutesTree(l,o)}parseFile(t){try{const r=e.readFileSync(t,"utf-8");return n.parse(r,{sourceType:"module",plugins:["typescript","jsx"]})}catch(e){return null}}getImportPath(e,t){let r=null,n=null;return o(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(o(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 o(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?(o(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 i={index:a,import:"",children:[]};e.properties.forEach((e=>{const n=e;if("children"===n.key.name&&"ArrayExpression"===n.value.type&&(i.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&&(i.import=t.value)}}if("Component"===n.key.name&&"Identifier"===n.value.type){const e=n.value.name,{path:r}=t[e]??{};r&&(i.import=r)}if("element"===n.key.name&&"JSXElement"===n.value.type){const e=n.value?.openingElement?.name?.name,{path:r}=t[e]??{};r&&(i.import=r)}if("children"===n.key.name&&"Identifier"===n.value.type){const e=n.value.name,{path:a,isDefault:o}=t[e]??{};if(a){const t=this.resolveFilename(a,r);t&&(i.children=this.recursiveBuildRoutesTree(t,o?null:e))}}})),(i.import||i.children.length>0)&&n.push(i)}})),n}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 i={};o(r,{ImportDeclaration(e){const t=e.node;t.specifiers.forEach((e=>{i[e.local.name]={path:t.source.value,isDefault:"ImportDefaultSpecifier"===e.type}}))}});const l=n.declarations[0].init?.elements;return a.push(...this.parseRoutesArray(l,i,e)),a}}export{l as default};
|
|
2
|
+
//# sourceMappingURL=parse-routes.js.map
|
|
@@ -0,0 +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 * 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 { CallExpression, File as BabelFile, VariableDeclaration } from '@babel/types';\nimport type { Alias } from 'vite';\nimport PathNormalize from '@services/path-normalize';\nimport type ServerConfig from '@services/server-config';\n\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);\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 * 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: 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 // @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\nexport default ParseRoutes;\n"],"names":["traverse","babelTraverse","default","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","routesNode","init"],"mappings":"4KAaA,MAAMA,EAAYC,EAAcC,SAAWD,EAuB3C,MAAME,EAIeC,cAKAC,OAKnBC,YAAYD,EAAsBE,GAChCC,KAAKH,OAASA,EACdG,KAAKJ,cAAgB,IAAIK,EAAcJ,EAAQE,EAChD,CAKMG,QACL,MAAMC,WAAEA,EAAUC,KAAEA,GAASJ,KAAKH,OAAOQ,YAEnCC,EAAmBC,EAAQH,EAAMD,GACjCK,EAAmBR,KAAKS,qBAAqBH,GAEnD,IAAKE,GAAkBE,WACrB,MAAM,IAAIC,MAAM,wCAAwCR,KAG1D,MAAMO,WAAEA,EAAUE,WAAEA,GAAeJ,EAC7BK,EAAgBb,KAAKc,gBAAgBJ,GAE3C,OAAOV,KAAKe,yBAAyBF,EAAeD,EACrD,CAKOI,UAAUC,GAChB,IACE,MAAMC,EAAOC,EAAGC,aAAaH,EAAU,SAEvC,OAAOI,EAAOnB,MAAMgB,EAAM,CACxBI,WAAY,SACZC,QAAS,CAAC,aAAc,QAE3B,CAAC,MAAOC,GACP,OAAO,IACR,CACF,CAKOC,cACNC,EACAC,GAEA,IAAIjB,EAA4B,KAC5BE,EAA4B,KAehC,OAbApB,EAASkC,EAAK,CACZE,kBAAkBC,GAChB,MAAMC,EAAaD,EAASE,KAE5BD,EAAWE,WAAWC,SAASC,IACzBA,EAAUC,MAAMC,OAAST,IAC3Bf,EAAgC,2BAAnBsB,EAAUG,KAAoC,KAAOV,EAClEjB,EAAaoB,EAAWQ,OAAOC,MAChC,GAEJ,IAGI,CACL7B,aACAE,aAEH,CAKO4B,qBACNd,EACAd,GAEA,IAAI6B,EAAqB7B,EAoCzB,GAjCApB,EAASkC,EAAK,CACZgB,wBAAuBX,KAAEA,KAClBA,EAAKY,aAAeZ,EAAKC,WAAWY,OAAS,GAChDb,EAAKC,WAAWC,SAASC,IAEvB,MAAMW,EAAeX,EAAUY,SAASV,KAErB,OAAfxB,GAA0C,oBAAnBsB,EAAUG,KACN,YAAzBH,EAAUC,MAAMC,OAClBK,EAAqBI,GAEdA,IAAiBjC,IAG1B6B,EAAqBP,EAAUC,MAAMC,KACtC,GAGN,EACDW,0BAAyBhB,KAAEA,IACN,OAAfnB,IAC4B,eAA1BmB,EAAKY,YAAYN,KACnBI,EAAqBV,EAAKY,YAAYP,KAEH,wBAA1BL,EAAKY,YAAYN,OAG1BI,EAAqBV,EAAKY,YAAYK,aAAa,GAAGC,GAAGb,MAG9D,IAGCK,EAAoB,CACtB,IAAIS,EAA2C,KAa/C,OAXA1D,EAASkC,EAAK,CACZyB,qBAAoBpB,KAAEA,IACpBA,EAAKiB,aAAaf,SAASU,IAErBA,EAAYM,GAAGb,OAASK,IAC1BS,EAAenB,EAChB,GAEJ,IAGImB,CACR,CAED,OAAO,IACR,CAKOzC,qBAAqBH,GAC3B,MAAMoB,EAAM1B,KAAKgB,UAAUV,GAE3B,IAAI8C,EAAgC,KAEpC,OAAK1B,GAILlC,EAASkC,EAAK,CACZ2B,gBAAetB,KAAEA,IAGQ,gBAArBA,EAAKuB,OAAOlB,MACZL,EAAKwB,UAAUX,QAAU,GACE,eAA3Bb,EAAKwB,UAAU,GAAGlB,OAElBe,EAAiBrB,EAAKwB,UAAU,GAAGnB,KAEtC,IAGIpC,KAAKyB,cAAcC,EAAK0B,IAhBtBA,CAiBV,CAKOtC,gBAAgBG,EAAkBuC,GACxC,IAAIC,EAAmBxC,GAElBA,EAASyC,WAAW,OAASzC,EAASyC,WAAW,SAAWF,IAC/DC,EAAmBE,EAAKpD,QAAQoD,EAAKC,QAAQJ,GAAevC,IAG9D,MAAM4C,EAAW7D,KAAKJ,cAAckE,WAAWL,GAAkB,GAEjE,OAAOzD,KAAKJ,cAAcmE,YAAYF,EACvC,CAKOG,iBACNC,EACAC,EACAV,GAEA,MAAMW,EAAyB,GAoF/B,OAlFAF,EAAShC,SAAQ,CAACF,EAAMqC,KACtB,GAAkB,qBAAdrC,EAAKM,KAA6B,CACpC,MAAMgC,EAAyB,CAAED,QAAOE,OAAQ,GAAIC,SAAU,IAE9DxC,EAAKyC,WAAWvC,SAASwC,IACvB,MAAMC,EAAaD,EAcnB,GAT4B,aAAxBC,EAAWC,IAAIvC,MAAiD,oBAA1BsC,EAAWnC,MAAMF,OACzDgC,EAAUE,SAAWvE,KAAKgE,iBACxBU,EAAWnC,MAAM0B,SACjBC,EACAV,IAMsB,SAAxBkB,EAAWC,IAAIvC,MACW,4BAA1BsC,EAAWnC,MAAMF,KACjB,CAEA,MAAMuC,EAAaF,EAAWnC,MAAMsC,KAEpC,GAAwB,mBAApBD,EAAWvC,MAAwD,WAA3BuC,EAAWtB,OAAOjB,KAAmB,CAC/E,MAAOyC,GAAaF,EAAWrB,UAER,kBAAnBuB,EAAUzC,OACZgC,EAAUC,OAASQ,EAAUvC,MAEhC,CACF,CAGD,GAA4B,cAAxBmC,EAAWC,IAAIvC,MAAkD,eAA1BsC,EAAWnC,MAAMF,KAAuB,CAEjF,MAAMV,EAAa+C,EAAWnC,MAAMH,MAC5BuB,KAAMoB,GAAeb,EAAWvC,IAAe,GAEnDoD,IACFV,EAAUC,OAASS,EAEtB,CAGD,GAA4B,YAAxBL,EAAWC,IAAIvC,MAAgD,eAA1BsC,EAAWnC,MAAMF,KAAuB,CAG/E,MAAMV,EAAa+C,EAAWnC,OAAOyC,gBAAgB5C,MAAMA,MACnDuB,KAAMoB,GAAeb,EAAWvC,IAAe,GAEnDoD,IACFV,EAAUC,OAASS,EAEtB,CAED,GAA4B,aAAxBL,EAAWC,IAAIvC,MAAiD,eAA1BsC,EAAWnC,MAAMF,KAAuB,CAEhF,MAAMV,EAAa+C,EAAWnC,MAAMH,MAC5BuB,KAAMoB,EAAUE,UAAEA,GAAcf,EAAWvC,IAAe,GAElE,GAAIoD,EAAY,CACd,MAAMG,EAAmBlF,KAAKc,gBAAgBiE,EAAYvB,GAEtD0B,IACFb,EAAUE,SAAWvE,KAAKe,yBACxBmE,EACAD,EAAY,KAAOtD,GAGxB,CACF,MAGC0C,EAAUC,QAAUD,EAAUE,SAAS3B,OAAS,IAClDuB,EAAQgB,KAAKd,EAEhB,KAGIF,CACR,CAKOpD,yBACNE,EACAL,EAA4B,MAE5B,IAAKK,EACH,MAAO,GAGT,MAAMS,EAAM1B,KAAKgB,UAAUC,GAE3B,IAAKS,EACH,MAAO,GAGT,MAAM0D,EAAapF,KAAKwC,qBAAqBd,EAAKd,GAC5CuD,EAAyB,GAE/B,IAAKiB,EACH,OAAOjB,EAGT,MAAMD,EAA0B,CAAA,EAEhC1E,EAASkC,EAAK,CACZE,kBAAkBC,GAChB,MAAMC,EAAaD,EAASE,KAE5BD,EAAWE,WAAWC,SAASC,IAC7BgC,EAAWhC,EAAUC,MAAMC,MAAQ,CACjCuB,KAAM7B,EAAWQ,OAAOC,MACxB0C,UAA8B,2BAAnB/C,EAAUG,KACtB,GAEJ,IAIH,MAAM4B,EAAWmB,EAAWpC,aAAa,GAAGqC,MAAMpB,SAIlD,OAFAE,EAAQgB,QAAQnF,KAAKgE,iBAAiBC,EAAUC,EAAYjD,IAErDkD,CACR"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Alias } from 'vite';
|
|
2
|
+
import ServerConfig from "./server-config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Service for work with path and aliases
|
|
5
|
+
*/
|
|
6
|
+
declare class PathNormalize {
|
|
7
|
+
/**
|
|
8
|
+
* Vite resolve aliases
|
|
9
|
+
*/
|
|
10
|
+
protected readonly viteAliases?: Alias[];
|
|
11
|
+
/**
|
|
12
|
+
* Server config
|
|
13
|
+
*/
|
|
14
|
+
protected readonly config: ServerConfig;
|
|
15
|
+
/**
|
|
16
|
+
* @constructor
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @constructor
|
|
20
|
+
*/
|
|
21
|
+
constructor(config: ServerConfig, viteAliases?: Alias[]);
|
|
22
|
+
/**
|
|
23
|
+
* Get vite aliases
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Get vite aliases
|
|
27
|
+
*/
|
|
28
|
+
getAliases(): Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Return filename postfix
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Return filename postfix
|
|
34
|
+
*/
|
|
35
|
+
getImportPostfix(): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Resolve app path
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Resolve app path
|
|
41
|
+
*/
|
|
42
|
+
getAppPath(appPath?: string, withRoot?: boolean): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Find app filepath
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Find app filepath
|
|
48
|
+
*/
|
|
49
|
+
findAppFile(basePath: string): string | null;
|
|
50
|
+
}
|
|
51
|
+
export { PathNormalize as default };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import t from"node:fs";import s from"path";class e{viteAliases;config;constructor(t,s){this.config=t,this.viteAliases=s??t.getVite()?.config?.resolve.alias}getAliases(){const t={};return this.viteAliases?.forEach((({find:s,replacement:e})=>{"string"==typeof s&&(t[s]=e)})),t}getImportPostfix(){return["","/index"].map((t=>["",".js",".ts",".tsx"].map((s=>`${t}${s}`)))).flat()}getAppPath(t,e=!1){if(!t)return;const{root:i}=this.config.getParams();let r=t;if(t.startsWith("./")||t.startsWith("../"))r=s.resolve(i,t);else{const s=this.getAliases(),[e]=t.split("/");s[e]&&(r=t.replace(e,s[e]))}return r=r.split(s.win32.sep).join(s.posix.sep),e?r:r.replace(i,"").replace(/^(\/)|(\/)$/g,"")}findAppFile(s){const e=this.getImportPostfix();for(const i of e){const e=`${s}${i}`;if(t.existsSync(e)&&t.statSync(e).isFile())return e}return null}}export{e as default};
|
|
2
|
+
//# sourceMappingURL=path-normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-normalize.js","sources":["../../src/services/path-normalize.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'path';\nimport type { Alias } from 'vite';\nimport type ServerConfig from '@services/server-config';\n\n/**\n * Service for work with path and aliases\n */\nclass PathNormalize {\n /**\n * Vite resolve aliases\n */\n protected readonly viteAliases?: Alias[];\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.viteAliases = viteAliases ?? config.getVite()?.config?.resolve.alias;\n }\n\n /**\n * Get vite aliases\n */\n public getAliases(): Record<string, string> {\n const aliases: Record<string, string> = {};\n\n this.viteAliases?.forEach(({ find, replacement }) => {\n if (typeof find !== 'string') {\n return;\n }\n\n aliases[find] = replacement;\n });\n\n return aliases;\n }\n\n /**\n * Return filename postfix\n */\n public getImportPostfix(): string[] {\n return ['', '/index']\n .map((prefix) => ['', '.js', '.ts', '.tsx'].map((ext) => `${prefix}${ext}`))\n .flat();\n }\n\n /**\n * Resolve app path\n */\n public getAppPath(appPath?: string, withRoot = false): string | undefined {\n if (!appPath) {\n return;\n }\n\n const { root } = this.config.getParams();\n let fullPath = appPath;\n\n // relative import\n if (appPath.startsWith('./') || appPath.startsWith('../')) {\n fullPath = path.resolve(root, appPath);\n } else {\n // alias import\n const aliases = this.getAliases();\n // get alias\n const [routeAlias] = appPath.split('/');\n\n if (aliases[routeAlias]) {\n fullPath = appPath.replace(routeAlias, aliases[routeAlias]);\n }\n }\n\n // normalize slashes\n fullPath = fullPath.split(path.win32.sep).join(path.posix.sep);\n\n if (withRoot) {\n return fullPath;\n }\n\n return fullPath.replace(root, '').replace(/^(\\/)|(\\/)$/g, '');\n }\n\n /**\n * Find app filepath\n */\n public findAppFile(basePath: string): string | null {\n const postfixes = this.getImportPostfix();\n\n for (const postfix of postfixes) {\n const filepath = `${basePath}${postfix}`;\n\n if (fs.existsSync(filepath) && fs.statSync(filepath).isFile()) {\n return filepath;\n }\n }\n\n return null;\n }\n}\n\nexport default PathNormalize;\n"],"names":["PathNormalize","viteAliases","config","constructor","this","getVite","resolve","alias","getAliases","aliases","forEach","find","replacement","getImportPostfix","map","prefix","ext","flat","getAppPath","appPath","withRoot","root","getParams","fullPath","startsWith","path","routeAlias","split","replace","win32","sep","join","posix","findAppFile","basePath","postfixes","postfix","filepath","fs","existsSync","statSync","isFile"],"mappings":"2CAQA,MAAMA,EAIeC,YAKAC,OAKnBC,YAAYD,EAAsBD,GAChCG,KAAKF,OAASA,EACdE,KAAKH,YAAcA,GAAeC,EAAOG,WAAWH,QAAQI,QAAQC,KACrE,CAKMC,aACL,MAAMC,EAAkC,CAAA,EAUxC,OARAL,KAAKH,aAAaS,SAAQ,EAAGC,OAAMC,kBACb,iBAATD,IAIXF,EAAQE,GAAQC,EAAW,IAGtBH,CACR,CAKMI,mBACL,MAAO,CAAC,GAAI,UACTC,KAAKC,GAAW,CAAC,GAAI,MAAO,MAAO,QAAQD,KAAKE,GAAQ,GAAGD,IAASC,QACpEC,MACJ,CAKMC,WAAWC,EAAkBC,GAAW,GAC7C,IAAKD,EACH,OAGF,MAAME,KAAEA,GAASjB,KAAKF,OAAOoB,YAC7B,IAAIC,EAAWJ,EAGf,GAAIA,EAAQK,WAAW,OAASL,EAAQK,WAAW,OACjDD,EAAWE,EAAKnB,QAAQe,EAAMF,OACzB,CAEL,MAAMV,EAAUL,KAAKI,cAEdkB,GAAcP,EAAQQ,MAAM,KAE/BlB,EAAQiB,KACVH,EAAWJ,EAAQS,QAAQF,EAAYjB,EAAQiB,IAElD,CAKD,OAFAH,EAAWA,EAASI,MAAMF,EAAKI,MAAMC,KAAKC,KAAKN,EAAKO,MAAMF,KAEtDV,EACKG,EAGFA,EAASK,QAAQP,EAAM,IAAIO,QAAQ,eAAgB,GAC3D,CAKMK,YAAYC,GACjB,MAAMC,EAAY/B,KAAKS,mBAEvB,IAAK,MAAMuB,KAAWD,EAAW,CAC/B,MAAME,EAAW,GAAGH,IAAWE,IAE/B,GAAIE,EAAGC,WAAWF,IAAaC,EAAGE,SAASH,GAAUI,SACnD,OAAOJ,CAEV,CAED,OAAO,IACR"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import i from"node:fs";import t from"node:path";import s from"../helpers/plugin-config.js";import
|
|
1
|
+
import i from"node:fs";import t from"node:path";import s from"../helpers/plugin-config.js";import e from"./logger.js";class r{isProd;isHost;isModulePreload;isSPA;mode;vite;app;params;prodParams;logger;defaultBuildRoots=["./build","./dist"];constructor({isProd:i=!1,isHost:t=!1,isOnlyClient:s=!1,isModulePreload:e=!1,mode:r="production"},o){this.isProd=i,this.isHost=t,this.isSPA=s,this.isModulePreload=e,this.mode=r,this.prodParams={publicDir:"/client",indexFile:"/client/index.html",serverFile:"/server/server.js",host:"127.0.0.1",port:3e3,...o},this.makeParams()}static init(i={},t={}){return new r(i,t)}getBuildDir(t){for(const s of[t,...this.defaultBuildRoots])if(s&&i.existsSync(s))return s;return t??this.defaultBuildRoots[0]}makeParams(){const i=this.getPluginConfig()??{},{config:s}=this.vite??{},r=s?.root??this.getBuildDir(this.prodParams.root),o=s?.publicDir??this.prodParams.publicDir,a=new URL(import.meta.url),l=i.pluginPath??t.resolve(t.dirname(a.pathname),"../"),h=i.indexFile??this.prodParams.indexFile,d=i.serverFile??this.prodParams.serverFile,n=i.clientFile??this.prodParams.clientFile,p="boolean"==typeof s?.server.host||this.isHost?"0.0.0.0":s?.server.host??this.prodParams.host,m=s?.server.port??(this.isProd?this.prodParams.port:5173);this.params={root:r,publicDir:o,pluginPath:l,indexFile:h,clientFile:n,serverFile:d,host:p,port:m,isSPA:this.isSPA,isProd:this.isProd},this.logger=this.vite?.config.logger??new e}setVite(i){this.vite=i,this.makeParams()}setApp(i){this.app=i}getVite(){return this.vite}getApp(){return this.app}getPluginConfig(){return this.vite?s(this.vite.config):void 0}getParams(){return this.params}getLogger(){return this.logger}}export{r as default};
|
|
2
2
|
//# sourceMappingURL=server-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.js","sources":["../../src/services/server-config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Express } from 'express';\nimport type { Logger, ViteDevServer } from 'vite';\nimport type { IPluginConfig } from '@helpers/plugin-config';\nimport getPluginConfig from '@helpers/plugin-config';\nimport DefaultLogger from '@services/logger';\n\ninterface IConfigOptions {\n isProd?: boolean;\n isHost?: boolean;\n isOnlyClient?: boolean; // SPA mode\n isModulePreload?: boolean;\n mode?: string;\n}\n\ninterface IConfigParams {\n root: string;\n publicDir: string;\n pluginPath: string;\n isProd: boolean;\n isSPA: boolean;\n indexFile: string;\n serverFile: string;\n host: string;\n port: number;\n}\n\n/**\n * Server config\n */\nclass ServerConfig {\n /**\n * Production build\n */\n public readonly isProd: boolean;\n\n /**\n * Server host mode\n */\n public readonly isHost: boolean;\n\n /**\n * Add module preload scripts to server output\n */\n public readonly isModulePreload: boolean;\n\n /**\n * SPA mode\n */\n public readonly isSPA: boolean;\n\n /**\n * Env mode\n */\n public readonly mode: string;\n\n /**\n * Vite config - only for development\n */\n protected vite?: ViteDevServer;\n\n /**\n * Express application\n */\n protected app?: Express;\n\n /**\n * Config params\n */\n protected params: IConfigParams;\n\n /**\n * Default production params\n */\n protected prodParams: Partial<IConfigParams>;\n\n /**\n * Vite logger for dev mode or console for production\n */\n protected logger: Logger;\n\n /**\n * Default root dir\n */\n protected defaultBuildRoots = ['./build', './dist'];\n\n /**\n * @constructor\n */\n protected constructor(\n {\n isProd = false,\n isHost = false,\n isOnlyClient = false,\n isModulePreload = false,\n mode = 'production',\n }: IConfigOptions,\n prodParams: Partial<IConfigParams>,\n ) {\n this.isProd = isProd;\n this.isHost = isHost;\n this.isSPA = isOnlyClient;\n this.isModulePreload = isModulePreload;\n this.mode = mode;\n this.prodParams = {\n publicDir: '/client', // default for production,\n indexFile: '/client/index.html',\n serverFile: '/server/server.js',\n host: '127.0.0.1',\n port: 3000,\n ...prodParams,\n };\n\n this.makeParams();\n }\n\n /**\n * Initialize service\n */\n public static init(\n options: IConfigOptions = {},\n prodOptions: Partial<IConfigParams> = {},\n ): ServerConfig {\n return new ServerConfig(options, prodOptions);\n }\n\n /**\n * Lookup build folder\n */\n protected getBuildDir(root?: string): string {\n for (const dir of [root, ...this.defaultBuildRoots]) {\n if (dir && fs.existsSync(dir)) {\n return dir;\n }\n }\n\n return root ?? this.defaultBuildRoots[0];\n }\n\n /**\n * Make config params\n */\n protected makeParams(): void {\n const pluginConfig = (this.getPluginConfig() ?? {}) as Partial<IPluginConfig>;\n const { config } = this.vite ?? {};\n\n const root = config?.root ?? this.getBuildDir(this.prodParams.root);\n const publicDir = config?.publicDir ?? this.prodParams.publicDir!;\n const dirInfo = new URL(import.meta.url);\n const pluginPath =\n pluginConfig.pluginPath ?? path.resolve(path.dirname(dirInfo.pathname), '../');\n const indexFile = pluginConfig.indexFile ?? this.prodParams.indexFile!;\n const serverFile = pluginConfig.serverFile ?? this.prodParams.serverFile!;\n const host =\n typeof config?.server.host === 'boolean' || this.isHost\n ? '0.0.0.0'\n : config?.server.host ?? this.prodParams.host!;\n const port = config?.server.port ?? (this.isProd ? this.prodParams.port! : 5173);\n\n this.params = {\n root,\n publicDir,\n pluginPath,\n indexFile,\n serverFile,\n host,\n port,\n isSPA: this.isSPA,\n isProd: this.isProd,\n };\n this.logger = this.vite?.config.logger ?? new DefaultLogger();\n }\n\n /**\n * Set vite server\n */\n public setVite(vite: ViteDevServer): void {\n this.vite = vite;\n\n this.makeParams();\n }\n\n /**\n * Set express server\n */\n public setApp(express: Express): void {\n this.app = express;\n }\n\n /**\n * Return vite dev server\n * NOTE: only on development mode\n */\n public getVite(): ViteDevServer | undefined {\n return this.vite;\n }\n\n /**\n * Return express server\n */\n public getApp(): Express | undefined {\n return this.app;\n }\n\n /**\n * return plugin config\n * NOTE: only on development mode\n */\n public getPluginConfig(): IPluginConfig | undefined {\n return this.vite ? getPluginConfig(this.vite.config) : undefined;\n }\n\n /**\n * Return config params\n */\n public getParams(): IConfigParams {\n return this.params;\n }\n\n /**\n * Get server logger\n */\n public getLogger(): Logger {\n return this.logger;\n }\n}\n\nexport default ServerConfig;\n"],"names":["ServerConfig","isProd","isHost","isModulePreload","isSPA","mode","vite","app","params","prodParams","logger","defaultBuildRoots","constructor","isOnlyClient","this","publicDir","indexFile","serverFile","host","port","makeParams","static","options","prodOptions","getBuildDir","root","dir","fs","existsSync","pluginConfig","getPluginConfig","config","dirInfo","URL","url","pluginPath","path","resolve","dirname","pathname","server","DefaultLogger","setVite","setApp","express","getVite","getApp","undefined","getParams","getLogger"],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-config.js","sources":["../../src/services/server-config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Express } from 'express';\nimport type { Logger, ViteDevServer } from 'vite';\nimport type { IPluginConfig } from '@helpers/plugin-config';\nimport getPluginConfig from '@helpers/plugin-config';\nimport DefaultLogger from '@services/logger';\n\ninterface IConfigOptions {\n isProd?: boolean;\n isHost?: boolean;\n isOnlyClient?: boolean; // SPA mode\n isModulePreload?: boolean;\n mode?: string;\n}\n\ninterface IConfigParams {\n root: string;\n publicDir: string;\n pluginPath: string;\n isProd: boolean;\n isSPA: boolean;\n indexFile: string;\n clientFile: string;\n serverFile: string;\n host: string;\n port: number;\n}\n\n/**\n * Server config\n */\nclass ServerConfig {\n /**\n * Production build\n */\n public readonly isProd: boolean;\n\n /**\n * Server host mode\n */\n public readonly isHost: boolean;\n\n /**\n * Add module preload scripts to server output\n */\n public readonly isModulePreload: boolean;\n\n /**\n * SPA mode\n */\n public readonly isSPA: boolean;\n\n /**\n * Env mode\n */\n public readonly mode: string;\n\n /**\n * Vite config - only for development\n */\n protected vite?: ViteDevServer;\n\n /**\n * Express application\n */\n protected app?: Express;\n\n /**\n * Config params\n */\n protected params: IConfigParams;\n\n /**\n * Default production params\n */\n protected prodParams: Partial<IConfigParams>;\n\n /**\n * Vite logger for dev mode or console for production\n */\n protected logger: Logger;\n\n /**\n * Default root dir\n */\n protected defaultBuildRoots = ['./build', './dist'];\n\n /**\n * @constructor\n */\n protected constructor(\n {\n isProd = false,\n isHost = false,\n isOnlyClient = false,\n isModulePreload = false,\n mode = 'production',\n }: IConfigOptions,\n prodParams: Partial<IConfigParams>,\n ) {\n this.isProd = isProd;\n this.isHost = isHost;\n this.isSPA = isOnlyClient;\n this.isModulePreload = isModulePreload;\n this.mode = mode;\n this.prodParams = {\n publicDir: '/client', // default for production,\n indexFile: '/client/index.html',\n serverFile: '/server/server.js',\n host: '127.0.0.1',\n port: 3000,\n ...prodParams,\n };\n\n this.makeParams();\n }\n\n /**\n * Initialize service\n */\n public static init(\n options: IConfigOptions = {},\n prodOptions: Partial<IConfigParams> = {},\n ): ServerConfig {\n return new ServerConfig(options, prodOptions);\n }\n\n /**\n * Lookup build folder\n */\n protected getBuildDir(root?: string): string {\n for (const dir of [root, ...this.defaultBuildRoots]) {\n if (dir && fs.existsSync(dir)) {\n return dir;\n }\n }\n\n return root ?? this.defaultBuildRoots[0];\n }\n\n /**\n * Make config params\n */\n protected makeParams(): void {\n const pluginConfig = (this.getPluginConfig() ?? {}) as Partial<IPluginConfig>;\n const { config } = this.vite ?? {};\n\n const root = config?.root ?? this.getBuildDir(this.prodParams.root);\n const publicDir = config?.publicDir ?? this.prodParams.publicDir!;\n const dirInfo = new URL(import.meta.url);\n const pluginPath =\n pluginConfig.pluginPath ?? path.resolve(path.dirname(dirInfo.pathname), '../');\n const indexFile = pluginConfig.indexFile ?? this.prodParams.indexFile!;\n const serverFile = pluginConfig.serverFile ?? this.prodParams.serverFile!;\n const clientFile = pluginConfig.clientFile ?? this.prodParams.clientFile!;\n const host =\n typeof config?.server.host === 'boolean' || this.isHost\n ? '0.0.0.0'\n : config?.server.host ?? this.prodParams.host!;\n const port = config?.server.port ?? (this.isProd ? this.prodParams.port! : 5173);\n\n this.params = {\n root,\n publicDir,\n pluginPath,\n indexFile,\n clientFile,\n serverFile,\n host,\n port,\n isSPA: this.isSPA,\n isProd: this.isProd,\n };\n this.logger = this.vite?.config.logger ?? new DefaultLogger();\n }\n\n /**\n * Set vite server\n */\n public setVite(vite: ViteDevServer): void {\n this.vite = vite;\n\n this.makeParams();\n }\n\n /**\n * Set express server\n */\n public setApp(express: Express): void {\n this.app = express;\n }\n\n /**\n * Return vite dev server\n * NOTE: only on development mode\n */\n public getVite(): ViteDevServer | undefined {\n return this.vite;\n }\n\n /**\n * Return express server\n */\n public getApp(): Express | undefined {\n return this.app;\n }\n\n /**\n * return plugin config\n * NOTE: only on development mode\n */\n public getPluginConfig(): IPluginConfig | undefined {\n return this.vite ? getPluginConfig(this.vite.config) : undefined;\n }\n\n /**\n * Return config params\n */\n public getParams(): IConfigParams {\n return this.params;\n }\n\n /**\n * Get server logger\n */\n public getLogger(): Logger {\n return this.logger;\n }\n}\n\nexport default ServerConfig;\n"],"names":["ServerConfig","isProd","isHost","isModulePreload","isSPA","mode","vite","app","params","prodParams","logger","defaultBuildRoots","constructor","isOnlyClient","this","publicDir","indexFile","serverFile","host","port","makeParams","static","options","prodOptions","getBuildDir","root","dir","fs","existsSync","pluginConfig","getPluginConfig","config","dirInfo","URL","url","pluginPath","path","resolve","dirname","pathname","clientFile","server","DefaultLogger","setVite","setApp","express","getVite","getApp","undefined","getParams","getLogger"],"mappings":"sHAgCA,MAAMA,EAIYC,OAKAC,OAKAC,gBAKAC,MAKAC,KAKNC,KAKAC,IAKAC,OAKAC,WAKAC,OAKAC,kBAAoB,CAAC,UAAW,UAK1CC,aACEX,OACEA,GAAS,EAAKC,OACdA,GAAS,EAAKW,aACdA,GAAe,EAAKV,gBACpBA,GAAkB,EAAKE,KACvBA,EAAO,cAETI,GAEAK,KAAKb,OAASA,EACda,KAAKZ,OAASA,EACdY,KAAKV,MAAQS,EACbC,KAAKX,gBAAkBA,EACvBW,KAAKT,KAAOA,EACZS,KAAKL,WAAa,CAChBM,UAAW,UACXC,UAAW,qBACXC,WAAY,oBACZC,KAAM,YACNC,KAAM,OACHV,GAGLK,KAAKM,YACN,CAKMC,YACLC,EAA0B,GAC1BC,EAAsC,CAAA,GAEtC,OAAO,IAAIvB,EAAasB,EAASC,EAClC,CAKSC,YAAYC,GACpB,IAAK,MAAMC,IAAO,CAACD,KAASX,KAAKH,mBAC/B,GAAIe,GAAOC,EAAGC,WAAWF,GACvB,OAAOA,EAIX,OAAOD,GAAQX,KAAKH,kBAAkB,EACvC,CAKSS,aACR,MAAMS,EAAgBf,KAAKgB,mBAAqB,CAAE,GAC5CC,OAAEA,GAAWjB,KAAKR,MAAQ,CAAA,EAE1BmB,EAAOM,GAAQN,MAAQX,KAAKU,YAAYV,KAAKL,WAAWgB,MACxDV,EAAYgB,GAAQhB,WAAaD,KAAKL,WAAWM,UACjDiB,EAAU,IAAIC,gBAAgBC,KAC9BC,EACJN,EAAaM,YAAcC,EAAKC,QAAQD,EAAKE,QAAQN,EAAQO,UAAW,OACpEvB,EAAYa,EAAab,WAAaF,KAAKL,WAAWO,UACtDC,EAAaY,EAAaZ,YAAcH,KAAKL,WAAWQ,WACxDuB,EAAaX,EAAaW,YAAc1B,KAAKL,WAAW+B,WACxDtB,EAC2B,kBAAxBa,GAAQU,OAAOvB,MAAsBJ,KAAKZ,OAC7C,UACA6B,GAAQU,OAAOvB,MAAQJ,KAAKL,WAAWS,KACvCC,EAAOY,GAAQU,OAAOtB,OAASL,KAAKb,OAASa,KAAKL,WAAWU,KAAQ,MAE3EL,KAAKN,OAAS,CACZiB,OACAV,YACAoB,aACAnB,YACAwB,aACAvB,aACAC,OACAC,OACAf,MAAOU,KAAKV,MACZH,OAAQa,KAAKb,QAEfa,KAAKJ,OAASI,KAAKR,MAAMyB,OAAOrB,QAAU,IAAIgC,CAC/C,CAKMC,QAAQrC,GACbQ,KAAKR,KAAOA,EAEZQ,KAAKM,YACN,CAKMwB,OAAOC,GACZ/B,KAAKP,IAAMsC,CACZ,CAMMC,UACL,OAAOhC,KAAKR,IACb,CAKMyC,SACL,OAAOjC,KAAKP,GACb,CAMMuB,kBACL,OAAOhB,KAAKR,KAAOwB,EAAgBhB,KAAKR,KAAKyB,aAAUiB,CACxD,CAKMC,YACL,OAAOnC,KAAKN,MACb,CAKM0C,YACL,OAAOpC,KAAKJ,MACb"}
|
|
@@ -4,6 +4,8 @@ import { AgnosticDataRouteMatch } from '@remix-run/router/dist/utils';
|
|
|
4
4
|
import { RouteObject } from 'react-router-dom';
|
|
5
5
|
import { Alias, ModuleNode } from 'vite';
|
|
6
6
|
import { IRequestContext } from "../node/render.js";
|
|
7
|
+
import { TRoutesTree } from "./parse-routes.js";
|
|
8
|
+
import PathNormalize from "./path-normalize.js";
|
|
7
9
|
import ServerConfig from "./server-config.js";
|
|
8
10
|
interface ISsrManifestParams {
|
|
9
11
|
buildDir?: string;
|
|
@@ -47,6 +49,10 @@ declare class SsrManifest {
|
|
|
47
49
|
* Server config
|
|
48
50
|
*/
|
|
49
51
|
protected readonly config: ServerConfig;
|
|
52
|
+
/**
|
|
53
|
+
* Path normalize service
|
|
54
|
+
*/
|
|
55
|
+
protected readonly pathNormalize: PathNormalize;
|
|
50
56
|
/**
|
|
51
57
|
* Project root path
|
|
52
58
|
*/
|
|
@@ -119,7 +125,14 @@ declare class SsrManifest {
|
|
|
119
125
|
/**
|
|
120
126
|
* Recursive walk routes and return id's with route import path
|
|
121
127
|
*/
|
|
122
|
-
protected
|
|
128
|
+
protected getAsyncRoutesIds(routes: RouteObject[], index?: string): Promise<Record<string, string | undefined>>;
|
|
129
|
+
/**
|
|
130
|
+
* Same as 'getAsyncRoutesIds' but for routes tree from 'ParseRoutes'
|
|
131
|
+
*/
|
|
132
|
+
/**
|
|
133
|
+
* Same as 'getAsyncRoutesIds' but for routes tree from 'ParseRoutes'
|
|
134
|
+
*/
|
|
135
|
+
protected getRoutesTreeIds(routes: TRoutesTree[], index?: string): Record<string, string | undefined>;
|
|
123
136
|
/**
|
|
124
137
|
* Sort assets
|
|
125
138
|
*/
|
|
@@ -140,28 +153,7 @@ declare class SsrManifest {
|
|
|
140
153
|
/**
|
|
141
154
|
* Build routes manifest file
|
|
142
155
|
*/
|
|
143
|
-
buildRoutesManifest(): Promise<void>;
|
|
144
|
-
/**
|
|
145
|
-
* Get vite aliases
|
|
146
|
-
*/
|
|
147
|
-
/**
|
|
148
|
-
* Get vite aliases
|
|
149
|
-
*/
|
|
150
|
-
protected getAliases(): Record<string, string>;
|
|
151
|
-
/**
|
|
152
|
-
* Return route postfix
|
|
153
|
-
*/
|
|
154
|
-
/**
|
|
155
|
-
* Return route postfix
|
|
156
|
-
*/
|
|
157
|
-
protected getRouteImportPostfix(): string[];
|
|
158
|
-
/**
|
|
159
|
-
* Normalized route path
|
|
160
|
-
*/
|
|
161
|
-
/**
|
|
162
|
-
* Normalized route path
|
|
163
|
-
*/
|
|
164
|
-
protected normalizeRoutePath(routePath?: string, withRoot?: boolean): string | undefined;
|
|
156
|
+
buildRoutesManifest(isNodeParsing: boolean): Promise<void>;
|
|
165
157
|
/**
|
|
166
158
|
* Get route assets
|
|
167
159
|
*/
|
package/services/ssr-manifest.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import t from"node:fs";import e from"node:path";import s from"chalk";import i from"./prepare-server.js";import
|
|
1
|
+
import t from"node:fs";import e from"node:path";import s from"chalk";import i from"./parse-routes.js";import r from"./path-normalize.js";import o from"./prepare-server.js";import n from"./server-config.js";var a;!function(t){t.style="style",t.script="script",t.image="image",t.font="font"}(a||(a={}));const l="\r\n";class c{static instance=null;config;pathNormalize;root;buildDir;manifestName="manifest.json";assetsManifest="assets-manifest.json";viteAliases;routesAssets=null;constructor(t,{buildDir:e,viteAliases:s}={}){this.config=t,this.root=t.getParams().root,this.buildDir=e,this.viteAliases=s??t.getVite()?.config?.resolve.alias,this.pathNormalize=new r(t,s)}static get(t,e={}){return null===c.instance&&(c.instance=new c(t,e)),c.instance}getOutDir(){return e.resolve(this.root,this.buildDir||"")}getAssetsManifestFile(){return`${this.getOutDir()}/server/${this.assetsManifest}`}loadClientManifest(){const s=e.resolve(this.root,`${this.buildDir||""}/client/.vite`),i=`${s}/${this.manifestName}`;if(!t.existsSync(i))return{};const r=JSON.parse(t.readFileSync(i,{encoding:"utf-8"}));return t.rmSync(i),0===t.readdirSync(s).length&&t.rmSync(s,{recursive:!0}),r}loadAssetsManifest(){if(null!==this.routesAssets)return this.routesAssets;const e=this.getAssetsManifestFile();return t.existsSync(e)?(this.routesAssets=JSON.parse(t.readFileSync(e,{encoding:"utf-8"})),this.routesAssets):{}}async getAsyncRoutesIds(t,e){const i={};for(const r in t){const o=t[r],n=[e,r].filter(Boolean).join("-");if(o.lazy)try{const t=await o.lazy();i[n]=this.pathNormalize.getAppPath(t?.pathId)}catch(t){console.error(s.red("Failed to load route:"),o.path,t)}else o.children&&Object.assign(i,await this.getAsyncRoutesIds(o.children,n))}return i}getRoutesTreeIds(t,e){const s={};return t.forEach(((t,i)=>{const r=[e,String(i)].filter(Boolean).join("-");t.import&&(s[r]=this.pathNormalize.getAppPath(t.import)),t.children.length>0&&Object.assign(s,this.getRoutesTreeIds(t.children,r))})),s}sortAssets(t){return t.sort(((t,e)=>t.weight===e.weight?Number(t.isNested)-Number(e.isNested):t.weight-e.weight))}getRouteAssets(t,e,s=!1){const i=[...e?.assets??[],...e?.css??[],e?.file].reduce(((t,i)=>{if(i){const r=this.getAssetType(i),o=e.isEntry&&e.file===i;r&&(t[i]={url:`/${i}`,weight:o?1.9:this.getAssetWeight(i),type:r,isNested:s,isPreload:!o})}return t}),{});return e?.imports?.length&&e.imports.forEach((e=>{const s=t[e];s&&Object.assign(i,this.getRouteAssets(t,s,!0))})),i}async buildRoutesManifest(e){const s=o.init(n.init({isProd:!0},{root:this.getOutDir()})),r=this.loadClientManifest();let a;if(e){const{routes:t}=await s.loadEntrypoint(!1);a=await this.getAsyncRoutesIds(t)}else{const t=new i(this.config,this.viteAliases);a=this.getRoutesTreeIds(t.parse())}const l=this.pathNormalize.getImportPostfix(),c={};Object.entries(a).forEach((([t,e])=>{const s=l.find((t=>void 0!==r[`${e}${t}`])),i=r[`${e}${s||""}`];c[t]=this.sortAssets(Object.values(this.getRouteAssets(r,i)))})),t.writeFileSync(this.getAssetsManifestFile(),JSON.stringify(c,null,2),{encoding:"utf-8"})}getAssets(t){if(this.config.getVite())return this.getAssetsDev(t);const e=t?.map((({route:t})=>t.id)).filter(Boolean)??[];if(!e.length)return[];const s=this.loadAssetsManifest();return this.sortAssets(e.map((t=>s[t])).flat().filter(Boolean))}getAssetsDev(t){const e=t?.map((({route:t})=>this.pathNormalize.getAppPath(t?.pathId,!0))).filter(Boolean)??[];if(!e.length)return[];let s={};const i=this.pathNormalize.getImportPostfix();return[`${this.root}/${this.config.getPluginConfig()?.clientFile??"client.ts"}`,...e].forEach((t=>{for(const e of i){const i=this.config.getVite()?.moduleGraph.getModuleById(`${t}${e}`);if(i){s={...s,...this.getModuleAssets(i)};break}}})),Object.values(s)}getModuleAssets(t,e=new Set){if(!t?.clientImportedModules.size||e.has(t.file))return{};let i={};return e.add(t.file),t.clientImportedModules.forEach((t=>{const{file:r,clientImportedModules:o,transformResult:n}=t,l=r?.split(".").at(-1);if(r&&l&&["css","scss"].includes(l)){const t=n?.code.match(/__vite__css\s+=\s+"(?<css>.+)"/)?.groups?.css;if(t)try{i[r]={type:a.style,url:r,weight:this.getAssetWeight(r),content:JSON.parse(`{"style": "${t}"}`).style,isNested:Boolean(e.size),isPreload:!1}}catch(t){console.warn(s.yellowBright("Failed to parse style: ",r))}}else o.size&&(i={...i,...this.getModuleAssets(t,e)})})),i}getAssetWeight(t){switch(this.getAssetType(t)){case a.style:return 1;case a.script:return 2;default:return 3}}getAssetType(t){const e=t.split(".").at(-1)?.toLowerCase();switch(e){case"css":case"scss":return a.style;case"js":return a.script;case"svg":case"jpg":case"jpeg":case"png":case"webp":case"gif":case"ico":return a.image;case"ttf":case"otf":case"woff":case"woff2":return a.font;default:return null}}writeEarlyHits(t,e){e.write(`HTTP/1.1 103 Early Hints${l}`),t.forEach((({type:t,url:s})=>{t&&["style","script"].includes(t)&&e.write(`Link: <${s}>; rel=preload; as=${t}${l}`)})),e.write(l)}injectAssets({routerContext:t,html:e,res:s,hasEarlyHints:i=!1}){const r=this.getAssets(t?.matches),o=r.map((({type:t,url:e,isPreload:s,content:i=""})=>{switch(t){case a.style:return this.config.getVite()?`<style data-vite-dev-id="${e}">${i}</style>`:`<link rel="stylesheet" href="${e}">`;case a.script:return s?this.config.isModulePreload?`<link rel="modulepreload" as="script" crossorigin href="${e}">`:null:`<script async type="module" crossorigin src="${e}"><\/script>`}return null})).filter(Boolean);e.header=e.header.replace("</head>",`${o.join("\n")}</head>`),i&&o.length&&s.socket&&this.writeEarlyHits(r,s.socket)}}export{c as default};
|
|
2
2
|
//# sourceMappingURL=ssr-manifest.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-manifest.js","sources":["../../src/services/ssr-manifest.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { Socket } from 'node:net';\nimport path from 'node:path';\nimport type { AgnosticDataRouteMatch } from '@remix-run/router/dist/utils';\nimport chalk from 'chalk';\nimport type { RouteObject } from 'react-router-dom';\nimport type { Alias, ModuleNode } from 'vite';\nimport type { IAsyncRoute } from '@helpers/import-route';\nimport type { IRequestContext } from '@node/render';\nimport PrepareServer from '@services/prepare-server';\nimport ServerConfig from '@services/server-config';\n\ninterface ISsrManifestParams {\n buildDir?: string;\n viteAliases?: Alias[];\n}\n\ninterface IManifest {\n [path: string]: {\n assets: string[];\n css: string[];\n file: string;\n isEntry?: boolean;\n imports: string[];\n };\n}\n\nenum AssetType {\n style = 'style',\n script = 'script',\n image = 'image',\n font = 'font',\n}\n\ninterface IAsset {\n type: AssetType;\n url: string;\n weight: number;\n isNested: boolean;\n isPreload: boolean;\n content?: string;\n}\n\ntype TAssets = { [id: string]: IAsset };\n\nconst CRLF = '\\r\\n';\n\n/**\n * Working with SSR Manifest file\n */\nclass SsrManifest {\n /**\n * Singleton\n */\n protected static instance: SsrManifest | null = null;\n\n /**\n * Server config\n */\n protected readonly config: ServerConfig;\n\n /**\n * Project root path\n */\n protected readonly root: string;\n\n /**\n * Build dir\n */\n protected readonly buildDir?: string;\n\n /**\n * Client manifest file name\n */\n protected readonly manifestName = 'manifest.json';\n\n /**\n * Assets manifest file name\n */\n protected readonly assetsManifest = 'assets-manifest.json';\n\n /**\n * Vite resolve aliases\n */\n protected readonly viteAliases?: Alias[];\n\n /**\n * Loaded assets manifest file\n */\n protected routesAssets: Record<string, IAsset[]> | null = null;\n\n /**\n * @constructor\n */\n protected constructor(config: ServerConfig, { buildDir, viteAliases }: ISsrManifestParams = {}) {\n this.config = config;\n this.root = config.getParams().root;\n this.buildDir = buildDir;\n this.viteAliases = viteAliases ?? config.getVite()?.config?.resolve.alias;\n }\n\n /**\n * Get singleton instance\n */\n public static get(config: ServerConfig, params: ISsrManifestParams = {}): SsrManifest {\n if (SsrManifest.instance === null) {\n SsrManifest.instance = new SsrManifest(config, params);\n }\n\n return SsrManifest.instance;\n }\n\n /**\n * Get output dir\n */\n protected getOutDir() {\n return path.resolve(this.root, this.buildDir || '');\n }\n\n /**\n * Get assets manifest file name\n */\n protected getAssetsManifestFile(): string {\n return `${this.getOutDir()}/server/${this.assetsManifest}`;\n }\n\n /**\n * Load client ssr manifest\n */\n protected loadClientManifest(): IManifest {\n const clientManifestDir = path.resolve(this.root, `${this.buildDir || ''}/client/.vite`);\n const clientSsrManifest = `${clientManifestDir}/${this.manifestName}`;\n\n if (!fs.existsSync(clientSsrManifest)) {\n return {};\n }\n\n const result = JSON.parse(\n fs.readFileSync(clientSsrManifest, { encoding: 'utf-8' }),\n ) as IManifest;\n\n fs.rmSync(clientSsrManifest);\n\n // try to remove empty .vite dir\n if (fs.readdirSync(clientManifestDir).length === 0) {\n fs.rmSync(clientManifestDir, { recursive: true });\n }\n\n return result;\n }\n\n /**\n * Load assets manifest\n */\n protected loadAssetsManifest(): Record<string, IAsset[]> {\n if (this.routesAssets !== null) {\n return this.routesAssets;\n }\n\n const manifestFile = this.getAssetsManifestFile();\n\n if (!fs.existsSync(manifestFile)) {\n return {};\n }\n\n this.routesAssets = JSON.parse(fs.readFileSync(manifestFile, { encoding: 'utf-8' })) as Record<\n string,\n IAsset[]\n >;\n\n return this.routesAssets;\n }\n\n /**\n * Recursive walk routes and return id's with route import path\n */\n protected async getRoutesIds(\n routes: RouteObject[],\n index?: string,\n ): Promise<Record<string, string | undefined>> {\n const result: Record<string, string | undefined> = {};\n\n // reason: await + array index\n // eslint-disable-next-line @typescript-eslint/no-for-in-array\n for (const routeIndex in routes) {\n const route = routes[routeIndex];\n const routeId = [index, routeIndex].filter(Boolean).join('-');\n\n if (route.lazy) {\n try {\n const resolvedRoute: IAsyncRoute = await route.lazy();\n\n result[routeId] = this.normalizeRoutePath(resolvedRoute?.pathId);\n } catch (e) {\n console.error(chalk.red('Failed to load route:'), route.path, e);\n }\n } else if (route.children) {\n Object.assign(result, await this.getRoutesIds(route.children, routeId));\n }\n }\n\n return result;\n }\n\n /**\n * Sort assets\n */\n protected sortAssets(assets: IAsset[]): IAsset[] {\n return assets.sort((a, b) =>\n a.weight === b.weight ? Number(a.isNested) - Number(b.isNested) : a.weight - b.weight,\n );\n }\n\n /**\n * Get recursive module assets\n */\n protected getRouteAssets(\n manifest: IManifest,\n module: IManifest[string],\n isNested = false,\n ): Record<string, IAsset> {\n const rootAssets = [...(module?.assets ?? []), ...(module?.css ?? []), module?.file];\n\n const assets = rootAssets.reduce(\n (res, asset) => {\n if (asset) {\n const type = this.getAssetType(asset);\n const isEntry = module.isEntry && module.file === asset;\n\n // keep only js,css,image,fonts files\n if (type) {\n res[asset] = {\n url: `/${asset}`,\n weight: isEntry ? 1.9 : this.getAssetWeight(asset),\n type,\n isNested,\n isPreload: !isEntry,\n };\n }\n }\n\n return res;\n },\n {} as Record<string, IAsset>,\n );\n\n // nested assets\n if (module?.imports?.length) {\n module.imports.forEach((nestedAsset) => {\n const nestedModule = manifest[nestedAsset];\n\n if (nestedModule) {\n Object.assign(assets, this.getRouteAssets(manifest, nestedModule, true));\n }\n });\n }\n\n return assets;\n }\n\n /**\n * Build routes manifest file\n */\n public async buildRoutesManifest(): Promise<void> {\n const prepareServer = PrepareServer.init(\n ServerConfig.init({ isProd: true }, { root: this.getOutDir() }),\n );\n const manifest = this.loadClientManifest();\n const { routes } = await prepareServer.loadEntrypoint(false);\n const routesPaths = await this.getRoutesIds(routes as RouteObject[]);\n const postfixes = this.getRouteImportPostfix();\n\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 vite aliases\n */\n protected getAliases(): Record<string, string> {\n const aliases: Record<string, string> = {};\n\n this.viteAliases?.forEach(({ find, replacement }) => {\n if (typeof find !== 'string') {\n return;\n }\n\n aliases[find] = replacement;\n });\n\n return aliases;\n }\n\n /**\n * Return route postfix\n */\n protected getRouteImportPostfix(): string[] {\n return ['', '/index']\n .map((prefix) => ['', '.js', '.ts', '.tsx'].map((ext) => `${prefix}${ext}`))\n .flat();\n }\n\n /**\n * Normalized route path\n */\n protected normalizeRoutePath(routePath?: string, withRoot = false): string | undefined {\n if (!routePath) {\n return;\n }\n\n let fullPath = '';\n\n // relative import\n if (routePath.startsWith('./') || routePath.startsWith('../')) {\n fullPath = path.resolve(this.root, routePath);\n } else {\n // alias import\n const aliases = this.getAliases();\n // get alias\n const [routeAlias] = routePath.split('/');\n\n if (aliases[routeAlias]) {\n fullPath = routePath.replace(routeAlias, aliases[routeAlias]);\n }\n }\n\n // normalize slashes\n fullPath = fullPath.split(path.win32.sep).join(path.posix.sep);\n\n if (withRoot) {\n return fullPath;\n }\n\n return fullPath.replace(this.root, '').replace(/^(\\/)|(\\/)$/g, '');\n }\n\n /**\n * Get route assets\n */\n protected getAssets(routes?: AgnosticDataRouteMatch[]): IAsset[] {\n if (this.config.getVite()) {\n return this.getAssetsDev(routes);\n }\n\n const routeIds = routes?.map(({ route }) => route.id).filter(Boolean) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n const routesAssets = this.loadAssetsManifest();\n\n return this.sortAssets(\n routeIds\n .map((routeId) => routesAssets[routeId])\n .flat()\n .filter(Boolean),\n );\n }\n\n /**\n * Get development route assets\n */\n protected getAssetsDev(routes?: AgnosticDataRouteMatch[]): IAsset[] {\n const routeIds =\n (routes\n ?.map(({ route }) => this.normalizeRoutePath((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.getRouteImportPostfix();\n const rootId = `${this.root}/${this.config.getPluginConfig()?.clientFile ?? 'client.ts'}`;\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","root","buildDir","manifestName","assetsManifest","viteAliases","routesAssets","constructor","this","getParams","getVite","resolve","alias","params","instance","getOutDir","path","getAssetsManifestFile","loadClientManifest","clientManifestDir","clientSsrManifest","fs","existsSync","result","JSON","parse","readFileSync","encoding","rmSync","readdirSync","length","recursive","loadAssetsManifest","manifestFile","async","routes","index","routeIndex","route","routeId","filter","Boolean","join","lazy","resolvedRoute","normalizeRoutePath","pathId","e","console","error","chalk","red","children","Object","assign","getRoutesIds","sortAssets","assets","sort","a","b","weight","Number","isNested","getRouteAssets","manifest","module","css","file","reduce","res","asset","type","getAssetType","isEntry","url","getAssetWeight","isPreload","imports","forEach","nestedAsset","nestedModule","prepareServer","PrepareServer","init","ServerConfig","isProd","loadEntrypoint","routesPaths","postfixes","getRouteImportPostfix","entries","routePath","routePostfix","find","postfix","undefined","routeMeta","values","writeFileSync","stringify","getAliases","aliases","replacement","map","prefix","ext","flat","withRoot","fullPath","startsWith","routeAlias","split","replace","win32","sep","posix","getAssets","getAssetsDev","routeIds","id","getPluginConfig","clientFile","moduleId","moduleGraph","getModuleById","getModuleAssets","skipModules","Set","clientImportedModules","size","has","add","subModule","transformResult","at","includes","code","match","groups","style","content","warn","yellowBright","script","toLowerCase","image","font","writeEarlyHits","socket","write","injectAssets","routerContext","html","hasEarlyHints","matches","htmlAssets","isModulePreload","header"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ssr-manifest.js","sources":["../../src/services/ssr-manifest.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { Socket } from 'node:net';\nimport path from 'node:path';\nimport type { AgnosticDataRouteMatch } from '@remix-run/router/dist/utils';\nimport chalk from 'chalk';\nimport type { RouteObject } from 'react-router-dom';\nimport type { Alias, ModuleNode } from 'vite';\nimport type { IAsyncRoute } from '@helpers/import-route';\nimport type { IRequestContext } from '@node/render';\nimport type { TRoutesTree } from '@services/parse-routes';\nimport ParseRoutes from '@services/parse-routes';\nimport PathNormalize from '@services/path-normalize';\nimport PrepareServer from '@services/prepare-server';\nimport ServerConfig from '@services/server-config';\n\ninterface ISsrManifestParams {\n buildDir?: string;\n viteAliases?: Alias[];\n}\n\ninterface IManifest {\n [path: string]: {\n assets: string[];\n css: string[];\n file: string;\n isEntry?: boolean;\n imports: string[];\n };\n}\n\nenum AssetType {\n style = 'style',\n script = 'script',\n image = 'image',\n font = 'font',\n}\n\ninterface IAsset {\n type: AssetType;\n url: string;\n weight: number;\n isNested: boolean;\n isPreload: boolean;\n content?: string;\n}\n\ntype TAssets = { [id: string]: IAsset };\n\nconst CRLF = '\\r\\n';\n\n/**\n * Working with SSR Manifest file\n */\nclass SsrManifest {\n /**\n * Singleton\n */\n protected static instance: SsrManifest | null = null;\n\n /**\n * Server config\n */\n protected readonly config: ServerConfig;\n\n /**\n * Path normalize service\n */\n protected readonly pathNormalize: PathNormalize;\n\n /**\n * Project root path\n */\n protected readonly root: string;\n\n /**\n * Build dir\n */\n protected readonly buildDir?: string;\n\n /**\n * Client manifest file name\n */\n protected readonly manifestName = 'manifest.json';\n\n /**\n * Assets manifest file name\n */\n protected readonly assetsManifest = 'assets-manifest.json';\n\n /**\n * Vite resolve aliases\n */\n protected readonly viteAliases?: Alias[];\n\n /**\n * Loaded assets manifest file\n */\n protected routesAssets: Record<string, IAsset[]> | null = null;\n\n /**\n * @constructor\n */\n protected constructor(config: ServerConfig, { buildDir, viteAliases }: ISsrManifestParams = {}) {\n this.config = config;\n this.root = config.getParams().root;\n this.buildDir = buildDir;\n this.viteAliases = viteAliases ?? config.getVite()?.config?.resolve.alias;\n this.pathNormalize = new PathNormalize(config, viteAliases);\n }\n\n /**\n * Get singleton instance\n */\n public static get(config: ServerConfig, params: ISsrManifestParams = {}): SsrManifest {\n if (SsrManifest.instance === null) {\n SsrManifest.instance = new SsrManifest(config, params);\n }\n\n return SsrManifest.instance;\n }\n\n /**\n * Get output dir\n */\n protected getOutDir() {\n return path.resolve(this.root, this.buildDir || '');\n }\n\n /**\n * Get assets manifest file name\n */\n protected getAssetsManifestFile(): string {\n return `${this.getOutDir()}/server/${this.assetsManifest}`;\n }\n\n /**\n * Load client ssr manifest\n */\n protected loadClientManifest(): IManifest {\n const clientManifestDir = path.resolve(this.root, `${this.buildDir || ''}/client/.vite`);\n const clientSsrManifest = `${clientManifestDir}/${this.manifestName}`;\n\n if (!fs.existsSync(clientSsrManifest)) {\n return {};\n }\n\n const result = JSON.parse(\n fs.readFileSync(clientSsrManifest, { encoding: 'utf-8' }),\n ) as IManifest;\n\n fs.rmSync(clientSsrManifest);\n\n // try to remove empty .vite dir\n if (fs.readdirSync(clientManifestDir).length === 0) {\n fs.rmSync(clientManifestDir, { recursive: true });\n }\n\n return result;\n }\n\n /**\n * Load assets manifest\n */\n protected loadAssetsManifest(): Record<string, IAsset[]> {\n if (this.routesAssets !== null) {\n return this.routesAssets;\n }\n\n const manifestFile = this.getAssetsManifestFile();\n\n if (!fs.existsSync(manifestFile)) {\n return {};\n }\n\n this.routesAssets = JSON.parse(fs.readFileSync(manifestFile, { encoding: 'utf-8' })) as Record<\n string,\n IAsset[]\n >;\n\n return this.routesAssets;\n }\n\n /**\n * Recursive walk routes and return id's with route import path\n */\n protected async getAsyncRoutesIds(\n routes: RouteObject[],\n index?: string,\n ): Promise<Record<string, string | undefined>> {\n const result: Record<string, string | undefined> = {};\n\n // reason: await + array index\n // eslint-disable-next-line @typescript-eslint/no-for-in-array\n for (const routeIndex in routes) {\n const route = routes[routeIndex];\n const routeId = [index, routeIndex].filter(Boolean).join('-');\n\n if (route.lazy) {\n try {\n const resolvedRoute: IAsyncRoute = await route.lazy();\n\n result[routeId] = this.pathNormalize.getAppPath(resolvedRoute?.pathId);\n } catch (e) {\n console.error(chalk.red('Failed to load route:'), route.path, e);\n }\n } else if (route.children) {\n Object.assign(result, await this.getAsyncRoutesIds(route.children, routeId));\n }\n }\n\n return result;\n }\n\n /**\n * Same as 'getAsyncRoutesIds' but for routes tree from 'ParseRoutes'\n */\n protected getRoutesTreeIds(\n routes: TRoutesTree[],\n index?: string,\n ): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {};\n\n routes.forEach((route, routeIndex) => {\n const routeId = [index, String(routeIndex)].filter(Boolean).join('-');\n\n if (route.import) {\n result[routeId] = this.pathNormalize.getAppPath(route.import);\n }\n\n if (route.children.length > 0) {\n Object.assign(result, this.getRoutesTreeIds(route.children, routeId));\n }\n });\n\n return result;\n }\n\n /**\n * Sort assets\n */\n protected sortAssets(assets: IAsset[]): IAsset[] {\n return assets.sort((a, b) =>\n a.weight === b.weight ? Number(a.isNested) - Number(b.isNested) : a.weight - b.weight,\n );\n }\n\n /**\n * Get recursive module assets\n */\n protected getRouteAssets(\n manifest: IManifest,\n module: IManifest[string],\n isNested = false,\n ): Record<string, IAsset> {\n const rootAssets = [...(module?.assets ?? []), ...(module?.css ?? []), module?.file];\n\n const assets = rootAssets.reduce(\n (res, asset) => {\n if (asset) {\n const type = this.getAssetType(asset);\n const isEntry = module.isEntry && module.file === asset;\n\n // keep only js,css,image,fonts files\n if (type) {\n res[asset] = {\n url: `/${asset}`,\n weight: isEntry ? 1.9 : this.getAssetWeight(asset),\n type,\n isNested,\n isPreload: !isEntry,\n };\n }\n }\n\n return res;\n },\n {} as Record<string, IAsset>,\n );\n\n // nested assets\n if (module?.imports?.length) {\n module.imports.forEach((nestedAsset) => {\n const nestedModule = manifest[nestedAsset];\n\n if (nestedModule) {\n Object.assign(assets, this.getRouteAssets(manifest, nestedModule, true));\n }\n });\n }\n\n return assets;\n }\n\n /**\n * Build routes manifest file\n */\n public async buildRoutesManifest(isNodeParsing: boolean): Promise<void> {\n const prepareServer = PrepareServer.init(\n ServerConfig.init({ isProd: true }, { root: this.getOutDir() }),\n );\n const manifest = this.loadClientManifest();\n let routesPaths: Record<string, string | undefined>;\n\n if (isNodeParsing) {\n const { routes } = await prepareServer.loadEntrypoint(false);\n\n routesPaths = await this.getAsyncRoutesIds(routes as RouteObject[]);\n } else {\n const routesService = new ParseRoutes(this.config, this.viteAliases);\n\n routesPaths = this.getRoutesTreeIds(routesService.parse());\n }\n\n const postfixes = this.pathNormalize.getImportPostfix();\n const result: Record<string, IAsset[]> = {};\n\n // find route assets\n Object.entries(routesPaths).forEach(([routeId, routePath]) => {\n const routePostfix = postfixes.find((postfix) => {\n const filePath = `${routePath}${postfix}`;\n\n return manifest[filePath] !== undefined;\n });\n const routeFile = `${routePath}${routePostfix || ''}`;\n const routeMeta = manifest[routeFile];\n\n result[routeId] = this.sortAssets(Object.values(this.getRouteAssets(manifest, routeMeta)));\n });\n\n fs.writeFileSync(this.getAssetsManifestFile(), JSON.stringify(result, null, 2), {\n encoding: 'utf-8',\n });\n }\n\n /**\n * Get route assets\n */\n protected getAssets(routes?: AgnosticDataRouteMatch[]): IAsset[] {\n if (this.config.getVite()) {\n return this.getAssetsDev(routes);\n }\n\n const routeIds = routes?.map(({ route }) => route.id).filter(Boolean) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n const routesAssets = this.loadAssetsManifest();\n\n return this.sortAssets(\n routeIds\n .map((routeId) => routesAssets[routeId])\n .flat()\n .filter(Boolean),\n );\n }\n\n /**\n * Get development route assets\n */\n protected getAssetsDev(routes?: AgnosticDataRouteMatch[]): IAsset[] {\n const routeIds =\n (routes\n ?.map(({ route }) => this.pathNormalize.getAppPath((route as IAsyncRoute)?.pathId, true))\n .filter(Boolean) as string[]) ?? [];\n\n if (!routeIds.length) {\n return [];\n }\n\n let assets: TAssets = {};\n const postfixes = this.pathNormalize.getImportPostfix();\n const rootId = `${this.root}/${this.config.getPluginConfig()?.clientFile ?? 'client.ts'}`;\n\n [rootId, ...routeIds].forEach((moduleId) => {\n for (const ext of postfixes) {\n const module = this.config.getVite()?.moduleGraph.getModuleById(`${moduleId}${ext}`);\n\n if (module) {\n assets = { ...assets, ...this.getModuleAssets(module) };\n break;\n }\n }\n });\n\n return Object.values(assets);\n }\n\n /**\n * Get module assets\n */\n protected getModuleAssets(module?: ModuleNode, skipModules: Set<string> = new Set()): TAssets {\n if (!module?.clientImportedModules.size || skipModules.has(module.file!)) {\n return {};\n }\n\n let assets: TAssets = {};\n\n skipModules.add(module.file!);\n\n module.clientImportedModules.forEach((subModule) => {\n const { file, clientImportedModules, transformResult } = subModule;\n const ext = file?.split('.').at(-1);\n\n if (file && ext && ['css', 'scss'].includes(ext)) {\n // @TODO investigate better method?\n const code = transformResult?.code.match(/__vite__css\\s+=\\s+\"(?<css>.+)\"/)?.groups?.css;\n\n if (code) {\n try {\n assets[file] = {\n type: AssetType.style,\n url: file,\n weight: this.getAssetWeight(file),\n content: (JSON.parse(`{\"style\": \"${code}\"}`) as { style: string }).style,\n isNested: Boolean(skipModules.size),\n isPreload: false,\n };\n } catch (e) {\n console.warn(chalk.yellowBright('Failed to parse style: ', file));\n }\n }\n } else if (clientImportedModules.size) {\n assets = {\n ...assets,\n ...this.getModuleAssets(subModule, skipModules),\n };\n }\n });\n\n return assets;\n }\n\n /**\n * Get asset weight\n */\n protected getAssetWeight(asset: string): number {\n const type = this.getAssetType(asset);\n\n switch (type) {\n case AssetType.style:\n return 1;\n\n case AssetType.script:\n return 2;\n\n default:\n return 3;\n }\n }\n\n /**\n * Get asset type\n */\n protected getAssetType(asset: string): AssetType | null {\n const ext = asset.split('.').at(-1)?.toLowerCase();\n\n switch (ext) {\n case 'css':\n case 'scss':\n return AssetType.style;\n\n case 'js':\n return AssetType.script;\n\n case 'svg':\n case 'jpg':\n case 'jpeg':\n case 'png':\n case 'webp':\n case 'gif':\n case 'ico':\n return AssetType.image;\n\n case 'ttf':\n case 'otf':\n case 'woff':\n case 'woff2':\n return AssetType.font;\n\n default:\n return null;\n }\n }\n\n /**\n * Write 103 Early Hits header\n */\n public writeEarlyHits(assets: IAsset[], socket: Socket): void {\n socket.write(`HTTP/1.1 103 Early Hints${CRLF}`);\n assets.forEach(({ type, url }) => {\n if (!type || !['style', 'script'].includes(type)) {\n return;\n }\n\n socket.write(`Link: <${url}>; rel=preload; as=${type}${CRLF}`);\n });\n socket.write(CRLF);\n }\n\n /**\n * Inject route assets to head html\n */\n public injectAssets({ routerContext, html, res, hasEarlyHints = false }: IRequestContext): void {\n const assets = this.getAssets(routerContext?.matches);\n const htmlAssets = assets\n .map(({ type, url, isPreload, content = '' }) => {\n switch (type) {\n case AssetType.style:\n return this.config.getVite()\n ? `<style data-vite-dev-id=\"${url}\">${content}</style>`\n : `<link rel=\"stylesheet\" href=\"${url}\">`;\n\n case AssetType.script:\n return isPreload\n ? this.config.isModulePreload\n ? // can reduce lighthouse performance\n `<link rel=\"modulepreload\" as=\"script\" crossorigin href=\"${url}\">`\n : null\n : `<script async type=\"module\" crossorigin src=\"${url}\"></script>`;\n }\n\n return null;\n })\n .filter(Boolean);\n\n html.header = html.header.replace('</head>', `${htmlAssets.join('\\n')}</head>`);\n\n if (hasEarlyHints && htmlAssets.length && res.socket) {\n this.writeEarlyHits(assets, res.socket);\n }\n }\n}\n\nexport default SsrManifest;\n"],"names":["AssetType","CRLF","SsrManifest","static","config","pathNormalize","root","buildDir","manifestName","assetsManifest","viteAliases","routesAssets","constructor","this","getParams","getVite","resolve","alias","PathNormalize","params","instance","getOutDir","path","getAssetsManifestFile","loadClientManifest","clientManifestDir","clientSsrManifest","fs","existsSync","result","JSON","parse","readFileSync","encoding","rmSync","readdirSync","length","recursive","loadAssetsManifest","manifestFile","async","routes","index","routeIndex","route","routeId","filter","Boolean","join","lazy","resolvedRoute","getAppPath","pathId","e","console","error","chalk","red","children","Object","assign","getAsyncRoutesIds","getRoutesTreeIds","forEach","String","import","sortAssets","assets","sort","a","b","weight","Number","isNested","getRouteAssets","manifest","module","css","file","reduce","res","asset","type","getAssetType","isEntry","url","getAssetWeight","isPreload","imports","nestedAsset","nestedModule","isNodeParsing","prepareServer","PrepareServer","init","ServerConfig","isProd","routesPaths","loadEntrypoint","routesService","ParseRoutes","postfixes","getImportPostfix","entries","routePath","routePostfix","find","postfix","undefined","routeMeta","values","writeFileSync","stringify","getAssets","getAssetsDev","routeIds","map","id","flat","getPluginConfig","clientFile","moduleId","ext","moduleGraph","getModuleById","getModuleAssets","skipModules","Set","clientImportedModules","size","has","add","subModule","transformResult","split","at","includes","code","match","groups","style","content","warn","yellowBright","script","toLowerCase","image","font","writeEarlyHits","socket","write","injectAssets","routerContext","html","hasEarlyHints","matches","htmlAssets","isModulePreload","header","replace"],"mappings":"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"}
|