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

vite-plugin-react-strangler

v1.5.1

Published

A Vite plugin for gradually migrating legacy server-side frontends to React using the Strangler Fig pattern

Readme

vite-plugin-react-strangler

A Vite plugin for gradually migrating legacy server-side frontends to React using the Strangler Fig pattern.

The Problem

Legacy server-side applications (PHP, Rails, Django, etc.) struggle with frontend modernization because traditional approaches require:

  • Complete rewrites instead of gradual migration
  • Complex integration between server templates and React
  • Manual component registration and loading
  • All-or-nothing bundle loading that hurts performance

The Solution

This plugin enables incremental React adoption in existing server applications by:

  • Automatic component discovery - just add React components to a folder
  • Web component generation - use <hello-widget> directly in server templates
  • Intelligent loading - components load only when actually used on the page
  • Zero integration complexity - one script tag replaces all manual setup

Automatically discovers React components, generates web component bundles, and creates a manifest for dynamic loading in any server-side framework.

Installation

npm install --save-dev vite-plugin-react-strangler
npm install react react-dom

Quick Start

1. Configure Vite

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { reactStrangler } from 'vite-plugin-react-strangler';

export default defineConfig({
  plugins: [
    react(),
    reactStrangler()
  ]
});

2. Create React Components

// components/Hello.tsx
import React, { useState } from 'react';
import styles from './Hello.module.css';

interface HelloProps {
  name?: string;
  onInputChange?: (value: string) => void;
}

export default function Hello({ name = "World", onInputChange }: HelloProps) {
  const [value, setValue] = useState(name);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newVal = e.target.value;
    setValue(newVal);
    onInputChange?.(newVal);
  };

  return (
    <div className={styles.hello}>
      <h3>Hello from React, {value}!</h3>
      <input value={value} onChange={handleChange} />
    </div>
  );
}

By default the plugin looks for components in components/ or src/components/. Using a different structure? Pass components: 'app/widgets' (or an array) to reactStrangler() and everything keeps working.

3. Build

npm run build

The plugin will automatically:

  • 🔍 Discover all components by scanning detected directories (defaults to components/ or src/components/)
  • 📦 Build individual component files (Hello.js, Card.js, etc.)
  • 🎨 Extract and bundle CSS with hashed names
  • 🌀 Inline discovery-level global CSS (Tailwind, base styles, etc.) so they load with discover.js
  • 📄 Generate manifest listing all available components with CSS references
  • 🧩 Register as web components (hello-widget, card-widget, etc.)
  • Shadow DOM encapsulation to isolate component styles from surrounding PHP markup
  • Generate type definitions on the web components for TypeScript support

Don’t have a discover.js yet? The plugin generates a minimal version automatically so you can start embedding widgets immediately.

4. Use in Any Server Framework

PHP

<script type="module" src="dist/discover.js"></script>
<hello-widget name="<?= $user['name'] ?>"></hello-widget>

Ruby on Rails

<%= javascript_include_tag "dist/discover.js", type: "module" %>
<hello-widget name="<%= @user.name %>"></hello-widget>

Django

{% load static %}
<script type="module" src="{% static 'dist/discover.js' %}"></script>
<hello-widget name="{{ user.name }}"></hello-widget>

Generated Output

The plugin automatically generates:

	dist/
	├── discover.js                    # Discovery runtime + inlined global styles
	├── components.manifest.json       # Component registry with CSS refs
	├── Hello.js                       # Individual component bundles
	├── Card.js
	├── DataTable.js
	└── assets/
	    ├── Hello-*.css               # Hashed CSS modules
	    ├── Card-*.css
	    └── jsx-runtime-*.js          # Shared React runtime

Automatic Features

  • 🔍 Component Discovery - Auto-detects component folders and scans for .tsx/.jsx files
  • 📦 Individual Bundles - Each component becomes a separate .js file
  • 🎨 CSS Extraction - CSS modules bundled with hashed names
  • 🌐 Global CSS Support - Plain .css/.scss/.sass/.less files auto-load once per component
  • 🛡️ Shadow DOM Isolation - Every component renders inside a shadow root so styles never leak into legacy pages
  • 📄 Manifest Generation - Components listed with paths and CSS references
  • 🧩 Web Component Registration - Auto-registers as custom elements
  • CSS Loading - Automatically loads component styles when used

