next.js/packages/next/src/build/output/index.ts
index.ts229 lines5.7 KB
import createStore from 'next/dist/compiled/unistore'
import formatWebpackMessages from '../../shared/lib/format-webpack-messages'
import { store as consoleStore } from './store'
import type { OutputState } from './store'
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import { COMPILER_NAMES } from '../../shared/lib/constants'
import type { CompilerNameValues } from '../../shared/lib/constants'

type CompilerDiagnostics = {
  totalModulesCount: number
  errors: string[] | null
  warnings: string[] | null
}

type WebpackStatus =
  | { loading: true }
  | ({ loading: false } & CompilerDiagnostics)

type BuildStatusStore = {
  client: WebpackStatus
  server: WebpackStatus
  edgeServer: WebpackStatus
  trigger: string | undefined
  url: string | undefined
}

const buildStore = createStore<BuildStatusStore>({
  // @ts-expect-error initial value
  client: {},
  // @ts-expect-error initial value
  server: {},
  // @ts-expect-error initial value
  edgeServer: {},
})
let buildWasDone = false
let clientWasLoading = true
let serverWasLoading = true
let edgeServerWasLoading = false

buildStore.subscribe((state) => {
  const { client, server, edgeServer, trigger, url } = state

  const { appUrl } = consoleStore.getState()

  if (client.loading || server.loading || edgeServer?.loading) {
    consoleStore.setState(
      {
        bootstrap: false,
        appUrl: appUrl!,
        // If it takes more than 3 seconds to compile, mark it as loading status
        loading: true,
        trigger,
        url,
      } as OutputState,
      true
    )
    clientWasLoading = (!buildWasDone && clientWasLoading) || client.loading
    serverWasLoading = (!buildWasDone && serverWasLoading) || server.loading
    edgeServerWasLoading =
      (!buildWasDone && edgeServerWasLoading) || edgeServer.loading
    buildWasDone = false
    return
  }

  buildWasDone = true

  let partialState: Partial<OutputState> = {
    bootstrap: false,
    appUrl: appUrl!,
    loading: false,
    typeChecking: false,
    totalModulesCount:
      (clientWasLoading ? client.totalModulesCount : 0) +
      (serverWasLoading ? server.totalModulesCount : 0) +
      (edgeServerWasLoading ? edgeServer?.totalModulesCount || 0 : 0),
    hasEdgeServer: !!edgeServer,
  }
  if (client.errors && clientWasLoading) {
    // Show only client errors
    consoleStore.setState(
      {
        ...partialState,
        errors: client.errors,
        warnings: null,
      } as OutputState,
      true
    )
  } else if (server.errors && serverWasLoading) {
    consoleStore.setState(
      {
        ...partialState,
        errors: server.errors,
        warnings: null,
      } as OutputState,
      true
    )
  } else if (edgeServer.errors && edgeServerWasLoading) {
    consoleStore.setState(
      {
        ...partialState,
        errors: edgeServer.errors,
        warnings: null,
      } as OutputState,
      true
    )
  } else {
    // Show warnings from all of them
    const warnings = [
      ...(client.warnings || []),
      ...(server.warnings || []),
      ...(edgeServer.warnings || []),
    ]

    consoleStore.setState(
      {
        ...partialState,
        errors: null,
        warnings: warnings.length === 0 ? null : warnings,
      } as OutputState,
      true
    )
  }
})

export function watchCompilers(
  client: webpack.Compiler,
  server: webpack.Compiler,
  edgeServer: webpack.Compiler
) {
  buildStore.setState({
    client: { loading: true },
    server: { loading: true },
    edgeServer: { loading: true },
    trigger: 'initial',
    url: undefined,
  })

  function tapCompiler(
    key: CompilerNameValues,
    compiler: webpack.Compiler,
    onEvent: (status: WebpackStatus) => void
  ) {
    compiler.hooks.invalid.tap(`NextJsInvalid-${key}`, () => {
      onEvent({ loading: true })
    })

    compiler.hooks.done.tap(`NextJsDone-${key}`, (stats: webpack.Stats) => {
      const { errors, warnings } = formatWebpackMessages(
        stats.toJson({
          preset: 'errors-warnings',
          moduleTrace: true,
        })
      )

      const hasErrors = !!errors?.length
      const hasWarnings = !!warnings?.length

      onEvent({
        loading: false,
        totalModulesCount: stats.compilation.modules.size,
        errors: hasErrors ? errors : null,
        warnings: hasWarnings ? warnings : null,
      })
    })
  }

  tapCompiler(COMPILER_NAMES.client, client, (status) => {
    if (
      !status.loading &&
      !buildStore.getState().server.loading &&
      !buildStore.getState().edgeServer.loading &&
      status.totalModulesCount > 0
    ) {
      buildStore.setState({
        client: status,
        trigger: undefined,
        url: undefined,
      })
    } else {
      buildStore.setState({
        client: status,
      })
    }
  })
  tapCompiler(COMPILER_NAMES.server, server, (status) => {
    if (
      !status.loading &&
      !buildStore.getState().client.loading &&
      !buildStore.getState().edgeServer.loading &&
      status.totalModulesCount > 0
    ) {
      buildStore.setState({
        server: status,
        trigger: undefined,
        url: undefined,
      })
    } else {
      buildStore.setState({
        server: status,
      })
    }
  })
  tapCompiler(COMPILER_NAMES.edgeServer, edgeServer, (status) => {
    if (
      !status.loading &&
      !buildStore.getState().client.loading &&
      !buildStore.getState().server.loading &&
      status.totalModulesCount > 0
    ) {
      buildStore.setState({
        edgeServer: status,
        trigger: undefined,
        url: undefined,
      })
    } else {
      buildStore.setState({
        edgeServer: status,
      })
    }
  })
}

export function reportTrigger(trigger: string, url?: string) {
  buildStore.setState({
    trigger,
    url,
  })
}
Quest for Codev2.0.0
/
SIGN IN