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

@improba/page-builder

v0.1.3

Published

A Vue 3 page builder component library — WYSIWYG editor with read/edit modes, custom component registry, and SSR support.

Readme

@improba/page-builder

Vue 3 library for building and rendering pages from a JSON tree. It provides read mode (static rendering, SSR-compatible) and edit mode (WYSIWYG editor with component palette, property panel, drag-and-drop, undo/redo). The backend sends a single JSON contract (IPageData); the frontend renders it and, in edit mode, allows visual editing.

In short: install the Vue plugin, provide IPageData, and use <PageBuilder> with mode="read" for display or mode="edit" for editing. You can register your own components (hero, cards, etc.) and use them as blocks in the tree.

Overview

Edit mode — WYSIWYG editor with component palette, property panel, and responsive preview.

Edit mode — toolbar, palette, canvas, properties

Read mode — Page rendering without editor UI (SSR-compatible).

Read mode — page rendering

To regenerate screenshots: docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run docs:screenshots".

Features

  • Read mode — Renders content from a JSON tree, SSR-compatible. Integrable with Nuxt or any Vue 3 app.
  • Edit mode — WYSIWYG editor with component palette, property panel, drag-and-drop, undo/redo, and responsive preview (desktop / tablet / mobile).
  • Component registry — Register custom Vue components (typed props, slots, edit metadata). Ships with layout and content components (PbColumn, PbRow, PbText, PbImage, etc.).
  • Single JSON contract — Backend sends one IPageData payload; frontend renders and edits it. Clear separation of concerns.

Quick Start

For a step-by-step guide (installation, first render, edit mode, custom components), see Quick Start.

Minimal summary:

Installation

npm install @improba/page-builder

Setup

import { createApp } from 'vue';
import { PageBuilderPlugin } from '@improba/page-builder';
import '@improba/page-builder/style.css';
import App from './App.vue';

const app = createApp(App);
app.use(PageBuilderPlugin);
app.mount('#app');

Usage

<script setup lang="ts">
import { PageBuilder } from '@improba/page-builder';
import type { IPageData } from '@improba/page-builder';

const pageData: IPageData = {
  meta: { id: '1', name: 'Home', url: '/', status: 'published' },
  content: {
    id: 0,
    name: 'PbColumn',
    slot: null,
    props: { gap: '16px' },
    children: [
      {
        id: 1,
        name: 'PbText',
        slot: 'default',
        props: { content: '<h1>Hello World</h1>' },
        children: [],
      },
    ],
  },
  layout: { id: 100, name: 'PbContainer', slot: null, props: {}, children: [] },
  maxId: 100,
  variables: {},
};
</script>

<template>
  <PageBuilder :page-data="pageData" mode="read" />
</template>

Edit Mode

<template>
  <PageBuilder
    :page-data="pageData"
    mode="edit"
    @save="handleSave"
    @change="handleChange"
  />
</template>

Custom Components

Register your own components for the page builder:

import { registerComponent } from '@improba/page-builder';
import type { IComponentDefinition } from '@improba/page-builder';
import MyHero from './MyHero.vue';

const myHero: IComponentDefinition = {
  name: 'MyHero',
  label: 'Hero Banner',
  description: 'Full-width hero section with title and CTA.',
  category: 'content',
  component: MyHero,
  slots: [{ name: 'default', label: 'Content' }],
  editableProps: [
    { key: 'title', label: 'Title', type: 'text', required: true },
    { key: 'backgroundImage', label: 'Background', type: 'image' },
  ],
  defaultProps: { title: 'Hero Title' },
};

registerComponent(myHero);

Built-in Components

| Component | Category | Description | |-----------|----------|-------------| | PbColumn | layout | Vertical flex container | | PbRow | layout | Horizontal flex container | | PbSection | layout | Full-width section with background | | PbContainer | layout | Centered max-width container | | PbText | content | Text/HTML block | | PbImage | media | Image with sizing options |

JSON Format

The page builder consumes a single IPageData JSON:

interface IPageData {
  meta: { id: string; name: string; url: string; status: string };
  content: INode;   // The page content tree
  layout: INode;    // The page layout wrapper
  maxId: number;    // For generating unique IDs
  variables: Record<string, string>;  // Template variables
}

interface INode {
  id: number;
  name: string;         // Must match a registered component
  slot: string | null;  // Target slot in parent
  props: Record<string, unknown>;
  children: INode[];
  readonly?: boolean;
}

Props support template variables: {{ PAGE_NAME }} is replaced at render time.

Development

All commands run through Docker (see AGENTS.md for details):

# Start dev server with hot reload
docker compose -f docker/docker-compose.yml up dev

# Run tests
docker compose -f docker/docker-compose.yml run --rm test

