chore: server hmr

This commit is contained in:
James
2024-03-05 14:24:05 -05:00
parent 2785eaab21
commit d2aab87faa
12 changed files with 141 additions and 33 deletions

View File

@@ -6,7 +6,14 @@ import mongoose from 'mongoose'
import type { MongooseAdapter } from '.'
export const connect: Connect = async function connect(this: MongooseAdapter) {
export const connect: Connect = async function connect(
this: MongooseAdapter,
options = {
hotReload: false,
},
) {
const { hotReload } = options
if (this.url === false) {
return
}
@@ -24,6 +31,8 @@ export const connect: Connect = async function connect(this: MongooseAdapter) {
useFacet: undefined,
}
if (hotReload) connectionOptions.autoIndex = false
try {
this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
@@ -34,12 +43,15 @@ export const connect: Connect = async function connect(this: MongooseAdapter) {
this.beginTransaction = undefined
}
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING DATABASE ----')
await mongoose.connection.dropDatabase()
this.payload.logger.info('---- DROPPED DATABASE ----')
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING DATABASE ----')
await mongoose.connection.dropDatabase()
this.payload.logger.info('---- DROPPED DATABASE ----')
}
this.payload.logger.info(successfulConnectionMessage)
}
this.payload.logger.info(successfulConnectionMessage)
} catch (err) {
this.payload.logger.error(`Error: cannot connect to MongoDB. Details: ${err.message}`, err)
process.exit(1)

View File

@@ -10,6 +10,7 @@ export const destroy: Destroy = async function destroy(this: MongooseAdapter) {
await mongoose.connection.close()
await this.mongoMemoryServer.stop()
} else {
await mongoose.connection.close()
await mongoose.disconnect()
}
Object.keys(mongoose.models).map((model) => mongoose.deleteModel(model))
}

View File

