Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/devtools-onboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The entry point of [Nuxt DevTools](https://devtools.nuxt.com). It is a tiny Nuxt module that Nuxt 5 ships instead of depending on `@nuxt/devtools` directly:

- when `@nuxt/devtools` is installed in your project, it loads it;
- when Nuxt DevTools is installed in your project, it loads it: `@nuxt/devtools` v3 or v4, or a [nightly release](https://devtools.nuxt.com/guide/getting-started#nightly-release-channel) aliased as `@nuxt/devtools` or installed as `@nuxt/devtools-nightly` (which wins when both are present);
- when it is not, `nuxt dev` shows a floating button (built on [`@devframes/hub-ui-onboard`](https://devfra.me/guide/hub-ui-onboard)) that installs it with your package manager and restarts the dev server, so the real DevTools dock takes its place.

Nothing is injected outside `nuxt dev`.
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-onboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"prepack": "turbo run build --filter=."
},
"peerDependencies": {
"@nuxt/devtools": "workspace:^",
"@nuxt/devtools": "*",
"@nuxt/kit": "^4.0.0-0 || ^5.0.0-0"
},
"peerDependenciesMeta": {
Expand Down
22 changes: 17 additions & 5 deletions packages/devtools-onboard/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ import { defineNuxtModule, installModule, tryResolveModule } from '@nuxt/kit'
import { readPackageJSON } from 'pkg-types'

const DEVTOOLS = '@nuxt/devtools'
const DEVTOOLS_NIGHTLY = `${DEVTOOLS}-nightly`

/** The names Nuxt itself recognizes as DevTools in `modules` (see `loadNuxt` in nuxt core). */
const DEVTOOLS_MODULES = new Set([DEVTOOLS, `${DEVTOOLS}-nightly`, `${DEVTOOLS}-edge`])
const DEVTOOLS_MODULES = new Set([DEVTOOLS, DEVTOOLS_NIGHTLY, `${DEVTOOLS}-edge`])

/**
* The packages loaded when installed, any major. A nightly under its own name
* is always a deliberate opt-in, while `@nuxt/devtools` may come along with
* another dependency, so the nightly wins.
*/
const DEVTOOLS_PACKAGES = [DEVTOOLS_NIGHTLY, DEVTOOLS]

function listsDevtools(modules: NuxtOptions['modules']): boolean {
return modules.some((entry) => {
Expand All @@ -19,7 +27,9 @@ function listsDevtools(modules: NuxtOptions['modules']): boolean {
/**
* The spec the button installs. Versions are released in lockstep, so a caret
* on our own version pins the matching DevTools; a nightly build carries an
* `npm:@nuxt/devtools-nightly@…` alias in its peer range instead.
* `npm:@nuxt/devtools-nightly@…` alias in its peer range instead. Otherwise
* the peer range is `*`: it must not conflict with an installed v3 or nightly,
* and npm only matches prereleases against `*`.
*/
async function devtoolsSpec(): Promise<string> {
const { version, peerDependencies } = await readPackageJSON(fileURLToPath(import.meta.url))
Expand All @@ -38,9 +48,11 @@ export default defineNuxtModule({
if (listsDevtools(nuxt.options.modules))
return

const installed = await tryResolveModule(DEVTOOLS, nuxt.options.modulesDir)
if (installed)
return installModule(installed)
for (const name of DEVTOOLS_PACKAGES) {
const installed = await tryResolveModule(name, nuxt.options.modulesDir)
if (installed)
return installModule(installed)
}
if (!nuxt.options.dev)
return

Expand Down
22 changes: 19 additions & 3 deletions packages/devtools-onboard/test/module.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Nuxt, NuxtConfig } from '@nuxt/schema'
import type { IncomingMessage, ServerResponse } from 'node:http'
import { mkdtemp, rm } from 'node:fs/promises'
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
Expand All @@ -15,14 +15,24 @@ afterEach(async () => {
await Promise.all(cleanups.splice(0).map(fn => fn()))
})

/** A stand-in DevTools package whose module records its name in `appConfig`. */
async function installStandIn(rootDir: string, name: string) {
const dir = join(rootDir, 'node_modules', name)
await mkdir(dir, { recursive: true })
await writeFile(join(dir, 'package.json'), JSON.stringify({ name, type: 'module', exports: './module.mjs' }))
await writeFile(join(dir, 'module.mjs'), `export default (_, nuxt) => { nuxt.options.appConfig.loadedDevtools = ${JSON.stringify(name)} }`)
}

/**
* Nuxt core pushes the bare `@nuxt/devtools` name into `_modules`; aliasing
* it to this module reproduces what Nuxt 5 does with `@nuxt/devtools-onboard`.
* `withoutDevtools` drops every `node_modules` Nuxt can resolve from, which is
* the situation of a Nuxt that no longer depends on `@nuxt/devtools` itself.
* the situation of a Nuxt that no longer depends on `@nuxt/devtools` itself;
* `installed` then puts stand-in packages back into the project.
*/
async function bootNuxt(overrides: NuxtConfig, { withoutDevtools = false } = {}): Promise<Nuxt> {
async function bootNuxt(overrides: NuxtConfig, { withoutDevtools = false, installed = [] }: { withoutDevtools?: boolean, installed?: string[] } = {}): Promise<Nuxt> {
const rootDir = await mkdtemp(join(tmpdir(), 'devtools-onboard-'))
await Promise.all(installed.map(name => installStandIn(rootDir, name)))
const nuxt = await loadNuxt({
cwd: rootDir,
ready: false,
Expand Down Expand Up @@ -80,6 +90,12 @@ describe('@nuxt/devtools-onboard', () => {
expect(onboardScripts(nuxt)).toHaveLength(0)
}, 60_000)

it('loads @nuxt/devtools-nightly installed under its own name, over @nuxt/devtools', async () => {
const nuxt = await bootNuxt({ dev: true }, { withoutDevtools: true, installed: ['@nuxt/devtools', '@nuxt/devtools-nightly'] })
expect(nuxt.options.appConfig.loadedDevtools).toBe('@nuxt/devtools-nightly')
expect(onboardScripts(nuxt)).toHaveLength(0)
}, 60_000)

it('offers to install @nuxt/devtools when it is missing', async () => {
const nuxt = await bootNuxt({ dev: true }, { withoutDevtools: true })
expect(nuxt.options._requiredModules['@nuxt/devtools']).toBeUndefined()
Expand Down
Loading