118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
import type { InitOptions, Payload, SanitizedConfig } from 'payload'
|
|
|
|
import { BasePayload } from 'payload'
|
|
import WebSocket from 'ws'
|
|
|
|
let cached: {
|
|
payload: Payload | null
|
|
promise: Promise<Payload> | null
|
|
reload: Promise<void> | boolean
|
|
} = global._payload
|
|
|
|
if (!cached) {
|
|
cached = global._payload = { payload: null, promise: null, reload: false }
|
|
}
|
|
|
|
export const reload = async (config: SanitizedConfig, payload: Payload): Promise<void> => {
|
|
if (typeof payload.db.destroy === 'function') {
|
|
await payload.db.destroy()
|
|
}
|
|
|
|
payload.config = config
|
|
|
|
payload.collections = config.collections.reduce((collections, collection) => {
|
|
collections[collection.slug] = {
|
|
config: collection,
|
|
customIDType: payload.collections[collection.slug]?.customIDType,
|
|
}
|
|
return collections
|
|
}, {})
|
|
|
|
payload.globals = {
|
|
config: config.globals,
|
|
}
|
|
|
|
// TODO: support HMR for other props in the future (see payload/src/index init()) hat may change on Payload singleton
|
|
|
|
// Generate types
|
|
if (config.typescript.autoGenerate !== false) {
|
|
// We cannot run it directly here, as generate-types imports json-schema-to-typescript, which breaks on turbopack.
|
|
// see: https://github.com/vercel/next.js/issues/66723
|
|
void payload.bin({
|
|
args: ['generate:types'],
|
|
log: false,
|
|
})
|
|
}
|
|
|
|
await payload.db.init()
|
|
if (payload.db.connect) {
|
|
await payload.db.connect({ hotReload: true })
|
|
}
|
|
}
|
|
|
|
export const getPayloadHMR = async (options: InitOptions): Promise<Payload> => {
|
|
if (!options?.config) {
|
|
throw new Error('Error: the payload config is required for getPayloadHMR to work.')
|
|
}
|
|
|
|
if (cached.payload) {
|
|
if (cached.reload === true) {
|
|
let resolve: () => void
|
|
|
|
// getPayloadHMR is called multiple times, in parallel. However, we only want to run `await reload` once. By immediately setting cached.reload to a promise,
|
|
// we can ensure that all subsequent calls will wait for the first reload to finish. So if we set it here, the 2nd call of getPayloadHMR
|
|
// will reach `if (cached.reload instanceof Promise) {` which then waits for the first reload to finish.
|
|
cached.reload = new Promise((res) => (resolve = res))
|
|
const config = await options.config
|
|
await reload(config, cached.payload)
|
|
|
|
resolve()
|
|
}
|
|
|
|
if (cached.reload instanceof Promise) {
|
|
await cached.reload
|
|
}
|
|
|
|
return cached.payload
|
|
}
|
|
|
|
if (!cached.promise) {
|
|
// no need to await options.config here, as it's already awaited in the BasePayload.init
|
|
cached.promise = new BasePayload().init(options)
|
|
}
|
|
|
|
try {
|
|
cached.payload = await cached.promise
|
|
|
|
if (
|
|
process.env.NODE_ENV !== 'production' &&
|
|
process.env.NODE_ENV !== 'test' &&
|
|
process.env.DISABLE_PAYLOAD_HMR !== 'true'
|
|
) {
|
|
try {
|
|
const port = process.env.PORT || '3000'
|
|
const ws = new WebSocket(
|
|
`ws://localhost:${port}${process.env.NEXT_BASE_PATH ?? ''}/_next/webpack-hmr`,
|
|
)
|
|
|
|
ws.onmessage = (event) => {
|
|
if (typeof event.data === 'string') {
|
|
const data = JSON.parse(event.data)
|
|
|
|
if ('action' in data && data.action === 'serverComponentChanges') {
|
|
cached.reload = true
|
|
}
|
|
}
|
|
}
|
|
} catch (_) {
|
|
// swallow e
|
|
}
|
|
}
|
|
} catch (e) {
|
|
cached.promise = null
|
|
throw e
|
|
}
|
|
|
|
return cached.payload
|
|
}
|