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

crsdevtool

v1.1.0

Published

CLI tool to scaffold a Playwright/Vite project template.

Downloads

205

Readme

crsdevtool

A CLI tool for rapid A/B testing and experimentation on real websites (for development and internal use only, not for production deployment). It scaffolds a TypeScript/Vite project, lets you write and organize code in src/, and builds a single JavaScript file ready for injection into any site. Live preview happens in a Playwright-driven browser that injects your bundle into the real page — no CORS or CSP issues.

Features

  • Project scaffolding: create a new experiment project with a single command.
  • Native npm / pnpm / yarn support: the CLI detects which package manager invoked it and uses the same one everywhere.
  • Vite 8 + TypeScript: modern build with live rebuild, SCSS support, and the native oxc minifier.
  • Preview on real sites: Playwright opens your test URL and injects the built script on every page load.
  • Reusable utilities: analytics (pushData), DOM manipulation (jQuery-like $el), visibility tracking, and more — imported from 'crsdevtool' and tree-shaken into the bundle.
  • Build-time injection: developer/project info (startLog) and the Clarity heatmap call are injected during build and can't be accidentally deleted.
  • Git initialization: every scaffolded project starts with a repo and an initial commit.

Requirements

  • Node.js >=22.12.0 (Vite 8 + native TypeScript type stripping; no ts-runner needed)
  • npm, pnpm, or yarn

Quick Start

# 1. Scaffold a project (npm)
npx crsdevtool my-project
# …or with pnpm
pnpm dlx crsdevtool my-project

# 2. Set your test URL in crs.config.ts, then develop with live preview
npm run dev            # pnpm dev

# 3. Ship the optimized bundle
npm run build          # pnpm build

The CLI detects the invoking package manager (npx → npm, pnpm dlx → pnpm) and uses it to install dependencies in the new project. Override with --pm:

npx crsdevtool my-project --pm=pnpm

Scaffolding

Interactive (default)

npx crsdevtool my-project     # or: npx crsdevtool .  (current directory)

Prompts for developer name, project name, test URL, language (TypeScript or JavaScript), and heatmap tool (Clarity or none; arrow-key selection). Everything is saved to crs.config.ts.

One-line (all fields via CLI args)

npx crsdevtool my-app "Project Name" "Dev Name" "https://test.url" clarity event_name

Arguments: [target] [project name] [dev name] [test url] [tool: clarity|none] [event name]. Missing arguments are prompted interactively.

Empty install (no prompts)

npx crsdevtool my-app -y      # or --yes

Creates a project with empty fields and no heatmap tool.

Flags

| Flag | Effect | |------|--------| | -y, --yes | Skip all prompts, use empty defaults | | --js | Scaffold a plain JavaScript project: src/index.js, no tsconfig.json, no TypeScript devDependencies | | --pm=<npm\|pnpm\|yarn> | Force a package manager for dependency installation | | --playwright-cli | Additionally install @playwright/cli and its agent skills |

Project Structure

my-project/
  crs.config.ts        # Project config (dev, name, testUrl, heatmapTool, heatmapEventName)
  package.json         # Dependencies and scripts
  tsconfig.json        # TypeScript config
  .gitignore           # Node, build output, Playwright artifacts, etc.
  src/
    index.ts           # Your main entry file (index.js when scaffolded with --js)
    globals.css        # Your CSS styles
  bin/                 # Build machinery (you normally don't touch this)
    app.ts             # Playwright runner: opens browser, injects build/index.js
    vite.config.mjs    # Vite build configuration
    global.d.ts        # Global type declarations (dataLayer, clarity, …)

Commands

Examples use npm; with pnpm the same commands are pnpm dev, pnpm start --browser=edge, pnpm build.

npm run dev / npm start — development with live preview

Both commands are identical:

npm run dev
npm start -- --browser=edge --device="iPhone 12"
  • Watches src/ and rebuilds on every change.
  • Opens a Playwright browser with DevTools, navigates to testUrl from crs.config.ts, and injects build/index.js into the page.
  • Re-injects on every page load and reloads the page after each rebuild.
  • The dev bundle is unminified with sourcemaps: errors and console.log() point to src/index.ts:42, not to minified code.
  • Exit with Ctrl+C.

