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

@purrtrait/view-tsx

v0.0.11

Published

Abstractions for modelling editable TSX code.

Readme

@purrtrait/view-tsx

Absractions for modelling editable TSX code.

This package is part of the @purrtrait toolkit.

This package provides high level abstractions for modelling editable TSX code.

It was designed for interactive component environments: documentation sandboxes, visual editors, and runtime JSX evaluation in the browser.

Features

Extract editable view models from TSX snippets into a framework-agnostic representation suitable for React, SolidJS, and custom runtimes.

Supports independent editing of:

  • Multitple target components
  • Template wrapper – everything around the target components
  • Target component props – including children, classified as TSXNode

Used together with a TypeScript parser and a runtime compiler, this package allows TSX snippets to be extracted into structured editable models, modified at a fine-grained level, and later recompiled into executable JSX.

This allows wrapper structure and target component props to be edited independently in a way that only requires re-extracting the view and re-compiling the wrapper component when the wrapper itself is modified.

Installation

npm install @purrtrait/view-tsx

⚠️ Peer dependencies: Also install if not in your project already:

  • typescript

Usage

Dependencies

This package is intended to be used together with:

  • @purrtrait/client-ts for parsing and AST utilities
  • @purrpose/client-babel for runtime compilation and evaluation

The extracted model itself contains no runtime execution logic.

Extracting a view model

Given the following TSX:

<h1>Hello</h1>
<div>
  <Button tsx-view-target />
  <Button tsx-view-target intent="negative" onClick={() => console.log('!')}>
    <Display>foo</Display>
  </Button>
</div>

Extract the view with extractTSXView()

import { extractTSXView } from '@purrtrait/view-tsx';
const view = extractTSXView(source);

The captured result extracts the targets from the wrapper expression, capturing all props with TSXNode nodes from @purrtrait/client-tsx.

wrapper:
  type: 'jsx'
  serialized: '<><h1>Hello</h1><div><TSXViewTargetPlaceholder key="0" component="Button" props={props} /><TSXViewTargetPlaceholder key="1" component="Button" props={props} /></div></>'
targets:
  '0':
  component:
    name: 'Button'
    1':
  component:
    name: 'Button'
    rops:
    intent:
      type: 'expression'
      tsNode: ...
      serialized: '"negative"'
    onClick:
      type: 'handler'
      tsNode: ...
      serialized: '() => console.log("!")'
    children:
      type: 'jsx'
      tsNode: ...
      serialized: '<><Display>foo</Display></>'
``

### Target nodes

Target nodes are identified using an attribute.

The attribute name is `tsx-view-target` by default and is configurable via `options`.