# Run Playwright end-to-end tests
docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run test:e2e"

# Build the library
docker compose -f docker/docker-compose.yml run --rm build

# Generate API reference docs (TypeDoc)
docker compose -f docker/docker-compose.yml run --rm dev npm run docs:api

# Install a new dependency
docker compose -f docker/docker-compose.yml run --rm dev npm install <package>

The dev server starts a Vite playground at http://localhost:5173 with a demo page for testing components.

End-to-End Tests (Playwright)

E2E tests live in tests/e2e/ and run against the playground via Playwright's webServer integration.

# Full E2E suite in Docker/CI
docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run test:e2e"

# Smoke workflow only (mode switch -> node selection -> prop edit -> save)
docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run test:e2e:smoke"

The e2e Docker image already includes Playwright browsers. If you need to (re)install browser binaries explicitly, run:

docker compose -f docker/docker-compose.yml run --rm e2e npm run e2e:install

Documentation

All documentation lives in docs/:

| Document | Description | |----------|-------------| | Quick Start | Get started: installation, setup, first render, edit mode, API | | Backend integration | Expected routes, contracts (IPageData, IPageSavePayload), validation, media, security | | Architecture | Overview, JSON schema, component system, rendering pipeline, edit mode architecture | | Features | Read mode, edit mode, component registry, JSON format | | Conventions | Code style, git workflow | | Roadmap | Phases and milestones | | API reference | TypeDoc output (public types and functions) |

To regenerate the API reference:

docker compose -f docker/docker-compose.yml run --rm dev npm run docs:api

Releases

Releases are tag-based. Pushing a tag release-vX.Y.Z triggers the GitHub Actions workflow (quality gate + publish to npm).

Creating a release

From the repo root, run the release script with the desired bump (patch is the default):

./scripts/release.sh [major|minor|patch]
# Examples:
./scripts/release.sh          # 0.1.0 → 0.1.1 (patch)
./scripts/release.sh minor    # 0.1.1 → 0.2.0
./scripts/release.sh major    # 0.2.0 → 1.0.0

The script bumps the version in package.json, commits, creates the tag release-vX.Y.Z, and pushes the branch and tag. The CI then runs the quality gate and publishes to npm. See Git Workflow — Releases for details.

Required repository secrets

  • NPM_TOKEN (npm automation token with publish permission on @improba/page-builder)

Local release verification and manual publish (Docker)

# Full release safety gate (typecheck + tests + build + types + docs)
docker compose -f docker/docker-compose.yml run --rm dev npm run release:prepare

# Inspect package contents before publish
docker compose -f docker/docker-compose.yml run --rm dev npm run release:dry-run

# Publish to npm manually (requires NPM_TOKEN in .env at project root)
source .env && docker compose -f docker/docker-compose.yml run --rm \
  -e NPM_TOKEN="$NPM_TOKEN" \
  dev sh -lc 'printf "//registry.npmjs.org/:_authToken=%s\n" "$NPM_TOKEN" > /tmp/.npmrc && npm publish --userconfig /tmp/.npmrc --access public'

See Git Workflow — Releases for the full release process (tag-based CI and manual publish).

API Reference

Vue Plugin

app.use(PageBuilderPlugin, {
  components: [],        // Additional IComponentDefinition[]
  registerBuiltIn: true, // Register PbColumn, PbRow, etc.
  globalName: 'PageBuilder', // Global component name (false to skip)
});

Registry Functions

| Function | Description | |----------|-------------| | registerComponent(def) | Register a single component | | registerComponents(defs) | Register multiple components | | replaceComponent(def) | Override an existing registration | | unregisterComponent(name) | Remove a registration | | getComponent(name) | Get definition by name | | resolveComponent(name) | Get Vue component (throws if missing) | | getRegisteredComponents() | Get all definitions | | getComponentsByCategory() | Get definitions grouped by category | | hasComponent(name) | Check if registered | | clearRegistry() | Remove all (testing) |

Tree Utilities

| Function | Description | |----------|-------------| | findNodeById(root, id) | Find node in tree | | findParent(root, childId) | Find parent of node | | removeNode(root, id) | Remove node from tree | | insertNode(root, parentId, node, index, slot) | Insert node | | moveNode(root, nodeId, parentId, index, slot) | Move node | | createNode(id, name, options) | Create new node | | walkTree(root, visitor) | Depth-first traversal | | cloneTree(node) | Deep clone | | interpolateProps(props, vars) | Replace template variables |

Composables

| Composable | Purpose | |------------|---------| | usePageBuilder(options) | Core state management (mode, content, history) | | useEditor() | Editor UI state (selection, drawers, viewport) | | useNodeTree(options) | Tree mutation operations | | useDragDrop() | Drag-and-drop interaction state |

License

MIT