Skip to content
/ youch Public

Pretty print JavaScript errors on the Web and the Terminal

License

Notifications You must be signed in to change notification settings

poppinss/youch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Youch

Pretty print JavaScript errors on the Web and the Terminal


gh-workflow-image npm-image license-image

What is Youch?

Youch is an error parsing library that pretty prints JavaScript errors on a web-page or the terminal.

As you can see in the following screenshots, the Youch output deconstructs the error and properly displays the error message, name, stack trace with source code and a lot more information about the error.

Raw stack trace
Youch Output

Basic usage

Install the package from the npm packages registry as follows.

npm i youch@beta

# Yarn
yarn add youch@beta

# Pnpm
pnpm add youch@beta

Once installed. You can render errors to HTML output using the youch.render method. The HTML output is self-contained and does not require separate CSS or JavaScript files.

In the following example, we use the hono framework and pretty print all the errors in development using Youch. You can replace Hono with any other framework of your choice.

import { Hono } from 'hono'
import { Youch } from 'youch'

const app = new Hono()
const IN_DEV = process.env.NODE_ENV === 'development'

app.onError(async (error, c) => {
  if (IN_DEV) {
    const html = await youch.render(error)
    return c.html(html)
  }
  return c.text(error.message)
})

Anatomy of the error page

Let's de-construct the error page and understand what each section of the web page represents.

Error info

View image

The top-most section displays the Error info, which includes:

  • The Error class constructor name
  • The Error title set using the options.title property.
  • And the Error message (highlighted in red).

See: How to override the Error info template

Stack trace

View image

The Stack trace section displays individual frames as accordion sections and clicking on the section title will reveal the frame source code. The soure code is not available for native stack frames that are part of the Node.js, Deno, and Bun internals.

Raw output

View image

Clicking the Raw button displays the Error object in its raw form with all the error properties (and not just the stack trace).

You might find the raw output helpful for errors that contains additional properties. For example: HTTP client libraries like Axios, Got, Undici and others usually contain the HTTP response details within the error object.

Error cause

View image

Error cause is a standard way to bubble errors while wrapping them within a generic error. Youch displays the error cause as an interactive property within its own section.

Metadata

Metadata refers to any additional data that you want to display on the error page. It could be the HTTP request headers, the logged-in user info, or the list of available application routes.

Metadata is structured as groups and sections. Each section contains an array of rows and each row is composed of a key-value pair.

In the following example, we display the request headers under the Request group and the Headers section.

const youch = new Youch()

youch.group('Request', {
  headers: [
    {
      key: 'cookie',
      value: req.headers.cookie,
    },
    {
      key: 'host',
      value: req.headers.host,
    },
  ],
})

Calling the youch.group method multiple times with the same group name will merge the new sections with existing sections.

Using a custom source code loader

Youch reads the source code of files within the stacktrace using the Node.js fs module. However, you can override this default and provide a custom source loader using the youch.sourceLoader method.

Note: The sourceLoader is called for every frame within the stack traces. Therefore you must perform the needed checks before attempting to read the source code of a file.

For example, you must not attempt to read the source code for fileNames pointing to native code.

import { Youch } from 'youch'
const youch = new Youch(options)

youch.sourceLoader(async (stackFrame) => {
  if (stackFrame.type !== 'native') {
    stackFrame.source = await getSourceForFile(stackFrame.fileName)
  }
})

Injecting custom styles

You may inject custom CSS styles using the youch.injectStyles method. The styles will be injected after the styles from the inbuilt templates.

import { Youch } from 'youch'
const youch = new Youch(options)

youch.injectStyles(`
  :root {
    // Override variables for light mode
    --surface-bg: #fff;
    --surface-fg: #000;
    --muted-fg: #999;
  }

  html.dark {
    // Override variables for dark mode
  }
`)

Overriding syntax highlighter

Youch uses the speed-highlight, which is lightweight code highlighting library for JavaScript. If you like you override the syntax highlighter, you can do so by registering a custom component for the errorStackSource template.

In the following example, we use Shiki to perform syntax highlighting using a custom component.

import { codeToHtml } from 'shiki'
import { BaseComponent } from 'youch'
import { ErrorStackSourceProps } from 'youch/types'

/**
 * A custom component that uses shiki to render the source
 * code snippet for a stack frame
 */
class CustomErrorStackSource extends BaseComponent<ErrorStackSourceProps> {
  /**
   * Return the styles you want to inject from this
   * component
   */
  async getStyles() {
    return `
    html.dark .shiki {
      background-color: var(--shiki-dark-bg) !important;
    }

    html.dark .shiki span {
      color: var(--shiki-dark) !important;
      font-weight: var(--shiki-dark-font-weight) !important;
      text-decoration: var(--shiki-dark-text-decoration) !important;
    }

    pre.shiki {
      padding: 16px 0;
    }

    code .line {
      position: relative;
      padding: 0 16px 0 48px;
      height: 24px;
      line-height: 24px;
      width: 100%;
      display: inline-block;
    }

    code .line:before {
      position: absolute;
      content: attr(data-line);
      opacity: 0.5;
      text-align: right;
      margin-right: 5px;
      left: 0;
      width: 32px;
    }

    code .highlighted {
      background-color: #ff000632;
    }

    html.dark code .highlighted {
      background-color: #ff173f2d !important;
    }`
  }

  async render(props: ErrorStackSourceProps) {
    if (props.frame.source) {
      const code = props.frame.source.map(({ chunk }) => chunk).join('\n')

      return codeToHtml(code, {
        lang: 'typescript',
        themes: {
          light: 'min-light',
          dark: 'vitesse-dark',
        },
        transformers: [
          {
            line(node, line) {
              const lineNumber = props.frame.source![line - 1].lineNumber
              node.properties['data-line'] = lineNumber

              if (lineNumber === props.frame.lineNumber) {
                this.addClassToHast(node, 'highlighted')
              }
            },
          },
        ],
      })
    }

    return ''
  }
}

const youch = new Youch()

/**
 * Register the component
 */
youch.templates.use('errorStackSource', new CustomErrorStackSource(false))

const html = await youch.render(error)

Contributing

One of the primary goals of Poppinss is to have a vibrant community of users and contributors who believes in the principles of the framework.

We encourage you to read the contribution guide before contributing to the framework.

Code of Conduct

In order to ensure that the Poppinss community is welcoming to all, please review and abide by the Code of Conduct.

License

Youch is open-sourced software licensed under the MIT license.