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

estree-util-to-static-value

v1.0.0

Published

> Safe extraction of static JavaScript data from ESTree expressions.

Readme

estree-util-to-static-value

Safe extraction of static JavaScript data from ESTree expressions.

estree-util-to-static-value converts ESTree expression nodes into plain JavaScript values at build time without executing code. Dynamic or unsupported subtrees are treated as unresolved.

Status/Scope: This utility extracts only the statically resolvable subset of ESTree and never evaluates or executes runtime code.

Reference usage: currently used in project-millipede/recma-static-refiner.

Features

  • Deterministic static extraction from ESTree expressions without executing code.
  • Explicit unresolved signaling via SKIP_VALUE.
  • Strict array policy (any unresolved element => SKIP_VALUE) and partial object policy (drop only unresolved entries).
  • Preserved-key transport for dynamic subtrees using onPreservedExpression (capture + placeholder return).
  • Root object adapter (extractStaticProps) for object-only outputs, returning null for non-object or unresolved roots.

Why Build-Time Processing?

When extracting data from AST, executing runtime code is unsafe and non-deterministic. This utility gives you deterministic static decoding only.

| Aspect | Runtime Evaluation | Build-Time Static Extraction (This Utility) | | --- | --- | --- | | Execution | Runs user code | Never executes user code | | Determinism | Depends on runtime state | Determined from AST-only rules | | Dynamic nodes | Produce runtime values | Return unresolved (SKIP_VALUE) | | Mixed objects | Full runtime result | Static subset only |

Static Data Processing Pipeline

At extraction time, the engine walks ESTree and applies three phases:

| Phase | Input | Output | Purpose | | --- | --- | --- | --- | | 1. Direct Resolution | Single expression node | Static value or unresolved | Resolve literals, constant identifiers, static templates | | 2. Recursive Extraction | Arrays/objects | Nested static value or unresolved | Traverse nested structures without execution | | 3. Policy Integration | Child results | Final container result | Apply strict array policy and partial object policy |

Policy summary:

  • ArrayExpression: any unresolved child collapses the whole array to SKIP_VALUE.
  • ObjectExpression: unresolved key/value drops only that entry.
  • Array elisions are preserved as holes.
  • Array spreads trigger strict bailout.
  • Object spreads are skipped.

Dynamic Analysis Limits and Preservation Modes

Static extraction is intentionally constrained:

  • Identifier nodes expose names, not runtime values ({ type: "Identifier", name: "x" }).
  • Runtime-dependent expressions cannot be safely evaluated during build.
  • Spreads can shadow or reorder effective runtime values.

Because of this, preservation happens in two different ways:

  • Implicit preservation (omission): unresolved subtrees are excluded from extracted data and remain untouched in source AST.
  • Explicit preservation (transport): keys listed in preservedKeys are captured via placeholder + side-channel callback for later re-inlining.

Installation

npm install estree-util-to-static-value
# or
pnpm add estree-util-to-static-value

Examples

Static Object

Input:

{ a: 1, b: "x", c: [true, 2] }

Output:

{ a: 1, b: "x", c: [true, 2] }

Partial Object Extraction

Input:

{ a: 1, b: someVar }

Output:

{ a: 1 }

Strict Array Bailout

Input:

[1, someVar, 3]

Output:

SKIP_VALUE

Root Object Adapter

Input root:

[1, 2]

extractStaticProps(...) output:

null

How to Use

Step 1: Parse a Single Expression into ESTree

import { parse } from 'meriyah';
import { is, type types } from 'estree-toolkit';

function parseExpression(code: string): types.Expression {
  const program = parse(`(${code})`) as unknown;

  if (!is.program(program)) {
    throw new Error('Expected parser output to be an ESTree Program node.');
  }

  const first = program.body.at(0);
  if (!first || !is.expressionStatement(first)) {
    throw new Error('Expected wrapped code to produce an ExpressionStatement.');
  }

  return first.expression;
}

Step 2: Extract Static Value and Handle Unresolved

import {
  extractStaticValueFromExpression,
  SKIP_VALUE
} from 'estree-util-to-static-value';

const node = parseExpression('{ a: 1, b: someVar }');
const result = extractStaticValueFromExpression(
  node,
  { preservedKeys: new Set() },
  'root'
);

if (result === SKIP_VALUE) {
  // unresolved subtree/root
} else {
  // static result
  // => { a: 1 }
}

For object-only root expectations, use extractStaticProps(...).

Step 3: Preserve Dynamic Subtrees (for example children)

import type { types } from 'estree-toolkit';
import { extractStaticProps } from 'estree-util-to-static-value';

const toPathKey = (path: ReadonlyArray<string | number>) =>
  JSON.stringify(path);

const preservedExpressionsByPath = new Map<string, types.Expression>();

