Repository: vitejs/vite
File: packages/vite/src/node/optimizer/scan.ts (TypeScript)

Code before:
      esbuildContext = prepareEsbuildScanner(
        environment,
        entries,
        deps,
        missing,
      )
      context = await esbuildContext
      if (scanContext.cancelled) return

      try {
        await context!.rebuild()
        return {
          // Ensure a fixed order so hashes are stable and improve logs
          deps: orderedDependencies(deps),
          missing,
        }
      } catch (e) {
        if (e.errors && e.message.includes('The build was canceled')) {
          // esbuild logs an error when cancelling, but this is expected so
          // return an empty result instead
          return { deps: {}, missing: {} }
        }

        const prependMessage = colors.red(`\
  Failed to scan for dependencies from entries:
  ${entries.join('\n')}

  `)
        if (e.errors) {
          const msgs = await formatMessages(e.errors, {
            kind: 'error',
            color: true,
          })
          e.message = prependMessage + msgs.join('\n')
        } else {
          e.message = prependMessage + e.message
        }
        throw e
      } finally {
        if (debug) {
          const duration = (performance.now() - start).toFixed(2)
          const depsStr =
            Object.keys(orderedDependencies(deps))
              .sort()
              .map((id) => `\n  ${colors.cyan(id)} -> ${colors.dim(deps[id])}`)
              .join('') || colors.dim('no dependencies found')
          debug(`Scan completed in ${duration}ms: ${depsStr}`)
        }
      }
    } finally {
      context?.dispose().catch((e) => {

Review comment (refactor):
This is the place the `TypeError: Cannot read properties of undefined (reading 'join')` error was happening.
When `computeEntries` throws an error, `entries = computedEntries` in the `then` after `computeEntries` is not executed and `entries` will be kept as `undefined`.

The old code was not clear which variable lives at which part of the code. TypeScript is not good with analyzing code flow that uses promise + then + temp variable.
I refactored the code to use a single async function so that the code flow is a straight line. This should be easier to understand for both us and TypeScript.


Code after:
      esbuildContext = prepareEsbuildScanner(
        environment,
        entries,
        deps,
        missing,
      )
      context = await esbuildContext
      if (scanContext.cancelled) return

      try {
        await context!.rebuild()
        return {
          // Ensure a fixed order so hashes are stable and improve logs
          deps: orderedDependencies(deps),
          missing,
        }
      } catch (e) {
        if (e.errors && e.message.includes('The build was canceled')) {
          // esbuild logs an error when cancelling, but this is expected so
          // return an empty result instead
          return
        }

        const prependMessage = colors.red(`\
  Failed to scan for dependencies from entries:
  ${entries.join('\n')}

  `)
        if (e.errors) {
          const msgs = await formatMessages(e.errors, {
            kind: 'error',
            color: true,
          })
          e.message = prependMessage + msgs.join('\n')
        } else {
          e.message = prependMessage + e.message
        }
        throw e
      } finally {
        if (debug) {
          const duration = (performance.now() - start).toFixed(2)
          const depsStr =
            Object.keys(orderedDependencies(deps))
              .sort()
              .map((id) => `\n  ${colors.cyan(id)} -> ${colors.dim(deps[id])}`)
              .join('') || colors.dim('no dependencies found')
          debug(`Scan completed in ${duration}ms: ${depsStr}`)
        }
      }
    } finally {
      context?.dispose().catch((e) => {