File size: 6,247 Bytes
b91e262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import type { NextServer, RequestHandler, UpgradeHandler } from '../next'
import type { DevBundlerService } from './dev-bundler-service'
import type { PropagateToWorkersField } from './router-utils/types'

import next from '../next'
import type { Span } from '../../trace'
import type { ServerResponse } from 'http'
import type { OnCacheEntryHandler } from '../request-meta'
import { interopDefault } from '../../lib/interop-default'
import { formatDynamicImportPath } from '../../lib/format-dynamic-import-path'
import type { ConfiguredExperimentalFeature } from '../config'

export type ServerInitResult = {
  requestHandler: RequestHandler
  upgradeHandler: UpgradeHandler
  server: NextServer
  // Make an effort to close upgraded HTTP requests (e.g. Turbopack HMR websockets)
  closeUpgraded: () => void
  // The distDir from config, used by the parent process for telemetry/trace
  distDir: string
  // Experimental features from config, used for logging after server is ready
  experimentalFeatures: ConfiguredExperimentalFeature[]
  // Whether cache components is enabled
  cacheComponents: boolean
}

let initializations: Record<string, Promise<ServerInitResult> | undefined> = {}

let sandboxContext: undefined | typeof import('../web/sandbox/context')

if (process.env.NODE_ENV !== 'production') {
  sandboxContext =
    require('../web/sandbox/context') as typeof import('../web/sandbox/context')
}

export function clearAllModuleContexts() {
  return sandboxContext?.clearAllModuleContexts()
}

export function clearModuleContext(target: string) {
  return sandboxContext?.clearModuleContext(target)
}

export async function getServerField(
  dir: string,
  field: PropagateToWorkersField
) {
  const initialization = await initializations[dir]
  if (!initialization) {
    throw new Error('Invariant cant propagate server field, no app initialized')
  }
  const { server } = initialization
  let wrappedServer = server['server']! // NextServer.server is private
  return wrappedServer[field as keyof typeof wrappedServer]
}

export async function propagateServerField(
  dir: string,
  field: PropagateToWorkersField,
  value: any
) {
  const initialization = await initializations[dir]
  if (!initialization) {
    throw new Error('Invariant cant propagate server field, no app initialized')
  }
  const { server } = initialization
  let wrappedServer = server['server']
  const _field = field as keyof NonNullable<typeof wrappedServer>

  if (wrappedServer) {
    if (typeof wrappedServer[_field] === 'function') {
      // @ts-expect-error
      await wrappedServer[_field].apply(
        wrappedServer,
        Array.isArray(value) ? value : []
      )
    } else {
      // @ts-expect-error
      wrappedServer[_field] = value
    }
  }
}

async function initializeImpl(opts: {
  dir: string
  port: number
  dev: boolean
  minimalMode?: boolean
  hostname?: string
  keepAliveTimeout?: number
  serverFields?: any
  server?: any
  experimentalTestProxy: boolean
  experimentalHttpsServer: boolean
  _ipcPort?: string
  _ipcKey?: string
  bundlerService: DevBundlerService | undefined
  startServerSpan: Span | undefined
  quiet?: boolean
  onDevServerCleanup: ((listener: () => Promise<void>) => void) | undefined
  distDir: string
  experimentalFeatures: ConfiguredExperimentalFeature[]
  cacheComponents: boolean
}): Promise<ServerInitResult> {
  const type = process.env.__NEXT_PRIVATE_RENDER_WORKER
  if (type) {
    process.title = 'next-render-worker-' + type
  }

  let requestHandler: RequestHandler
  let upgradeHandler: UpgradeHandler

  const server = next({
    ...opts,
    hostname: opts.hostname || 'localhost',
    customServer: false,
    httpServer: opts.server,
    port: opts.port,
  }) as NextServer // should return a NextServer when `customServer: false`

  // If we're in test mode and there's a debug cache entry handler available,
  // then use it to wrap the request handler instead of using the default one.
  if (
    process.env.__NEXT_TEST_MODE &&
    process.env.NEXT_PRIVATE_DEBUG_CACHE_ENTRY_HANDLERS
  ) {
    // This mirrors the sole implementation of this over in:
    // test/production/standalone-mode/required-server-files/cache-entry-handler.js
    const createOnCacheEntryHandlers = interopDefault(
      await import(
        formatDynamicImportPath(
          opts.dir,
          process.env.NEXT_PRIVATE_DEBUG_CACHE_ENTRY_HANDLERS
        )
      )
    ) as (res: ServerResponse) => {
      // TODO: remove onCacheEntry once onCacheEntryV2 is the default.
      onCacheEntry: OnCacheEntryHandler
      onCacheEntryV2: OnCacheEntryHandler
    }

    // This is not to be used in any environment other than testing, as it is
    // not memoized and is subject to constant change.
    requestHandler = async (req, res, parsedUrl) => {
      // Re re-create the entry handler for each request. This is not
      // performant, and is only used in testing environments.
      const {
        // TODO: remove onCacheEntry once onCacheEntryV2 is the default.
        onCacheEntry,
        onCacheEntryV2,
      } = createOnCacheEntryHandlers(res)

      // Get the request handler, using the entry handler as the metadata each
      // request.
      const handler = server.getRequestHandlerWithMetadata({
        // TODO: remove onCacheEntry once onCacheEntryV2 is the default.
        onCacheEntry,
        onCacheEntryV2,
      })

      return handler(req, res, parsedUrl)
    }

    upgradeHandler = server.getUpgradeHandler()
  } else {
    requestHandler = server.getRequestHandler()
    upgradeHandler = server.getUpgradeHandler()
  }

  await server.prepare(opts.serverFields)

  return {
    requestHandler,
    upgradeHandler,
    server,
    closeUpgraded() {
      opts.bundlerService?.close()
    },
    distDir: opts.distDir,
    experimentalFeatures: opts.experimentalFeatures,
    cacheComponents: opts.cacheComponents,
  }
}

export async function initialize(
  opts: Parameters<typeof initializeImpl>[0]
): Promise<ServerInitResult> {
  // if we already setup the server return as we only need to do
  // this on first worker boot
  if (initializations[opts.dir]) {
    return initializations[opts.dir]!
  }
  return (initializations[opts.dir] = initializeImpl(opts))
}