const extracted = extractStaticProps(
  propsNode,
  {
    preservedKeys: new Set(['children']),
    onPreservedExpression: ({ path, node, expression }) => {
      // `node` is always present; `expression` can be null.
      if (expression) {
        preservedExpressionsByPath.set(toPathKey(path), expression);
      }
      return {
        kind: 'preserved',
        path
      };
    }
  },
  'Component.props'
);

In recma-static-refiner integration, path keys are encoded with a canonical helper (stringifyPropertyPath).

Behavior summary:

  • static fields are extracted as plain JS values
  • unresolved fields are normally omitted in objects
  • preserved keys (like children) are emitted as placeholders and captured via callback
  • use a canonical path-key encoder to avoid collisions

Step 4: Re-inline Preserved Expressions (Advanced)

If your downstream pipeline rebuilds AST from extracted/derived values, preserved placeholders must be resolved back to their original expressions.

Minimal flow:

  1. Capture preserved expressions in onPreservedExpression.
  2. Carry placeholders through validation/transforms.
  3. During AST rebuild, replace each placeholder with the side-channel expression stored for that path.

Configuration Reference

ExtractOptions

export type ExtractOptions = {
  preservedKeys: ReadonlySet<string>;
  onPreservedExpression?: (info: PreservedExpressionInfo) => unknown;
};

ExtractOptions controls how dynamic/unresolved subtrees are handled during extraction.

| Field | Type | Required | Description | | --- | --- | --- | --- | | preservedKeys | ReadonlySet<string> | Yes | Keys that bypass normal static evaluation and are emitted as expression placeholders (for example children) | | onPreservedExpression | (info) => unknown | Conditional | Called for each preserved key; callback return is written as the placeholder. Required at runtime when a preserved key is encountered |

PreservedExpressionInfo shape:

type PreservedExpressionInfo = {
  path: (string | number)[];
  node: types.Node;
  expression: types.Expression | null;
};

Preserved Placeholder Model

When a key is listed in preservedKeys (for example children), the extractor does this:

  1. Calls onPreservedExpression({ path, node, expression }).
  2. Writes the callback return value into extracted data at the same path.
  3. Lets downstream AST rebuild code resolve that placeholder back to the stored expression.

This is the mechanism that lets dynamic runtime subtrees pass through static-data pipelines safely.

Note:

  • PreservedSubtreeLifecycle and ExpressionRefPlaceholder are documentation marker types (never) used to describe this design.
  • Consumers should rely on ExtractOptions + callback behavior, not those marker types.
  • If a preserved key is encountered and onPreservedExpression is missing, extraction throws.

Deep dive source:

  • src/architecture.ts

For end-to-end round-trip integration details (capture, patch planning, and re-inlining), see project-millipede/recma-static-refiner.

API Surface

| API | Returns | Notes | | --- | --- | --- | | extractStaticValueFromExpression(node, options, pathLabel, pathSegments?) | unknown | typeof SKIP_VALUE | Core recursive extractor | | extractStaticProps(propsNode, options, pathLabel) | Record<string, unknown> | null | Root adapter that accepts only plain-object results | | formatPath(base, segment) | string | Utility to format human-readable property paths for diagnostics | | extractPropertyKey(propertyNode) | string | number | null | Resolves object/property keys from ESTree Property / PropertyDefinition nodes | | tryResolveStaticValue(node) | StaticResult | Direct static resolver for supported expression forms | | SKIP_VALUE | symbol | Sentinel for unresolved/dynamic extraction |

Exported Types

| Type | Description | | --- | --- | | ExtractOptions | Extractor runtime options (preservedKeys, onPreservedExpression) | | PreservedExpressionInfo | Preserved callback payload (path, node, expression) | | PropertyPath | Logical nested path as (string | number)[] | | StaticSuccess | Successful direct static resolution shape | | StaticFailure | Failed direct static resolution shape | | StaticResult | Union of StaticSuccess and StaticFailure |

Property Key Rules

  • Non-computed identifier keys resolve by label ({ title: 1 } -> "title").
  • Literal/computed keys go through static resolution.
  • Accepted resolved key types: string and number.
  • Rejected key results: null, undefined, bigint, symbol, and dynamic expressions.

Capabilities

Supported Direct Resolution

  • Literals: string, number, boolean, bigint, null, RegExp
  • Identifier constants: undefined, NaN, Infinity
  • Template literals where all interpolations are statically resolvable

Currently Unresolved by Default

  • Variable identifiers (for example someVar)
  • Member access (for example obj.value)
  • Function calls and constructors
  • Unary, binary, logical, conditional, and sequence expressions
  • JSX and other runtime-only expression forms

Runtime vs Static Semantics

  • Static data is extracted and represented as plain JS values.
  • Runtime code is never executed; unresolved expressions stay unresolved.
  • Preserved keys provide explicit transport for runtime subtrees through data-oriented pipelines.

Invariants

  • Extraction never executes user code.
  • Arrays preserve positional integrity via strict bailout.
  • Objects maximize recoverable data via selective omission.
  • Unresolved is always explicit (SKIP_VALUE, or null in object-only root adapter).