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

editor-ts

v0.0.13

Published

TypeScript library for editing HTML content with JSON representation

Readme

EditorTs

DO NOT USE THIS IN PRODUCTION YET. Early development.

EditorTs is a TypeScript library for editing HTML content while keeping the source of truth in clean, portable JSON (components/styles/assets). The editor runtime (toolbars, permissions, UI layout, event handlers) stays in JavaScript.

Quickstart (run the demo)

bun install
bun run dev

Open http://localhost:5021.

The demo UI is index.html and is wired by examples/quickstart.ts.

Core concepts

Data vs runtime config

  • JSON (“data”): components, styles/CSS, assets.
  • JS (“runtime”): toolbars, UI wiring, event handlers, editor behaviors.

This separation is intentional: the same JSON can be used in different apps with different editor experiences.

Components-first rendering

When both are present:

  • components are the source of truth.
  • Page.getHTML() renders from components.

When only HTML is present:

  • HTML can be converted to components when DOM is available.

Usage

Minimal init

import { init, type PageData } from 'editorts'

const editor = init({
  iframeId: 'preview-iframe',
  data: pageData satisfies PageData,
})

Storage adapters

Local storage is the default, but we recommend SQLocal for persistent, browser-native SQLite storage. SQLocal requires cross-origin isolation headers, so the easiest way is to use the Vite examples in examples/localsql or examples/solid.

import { init } from 'editorts'
import { SQLocal } from 'sqlocal'

const sqlocalClient = new SQLocal('editorts.sqlite')

const editor = init({
  iframeId: 'preview-iframe',
  data: pageData,
  storage: {
    type: 'sqlocal',
    client: sqlocalClient,
    // databaseName: 'editorts.sqlite', // when not passing client
  },
})

Run the SQLocal demos (Vite):

cd examples/localsql
bun install
bun run dev
cd examples/solid
bun install
bun run dev

Open http://localhost:5173.

Toolbars (runtime only)

import { init } from 'editorts'

const editor = init({
  iframeId: 'preview-iframe',
  data: pageData,
  toolbars: {
    byId: {
      header: {
        enabled: true,
        actions: [
          { id: 'edit', label: 'Edit', icon: '✏️', enabled: true },
          { id: 'duplicate', label: 'Duplicate', icon: '📋', enabled: true },
        ],
      },
    },
  },
})

UI containers (you own the layout)

EditorTs does not create your sidebar/tabs/layout. You provide containers and init() wires them.

const editor = init({
  iframeId: 'preview-iframe',
  data: pageData,
  ui: {
    stats: { containerId: 'stats-container' },
    layers: { containerId: 'layers-container' },
    selectedInfo: { containerId: 'selected-info' },
    viewTabs: {
      editorButtonId: 'tab-editor',
      codeButtonId: 'tab-code',
      defaultView: 'editor',
    },
    editors: {
      js: { containerId: 'js-editor-container' },
      css: { containerId: 'css-editor-container' },
      json: { containerId: 'json-editor-container' },
      jsx: { containerId: 'jsx-editor-container' },
    },
  },
})

Built-in code editors

EditorTs can render editors into your containers:

  • Default: textarea (zero deps)
  • Optional: modern-monaco (syntax highlighting)
const editor = init({
  iframeId: 'preview-iframe',
  data: pageData,
  codeEditor: { provider: 'modern-monaco' },
})

Notes:

  • modern-monaco is an optional peer dependency.
  • typescript is an optional peer dependency (used for TSX/JSX parsing).

Component conversions

Components → HTML

const html = editor.page.components.toHTML()

HTML → Components

// Requires DOM (browser). Server-side: inject an adapter or it will warn and no-op.
editor.page.components.setFromHTML('<body><div id="root">Hello</div></body>')

Components → JSX/TSX

const jsxSource = editor.page.components.toJSX({ pretty: true })

toJSX() outputs React-style function components named from attributes.id when possible.

JSX/TSX → Components

// Uses optional peer dependency `typescript`.
await editor.page.components.setFromJSX(`
export function Header() {
  return <div id="header">Hello</div>
}
`)

Server sync (Bun + Cloudflare)

EditorTs ships lightweight websocket utilities for server-side sync.

Bun server

import { createBunSyncServer, createSyncMessage } from 'editorts'

const server = createBunSyncServer({
  port: 8787,
  onSync: async (message) => {
    console.log('received', message.payload)
  },
})

// elsewhere, send a message
const payload = createSyncMessage(pageData)

Cloudflare worker

import { createCfSyncWorker } from 'editorts'

export default createCfSyncWorker({
  onSync: async (message) => {
    console.log('received', message.payload)
  },
})

Message helpers

import { createSyncMessage, parseSyncEnvelope } from 'editorts'

const message = createSyncMessage(pageData)
const parsed = parseSyncEnvelope(JSON.stringify(message))

AI provider (OpenCode)

Optional integration via @opencode-ai/sdk.

import { createOpencodeClient } from '@opencode-ai/sdk'

const editor = init({
  iframeId: 'preview-iframe',
  data: pageData,
  aiProvider: {
    provider: 'opencode',
    mode: 'client',
    baseUrl: 'http://localhost:4096',

    // Optional: pass your own client
    client: createOpencodeClient({ baseUrl: 'http://localhost:4096' }),
  },
})

// Later
const client = await editor.ai?.getClient()

Events

The editor emits typed events:

  • componentSelect
  • componentEdit, componentEditJS
  • componentDuplicate, componentDelete
  • componentReorder
  • pageEditCSS, pageEditJSON
  • pageSaved, pageLoaded

See src/types.ts for the full event map.

Development

bun run build
bun run test

Project map

  • Core entry: src/core/init.ts
  • Page model: src/core/Page.ts
  • Data managers: src/core/ComponentManager.ts, src/core/StyleManager.ts, src/core/AssetManager.ts
  • Storage: src/core/StorageManager.ts
  • Demo: index.html + examples/quickstart.ts
  • Architecture + workflow: AGENTS.md