@@ -19,7 +19,7 @@ import { buildGlobalModel } from './models/buildGlobalModel'
import buildSchema from './models/buildSchema'
import getBuildQueryPlugin from './queries/buildQuery'
export const init: Init = async function init(this: MongooseAdapter) {
export const init: Init = function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schema = buildCollectionSchema(collection, this.payload.config)

View File

@@ -49,6 +49,7 @@
"@payloadcms/eslint-config": "workspace:*",
"@types/react": "18.2.15",
"@types/react-dom": "18.2.7",
"@types/ws": "^8.5.10",
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"file-loader": "6.2.0",
@@ -78,7 +79,8 @@
"path-to-regexp": "^6.2.1",
"react-diff-viewer-continued": "3.2.6",
"react-toastify": "8.2.0",
"sass": "^1.71.1"
"sass": "^1.71.1",
"ws": "^8.16.0"
},
"peerDependencies": {
"http-status": "1.6.2",

View File

@@ -7,13 +7,13 @@ import type {
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
import { getPayload } from 'payload'
import { getAuthenticatedUser } from 'payload/auth'
import { parseCookies } from 'payload/auth'
import { getDataLoader } from 'payload/utilities'
import { URL } from 'url'
import { getDataAndFile } from './getDataAndFile'
import { getPayload } from './getPayload'
import { getRequestLanguage } from './getRequestLanguage'
import { getRequestLocales } from './getRequestLocales'
@@ -32,6 +32,7 @@ export const createPayloadRequest = async ({
}: Args): Promise<PayloadRequest> => {
const cookies = parseCookies(request.headers)
const payload = await getPayload({ config: configPromise })
const { collections, config } = payload
let collection: Collection = undefined

View File

@@ -0,0 +1,72 @@
import type { GeneratedTypes, Payload } from 'payload'
import type { InitOptions } from 'payload/config'
import { BasePayload } from 'payload'
import WebSocket from 'ws'
let cached = global._payload
if (!cached) {
// eslint-disable-next-line no-multi-assign
cached = global._payload = { payload: null, promise: null }
}
export const getPayload = async (options: InitOptions): Promise<Payload> => {
if (cached.payload) {
const config = await options.config
if (cached.reload) {
if (typeof cached.payload.db.destroy === 'function') {
await cached.payload.db.destroy()
}
cached.payload.config = config
cached.payload.collections = config.collections.reduce((collections, collection) => {
collections[collection.slug] = { config: collection }
return collections
}, {})
// TODO: re-build payload.globals as well as any other properties
// that may change on Payload singleton
await cached.payload.db.init()
await cached.payload.db.connect({ hotReload: true })
cached.reload = false
}
return cached.payload
}
if (!cached.promise) {
cached.promise = new BasePayload<GeneratedTypes>().init(options)
}
try {
cached.payload = await cached.promise
if (process.env.NODE_ENV !== 'production') {
try {
const ws = new WebSocket('ws://localhost:3000/_next/webpack-hmr')
ws.onmessage = async (event) => {
if (typeof event.data === 'string') {
const data = JSON.parse(event.data)
if ('action' in data && data.action === 'serverComponentChanges') {
console.log(data)
cached.reload = true
}
}
}
} catch (_) {
// swallow e
}
}
} catch (e) {
cached.promise = null
throw e
}
return cached.payload
}

View File

@@ -12,6 +12,7 @@ import { findLocaleFromCode } from '@payloadcms/ui'
import { headers as getHeaders } from 'next/headers'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import { parseCookies } from 'payload/auth'
import { createLocalReq } from 'payload/utilities'
import qs from 'qs'
@@ -29,25 +30,27 @@ type Args = {
}
export const initPage = async ({
config: configPromise,
config,
localeParam,
redirectUnauthenticatedUser = false,
route,
searchParams,
}: Args): Promise<InitPageResult> => {
const headers = getHeaders()
const cookies = parseCookies(headers)
const { cookies, permissions, user } = await auth({
config: configPromise,
const payload = await getPayload({ config })
const { permissions, user } = await auth({
headers,
payload,
})
const config = await configPromise
const routeSegments = route.replace(config.routes.admin, '').split('/').filter(Boolean)
const routeSegments = route.replace(payload.config.routes.admin, '').split('/').filter(Boolean)
const collectionSlug = routeSegments[0] === 'collections' ? routeSegments[1] : undefined
const globalSlug = routeSegments[0] === 'globals' ? routeSegments[1] : undefined
const { collections, globals, localization, routes } = config
const { collections, globals, localization, routes } = payload.config
if (redirectUnauthenticatedUser && !user && route !== '/login') {
const stringifiedSearchParams = Object.keys(searchParams ?? {}).length
@@ -57,15 +60,14 @@ export const initPage = async ({
redirect(`${routes.admin}/login?redirect=${route + stringifiedSearchParams}`)
}
const payload = await getPayload({ config })
const defaultLocale =
localization && localization.defaultLocale ? localization.defaultLocale : 'en'
const localeCode = localeParam || defaultLocale
const locale = localization && findLocaleFromCode(localization, localeCode)
const language = getRequestLanguage({ cookies, headers })
const i18n = initI18n({
config: config.i18n,
config: payload.config.i18n,
context: 'client',
language,
translations,
@@ -96,9 +98,11 @@ export const initPage = async ({
return {
collectionConfig,
cookies,
globalConfig,
locale,
permissions,
req,
translations: i18n.translations,
}
}

View File

@@ -0,0 +1,5 @@
export const timestamp = (label: string) => {
if (!process.env.PAYLOAD_TIME) process.env.PAYLOAD_TIME = String(new Date().getTime())
const now = new Date()
console.log(`[${now.getTime() - Number(process.env.PAYLOAD_TIME)}ms] ${label}`)
}

View File

@@ -1,3 +1,5 @@
import type { Translations } from '@payloadcms/translations'
import type { DocumentPermissions, Permissions, User } from '../../auth'
import type { SanitizedCollectionConfig } from '../../collections/config/types'
import type { SanitizedGlobalConfig } from '../../globals/config/types'
@@ -30,10 +32,12 @@ export type EditViewProps = {
export type InitPageResult = {
collectionConfig?: SanitizedCollectionConfig
cookies: Map<string, string>
globalConfig?: SanitizedGlobalConfig
locale: Locale
permissions: Permissions
req: PayloadRequest
translations: Translations
}
export type ServerSideEditViewProps = EditViewProps & {

View File

@@ -130,9 +130,13 @@ export interface BaseDatabaseAdapter {
updateVersion: UpdateVersion
}
export type Init = (payload: Payload) => Promise<void>
export type Init = (payload: Payload) => Promise<void> | void
export type Connect = (payload: Payload) => Promise<void>
type ConnectArgs = {
hotReload: boolean
}
export type Connect = (args?: ConnectArgs) => Promise<void>
export type Destroy = (payload: Payload) => Promise<void>

View File

@@ -316,6 +316,7 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
this.globals = {
config: this.config.globals,
}
this.config.collections.forEach((collection) => {
this.collections[collection.slug] = {
config: collection,
@@ -330,7 +331,7 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
}
if (!options.disableDBConnect && this.db.connect) {
await this.db.connect(this)
await this.db.connect()
}
this.logger.info('Starting Payload...')

View File

@@ -1,13 +1,15 @@
export type LanguageTranslations = {
[namespace: string]: {
[key: string]: string
}
}
export type Translations = {
[language: string]:
| {
$schema: string
}
| {
[namespace: string]: {
[key: string]: string
}
}
| LanguageTranslations
}
export type TFunction = (key: string, options?: Record<string, any>) => string
@@ -19,6 +21,7 @@ export type I18n = {
language: string
/** Translate function */
t: (key: string, options?: Record<string, unknown>) => string
translations: Translations
}
export type I18nOptions = {
@@ -29,11 +32,7 @@ export type I18nOptions = {
| {
$schema: string
}
| {
[namespace: string]: {
[key: string]: string
}
}
| LanguageTranslations
}
}
@@ -41,7 +40,10 @@ export type InitTFunction = (args: {
config: I18nOptions
language?: string
translations?: Translations
}) => TFunction
}) => {
t: TFunction
translations: Translations
}
export type InitI18n = (args: {
config: I18nOptions