Browser and device options

npm run dev -- --browser=webkit
npm start -- --browser=edge --device="iPad Pro"
  • Browsers: chromium (default), webkit, edge (uses the system Edge)
  • Devices: any name from Playwright's device registry ("iPhone 12", "Pixel 5", …)

npm run build — production bundle

npm run build
ls -lh build/index.js   # check the size before shipping

One-time optimized build of build/index.js, ready for injection on live sites.

| | dev / start | build | |---|---|---| | Browser preview (Playwright) | ✅ | ❌ | | Sourcemaps | ✅ | ❌ | | Minification (oxc) | ❌ readable output | ✅ | | Your console.* calls | ✅ kept | ❌ stripped | | Core APIs (pushData, startLog, …) | ✅ | ✅ always kept |

Typical production sizes: a simple test is roughly 8–20KB raw (2–8KB gzipped), depending on how much code and CSS you write. If the size jumps unexpectedly, check for new imports — every external dependency is bundled in.

Creating Markup

A/B tests constantly create new elements. Besides plain template literals, the tool supports two approaches with proper syntax highlighting in any editor — no plugins required.

Static blocks: .html files

Import any HTML file as a string with Vite's ?raw suffix — the file gets full native HTML highlighting because it is an HTML file, and it's inlined into the bundle at build time:

// src/components/promo.html — regular HTML file
import promoHtml from './components/promo.html?raw';

document.querySelector('.target').insertAdjacentHTML('beforeend', promoHtml);

Dynamic markup: JSX without a framework

.tsx / .jsx files compile straight to real DOM elements via crsdevtool/jsx-runtime (~150 bytes in the bundle, tree-shaken away when unused — no React, no virtual DOM). JSX highlighting is built into every editor, and in .tsx TypeScript also checks your markup:

// src/components/banner.tsx — no imports needed, the runtime is wired automatically
export const Banner = (price: string) => (
  <div class="crs-banner">
    <span class="crs-banner__title">Special offer</span>
    <b class="crs-banner__price">{price}</b>
    <button class="crs-banner__cta" onClick={() => trackClick()}>Buy now</button>
  </div>
);

// src/index.ts
import { Banner } from './components/banner.tsx';
document.querySelector('.target').append(Banner('$9.99'));
  • Attributes are written as in HTML (class, not className); onClick/onInput/… attach real event listeners.
  • Expressions interpolate with {value}, fragments (<>…</>) and function components work as expected.
  • The result is an HTMLElement — append it with append() / insertAdjacentElement(), query into it, attach more listeners.
  • Works in both project flavors: .tsx in TypeScript projects, .jsx in --js projects.

How the Build Works

  • Vite 8 (rolldown) bundles src/index.ts into a single IIFE file — no chunks, safe to inject as one <script>.
  • Production builds are minified by the native oxc minifier with console.* stripping; dev builds skip minification entirely and keep sourcemaps.
  • Tree-shaking removes unused crsdevtool exports: if you only use pushData(), nothing else is bundled.
  • CSS (including SCSS) is minified with LightningCSS and inlined via Vite's ?raw imports.
  • startLog(...) (and clarityInterval(...) when Clarity is selected) are prepended to your entry at build time from crs.config.ts.

Bundle-size tips: prefer native browser APIs over utility libraries ([...new Set(arr)] instead of lodash), keep styles small, and re-check ls -lh build/index.js after adding imports.

Typical Workflow

npx crsdevtool my-test          # scaffold (git repo + initial commit included)
cd my-test
# set testUrl in crs.config.ts

npm run dev                     # write code, test in the Playwright browser
git add . && git commit -m "Add experiment logic"

npm run build                   # produce the final build/index.js for injection

Troubleshooting

