| import type { IncomingMessage } from 'http' |
| import type { DevBundler } from './router-utils/setup-dev-bundler' |
| import type { WorkerRequestHandler } from './types' |
|
|
| import { LRUCache } from './lru-cache' |
| import { createRequestResponseMocks } from './mock-request' |
| import { |
| HMR_MESSAGE_SENT_TO_BROWSER, |
| type HmrMessageSentToBrowser, |
| type NextJsHotReloaderInterface, |
| } from '../dev/hot-reloader-types' |
|
|
| |
| |
| |
| |
| export class DevBundlerService { |
| public appIsrManifestInner: InstanceType<typeof LRUCache<boolean>> |
| public close: NextJsHotReloaderInterface['close'] |
| public setCacheStatus: NextJsHotReloaderInterface['setCacheStatus'] |
| public setReactDebugChannel: NextJsHotReloaderInterface['setReactDebugChannel'] |
| public sendErrorsToBrowser: NextJsHotReloaderInterface['sendErrorsToBrowser'] |
|
|
| constructor( |
| private readonly bundler: DevBundler, |
| private readonly handler: WorkerRequestHandler |
| ) { |
| this.appIsrManifestInner = new LRUCache( |
| 8_000, |
|
|
| function length() { |
| return 16 |
| } |
| ) |
|
|
| const { hotReloader } = bundler |
|
|
| this.close = hotReloader.close.bind(hotReloader) |
| this.setCacheStatus = hotReloader.setCacheStatus.bind(hotReloader) |
| this.setReactDebugChannel = |
| hotReloader.setReactDebugChannel.bind(hotReloader) |
| this.sendErrorsToBrowser = hotReloader.sendErrorsToBrowser.bind(hotReloader) |
| } |
|
|
| public ensurePage: typeof this.bundler.hotReloader.ensurePage = async ( |
| definition |
| ) => { |
| |
| return await this.bundler.hotReloader.ensurePage(definition) |
| } |
|
|
| public logErrorWithOriginalStack = |
| this.bundler.logErrorWithOriginalStack.bind(this.bundler) |
|
|
| public async getFallbackErrorComponents(url?: string) { |
| await this.bundler.hotReloader.buildFallbackError() |
| |
| |
| await this.bundler.hotReloader.ensurePage({ |
| page: '/_error', |
| clientOnly: false, |
| definition: undefined, |
| url, |
| }) |
| } |
|
|
| public async getCompilationError(page: string) { |
| const errors = await this.bundler.hotReloader.getCompilationErrors(page) |
| if (!errors) return |
|
|
| |
| return errors[0] |
| } |
|
|
| public async revalidate({ |
| urlPath, |
| revalidateHeaders, |
| opts: revalidateOpts, |
| }: { |
| urlPath: string |
| revalidateHeaders: IncomingMessage['headers'] |
| opts: any |
| }) { |
| const mocked = createRequestResponseMocks({ |
| url: urlPath, |
| headers: revalidateHeaders, |
| }) |
|
|
| await this.handler(mocked.req, mocked.res) |
| await mocked.res.hasStreamed |
|
|
| if ( |
| mocked.res.getHeader('x-nextjs-cache') !== 'REVALIDATED' && |
| mocked.res.statusCode !== 200 && |
| !(mocked.res.statusCode === 404 && revalidateOpts.unstable_onlyGenerated) |
| ) { |
| throw new Error(`Invalid response ${mocked.res.statusCode}`) |
| } |
|
|
| return {} |
| } |
|
|
| public get appIsrManifest() { |
| const serializableManifest: Record<string, boolean> = {} |
|
|
| for (const [key, value] of this.appIsrManifestInner) { |
| serializableManifest[key] = value |
| } |
|
|
| return serializableManifest |
| } |
|
|
| public setIsrStatus(key: string, value: boolean | undefined) { |
| if (value === undefined) { |
| this.appIsrManifestInner.remove(key) |
| } else { |
| this.appIsrManifestInner.set(key, value) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| this.bundler?.hotReloader?.sendToLegacyClients({ |
| type: HMR_MESSAGE_SENT_TO_BROWSER.ISR_MANIFEST, |
| data: this.appIsrManifest, |
| }) |
| } |
|
|
| public sendHmrMessage(message: HmrMessageSentToBrowser) { |
| this.bundler.hotReloader.send(message) |
| } |
| } |
|
|