one 1.12.1 → 1.12.3
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/dist/cjs/Root.cjs +7 -1
- package/dist/cjs/Root.js +7 -2
- package/dist/cjs/Root.js.map +1 -1
- package/dist/cjs/Root.native.js +8 -2
- package/dist/cjs/Root.native.js.map +1 -1
- package/dist/cjs/cli/build.cjs +24 -10
- package/dist/cjs/cli/build.js +24 -9
- package/dist/cjs/cli/build.js.map +1 -1
- package/dist/cjs/cli/build.native.js +29 -14
- package/dist/cjs/cli/build.native.js.map +1 -1
- package/dist/cjs/createApp.cjs +2 -2
- package/dist/cjs/createApp.js +2 -2
- package/dist/cjs/createApp.js.map +1 -1
- package/dist/cjs/createHandleRequest.cjs +5 -1
- package/dist/cjs/createHandleRequest.js +6 -2
- package/dist/cjs/createHandleRequest.js.map +1 -1
- package/dist/cjs/createHandleRequest.native.js +5 -1
- package/dist/cjs/createHandleRequest.native.js.map +1 -1
- package/dist/cjs/fork/SSRNavigationContainer.cjs +18 -9
- package/dist/cjs/fork/SSRNavigationContainer.js +17 -6
- package/dist/cjs/fork/SSRNavigationContainer.js.map +1 -1
- package/dist/cjs/fork/SSRNavigationContainer.native.js +21 -10
- package/dist/cjs/fork/SSRNavigationContainer.native.js.map +1 -1
- package/dist/cjs/router/linkingConfig.cjs +2 -1
- package/dist/cjs/router/linkingConfig.js +2 -2
- package/dist/cjs/router/linkingConfig.js.map +1 -1
- package/dist/cjs/router/linkingConfig.native.js +2 -1
- package/dist/cjs/router/linkingConfig.native.js.map +1 -1
- package/dist/cjs/serve.cjs +67 -28
- package/dist/cjs/serve.js +68 -28
- package/dist/cjs/serve.js.map +1 -1
- package/dist/cjs/serve.native.js +96 -35
- package/dist/cjs/serve.native.js.map +1 -1
- package/dist/cjs/server/oneServe.cjs +112 -39
- package/dist/cjs/server/oneServe.js +94 -41
- package/dist/cjs/server/oneServe.js.map +2 -2
- package/dist/cjs/server/oneServe.native.js +180 -80
- package/dist/cjs/server/oneServe.native.js.map +1 -1
- package/dist/cjs/utils/evictOldest.cjs +34 -0
- package/dist/cjs/utils/evictOldest.js +29 -0
- package/dist/cjs/utils/evictOldest.js.map +6 -0
- package/dist/cjs/utils/evictOldest.native.js +34 -0
- package/dist/cjs/utils/evictOldest.native.js.map +1 -0
- package/dist/cjs/utils/isResponse.cjs +1 -1
- package/dist/cjs/utils/isResponse.js +1 -1
- package/dist/cjs/utils/isResponse.js.map +1 -1
- package/dist/cjs/utils/isResponse.native.js +1 -1
- package/dist/cjs/utils/isResponse.native.js.map +1 -1
- package/dist/cjs/vite/one-server-only.cjs +9 -6
- package/dist/cjs/vite/one-server-only.js +8 -7
- package/dist/cjs/vite/one-server-only.js.map +1 -1
- package/dist/cjs/vite/resolveResponse.cjs +17 -4
- package/dist/cjs/vite/resolveResponse.js +15 -2
- package/dist/cjs/vite/resolveResponse.js.map +1 -1
- package/dist/cjs/vite/resolveResponse.native.js +17 -4
- package/dist/cjs/vite/resolveResponse.native.js.map +1 -1
- package/dist/esm/Root.js +7 -1
- package/dist/esm/Root.js.map +1 -1
- package/dist/esm/Root.mjs +7 -1
- package/dist/esm/Root.mjs.map +1 -1
- package/dist/esm/Root.native.js +7 -1
- package/dist/esm/Root.native.js.map +1 -1
- package/dist/esm/cli/build.js +24 -9
- package/dist/esm/cli/build.js.map +1 -1
- package/dist/esm/cli/build.mjs +26 -12
- package/dist/esm/cli/build.mjs.map +1 -1
- package/dist/esm/cli/build.native.js +29 -14
- package/dist/esm/cli/build.native.js.map +1 -1
- package/dist/esm/createApp.js +2 -2
- package/dist/esm/createApp.js.map +1 -1
- package/dist/esm/createApp.mjs +2 -2
- package/dist/esm/createApp.mjs.map +1 -1
- package/dist/esm/createHandleRequest.js +6 -2
- package/dist/esm/createHandleRequest.js.map +1 -1
- package/dist/esm/createHandleRequest.mjs +5 -2
- package/dist/esm/createHandleRequest.mjs.map +1 -1
- package/dist/esm/createHandleRequest.native.js +5 -2
- package/dist/esm/createHandleRequest.native.js.map +1 -1
- package/dist/esm/fork/SSRNavigationContainer.js +17 -6
- package/dist/esm/fork/SSRNavigationContainer.js.map +1 -1
- package/dist/esm/fork/SSRNavigationContainer.mjs +18 -9
- package/dist/esm/fork/SSRNavigationContainer.mjs.map +1 -1
- package/dist/esm/fork/SSRNavigationContainer.native.js +21 -10
- package/dist/esm/fork/SSRNavigationContainer.native.js.map +1 -1
- package/dist/esm/router/linkingConfig.js +2 -1
- package/dist/esm/router/linkingConfig.js.map +1 -1
- package/dist/esm/router/linkingConfig.mjs +2 -1
- package/dist/esm/router/linkingConfig.mjs.map +1 -1
- package/dist/esm/router/linkingConfig.native.js +2 -1
- package/dist/esm/router/linkingConfig.native.js.map +1 -1
- package/dist/esm/serve.js +68 -28
- package/dist/esm/serve.js.map +1 -1
- package/dist/esm/serve.mjs +67 -28
- package/dist/esm/serve.mjs.map +1 -1
- package/dist/esm/serve.native.js +96 -35
- package/dist/esm/serve.native.js.map +1 -1
- package/dist/esm/server/oneServe.js +95 -42
- package/dist/esm/server/oneServe.js.map +2 -2
- package/dist/esm/server/oneServe.mjs +113 -40
- package/dist/esm/server/oneServe.mjs.map +1 -1
- package/dist/esm/server/oneServe.native.js +181 -81
- package/dist/esm/server/oneServe.native.js.map +1 -1
- package/dist/esm/utils/evictOldest.js +13 -0
- package/dist/esm/utils/evictOldest.js.map +6 -0
- package/dist/esm/utils/evictOldest.mjs +11 -0
- package/dist/esm/utils/evictOldest.mjs.map +1 -0
- package/dist/esm/utils/evictOldest.native.js +8 -0
- package/dist/esm/utils/evictOldest.native.js.map +1 -0
- package/dist/esm/utils/isResponse.js +1 -1
- package/dist/esm/utils/isResponse.js.map +1 -1
- package/dist/esm/utils/isResponse.mjs +1 -1
- package/dist/esm/utils/isResponse.mjs.map +1 -1
- package/dist/esm/utils/isResponse.native.js +1 -1
- package/dist/esm/utils/isResponse.native.js.map +1 -1
- package/dist/esm/vite/one-server-only.js +8 -7
- package/dist/esm/vite/one-server-only.js.map +1 -1
- package/dist/esm/vite/one-server-only.mjs +9 -6
- package/dist/esm/vite/one-server-only.mjs.map +1 -1
- package/dist/esm/vite/resolveResponse.js +15 -2
- package/dist/esm/vite/resolveResponse.js.map +1 -1
- package/dist/esm/vite/resolveResponse.mjs +16 -4
- package/dist/esm/vite/resolveResponse.mjs.map +1 -1
- package/dist/esm/vite/resolveResponse.native.js +16 -4
- package/dist/esm/vite/resolveResponse.native.js.map +1 -1
- package/package.json +13 -11
- package/src/Root.tsx +14 -1
- package/src/cli/build.ts +38 -12
- package/src/createApp.tsx +8 -2
- package/src/createHandleRequest.ts +9 -2
- package/src/fork/SSRNavigationContainer.tsx +30 -7
- package/src/router/linkingConfig.ts +2 -2
- package/src/serve.ts +134 -48
- package/src/server/oneServe.ts +153 -47
- package/src/utils/evictOldest.ts +13 -0
- package/src/utils/isResponse.ts +4 -4
- package/src/vite/one-server-only.tsx +25 -11
- package/src/vite/resolveResponse.ts +20 -1
- package/src/vite/types.ts +20 -2
- package/types/Root.d.ts.map +1 -1
- package/types/cli/build.d.ts.map +1 -1
- package/types/createApp.d.ts.map +1 -1
- package/types/createHandleRequest.d.ts +4 -0
- package/types/createHandleRequest.d.ts.map +1 -1
- package/types/fork/SSRNavigationContainer.d.ts.map +1 -1
- package/types/router/linkingConfig.d.ts.map +1 -1
- package/types/serve.d.ts.map +1 -1
- package/types/server/oneServe.d.ts.map +1 -1
- package/types/utils/evictOldest.d.ts +6 -0
- package/types/utils/evictOldest.d.ts.map +1 -0
- package/types/vite/one-server-only.d.ts +9 -3
- package/types/vite/one-server-only.d.ts.map +1 -1
- package/types/vite/resolveResponse.d.ts +4 -0
- package/types/vite/resolveResponse.d.ts.map +1 -1
- package/types/vite/types.d.ts +18 -2
- package/types/vite/types.d.ts.map +1 -1
package/src/Root.tsx
CHANGED
|
@@ -27,12 +27,25 @@ import { ServerLocationContext } from './router/serverLocationContext'
|
|
|
27
27
|
import { useInitializeOneRouter } from './router/useInitializeOneRouter'
|
|
28
28
|
import { useViteRoutes } from './router/useViteRoutes'
|
|
29
29
|
import type { GlobbedRouteImports } from './types'
|
|
30
|
+
import { evictOldest } from './utils/evictOldest'
|
|
30
31
|
import { ServerRenderID } from './useServerHeadInsertion'
|
|
31
32
|
import { PreloadLinks } from './views/PreloadLinks'
|
|
32
33
|
import { RootErrorBoundary } from './views/RootErrorBoundary'
|
|
33
34
|
import { ScrollBehavior } from './views/ScrollBehavior'
|
|
34
35
|
import type { One } from './vite/types'
|
|
35
36
|
|
|
37
|
+
// cache URL objects for SSR to avoid per-render allocation
|
|
38
|
+
// keyed by path string, evicted when cache grows too large
|
|
39
|
+
const ssrLocationCache = new Map<string, URL>()
|
|
40
|
+
function getCachedSSRLocation(path: string): URL {
|
|
41
|
+
let url = ssrLocationCache.get(path)
|
|
42
|
+
if (url) return url
|
|
43
|
+
url = new URL(path, getURL())
|
|
44
|
+
evictOldest(ssrLocationCache, 5000, 1000)
|
|
45
|
+
ssrLocationCache.set(path, url)
|
|
46
|
+
return url
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
type RootProps = Omit<InnerProps, 'context'> & {
|
|
37
50
|
onRenderId?: (id: string) => void
|
|
38
51
|
path: string
|
|
@@ -87,7 +100,7 @@ export function Root(props: RootProps) {
|
|
|
87
100
|
const location =
|
|
88
101
|
typeof window !== 'undefined' && window.location
|
|
89
102
|
? new URL(path || window.location.href || '/', window.location.href)
|
|
90
|
-
:
|
|
103
|
+
: getCachedSSRLocation(path || '/')
|
|
91
104
|
|
|
92
105
|
const store = useInitializeOneRouter(context, location)
|
|
93
106
|
const userScheme = useUserScheme()
|
package/src/cli/build.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { buildVercelOutputDirectory } from '../vercel/build/buildVercelOutputDir
|
|
|
24
24
|
import { getManifest } from '../vite/getManifest'
|
|
25
25
|
import { loadUserOneOptions } from '../vite/loadConfig'
|
|
26
26
|
import { runWithAsyncLocalContext } from '../vite/one-server-only'
|
|
27
|
-
import type { One, RouteInfo } from '../vite/types'
|
|
27
|
+
import type { DeployConfig, DeployTarget, One, RouteInfo } from '../vite/types'
|
|
28
28
|
import { buildPage, printBuildTimings } from './buildPage'
|
|
29
29
|
import { checkNodeVersion } from './checkNodeVersion'
|
|
30
30
|
import { getWorkerPool, terminateWorkerPool } from './workerPool'
|
|
@@ -35,6 +35,25 @@ import { getCriticalCSSOutputPaths } from '../vite/plugins/criticalCSSPlugin'
|
|
|
35
35
|
|
|
36
36
|
const { ensureDir, writeJSON } = FSExtra
|
|
37
37
|
|
|
38
|
+
function normalizeDeploy(
|
|
39
|
+
deploy?: DeployTarget | DeployConfig
|
|
40
|
+
): { target: DeployTarget; url?: string } | undefined {
|
|
41
|
+
if (!deploy) return undefined
|
|
42
|
+
if (typeof deploy === 'string') return { target: deploy }
|
|
43
|
+
return deploy
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// reads package.json name, strips npm scope prefix for use as cloudflare worker name
|
|
47
|
+
async function getCloudflareProjectName(root: string): Promise<string> {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(await FSExtra.readFile(join(root, 'package.json'), 'utf-8'))
|
|
50
|
+
if (pkg.name) {
|
|
51
|
+
return pkg.name.replace(/^@[^/]+\//, '')
|
|
52
|
+
}
|
|
53
|
+
} catch {}
|
|
54
|
+
return 'one-app'
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
// concurrency limit for parallel page builds
|
|
39
58
|
// can be overridden with ONE_BUILD_CONCURRENCY env var
|
|
40
59
|
// default based on CPU count for I/O parallelism benefits
|
|
@@ -117,6 +136,22 @@ export async function build(args: {
|
|
|
117
136
|
process.env.ONE_DEFAULT_RENDER_MODE = oneOptions.web.defaultRenderMode
|
|
118
137
|
}
|
|
119
138
|
|
|
139
|
+
const deployConfig = normalizeDeploy(oneOptions.web?.deploy)
|
|
140
|
+
|
|
141
|
+
// auto-detect ONE_SERVER_URL from deploy config when not explicitly set
|
|
142
|
+
if (!process.env.ONE_SERVER_URL && deployConfig) {
|
|
143
|
+
const url =
|
|
144
|
+
deployConfig.url ??
|
|
145
|
+
(deployConfig.target === 'cloudflare'
|
|
146
|
+
? `https://${await getCloudflareProjectName(process.cwd())}.workers.dev`
|
|
147
|
+
: undefined)
|
|
148
|
+
|
|
149
|
+
if (url) {
|
|
150
|
+
process.env.ONE_SERVER_URL = url
|
|
151
|
+
console.info(`\n ☁️ ONE_SERVER_URL: ${url}\n`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
120
155
|
// respect vite's build.outDir config, default to 'dist'
|
|
121
156
|
const outDir = viteLoadedConfig?.config?.build?.outDir ?? 'dist'
|
|
122
157
|
|
|
@@ -981,7 +1016,7 @@ export async function build(args: {
|
|
|
981
1016
|
|
|
982
1017
|
const postBuildLogs: string[] = []
|
|
983
1018
|
|
|
984
|
-
const platform =
|
|
1019
|
+
const platform = deployConfig?.target
|
|
985
1020
|
|
|
986
1021
|
if (platform) {
|
|
987
1022
|
postBuildLogs.push(`[one.build] platform ${platform}`)
|
|
@@ -1205,16 +1240,7 @@ export default {
|
|
|
1205
1240
|
|
|
1206
1241
|
// Use jsonc for wrangler config (recommended for new projects)
|
|
1207
1242
|
// Use assets with run_worker_first so all requests go through worker (enables middleware on SSG pages)
|
|
1208
|
-
|
|
1209
|
-
let projectName = 'one-app'
|
|
1210
|
-
try {
|
|
1211
|
-
const pkgPath = join(options.root, 'package.json')
|
|
1212
|
-
const pkg = JSON.parse(await FSExtra.readFile(pkgPath, 'utf-8'))
|
|
1213
|
-
if (pkg.name) {
|
|
1214
|
-
// strip scope prefix for CF worker name
|
|
1215
|
-
projectName = pkg.name.replace(/^@[^/]+\//, '')
|
|
1216
|
-
}
|
|
1217
|
-
} catch {}
|
|
1243
|
+
const projectName = await getCloudflareProjectName(options.root)
|
|
1218
1244
|
|
|
1219
1245
|
const wranglerConfig = `{
|
|
1220
1246
|
"name": ${JSON.stringify(projectName)},
|
package/src/createApp.tsx
CHANGED
|
@@ -41,7 +41,10 @@ export function createApp(options: CreateAppProps) {
|
|
|
41
41
|
render: async (props: RenderAppProps) => {
|
|
42
42
|
// set render mode env before setup so users can conditionally skip setup in ssg/spa
|
|
43
43
|
const renderMode = props.mode === 'spa-shell' ? 'spa' : props.mode
|
|
44
|
-
process.env
|
|
44
|
+
// only set if changed to avoid process.env setter overhead
|
|
45
|
+
if (process.env.ONE_RENDER_MODE !== renderMode) {
|
|
46
|
+
process.env.ONE_RENDER_MODE = renderMode
|
|
47
|
+
}
|
|
45
48
|
|
|
46
49
|
if (!setupDone && options.getSetupPromise) {
|
|
47
50
|
await options.getSetupPromise()
|
|
@@ -165,7 +168,10 @@ export function createApp(options: CreateAppProps) {
|
|
|
165
168
|
// streaming SSR - returns ReadableStream, no post-processing
|
|
166
169
|
renderStream: async (props: RenderAppProps): Promise<ReadableStream> => {
|
|
167
170
|
const renderMode = props.mode === 'spa-shell' ? 'spa' : props.mode
|
|
168
|
-
process.env
|
|
171
|
+
// only set if changed to avoid process.env setter overhead
|
|
172
|
+
if (process.env.ONE_RENDER_MODE !== renderMode) {
|
|
173
|
+
process.env.ONE_RENDER_MODE = renderMode
|
|
174
|
+
}
|
|
169
175
|
|
|
170
176
|
if (!setupDone && options.getSetupPromise) {
|
|
171
177
|
await options.getSetupPromise()
|
|
@@ -290,12 +290,19 @@ export async function resolvePageRoute(
|
|
|
290
290
|
})
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
// weakmap cache to avoid re-parsing the same request URL
|
|
294
|
+
const _urlCache = new WeakMap<Request, URL>()
|
|
295
|
+
|
|
293
296
|
export function getURLfromRequestURL(request: Request) {
|
|
297
|
+
let url = _urlCache.get(request)
|
|
298
|
+
if (url) return url
|
|
294
299
|
const urlString = request.url || ''
|
|
295
|
-
|
|
300
|
+
url = new URL(
|
|
296
301
|
urlString || '',
|
|
297
302
|
request.headers.get('host') ? `http://${request.headers.get('host')}` : ''
|
|
298
303
|
)
|
|
304
|
+
_urlCache.set(request, url)
|
|
305
|
+
return url
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
function compileRouteRegex(route: RouteInfo): RouteInfoCompiled {
|
|
@@ -456,7 +463,7 @@ export function createHandleRequest(
|
|
|
456
463
|
}
|
|
457
464
|
}
|
|
458
465
|
|
|
459
|
-
function getLoaderParams(
|
|
466
|
+
export function getLoaderParams(
|
|
460
467
|
url: URL,
|
|
461
468
|
config: { compiledRegex: RegExp; routeKeys: Record<string, string> }
|
|
462
469
|
) {
|
|
@@ -49,14 +49,37 @@ const getPartialState = (state: any): any => {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// cache by
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
// cache state contexts by navigation state identity to avoid deep clone thrashing
|
|
53
|
+
// under concurrent SSR requests with different URLs
|
|
54
|
+
const stateCtxCache = new WeakMap<
|
|
55
|
+
object,
|
|
56
|
+
{
|
|
57
|
+
state: any
|
|
58
|
+
getKey: () => string | undefined
|
|
59
|
+
setKey: () => void
|
|
60
|
+
getState: () => any
|
|
61
|
+
setState: () => void
|
|
62
|
+
getIsInitial: () => boolean
|
|
63
|
+
}
|
|
64
|
+
>()
|
|
55
65
|
|
|
56
66
|
function getStateContext(initialState: any) {
|
|
57
|
-
if (
|
|
67
|
+
if (!initialState) {
|
|
68
|
+
return {
|
|
69
|
+
state: undefined,
|
|
70
|
+
getKey: () => undefined as string | undefined,
|
|
71
|
+
setKey: noop,
|
|
72
|
+
getState: () => undefined,
|
|
73
|
+
setState: noop,
|
|
74
|
+
getIsInitial: () => true,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const cached = stateCtxCache.get(initialState)
|
|
79
|
+
if (cached) return cached
|
|
80
|
+
|
|
58
81
|
const partial = getPartialState(initialState)
|
|
59
|
-
|
|
82
|
+
const ctx = {
|
|
60
83
|
state: partial,
|
|
61
84
|
getKey: () => undefined as string | undefined,
|
|
62
85
|
setKey: noop,
|
|
@@ -64,8 +87,8 @@ function getStateContext(initialState: any) {
|
|
|
64
87
|
setState: noop,
|
|
65
88
|
getIsInitial: () => true,
|
|
66
89
|
}
|
|
67
|
-
|
|
68
|
-
return
|
|
90
|
+
stateCtxCache.set(initialState, ctx)
|
|
91
|
+
return ctx
|
|
69
92
|
}
|
|
70
93
|
|
|
71
94
|
export function SSRNavigationContainer({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isWebClient } from '../constants'
|
|
2
2
|
import type { OneRouter } from '../interfaces/router'
|
|
3
|
+
import { evictOldest } from '../utils/evictOldest'
|
|
3
4
|
import {
|
|
4
5
|
getLinkingConfig as createLinkingConfig,
|
|
5
6
|
type OneLinkingOptions,
|
|
@@ -62,8 +63,7 @@ export function getSSRInitialState(
|
|
|
62
63
|
path,
|
|
63
64
|
cachedBaseLinkingConfig.config
|
|
64
65
|
)
|
|
65
|
-
|
|
66
|
-
if (ssrStateCache.size > 10000) ssrStateCache.clear()
|
|
66
|
+
evictOldest(ssrStateCache, 5000, 1000)
|
|
67
67
|
ssrStateCache.set(path, state)
|
|
68
68
|
return state
|
|
69
69
|
}
|
package/src/serve.ts
CHANGED
|
@@ -21,60 +21,146 @@ export async function serve(
|
|
|
21
21
|
) {
|
|
22
22
|
// cluster mode: --cluster or --cluster=N
|
|
23
23
|
if (args.cluster) {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
24
|
+
const { cpus, platform } = await import('node:os')
|
|
25
|
+
const numWorkers = typeof args.cluster === 'number' ? args.cluster : cpus().length
|
|
26
|
+
|
|
27
|
+
// check if we can use SO_REUSEPORT (linux with node 22.12+)
|
|
28
|
+
const [major, minor] = process.versions.node.split('.').map(Number)
|
|
29
|
+
const canReusePort =
|
|
30
|
+
!['win32', 'darwin'].includes(platform()) &&
|
|
31
|
+
(major > 22 || (major === 22 && minor >= 12) || major >= 23)
|
|
32
|
+
|
|
33
|
+
if (canReusePort) {
|
|
34
|
+
// SO_REUSEPORT: spawn independent child processes, each binds to port directly
|
|
35
|
+
// kernel distributes connections - no IPC bottleneck
|
|
36
|
+
return await serveWithReusePort(args, numWorkers)
|
|
37
|
+
} else {
|
|
38
|
+
// fallback: node cluster module (IPC-based, works on macOS)
|
|
39
|
+
return await serveWithCluster(args, numWorkers)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// single-process mode
|
|
44
|
+
return await startWorker(args)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function serveWithReusePort(args: Parameters<typeof serve>[0], numWorkers: number) {
|
|
48
|
+
const { fork } = await import('node:child_process')
|
|
49
|
+
|
|
50
|
+
console.info(`[one] cluster: starting ${numWorkers} workers (SO_REUSEPORT)`)
|
|
51
|
+
|
|
52
|
+
const workers: ReturnType<typeof fork>[] = []
|
|
53
|
+
let recentCrashes = 0
|
|
54
|
+
let lastCrashTime = 0
|
|
55
|
+
|
|
56
|
+
function spawnWorker() {
|
|
57
|
+
const child = fork(
|
|
58
|
+
process.argv[1]!,
|
|
59
|
+
process.argv.slice(2).filter((a) => !a.startsWith('--cluster')),
|
|
60
|
+
{
|
|
61
|
+
env: { ...process.env, ONE_CLUSTER_WORKER: '1' },
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
workers.push(child)
|
|
26
66
|
|
|
27
|
-
|
|
28
|
-
const
|
|
67
|
+
child.on('exit', (code, signal) => {
|
|
68
|
+
const idx = workers.indexOf(child)
|
|
69
|
+
if (idx >= 0) workers.splice(idx, 1)
|
|
29
70
|
|
|
30
|
-
|
|
71
|
+
if (code === 0 || signal === 'SIGTERM' || signal === 'SIGINT') return
|
|
31
72
|
|
|
32
|
-
|
|
33
|
-
|
|
73
|
+
const now = Date.now()
|
|
74
|
+
if (now - lastCrashTime < 5000) {
|
|
75
|
+
recentCrashes++
|
|
76
|
+
} else {
|
|
77
|
+
recentCrashes = 1
|
|
34
78
|
}
|
|
79
|
+
lastCrashTime = now
|
|
35
80
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
cluster.default.on('exit', (worker, code, signal) => {
|
|
41
|
-
if (code === 0 || signal === 'SIGTERM' || signal === 'SIGINT') return
|
|
42
|
-
|
|
43
|
-
const now = Date.now()
|
|
44
|
-
if (now - lastCrashTime < 5000) {
|
|
45
|
-
recentCrashes++
|
|
46
|
-
} else {
|
|
47
|
-
recentCrashes = 1
|
|
48
|
-
}
|
|
49
|
-
lastCrashTime = now
|
|
50
|
-
|
|
51
|
-
if (recentCrashes > numWorkers * 2) {
|
|
52
|
-
console.error(`[one] too many worker crashes, stopping`)
|
|
53
|
-
process.exit(1)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
console.error(
|
|
57
|
-
`[one] worker ${worker.process.pid} died (code ${code}, signal ${signal}), restarting`
|
|
58
|
-
)
|
|
59
|
-
setTimeout(() => cluster.default.fork(), Math.min(recentCrashes * 500, 5000))
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const shutdown = () => {
|
|
63
|
-
for (const id in cluster.default.workers) {
|
|
64
|
-
cluster.default.workers[id]?.process.kill('SIGTERM')
|
|
65
|
-
}
|
|
66
|
-
setTimeout(() => process.exit(0), 5000)
|
|
81
|
+
if (recentCrashes > numWorkers * 2) {
|
|
82
|
+
console.error(`[one] too many worker crashes, stopping`)
|
|
83
|
+
process.exit(1)
|
|
67
84
|
}
|
|
68
|
-
process.on('SIGINT', shutdown)
|
|
69
|
-
process.on('SIGTERM', shutdown)
|
|
70
85
|
|
|
71
|
-
|
|
86
|
+
console.error(
|
|
87
|
+
`[one] worker ${child.pid} died (code ${code}, signal ${signal}), restarting`
|
|
88
|
+
)
|
|
89
|
+
setTimeout(spawnWorker, Math.min(recentCrashes * 500, 5000))
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < numWorkers; i++) {
|
|
94
|
+
spawnWorker()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const shutdown = () => {
|
|
98
|
+
for (const w of workers) {
|
|
99
|
+
w.kill('SIGTERM')
|
|
72
100
|
}
|
|
101
|
+
setTimeout(() => process.exit(0), 5000)
|
|
73
102
|
}
|
|
103
|
+
process.on('SIGINT', shutdown)
|
|
104
|
+
process.on('SIGTERM', shutdown)
|
|
105
|
+
|
|
106
|
+
// keep primary alive
|
|
107
|
+
await new Promise(() => {})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function serveWithCluster(args: Parameters<typeof serve>[0], numWorkers: number) {
|
|
111
|
+
const cluster = await import('node:cluster')
|
|
112
|
+
|
|
113
|
+
if (cluster.default.isPrimary) {
|
|
114
|
+
console.info(`[one] cluster: starting ${numWorkers} workers (IPC)`)
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < numWorkers; i++) {
|
|
117
|
+
cluster.default.fork()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let recentCrashes = 0
|
|
121
|
+
let lastCrashTime = 0
|
|
122
|
+
|
|
123
|
+
cluster.default.on('exit', (worker, code, signal) => {
|
|
124
|
+
if (code === 0 || signal === 'SIGTERM' || signal === 'SIGINT') return
|
|
125
|
+
|
|
126
|
+
const now = Date.now()
|
|
127
|
+
if (now - lastCrashTime < 5000) {
|
|
128
|
+
recentCrashes++
|
|
129
|
+
} else {
|
|
130
|
+
recentCrashes = 1
|
|
131
|
+
}
|
|
132
|
+
lastCrashTime = now
|
|
133
|
+
|
|
134
|
+
if (recentCrashes > numWorkers * 2) {
|
|
135
|
+
console.error(`[one] too many worker crashes, stopping`)
|
|
136
|
+
process.exit(1)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.error(
|
|
140
|
+
`[one] worker ${worker.process.pid} died (code ${code}, signal ${signal}), restarting`
|
|
141
|
+
)
|
|
142
|
+
setTimeout(() => cluster.default.fork(), Math.min(recentCrashes * 500, 5000))
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const shutdown = () => {
|
|
146
|
+
for (const id in cluster.default.workers) {
|
|
147
|
+
cluster.default.workers[id]?.process.kill('SIGTERM')
|
|
148
|
+
}
|
|
149
|
+
setTimeout(() => process.exit(0), 5000)
|
|
150
|
+
}
|
|
151
|
+
process.on('SIGINT', shutdown)
|
|
152
|
+
process.on('SIGTERM', shutdown)
|
|
153
|
+
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// cluster worker
|
|
158
|
+
return await startWorker(args)
|
|
159
|
+
}
|
|
74
160
|
|
|
75
|
-
|
|
161
|
+
async function startWorker(args: Parameters<typeof serve>[0]) {
|
|
76
162
|
const outDir =
|
|
77
|
-
args
|
|
163
|
+
args?.outDir || (FSExtra.existsSync('buildInfo.json') ? '.' : null) || 'dist'
|
|
78
164
|
const buildInfo = (await FSExtra.readJSON(`${outDir}/buildInfo.json`)) as One.BuildInfo
|
|
79
165
|
const { oneOptions } = buildInfo
|
|
80
166
|
|
|
@@ -89,18 +175,18 @@ export async function serve(
|
|
|
89
175
|
|
|
90
176
|
labelProcess('serve')
|
|
91
177
|
|
|
92
|
-
if (args
|
|
178
|
+
if (args?.loadEnv) {
|
|
93
179
|
await loadEnv('production')
|
|
94
180
|
}
|
|
95
181
|
|
|
96
182
|
return await vxrnServe({
|
|
97
183
|
outDir: buildInfo.outDir || outDir,
|
|
98
|
-
app: args
|
|
184
|
+
app: args?.app,
|
|
99
185
|
...oneOptions.server,
|
|
100
186
|
...removeUndefined({
|
|
101
|
-
port: args
|
|
102
|
-
host: args
|
|
103
|
-
compress: args
|
|
187
|
+
port: args?.port ? +args.port : undefined,
|
|
188
|
+
host: args?.host,
|
|
189
|
+
compress: args?.compress,
|
|
104
190
|
}),
|
|
105
191
|
|
|
106
192
|
async beforeRegisterRoutes(options, app) {
|