```tsx
<Button tsx-view-target />

You can provide a name for each, some, or all of the targets.

If multitple targets are present and no names are provided, an auto-incremented index is generated for each one.

<Button tsx-view-target="first" />
<Button tsx-view-target /> // becomes '1'
<Button tsx-view-target /> // becomes '2'
<Button tsx-view-target="last" />

If nested targets are found, only the top-most targets are kept. Any nested content inside a target is treated as its children prop.

<Button tsx-view-target />
<Button tsx-view-target="parent">
  <Button tsx-view-target /> // not captured as a target, but as children of `parent`
</Button>

The tsx-view-target attribute is used only during extraction and is not included in the resulting view model.

Placeholder components

All valid targets are replaced in the the wrapper (both in the enclosed tsNode and the serialized value) by a placeholder component.

This component will receive the target key and component name, along with an extra props prop.

<TSXViewTargetPlaceholder key="0" component="Button" props={props} />

There is no implementation for the placeholder.

When compiling the wrapper, you will need to populate the scope with the implementation for the placeholder, along with the implementations of any components invoked in the wrapper template.

This is easy to achieve with a library such as @purrpose/babel-client.

Example implementation

If you chose to provide the compiled wrapper with all props of all targets at once (ideally indexed by key).

You can easily retrieve the original props inside of each instance, before rendering the target component.

const TSXViewTargetPlaceholder = (props: Record<string, unknown>) => {
  const key = () => props[PLACEHOLDER_KEY_PROP] || '';
  const component = () => props[PLACEHOLDER_COMPONENT_PROP] || '';
  const allProps = () => props[PLACEHOLDER_PROPS_PROP] || {};
  const ownProps = () => allProps()[key()] || {};

  return <Dynamic component={component()} {...ownProps} />;
};

You can also use the placeholder renderer to merge props from other sources such as a playground props table.

Props and Children extraction

All props passed to each target are stored in the view.

You can use these values to populate props tables or provide further documentation.

Children are extracted as a normal prop:

view.targets[0].props.children = `<span>World</span>`;

Multiple children are normalized into a fragment:

<>
  <Display />
  <Text />
</>

This keeps all editable values represented by a single TSXNode.

API

extractTSXView(source, options)

Extracts a TSX view model synchronously.

function extractTSXView(source: string, options?: Partial<TSXViewOptions>): TSXView;

TSXView

type TSXView = {
  source: string;
  wrapper: TSXElementNode;
  targets: {
    [key: string]: TSXViewTarget;
  };
};

export type TSXViewTarget = {
  component: {
    name: string;
  };
  raw: TSXElementNode;
  props: Record<string, TSXNode>;
};

Options

export type TSXViewOptions = {
  targetAttributeName: string;
  placeholderName: string;
  placeholderKeyProp: string;
  placeholderComponentProp: string;
  placeholderPropsProp: string;
  placeholderPropsVar: string;
};

The default values are exported as constants so that you can reference them in your rendering code.

TARGET_ATTRIBUTE_NAME = 'tsx-view-target';
PLACEHOLDER_NAME = 'TSXViewTargetPlaceholder';
PLACEHOLDER_KEY_PROP = 'key';
PLACEHOLDER_COMPONENT_PROP = 'component';
PLACEHOLDER_PROPS_PROP = 'props';
PLACEHOLDER_PROPS_VARIABLE = 'props';

Helpers

viewPropsByTarget(...)

Retrieve props for a specific target.

function viewTargetProps(
  view: TSXView,
  targetKey: string,
  evaluate: PropEvaluator,
): Record<string, unknown>;

The evalutator function determines the prop values based on each node. It is invoked with the prop name the full TSXNode and the targetKey in case you need fulll context.

type PropEvaluator = (entry: [name: string, node: TSXNode, targetKey: string]) => unknown;

The simplest use case is it to retrieve the original serialized value of each prop.

const targetProps = viewTargetProps(view, '0', ([, node]) => {
  return node.serialized;
});
// {
//   intent: '"negative"'
//   onClick: '() => console.log("!")'
//   children: '<><Display>foo</Display></>'
// }

You can also use it to compile to executable javascript.

const targetProps = viewTargetProps(view, '0', ([, value]) => {
  return evaluateValue(compiler, value, components),
});
// {
//   intent: "negative",
//   onClick: () => console.log("!"),
//   children: JSX.Component => <><Display...
// }

viewPropsByTarget(...)

Retrieve props of all props at once.

function viewPropsByTarget(
  view: TSXView,
  evaluate: PropEvaluator,
): Record<string, ViewTargetPropsTransformed>;

The evalutator function determines the prop values based on each node.

This example retrieves the orifginal serialized value of each prop.

const targetProps = viewTargetProps(view, '0', ([, node]) => {
  return node.serialized;
});
// "0": { },
// "1": {
//   intent: '"negative"'
//   onClick: '() => console.log("!")'
//   children: '<><Display>foo</Display></>'
// }

## Development

Make sure you read the [namespace README](../../README.md) first.

### Build Targets

This library is distributed as ESM and intended to be processed by a bundler such as Vite or Astro. The main entry point is the Typescript source code.

### Scripts

- **$** `npm run dev` - uses `Vite` to (re)build on changes
- **$** `npm run build` - uses `Vite` to do produce a dry build in `dist/`.
- **$** `npm run lint` / `npm run lint:fix` - uses [@noodlestan/eslint-config](https://www.npmjs.com/package/@noodlestan/eslint-config).

## MIT License

Copyright (c) 2026 [Noodlestan](https://noodlestan.org/).

Published under a [MIT license](https://noodlestan.mit-license.org/).