diff --git a/packages/payload/src/bin/generateImportMap/index.ts b/packages/payload/src/bin/generateImportMap/index.ts index 98d5e09ecd5..3e4a0678502 100644 --- a/packages/payload/src/bin/generateImportMap/index.ts +++ b/packages/payload/src/bin/generateImportMap/index.ts @@ -42,7 +42,14 @@ export type AddToImportMap = (payloadComponent?: PayloadComponent | PayloadCompo export async function generateImportMap( config: SanitizedConfig, - options?: { force?: boolean; log: boolean }, + options?: { + force?: boolean /** + * If true, will not throw an error if the import map file path cannot be resolved + Instead, it will return silently. + */ + ignoreResolveError?: boolean + log: boolean + }, ): Promise { const shouldLog = options?.log ?? true @@ -64,6 +71,14 @@ export async function generateImportMap( rootDir, }) + if (importMapFilePath instanceof Error) { + if (options?.ignoreResolveError) { + return + } else { + throw importMapFilePath + } + } + const importMapToBaseDirPath = getImportMapToBaseDirPath({ baseDir, importMapPath: importMapFilePath, diff --git a/packages/payload/src/bin/generateImportMap/utilities/resolveImportMapFilePath.ts b/packages/payload/src/bin/generateImportMap/utilities/resolveImportMapFilePath.ts index c31648a08ac..48c1c5420e1 100644 --- a/packages/payload/src/bin/generateImportMap/utilities/resolveImportMapFilePath.ts +++ b/packages/payload/src/bin/generateImportMap/utilities/resolveImportMapFilePath.ts @@ -21,7 +21,7 @@ export async function resolveImportMapFilePath({ adminRoute?: string importMapFile?: string rootDir: string -}) { +}): Promise { let importMapFilePath: string | undefined = undefined if (importMapFile?.length) { @@ -29,7 +29,7 @@ export async function resolveImportMapFilePath({ try { await fs.writeFile(importMapFile, '', { flag: 'wx' }) } catch (err) { - throw new Error( + return new Error( `Could not find the import map file at ${importMapFile}${err instanceof Error && err?.message ? `: ${err.message}` : ''}`, ) } @@ -50,7 +50,7 @@ export async function resolveImportMapFilePath({ await fs.writeFile(importMapFilePath, '', { flag: 'wx' }) } } else { - throw new Error( + return new Error( `Could not find Payload import map folder. Looked in ${appLocation} and ${srcAppLocation}`, ) } diff --git a/packages/payload/src/index.ts b/packages/payload/src/index.ts index 5cfa1c9ed23..9411143f3b0 100644 --- a/packages/payload/src/index.ts +++ b/packages/payload/src/index.ts @@ -1000,9 +1000,13 @@ export const reload = async ( }) } - // Generate component map + // Generate import map if (skipImportMapGeneration !== true && config.admin?.importMap?.autoGenerate !== false) { + // This may run outside of the admin panel, e.g. in the user's frontend, where we don't have an import map file. + // We don't want to throw an error in this case, as it would break the user's frontend. + // => just skip it => ignoreResolveError: true await generateImportMap(config, { + ignoreResolveError: true, log: true, }) } @@ -1098,7 +1102,18 @@ export const getPayload = async ( // 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, !options.importMap, options) + + // Reload the payload instance after a config change (triggered by HMR in development). + // The second parameter (false) forces import map regeneration rather than deciding based on options.importMap. + // + // Why we always regenerate import map: getPayload() may be called from multiple sources (admin panel, frontend, etc.) + // that share the same cache but may pass different importMap values. Since call order is unpredictable, + // we cannot rely on options.importMap to determine if regeneration is needed. + // + // Example scenario: If the frontend calls getPayload() without importMap first, followed by the admin + // panel calling it with importMap, we'd incorrectly skip generation for the admin panel's needs. + // By always regenerating on reload, we ensure the import map stays in sync with the updated config. + await reload(config, cached.payload, false, options) resolve() }