diff --git a/packages/oxc/package.json b/packages/oxc/package.json
index 24be793de..4106f1562 100644
--- a/packages/oxc/package.json
+++ b/packages/oxc/package.json
@@ -29,6 +29,7 @@
"devtools-oxc": "./bin/devtools-oxc.mjs"
},
"files": [
+ "bin",
"dist"
],
"scripts": {
diff --git a/playgrounds/production/.gitignore b/playgrounds/production/.gitignore
new file mode 100644
index 000000000..0d856d583
--- /dev/null
+++ b/playgrounds/production/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+.tarballs
+pnpm-lock.yaml
+*.local
diff --git a/playgrounds/production/README.md b/playgrounds/production/README.md
new file mode 100644
index 000000000..1221a8483
--- /dev/null
+++ b/playgrounds/production/README.md
@@ -0,0 +1,60 @@
+# Production Playground
+
+A **standalone pnpm workspace** that installs Vite DevTools from the **local
+built dist**, so you can test the tools the way a real user consumes them —
+from the packaged artifacts, with no monorepo aliases, `src` imports, or
+`workspace:` links.
+
+The regular playground at `packages/core/playground` aliases everything to
+source for fast iteration. This one does the opposite: it packs the published
+packages into tarballs and installs them, exercising the real npm install path
+and the `dist` output.
+
+## How it works
+
+`scripts/pack-local.mjs` builds the monorepo and runs `pnpm pack` on the five
+published packages (`@vitejs/devtools`, `-kit`, `-rolldown`, `-vite`,
+`-vitest`). `pnpm pack` rewrites each package's `workspace:*` and `catalog:*`
+protocols into concrete versions, producing the exact tarballs npm would serve.
+The tarballs land in `.tarballs/` under stable names; `pnpm-workspace.yaml`
+points `@vitejs/devtools` and every inter-package dependency at them through
+`overrides`. Everything else (devframe, `@devframes/*`, vite, vue) resolves
+from the public registry — just like a real install.
+
+Its own `pnpm-workspace.yaml` keeps it isolated from the monorepo above, so a
+`pnpm install` here builds an independent dependency tree with its own lockfile.
+
+## Usage
+
+From this directory:
+
+```sh
+# Build the monorepo, pack the packages, and install them
+pnpm run setup
+
+# Start the dev server (embedded DevTools panel)
+pnpm dev
+
+# Or produce a production build (standalone DevTools output)
+pnpm build
+```
+
+To re-pack after changing package source without rebuilding untouched packages,
+`pnpm run setup` re-runs the turbo build (cached) and re-packs. If you already
+have fresh `dist` output and only want to re-pack + reinstall:
+
+```sh
+pnpm run setup:no-build
+```
+
+If pnpm doesn't pick up freshly re-packed tarballs, force it:
+
+```sh
+pnpm install --no-frozen-lockfile --force
+```
+
+## Layout
+
+- `scripts/pack-local.mjs` — build + pack the published packages into `.tarballs/`
+- `vite.config.ts` — a plain user config importing `DevTools` from `@vitejs/devtools`
+- `src/` — a minimal Vue app so the panels have real build/module data
diff --git a/playgrounds/production/index.html b/playgrounds/production/index.html
new file mode 100644
index 000000000..03b51a551
--- /dev/null
+++ b/playgrounds/production/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Vite DevTools — Production Playground
+
+
+
+
+
+
diff --git a/playgrounds/production/package.json b/playgrounds/production/package.json
new file mode 100644
index 000000000..03e0069bc
--- /dev/null
+++ b/playgrounds/production/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "playground-production",
+ "type": "module",
+ "version": "0.0.0",
+ "private": true,
+ "description": "Standalone workspace that installs Vite DevTools from the local built dist, to test the real user install experience.",
+ "scripts": {
+ "setup": "node scripts/pack-local.mjs && pnpm install --no-frozen-lockfile",
+ "setup:no-build": "node scripts/pack-local.mjs --no-build && pnpm install --no-frozen-lockfile",
+ "pack:local": "node scripts/pack-local.mjs",
+ "dev": "vite --host",
+ "build": "vite build",
+ "preview": "vite preview --host"
+ },
+ "dependencies": {
+ "@vitejs/devtools": "file:.tarballs/vitejs-devtools.tgz",
+ "vue": "^3.5.39"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^6.0.7",
+ "vite": "^8.1.4"
+ }
+}
diff --git a/playgrounds/production/pnpm-workspace.yaml b/playgrounds/production/pnpm-workspace.yaml
new file mode 100644
index 000000000..a01b2017d
--- /dev/null
+++ b/playgrounds/production/pnpm-workspace.yaml
@@ -0,0 +1,27 @@
+# Standalone workspace root.
+#
+# This file exists so pnpm treats `playgrounds/production` as its own,
+# self-contained project instead of a member of the monorepo above it.
+# It resolves `@vitejs/devtools` (and friends) from the local built dist
+# tarballs produced by `scripts/pack-local.mjs`, and every other dependency
+# (devframe, @devframes/*, vite, vue, ...) from the public registry — exactly
+# what a real user gets when they `pnpm add -D @vitejs/devtools`.
+packages: []
+
+# Resolve Vite DevTools and every inter-package dependency from the local built
+# dist tarballs produced by `scripts/pack-local.mjs`. Everything else resolves
+# from the public registry, exactly like a real user install.
+overrides:
+ '@vitejs/devtools': 'file:.tarballs/vitejs-devtools.tgz'
+ '@vitejs/devtools-kit': 'file:.tarballs/vitejs-devtools-kit.tgz'
+ '@vitejs/devtools-rolldown': 'file:.tarballs/vitejs-devtools-rolldown.tgz'
+ '@vitejs/devtools-vite': 'file:.tarballs/vitejs-devtools-vite.tgz'
+ '@vitejs/devtools-vitest': 'file:.tarballs/vitejs-devtools-vitest.tgz'
+ '@vitejs/devtools-oxc': 'file:.tarballs/vitejs-devtools-oxc.tgz'
+
+verifyDepsBeforeRun: false
+allowBuilds:
+ esbuild: true
+ rolldown: true
+ unrs-resolver: true
+minimumReleaseAge: 0
diff --git a/playgrounds/production/scripts/pack-local.mjs b/playgrounds/production/scripts/pack-local.mjs
new file mode 100644
index 000000000..b6b25591d
--- /dev/null
+++ b/playgrounds/production/scripts/pack-local.mjs
@@ -0,0 +1,78 @@
+// @ts-check
+/**
+ * Build the Vite DevTools packages and pack them into local tarballs that this
+ * standalone playground installs — the same artifacts that would be published
+ * to npm, so the playground exercises the real user install path.
+ *
+ * Usage:
+ * node scripts/pack-local.mjs # build the monorepo, then pack
+ * node scripts/pack-local.mjs --no-build # skip the build, just (re)pack dist
+ *
+ * `pnpm pack` resolves each package's `workspace:*` and `catalog:*` protocols
+ * into concrete versions, so the tarballs are exactly what end users receive.
+ * The playground's package.json then points `@vitejs/devtools` and every
+ * inter-package dependency at these tarballs via `pnpm.overrides`.
+ */
+import { execSync } from 'node:child_process'
+import { mkdirSync, readdirSync, renameSync, rmSync } from 'node:fs'
+import { dirname, join, resolve } from 'node:path'
+import process from 'node:process'
+import { fileURLToPath } from 'node:url'
+
+const scriptDir = dirname(fileURLToPath(import.meta.url))
+const playgroundDir = resolve(scriptDir, '..')
+const repoRoot = resolve(playgroundDir, '../..')
+const tarballDir = join(playgroundDir, '.tarballs')
+
+/**
+ * The set of packages published to npm as part of Vite DevTools.
+ * `@vitejs/devtools-ui` is private (bundled into the others at build time) and
+ * `@vitejs/devtools-oxc` is a separate opt-in package, so neither is packed.
+ *
+ * @type {Array<{ name: string, dir: string, out: string }>}
+ */
+const PACKAGES = [
+ { name: '@vitejs/devtools', dir: 'packages/core', out: 'vitejs-devtools.tgz' },
+ { name: '@vitejs/devtools-kit', dir: 'packages/kit', out: 'vitejs-devtools-kit.tgz' },
+ { name: '@vitejs/devtools-rolldown', dir: 'packages/rolldown', out: 'vitejs-devtools-rolldown.tgz' },
+ { name: '@vitejs/devtools-vite', dir: 'packages/vite', out: 'vitejs-devtools-vite.tgz' },
+ { name: '@vitejs/devtools-vitest', dir: 'packages/vitest', out: 'vitejs-devtools-vitest.tgz' },
+ { name: '@vitejs/devtools-oxc', dir: 'packages/oxc', out: 'vitejs-devtools-oxc.tgz' },
+]
+
+const skipBuild = process.argv.includes('--no-build')
+
+/** @param {string} cmd @param {string} cwd */
+function run(cmd, cwd) {
+ console.log(`\n$ ${cmd}\n (cwd: ${cwd})`)
+ execSync(cmd, { cwd, stdio: 'inherit' })
+}
+
+if (!skipBuild) {
+ run('pnpm build', repoRoot)
+}
+
+rmSync(tarballDir, { recursive: true, force: true })
+mkdirSync(tarballDir, { recursive: true })
+
+// Pack each package. `--ignore-scripts` skips the `prepack` rebuild since the
+// monorepo build above already produced fresh dist output.
+for (const { name, dir } of PACKAGES) {
+ console.log(`\nPacking ${name}...`)
+ run(`pnpm pack --config.ignore-scripts=true --pack-destination "${tarballDir}"`, join(repoRoot, dir))
+}
+
+// pnpm writes `-.tgz`; rename to the stable, version-agnostic
+// names the playground's package.json / overrides reference.
+const produced = readdirSync(tarballDir)
+for (const { name, out } of PACKAGES) {
+ const base = name.replace('@', '').replace('/', '-')
+ const pattern = new RegExp(`^${base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-\\d[^/]*\\.tgz$`)
+ const match = produced.find(f => pattern.test(f))
+ if (!match)
+ throw new Error(`Could not find packed tarball for ${name} (expected ${base}-.tgz)`)
+ renameSync(join(tarballDir, match), join(tarballDir, out))
+ console.log(`✓ ${name} -> .tarballs/${out}`)
+}
+
+console.log('\nDone. Now run: pnpm install --no-frozen-lockfile')
diff --git a/playgrounds/production/src/App.vue b/playgrounds/production/src/App.vue
new file mode 100644
index 000000000..e8f21d13d
--- /dev/null
+++ b/playgrounds/production/src/App.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
Vite DevTools — Production Playground
+
+ This app installs @vitejs/devtools from the local built dist,
+ inside a standalone pnpm workspace, to test the real user install
+ experience.
+
+
+
+
+
+
diff --git a/playgrounds/production/src/env.d.ts b/playgrounds/production/src/env.d.ts
new file mode 100644
index 000000000..f4556c763
--- /dev/null
+++ b/playgrounds/production/src/env.d.ts
@@ -0,0 +1,8 @@
+///
+
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+
+ const component: DefineComponent, Record, unknown>
+ export default component
+}
diff --git a/playgrounds/production/src/main.ts b/playgrounds/production/src/main.ts
new file mode 100644
index 000000000..fe5bae398
--- /dev/null
+++ b/playgrounds/production/src/main.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import './style.css'
+
+createApp(App).mount('#app')
diff --git a/playgrounds/production/src/style.css b/playgrounds/production/src/style.css
new file mode 100644
index 000000000..7a8a4fbcf
--- /dev/null
+++ b/playgrounds/production/src/style.css
@@ -0,0 +1,2 @@
+:root { color-scheme: light dark; }
+body { margin: 0; }
diff --git a/playgrounds/production/tsconfig.json b/playgrounds/production/tsconfig.json
new file mode 100644
index 000000000..126e525d6
--- /dev/null
+++ b/playgrounds/production/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "strict": true,
+ "jsx": "preserve",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+ "noEmit": true,
+ "types": ["vite/client"]
+ },
+ "include": ["src", "vite.config.ts", "env.d.ts"]
+}
diff --git a/playgrounds/production/vite.config.ts b/playgrounds/production/vite.config.ts
new file mode 100644
index 000000000..545b29473
--- /dev/null
+++ b/playgrounds/production/vite.config.ts
@@ -0,0 +1,20 @@
+import { DevTools } from '@vitejs/devtools'
+import Vue from '@vitejs/plugin-vue'
+import { defineConfig } from 'vite'
+
+// This config is intentionally written the way a real user would write it:
+// it imports `DevTools` from the published `@vitejs/devtools` package (resolved
+// here from the local built dist), with no monorepo aliases or `src` imports.
+export default defineConfig({
+ plugins: [
+ Vue(),
+ DevTools(),
+ ],
+ build: {
+ rolldownOptions: {
+ // Enable Rolldown's devtools build instrumentation so the Rolldown
+ // panels have real build data to show.
+ devtools: {},
+ },
+ },
+})