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-legacy-pass-through

v1.2.0

Published

Vite plugin to pass through legacy assets without transformation

Downloads

640

Readme

vite-legacy-pass-through ⚡

npm version npm downloads license CI

A Vite plugin that marks legacy libraries as external, preventing Rolldown from bundling them and causing CommonJS interop errors at runtime.


🧩 The story behind this plugin

This plugin was born out of a real-world headache while juggling a legacy component library and a newer one built on top of it.

The setup looked like this:

  • 🏛️ lib-legacy — an older component library with prop-types as a dependency. Used directly inside a Vite-powered web app, everything worked perfectly fine.
  • lib-awesome — a newer library built to override and extend UI and functionality from lib-legacy. Its components imported from lib-legacy, added behaviour, and re-exported them.

The problem surfaced the moment lib-awesome was built with Vite 8. Because it imported components from lib-legacy and re-exported them, Rolldown pulled prop-types deep into the bundle. The output contained a file named something like prop-types-a1b2c3d4.js with a bare require(...) call — which blew up at runtime in ESM environments:

ReferenceError: require is not defined
    at prop-types-a1b2c3d4.js:1:1

After a lot of reading about how Vite 8 and Rolldown handle module bundling and CJS/ESM interop, the cleanest escape hatch turned out to be telling Rolldown: "don't touch lib-legacy — let it pass through as-is."

That's exactly what this plugin does.

flowchart TD
    subgraph without["❌ Without the plugin"]
        A[lib-awesome] -->|imports & re-exports| B[lib-legacy]
        B -->|has dependency| C[prop-types CJS]
        A -->|build| D[Rolldown bundles everything]
        D --> E["prop-types-a1b2c3d4.js\n⚠️ require() call inside"]
        E --> F["💥 ReferenceError: require is not defined"]
    end

    subgraph with["✅ With vite-legacy-pass-through"]
        G[lib-awesome] -->|imports & re-exports| H[lib-legacy]
        H -->|has dependency| I[prop-types CJS]
        G -->|build| J[Rolldown sees lib-legacy as external]
        J --> K["lib-legacy stays as import statement\n✅ no bundling, no require()"]
        K --> L["🚀 Works at runtime"]
    end

⚠️ Important: Rolldown does not recommend marking packages as external this way in library builds. Doing so shifts the module resolution responsibility entirely to the consumer — they must have the library available in their environment. Use this plugin only when you understand that trade-off and the legacy library is guaranteed to be present at runtime.


📦 Installation

npm install -D vite-legacy-pass-through

🚀 Usage

// vite.config.ts
import { defineConfig } from 'vite'
import { legacyPassThrough } from 'vite-legacy-pass-through'

export default defineConfig({
  plugins: [
    legacyPassThrough({
      libs: ['lib-legacy'],
    }),
  ],
})

Multiple libraries:

legacyPassThrough({
  libs: ['lib-legacy', 'another-legacy-lib'],
})

With logging enabled (useful during development to confirm which imports are being bypassed):

legacyPassThrough({
  libs: ['lib-legacy'],
  showLog: true,
})

Output when showLog: true:

[vite-legacy-pass-through] Resolving: lib-legacy/components/Button
[vite-legacy-pass-through] Resolving: lib-legacy/utils/format

Running in both build and dev (e.g. if you need it in Storybook too):

legacyPassThrough({
  libs: ['lib-legacy'],
  apply: 'serve', // or omit for the default 'build'
})

Overriding the excluded extensions (replaces the default list entirely):

import { legacyPassThrough, DEFAULT_EXCLUDE_EXTENSIONS } from 'vite-legacy-pass-through'

legacyPassThrough({
  libs: ['lib-legacy'],
  // extend the default list
  excludeExtensions: [...DEFAULT_EXCLUDE_EXTENSIONS, '.yaml'],
})

⚙️ Options

| Option | Type | Required | Default | Description | |---|---|---|---|---| | libs | string[] | Yes | — | List of library names to mark as external. Empty strings are ignored. Must have at least one valid entry. | | apply | 'build' \| 'serve' | No | 'build' | When to apply the plugin. Defaults to 'build' to avoid interfering with dev tools like Storybook. | | excludeExtensions | string[] | No | See below | File extensions to skip — imports ending with these are left for Vite to handle normally. Replaces the default list when provided. | | showLog | boolean | No | false | Logs each resolved import to the console. |

Default excluded extensions

Imports from a matched lib that end with any of these extensions are not marked as external:

.css .scss .sass .less .styl
.png .jpg .jpeg .gif .svg .webp
.woff .woff2 .ttf .eot
.json .html

To disable exclusions entirely, pass excludeExtensions: [].


🔍 How it works

The plugin hooks into Vite's resolveId phase with enforce: 'pre' — meaning it runs before any other plugin — and marks any import whose path starts with <lib>/ as external. Rolldown then skips bundling it entirely and leaves the import statement untouched in the output.

import Button from 'lib-legacy/components/Button'
           ↓  resolveId hook intercepts
{ id: 'lib-legacy/components/Button', external: true }
           ↓  Rolldown skips it, output keeps the import
import Button from 'lib-legacy/components/Button'

Note: bare imports (import 'lib-legacy' without a subpath) are not affected — only subpath imports (lib-legacy/...) are matched. This is intentional to avoid over-matching.


🤔 When to use this

  • You are building a library that imports and re-exports from a legacy package.
  • That legacy package uses CommonJS internally (e.g. prop-types, older UI kits).
  • Vite 8 / Rolldown is wrapping those CJS modules into the bundle and generating require() calls that break in ESM environments.
  • The legacy package will be available at runtime in the consumer's environment (i.e. it is a peer or runtime dependency, not something you need to ship inside your bundle).

📋 Requirements

  • Vite: ^8.0.0
  • Node.js: >=18

📄 License

MIT