| Problem | Solution | |---------|----------| | Source files not shown in console | Use npm run dev / npm start, not npm run build; dev builds inline the sourcemap into build/index.js | | Browser won't open | Check testUrl in crs.config.ts is set and reachable; verify Playwright: npm ls playwright | | Browsers not installed | Run npx playwright install chromium webkit (normally done by postinstall) | | Changes don't reload the page | Watch mode runs only in dev / start, not in build | | Bundle too large | Compare after npm run build (dev bundles are intentionally bigger); look for heavy imports | | console.log missing on the live site | Expected: production builds strip console.*; pushData/analytics still work |

API Reference

Core Functions

startLog({ name, dev })

Logs experiment/project info in the browser console with styled output.

pushData(name, desc, type, loc?)

Pushes analytics events to window.dataLayer for Google Analytics 4.

  • type: 'click' | 'view' | 'submit' | 'input' | 'change' | 'error' | 'success' | 'other'

clarityInterval(name)

Sets Microsoft Clarity custom event (only works when Clarity is enabled).

log(text, style?)

Console logging with colored styles.

  • style: 'info' | 'warn' | 'error' | 'success'

DOM Manipulation

$el(selector) - NativeQuery Class

jQuery-like DOM manipulation library without external dependencies.

// Select elements and chain methods
$el('.button')
  .addClass('active')
  .removeClass('inactive')
  .toggleClass('highlighted')
  .style('color', 'blue')
  .attr('data-test', 'value')
  .text('New Text')
  .html('<span>HTML Content</span>')
  .on('click', (event) => console.log('Clicked!'))
  .each((element, index) => {
    // Process each element
  })
  .find('.child-element')
  .addClass('found');

waitEl<T>(selector): Promise<T>

Wait for an element to appear in the DOM using MutationObserver.

Utility Functions

loadScriptsOrStyles(urls: string[])

Dynamically load external scripts (.js) or stylesheets (.css). Prevents duplicate loading.

visibilityOfTime(selector, eventName, visiblePlace, description?, time?, threshold?)

Track element visibility using IntersectionObserver. Fires analytics event after element is visible for specified time.

scrollToElement(selector, offset?)

Smooth scroll to an element with optional offset.

checkScrollSpeed(selector, callback)

Monitor scroll speed and direction. Works with window or any scrollable element.

getCookies(name)

Get cookie value by name.

slideup(element, duration?) / slidedown(element, duration?)

CSS transition-based slide animations.

Usage Example

import {
  startLog,
  pushData,
  clarityInterval,
  $el,
  waitEl,
  log,
  visibilityOfTime,
  scrollToElement
} from 'crsdevtool';

// Basic logging and analytics
pushData('button_click', 'User clicked CTA', 'click', 'header');

// DOM manipulation with the built-in jQuery-like library
$el('.button')
  .addClass('active')
  .style('color', 'red')
  .on('click', () => {
    log('Button clicked!', 'success');
  });

// Wait for elements and track visibility
await waitEl('.dynamic-content');
visibilityOfTime('.product-card', 'product_view', 'homepage', 'Product card viewed', 2000);

// Smooth scrolling
scrollToElement('.target-section', 100);

Note: startLog and the Clarity call are injected automatically at build time — you don't need to call them yourself.

Legacy Functions (Removed)

  • $$el — use $el().elements instead
  • waitForElement — use waitEl instead
  • blockVisibility — use visibilityOfTime instead

Internal Details

  • The CLI copies only user-facing files from the template; build machinery lives in bin/ of the scaffolded project.
  • Dev/start spawn two linked processes: vite build --watch and the Playwright runner (bin/app.ts, executed by Node's native TypeScript type stripping). If either exits, the other is killed.
  • No dependency ships native build scripts, so pnpm installs work without any allowBuilds approvals.
  • The Playwright context runs with bypassCSP: true, which is what makes injection work on arbitrary sites.
  • Browser and device options are passed to the Playwright runner via environment variables.
  • crs.config.ts is the single source of truth for project metadata; both the Vite plugin and the Playwright runner read it.

Repository

License

MIT