diff --git a/eslint.config.js b/eslint.config.js index 5ac10fcf2..989182fda 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,11 +9,12 @@ export default antfu({ 'plans', 'e2e/fixtures/**/dist', 'e2e/fixtures/**/.vite-devtools', - // The production playground is a standalone workspace (its own - // `pnpm-workspace.yaml`) that mirrors a real user install, so it uses plain - // dependency specifiers rather than the repo catalog and stays out of the - // repo-wide ESLint run. + // The production playgrounds are standalone workspaces (their own + // `pnpm-workspace.yaml`) that mirror a real user install, so they use + // plain dependency specifiers rather than the repo catalog and stay out + // of the repo-wide ESLint run. 'playgrounds/production', + 'playgrounds/production-nuxt', // `packages/oxc` is DevTools for the Oxc toolchain and dogfoods its own // oxlint/oxfmt on itself, whose formatting (e.g. `arrowParens: "avoid"`) // conflicts with this shared antfu config, so it is linted by its own diff --git a/playgrounds/production-nuxt/.gitignore b/playgrounds/production-nuxt/.gitignore new file mode 100644 index 000000000..e112af394 --- /dev/null +++ b/playgrounds/production-nuxt/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +.output +.nuxt +.tarballs +pnpm-lock.yaml +*.local diff --git a/playgrounds/production-nuxt/README.md b/playgrounds/production-nuxt/README.md new file mode 100644 index 000000000..7e55ab3c9 --- /dev/null +++ b/playgrounds/production-nuxt/README.md @@ -0,0 +1,71 @@ +# Production Nuxt Playground + +A **standalone pnpm workspace** running a **Nuxt 5** app with **Nuxt DevTools +4**, whose internal Vite DevTools dependency is overridden to the **local +built dist** — so you can test how Vite DevTools behaves when it's running as +Nuxt DevTools' own engine, rather than mounted directly as a Vite plugin. + +The playground at `playgrounds/production` installs `@vitejs/devtools` +straight into a plain Vite app. This one exercises the other real-world +integration path: Nuxt DevTools v4 (`@nuxt/devtools@^4.0.0-alpha.9`) depends +directly on `@vitejs/devtools` and `@vitejs/devtools-kit`, so a Nuxt app with +`devtools: { enabled: true }` gets Vite DevTools for free, through Nuxt +DevTools. + +Nuxt 5 isn't published to the `nuxt` package's stable dist-tags yet, so +`package.json` pulls it from Nuxt's [nightly release +channel](https://nuxt.com/docs/guide/going-further/nightly-release-channel) +via the `npm:nuxt-nightly@5x` alias — the same channel Nuxt itself documents +for trying the next major early. Nuxt DevTools 4 (`@nuxt/devtools@^4.0.0-alpha.9`) +is already on its regular dist-tags as a pre-release, so it's installed +directly with no alias needed. + +## How it works + +`scripts/pack-local.mjs` builds the monorepo and packs the six published +Vite DevTools packages into `.tarballs/`, exactly like `playgrounds/production` +(it shares the same `scripts/pack-devtools-tarballs.mjs` implementation). +Every run wipes and repacks under stable, version-agnostic names, then +installs with `--force`, so the install always reflects the tarballs that +were just packed, never a stale previous build. + +`pnpm-workspace.yaml` overrides `@vitejs/devtools`, `@vitejs/devtools-kit`, +and every sibling package to those tarballs. Because `@nuxt/devtools` pulls +those same two packages in as regular (non-`workspace:`) npm dependencies, +the override reaches into Nuxt DevTools' own dependency tree and forces it to +run on the exact Vite DevTools build under test here — not whatever version +`@nuxt/devtools` has pinned on npm. Everything else (`nuxt`, `vue`, +`@nuxt/devtools` itself, ...) 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 Nuxt dev server (Nuxt DevTools panel) +pnpm dev + +# Or produce a production build +pnpm build +``` + +To re-pack after changing package source without rebuilding untouched packages, +`pnpm run setup` re-runs the turbo build (cached), repacks, and reinstalls. If +you already have fresh `dist` output and only want to re-pack + reinstall: + +```sh +pnpm run setup:no-build +``` + +## Layout + +- `scripts/pack-local.mjs` — build + pack the published packages into `.tarballs/`, then install +- `nuxt.config.ts` — a plain user config with `devtools: { enabled: true }` +- `app/app.vue` — a minimal Nuxt app so the panels have real build/module data diff --git a/playgrounds/production-nuxt/app/app.vue b/playgrounds/production-nuxt/app/app.vue new file mode 100644 index 000000000..9fbf2cf51 --- /dev/null +++ b/playgrounds/production-nuxt/app/app.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/playgrounds/production-nuxt/nuxt.config.ts b/playgrounds/production-nuxt/nuxt.config.ts new file mode 100644 index 000000000..3b3a66708 --- /dev/null +++ b/playgrounds/production-nuxt/nuxt.config.ts @@ -0,0 +1,12 @@ +import { defineNuxtConfig } from 'nuxt/config' + +// This config is intentionally written the way a real user would write it: +// enabling Nuxt DevTools is the only integration point needed. Nuxt DevTools +// v4 (`@nuxt/devtools`, overridden here to resolve its internal Vite DevTools +// dependency from the local built dist — see `pnpm-workspace.yaml`) embeds +// Vite DevTools as its own engine, so there's no separate `@vitejs/devtools` +// Vite plugin to add by hand. +export default defineNuxtConfig({ + compatibilityDate: '2024-11-01', + devtools: { enabled: true }, +}) diff --git a/playgrounds/production-nuxt/package.json b/playgrounds/production-nuxt/package.json new file mode 100644 index 000000000..1060e2412 --- /dev/null +++ b/playgrounds/production-nuxt/package.json @@ -0,0 +1,21 @@ +{ + "name": "playground-production-nuxt", + "type": "module", + "version": "0.4.11", + "private": true, + "description": "Standalone workspace that installs a Nuxt 5 app with Nuxt DevTools 4 from npm, overriding its internal Vite DevTools dependency to the local built dist, to test the real Nuxt-DevTools-embeds-Vite-DevTools install path.", + "scripts": { + "setup": "node scripts/pack-local.mjs", + "setup:no-build": "node scripts/pack-local.mjs --no-build", + "dev": "nuxt dev --host 0.0.0.0", + "build": "nuxt build", + "preview": "nuxt preview" + }, + "dependencies": { + "nuxt": "npm:nuxt-nightly@5x", + "vue": "^3.5.39" + }, + "devDependencies": { + "@nuxt/devtools": "^4.0.0-alpha.9" + } +} diff --git a/playgrounds/production-nuxt/pnpm-workspace.yaml b/playgrounds/production-nuxt/pnpm-workspace.yaml new file mode 100644 index 000000000..92b509e66 --- /dev/null +++ b/playgrounds/production-nuxt/pnpm-workspace.yaml @@ -0,0 +1,29 @@ +# Standalone workspace root. +# +# This file exists so pnpm treats `playgrounds/production-nuxt` as its own, +# self-contained project instead of a member of the monorepo above it. +# +# Nuxt DevTools v4 (`@nuxt/devtools@^4.0.0-alpha.9`) embeds Vite DevTools: it +# depends directly on `@vitejs/devtools` and `@vitejs/devtools-kit` as its own +# engine. These overrides redirect that internal dependency — and every other +# Vite DevTools package — to the local built dist tarballs produced by +# `scripts/pack-local.mjs`, so Nuxt DevTools always runs on the exact Vite +# DevTools build under test here rather than whatever version `@nuxt/devtools` +# has pinned on npm. Everything else (nuxt, vue, ...) resolves from the public +# registry — exactly what a real user gets. +packages: [] + +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-nuxt/scripts/pack-local.mjs b/playgrounds/production-nuxt/scripts/pack-local.mjs new file mode 100644 index 000000000..f15f823f8 --- /dev/null +++ b/playgrounds/production-nuxt/scripts/pack-local.mjs @@ -0,0 +1,36 @@ +// @ts-check +/** + * Build the Vite DevTools packages, pack them into local tarballs, and + * install this playground from them — the same artifacts that would be + * published to npm. Nuxt DevTools v4 (`@nuxt/devtools`) depends directly on + * `@vitejs/devtools` and `@vitejs/devtools-kit`; `pnpm-workspace.yaml` + * overrides those (and every other Vite DevTools package) to these tarballs, + * so Nuxt DevTools runs on the build under test here. + * + * Every run wipes `.tarballs/` and repacks from scratch, then reinstalls with + * `--force` so pnpm never mistakes a freshly rebuilt tarball (same stable + * filename, new content) for the previous stale install. + * + * Usage: + * node scripts/pack-local.mjs # build the monorepo, pack, install + * node scripts/pack-local.mjs --no-build # skip the build, just (re)pack + install + */ +import { dirname, join, resolve } from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import { packDevToolsTarballs, run } from '../../../scripts/pack-devtools-tarballs.mjs' + +const scriptDir = dirname(fileURLToPath(import.meta.url)) +const playgroundDir = resolve(scriptDir, '..') +const repoRoot = resolve(playgroundDir, '../..') +const tarballDir = join(playgroundDir, '.tarballs') + +const skipBuild = process.argv.includes('--no-build') + +packDevToolsTarballs({ repoRoot, tarballDir, skipBuild }) + +// `--force` bypasses pnpm's content-addressable store cache, guaranteeing the +// install always reflects the tarballs that were just packed above. +run('pnpm install --no-frozen-lockfile --force', playgroundDir) + +console.log('\nDone. Run `pnpm dev` to start the playground.') diff --git a/playgrounds/production-nuxt/tsconfig.json b/playgrounds/production-nuxt/tsconfig.json new file mode 100644 index 000000000..4b34df157 --- /dev/null +++ b/playgrounds/production-nuxt/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.nuxt/tsconfig.json" +} diff --git a/playgrounds/production/README.md b/playgrounds/production/README.md index 1221a8483..68f60c2b9 100644 --- a/playgrounds/production/README.md +++ b/playgrounds/production/README.md @@ -12,14 +12,17 @@ and the `dist` output. ## How it works -`scripts/pack-local.mjs` builds the monorepo and runs `pnpm pack` on the five +`scripts/pack-local.mjs` builds the monorepo and runs `pnpm pack` on the six 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. +`-vitest`, `-oxc`). `pnpm pack` rewrites each package's `workspace:*` and +`catalog:*` protocols into concrete versions, producing the exact tarballs npm +would serve. Every run wipes `.tarballs/` and repacks from scratch under +stable, version-agnostic names, then installs the playground with `--force` — +so the install always reflects the tarballs that were just packed, never a +stale previous build. `pnpm-workspace.yaml` points `@vitejs/devtools` and +every inter-package dependency at those tarballs 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. @@ -40,21 +43,15 @@ 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: +`pnpm run setup` re-runs the turbo build (cached), repacks, and reinstalls. 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/` +- `scripts/pack-local.mjs` — build + pack the published packages into `.tarballs/`, then install - `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/package.json b/playgrounds/production/package.json index 09c3d405e..d6693bc0f 100644 --- a/playgrounds/production/package.json +++ b/playgrounds/production/package.json @@ -5,9 +5,8 @@ "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", + "setup": "node scripts/pack-local.mjs", + "setup:no-build": "node scripts/pack-local.mjs --no-build", "dev": "vite --host", "build": "vite build", "preview": "vite preview --host" diff --git a/playgrounds/production/scripts/pack-local.mjs b/playgrounds/production/scripts/pack-local.mjs index b6b25591d..ec0750498 100644 --- a/playgrounds/production/scripts/pack-local.mjs +++ b/playgrounds/production/scripts/pack-local.mjs @@ -1,78 +1,36 @@ // @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. + * Build the Vite DevTools packages, pack them into local tarballs, and + * install this playground from them — the same artifacts that would be + * published to npm, so the playground exercises the real user install path. + * + * Every run wipes `.tarballs/` and repacks from scratch, then reinstalls with + * `--force` so pnpm never mistakes a freshly rebuilt tarball (same stable + * filename, new content) for the previous stale install. * * 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 + * node scripts/pack-local.mjs # build the monorepo, pack, install + * node scripts/pack-local.mjs --no-build # skip the build, just (re)pack + install * - * `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`. + * The playground's package.json / pnpm-workspace.yaml point `@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' +import { packDevToolsTarballs, run } from '../../../scripts/pack-devtools-tarballs.mjs' 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)) -} +packDevToolsTarballs({ repoRoot, tarballDir, skipBuild }) -// 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}`) -} +// `--force` bypasses pnpm's content-addressable store cache, guaranteeing the +// install always reflects the tarballs that were just packed above. +run('pnpm install --no-frozen-lockfile --force', playgroundDir) -console.log('\nDone. Now run: pnpm install --no-frozen-lockfile') +console.log('\nDone. Run `pnpm dev` to start the playground.') diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2d3aa46ca..fa60d224d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -20,6 +20,7 @@ packages: - examples/* - playgrounds/* - '!playgrounds/production' + - '!playgrounds/production-nuxt' - docs - e2e - storybook diff --git a/scripts/pack-devtools-tarballs.mjs b/scripts/pack-devtools-tarballs.mjs new file mode 100644 index 000000000..4c7ccfe72 --- /dev/null +++ b/scripts/pack-devtools-tarballs.mjs @@ -0,0 +1,81 @@ +// @ts-check +/** + * Shared implementation behind every standalone playground's + * `scripts/pack-local.mjs` (currently `playgrounds/production` and + * `playgrounds/production-nuxt`). + * + * Builds the monorepo and packs the published Vite DevTools packages into + * local tarballs, the same artifacts that would be published to npm, so a + * standalone playground can install them and exercise the real user install + * path. `pnpm pack` resolves each package's `workspace:*` and `catalog:*` + * protocols into concrete versions, so the tarballs are exactly what end + * users receive. + * + * Each call wipes and fully repacks the target tarball directory — there is + * no incremental/stale state to reason about, and the produced filenames are + * stable (version-agnostic) so a playground's `package.json` / + * `pnpm-workspace.yaml` overrides never need to change between packs. + */ +import { execSync } from 'node:child_process' +import { mkdirSync, readdirSync, renameSync, rmSync } from 'node:fs' +import { join } from 'node:path' + +/** + * 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 + * is not packed here. + * + * @type {Array<{ name: string, dir: string, out: string }>} + */ +export const DEVTOOLS_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' }, +] + +/** @param {string} cmd @param {string} cwd */ +export function run(cmd, cwd) { + console.log(`\n$ ${cmd}\n (cwd: ${cwd})`) + execSync(cmd, { cwd, stdio: 'inherit' }) +} + +/** + * Build the monorepo (unless `skipBuild`) and pack every entry in + * `DEVTOOLS_PACKAGES` into `tarballDir`, replacing whatever was there before. + * + * @param {{ repoRoot: string, tarballDir: string, skipBuild?: boolean }} options + */ +export function packDevToolsTarballs({ repoRoot, tarballDir, skipBuild = false }) { + if (!skipBuild) + run('pnpm build', repoRoot) + + // Always start from a clean directory: a full wipe-then-repack means there + // is never a stale tarball left behind from a package that used to exist, + // an interrupted previous run, or a version-suffixed leftover. + 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 DEVTOOLS_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, so the latest + // pack always lands under the exact same filename as the previous one. + const produced = readdirSync(tarballDir) + for (const { name, out } of DEVTOOLS_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} -> ${out}`) + } +}