PostCSS & Tailwind CSS

  • Honors any postcss.config.* (or inline plugin configuration) and re-processes extracted component CSS so tools like Tailwind, Autoprefixer, or cssnano run automatically.
  • Plain CSS imports (e.g. import './styles.css';) are recognized as component-scoped globals; they flow through PostCSS and load exactly once when the component script runs.
  • When the discovery entry (discover.js) emits a global CSS asset—such as a Tailwind base file—the plugin inlines the compiled stylesheet into discover.js and rewrites the emitted file. Including <script type="module" src="dist/discover.js"></script> is enough to make those utilities available on every page.

To enable Tailwind you can follow the standard Vite setup and the plugin will do the rest:

npm install -D tailwindcss @tailwindcss/postcss
npx tailwindcss init --ts
// tailwind.config.ts
import type { Config } from 'tailwindcss';

export default {
  content: ['./components/**/*.{js,jsx,ts,tsx}', './public/**/*.{php,html}'],
  theme: { extend: {} },
  plugins: [],
} satisfies Config;
// postcss.config.cjs
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {}
  }
};
/* components/styles/tailwind.css */
@import "tailwindcss";
@layer components {
  .strangler-section-title {
    @apply text-xl font-semibold;
  }
}

With that in place, importing the stylesheet once (e.g. import './components/styles/tailwind.css'; inside discover.js) is all that’s required—the plugin detects the discovery CSS output, inlines it into discover.js, and serves the utilities on every page. Component-level Tailwind usage works out of the box, and you can mix in module or global SCSS/Less files as shown in the PHP example.

If the tailwindcss CLI is present in your project, the plugin will also run it per component (using the component source as the content scope) and emit slim Tailwind bundles like assets/Card-tailwind.css. These are automatically referenced in the manifest so consumers can opt-in to component-scoped Tailwind. When the CLI is missing, the plugin simply falls back to the shared discovery stylesheet.

Shadow DOM Rendering

Every registered widget now renders into its own Shadow DOM root. The plugin injects the component’s CSS (module output, plain CSS/SCSS/Less, Tailwind extractions, etc.) directly into that shadow root, ensuring:

  • Legacy PHP markup keeps its original styling (no Tailwind preflight bleed).
  • Component styles can’t leak out, and external styles can’t accidentally override React widgets.
  • Styling remains deterministic regardless of where the custom element is placed.

Configuration Options

interface ReactStranglerOptions {
  /** Directories to scan for React components (auto-detected by default) */
  components?: string | string[];
  /** Discovery system entry point override (auto-detected or generated) */
  discoveryEntry?: string;
  /** PostCSS integration configuration */
  postcss?: boolean | ReactStranglerPostCSSOptions;
}

Control output locations and manifest destinations through standard Vite settings (e.g. build.outDir).

  • Components: Automatically picks components/ or src/components/ when present, falling back to src/ or components/ if nothing matches. Supply components (string or array) to override.
  • Discovery entry: Uses the first discover.{js,ts,jsx,tsx} it finds in the project root or src/. If none exist, a stub is generated in .vite-strangler-temp/ that exports the discovery runtime so you can ship immediately.
  • Manifest copies: The manifest now lives alongside your build output. If you need to duplicate it, add a post-build script tailored to your deployment.

Component Naming Convention

The plugin automatically generates web component names:

  • Hello.tsxhello-widget
  • UserCard.tsxusercard-widget
  • DataTable.tsxdatatable-widget
  • Card/Card.jsxcard-widget

How It Works

  1. Build Time: Plugin auto-discovers components from detected directories
  2. Bundle Generation: Creates individual .js files for each component
  3. CSS Extraction: Bundles CSS modules with hashed names
  4. Manifest Creation: Lists all components with JS/CSS file references
  5. Runtime Loading: Discovery system loads components and CSS on-demand

Browser API

The discovery system provides these global functions:

// List available components
window.componentDiscovery.listAvailable()

// Get loading stats
window.componentDiscovery.getStats()

// Load specific component
await window.componentDiscovery.loadComponent('hello-widget')

// Load multiple components synchronously (prevents FOUC)
await window.loadCriticalComponents(['hello-widget', 'card-widget'])

Project Structure

your-ui-library/
├── components/
│   ├── Hello.tsx
│   ├── Hello.module.css
│   ├── Card/
│   │   ├── Card.jsx
│   │   └── Card.module.css
│   └── DataTable.tsx
├── vite.config.js
├── discover.js                   # Entry point for discovery system
└── dist/                         # Generated by plugin
    ├── discover.js
    ├── components.manifest.json  # Component registry with CSS refs
    ├── Hello.js                  # Individual component bundles
    ├── Card.js
    └── assets/                   # CSS and shared assets

Examples

See examples/php-example for a complete working PHP application demonstrating component discovery, critical vs async loading, and Docker setup.

License

MIT