Lessons from open-source: Have you ever used console object like this `console[consoleMethod](‘’)`?

Lessons from open-source: Have you ever used console object like this `console[consoleMethod](‘’)`?

This lesson is picked from Next.js source code. In this article, you will learn how to create generic, resuable console methods, how Next.js leverages this way of using console object.

Console

In javascript, console is object that provides access to the debugging console. The following image shows instance methods of console object.

You can find out more about these instance methods in the MDN Docs.

Generic, reusable log.ts file in Next.js source code

build/output/log.ts has the following code:

Practice the exercises based on documentation to become an expert in Next.js

import { bold, green, magenta, red, yellow, white } from '../../lib/picocolors'

export const prefixes = {
  wait: white(bold('○')),
  error: red(bold('⨯')),
  warn: yellow(bold('⚠')),
  ready: '▲', // no color
  info: white(bold(' ')),
  event: green(bold('✓')),
  trace: magenta(bold('»')),
} as const

const LOGGING_METHOD = {
  log: 'log',
  warn: 'warn',
  error: 'error',
} as const

function prefixedLog(prefixType: keyof typeof prefixes, ...message: any[]) {
  if ((message[0] === '' || message[0] === undefined) && message.length === 1) {
    message.shift()
  }

  const consoleMethod: keyof typeof LOGGING_METHOD =
    prefixType in LOGGING_METHOD
      ? LOGGING_METHOD[prefixType as keyof typeof LOGGING_METHOD]
      : 'log'

  const prefix = prefixes[prefixType]
  // If there's no message, don't print the prefix but a new line
  if (message.length === 0) {
    console[consoleMethod]('')
  } else {
    console[consoleMethod](' ' + prefix, ...message)
  }
}

export function bootstrap(...message: any[]) {
  console.log(' ', ...message)
}

export function wait(...message: any[]) {
  prefixedLog('wait', ...message)
}

export function error(...message: any[]) {
  prefixedLog('error', ...message)
}

export function warn(...message: any[]) {
  prefixedLog('warn', ...message)
}

export function ready(...message: any[]) {
  prefixedLog('ready', ...message)
}

export function info(...message: any[]) {
  prefixedLog('info', ...message)
}

export function event(...message: any[]) {
  prefixedLog('event', ...message)
}

export function trace(...message: any[]) {
  prefixedLog('trace', ...message)
}

const warnOnceMessages = new Set()
export function warnOnce(...message: any[]) {
  if (!warnOnceMessages.has(message[0])) {
    warnOnceMessages.add(message.join(' '))

    warn(...message)
  }
}

The important question here is why bother to create such a file when you could just do console.log or console.warn, it is only few letters difference , right?

Look closer. wait, error, warn, ready, info, event, trace call a function named prefixedLog.

prefixedLog

function prefixedLog(prefixType: keyof typeof prefixes, ...message: any[]) {
  if ((message[0] === '' || message[0] === undefined) && message.length === 1) {
    message.shift()
  }

  const consoleMethod: keyof typeof LOGGING_METHOD =
    prefixType in LOGGING_METHOD
      ? LOGGING_METHOD[prefixType as keyof typeof LOGGING_METHOD]
      : 'log'

  const prefix = prefixes[prefixType]
  // If there's no message, don't print the prefix but a new line
  if (message.length === 0) {
    console[consoleMethod]('')
  } else {
    console[consoleMethod](' ' + prefix, ...message)
  }
}

This function has console object instance method name as the first parameter but the catch here is the “prefix”.

export const prefixes = {
  wait: white(bold('○')),
  error: red(bold('⨯')),
  warn: yellow(bold('⚠')),
  ready: '▲', // no color
  info: white(bold(' ')),
  event: green(bold('✓')),
  trace: magenta(bold('»')),
} as const

const prefix = prefixes[prefixType]

prefixes object has some picocolors applied symbol that show up as part of the logs in your terminal.

Because Next.js codebase is large, this is a standard set to follow to stay consistent with the symbols shown when console instance methods such as warn,, error, wait etc., are called.

This file is imported in 62 other files across the codebase.

Conclusion:

Have you ever used console object like this console[consoleMethod](‘ + prefix, …message’)? I haven’t yet. When I first looked at this log.ts file in next.js source code, my thoughts were “I wouldn’t mind writing console.log or console.warn”. The important question here is why bother to create such a file when you could just do console.log or console.warn, it is only few letters difference, right? the catch is symbols that are prefixed with the console instance methods in this resuable, generic log.ts file.

I found that these methods are reused in 62 files, just to stay consistent with the symbols shown when you call certain methods. These tiny details show the importance of staying consistent across your codebase especially since this is logged to the user terminal, I would be careful too.