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

@accelerated-agency/visual-editor

v0.4.5

Published

Conversion visual editor as a reusable React package

Readme

@conversion/visual-editor

Reusable visual editor package for embedding Conversion's editing UI inside a host React app.

Exports

This package exports:

  • PlatformVisualEditor (recommended host wrapper)
  • EditorShell (low-level editor shell UI)
  • ToastProvider, useToast
  • visualEditorProxyPlugin (Vite dev-server plugin for /api/proxy and optional AI API route)
  • Types: PlatformVisualEditorProps, VisualEditorExperiment, VisualEditorVariation, VisualEditorTab, plus mutation/message types

Install

yarn add @conversion/visual-editor

Local development (package + consumer app)

Use this setup so changes in this package reflect in your consumer app immediately.

1) Consumer app dependency

In consumer app package.json:

"@conversion/visual-editor": "file:../conversion-visual-editor"

Run:

yarn install

2) Run package in watch mode

In conversion-visual-editor terminal:

npm run watch

3) Consumer app Vite config (local-dev only)

import { defineConfig } from "vite";
import path from "path";

export default defineConfig({
  resolve: {
    preserveSymlinks: true, // local-dev only
  },
  optimizeDeps: {
    exclude: ["@conversion/visual-editor"], // local-dev only
  },
  server: {
    fs: {
      allow: [path.resolve(__dirname, "..")], // local-dev only
    },
    watch: {
      ignored: ["!**/node_modules/@conversion/visual-editor/**"], // local-dev only
    },
  },
});

Start consumer app:

yarn dev --force

If one update is stale, clear cache once:

rm -rf node_modules/.vite

Remove these before/after npm publish

When you move from local file: development to npm package usage, remove/revert in consumer app:

  • file:../conversion-visual-editor dependency (replace with npm version)
  • resolve.preserveSymlinks: true
  • optimizeDeps.exclude: ["@conversion/visual-editor"] (if no longer needed)
  • server.fs.allow override used for local package path
  • server.watch.ignored override for package path

Required host setup

PlatformVisualEditor is designed to run in embedded mode and expects a proxied page-loading flow.

1) Add the Vite plugin (recommended)

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualEditorProxyPlugin } from "@conversion/visual-editor";

export default defineConfig({
  plugins: [
    react(),
    visualEditorProxyPlugin({
      // optional, only needed for AI test generation endpoint
      anthropicApiKey: process.env.ANTHROPIC_API_KEY,
      // optional, defaults to true
      enableGenerateTestApi: true,
    }),
  ],
});

What this plugin provides:

  • GET/POST /api/proxy for loading target storefront pages through your app origin
  • HTML rewriting for proxied pages (asset/action rewriting, popup suppression, bridge script injection)
  • POST /api/generate-test endpoint (optional; used by AI panel)

2) Ensure /bridge.js is served by your app

The proxy injects:

<script src="/bridge.js"></script>

Your host app must serve this file at /bridge.js for iframe/editor communication to work correctly.

3) Mount PlatformVisualEditor in your route/page

import { PlatformVisualEditor } from "@conversion/visual-editor";

export function VisualEditorPage() {
  return (
    <PlatformVisualEditor
      experiment={{
        experimentId: "exp_1",
        name: "Homepage Hero Test",
        status: "draft",
        pageUrl: "https://store.example.com",
        editorPassword: "",
        variations: [
          {
            _id: "control",
            iid: 0,
            name: "Control",
            baseline: true,
            traffic_allocation: 50,
            changesets: "[]",
            csscode: "",
            jscode: "",
          },
        ],
      }}
      onRequestSave={async ({ experimentId, variations, hash }) => {
        // Save to your backend
        // hash is set for save-and-navigate requests (for example "#code-editor")
      }}
      onNavigateRequested={(hash) => {
        // Move to host tab/route after successful save-and-navigate
      }}
      onEditorUrlChanged={async ({ url, password }) => {
        // Optional: persist latest URL/password selection
      }}
      onClose={() => {
        // Close editor page/modal
      }}
    />
  );
}

Environment variables

Required

  • None for core visual-editor functionality.

Optional

  • ANTHROPIC_API_KEY: required only if you use AI test generation (/api/generate-test).
    • If missing and AI route is enabled, AI generation requests will fail with a server error.
    • You can disable the route with visualEditorProxyPlugin({ enableGenerateTestApi: false }).

Data contracts

VisualEditorExperiment

  • experimentId?: string
  • name?: string
  • status?: string
  • pageUrl?: string
  • editorPassword?: string
  • variations?: VisualEditorVariation[]

VisualEditorVariation

  • _id: string
  • iid?: number
  • name: string
  • baseline?: boolean
  • traffic_allocation?: number
  • csscode?: string
  • jscode?: string
  • changesets?: string (JSON string)

Save and navigation flow

When users click save/finalize in the editor:

  1. Editor emits save-experiment or save-and-navigate
  2. PlatformVisualEditor calls your onRequestSave(payload)
  3. On success:
    • dirty state resets
    • onSaveSuccess is called
    • for save-and-navigate, onNavigateRequested(hash) is called
  4. On failure:
    • onSaveError(error) is called

Main props reference (PlatformVisualEditor)

  • channel?: string postMessage channel name, default conversion-platform
  • embeddedGlobalKey?: string global embedded marker, default __CONVERSION_EMBEDDED__
  • className?: string outer wrapper classes
  • editorClassName?: string editor content wrapper classes
  • showHeader?: boolean show default header
  • title?: string header title override
  • status?: string header status badge
  • tabs?: VisualEditorTab[] tabs to render in default header
  • activeTab?: string active tab label in default header
  • loading?: boolean loading state
  • error?: string | null error state
  • showCloseButton?: boolean toggle default close button
  • closeLabel?: string close button text
  • loadingText?: string loading text
  • saveDebounceSkips?: number mutation-change events to ignore before setting dirty
  • experiment?: VisualEditorExperiment input experiment payload
  • onClose?: () => void close handler
  • onTabChange?: (tab: VisualEditorTab) => void tab click handler
  • onDirtyChange?: (dirty: boolean) => void dirty-state callback
  • onEditorReady?: () => void called after editor bootstraps
  • onSaveSuccess?: (payload) => void called after successful save
  • onSaveError?: (error: unknown) => void called on save failure
  • onRequestSave?: (payload) => Promise<void> | void required to persist changes
  • onEditorUrlChanged?: (payload) => Promise<void> | void URL/password change callback
  • onNavigateRequested?: (hash: string) => void post-save navigation callback
  • onRestoreVersion?: (payload) => void restore callback
  • onDiscardDirty?: () => boolean | Promise<boolean> custom unsaved-changes guard
  • renderHeader?: (...) => ReactNode custom header renderer
  • renderLoading?: () => ReactNode custom loading renderer
  • renderError?: (message) => ReactNode custom error renderer

Known integration notes

  • Keep the editor component mounted while async save is running.
  • The editor loads target pages via /api/proxy; cross-origin requests made by the target page may still need proxying/server-side handling.
  • If you customize channel, keep the same value on both host and editor surfaces.