Merge branches 'feat/unhead-v2' and 'main' of github.com:nuxt/nuxt into feat/unhead-v2

This commit is contained in:
harlan 2025-02-14 18:05:56 +11:00
commit f49b907cdd
22 changed files with 508 additions and 237 deletions

View File

@ -236,7 +236,7 @@ jobs:
path: packages
- name: Run benchmarks
uses: CodSpeedHQ/action@513a19673a831f139e8717bf45ead67e47f00044 # v3.2.0
uses: CodSpeedHQ/action@da7c57859a7a565a3a92789ac64c41aca031ca1f # v3.3.0
with:
run: pnpm vitest bench
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@ -120,7 +120,7 @@ interface MetaObject {
}
```
See [unhead/types](https://github.com/unjs/unhead/blob/main/packages/unhead/src/types/schema/head.ts) for more detailed types.
See [@unhead/vue](https://github.com/unjs/unhead/blob/main/packages/vue/src/types/schema.ts) for more detailed types.
## Features

View File

@ -53,6 +53,26 @@ export default defineNuxtConfig({
})
```
::tip
You can override a layer's alias by specifying it in the options next to the layer source.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
extends: [
[
'github:my-themes/awesome',
{
meta: {
name: 'my-awesome-theme',
},
},
],
]
})
```
::
Nuxt uses [unjs/c12](https://c12.unjs.io) and [unjs/giget](https://giget.unjs.io) for extending remote layers. Check the documentation for more information and all available options.
::read-more{to="/docs/guide/going-further/layers"}

View File

@ -136,6 +136,6 @@ Before mounting the Vue application, Nuxt calls the [`app:beforeMount`](/docs/ap
After mounting the Vue application, Nuxt calls the [`app:mounted`](/docs/api/advanced/hooks#app-hooks-runtime) hook.
::
### Step 6: Vue Lifecycle
### Step 5: Vue Lifecycle
Unlike on the server, the browser executes the full [Vue lifecycle](https://vuejs.org/guide/essentials/lifecycle).

View File

@ -11,9 +11,9 @@ The hooking system is powered by [unjs/hookable](https://github.com/unjs/hookabl
These hooks are available for [Nuxt Modules](/docs/guide/going-further/modules) and build context.
### Within `nuxt.config`
### Within `nuxt.config.ts`
```js [nuxt.config]
```js [nuxt.config.ts]
export default defineNuxtConfig({
hooks: {
close: () => { }

View File

@ -52,4 +52,4 @@ const WhitelistAttributes = {
}
```
See [SafeInputPlugin](https://github.com/unjs/unhead/blob/main/packages/unhead/src/plugins/safe.ts) for more detailed types.
See [@unhead/vue](https://github.com/unjs/unhead/blob/main/packages/vue/src/types/safeSchema.ts) for more detailed types.

View File

@ -35,7 +35,7 @@ interface MetaObject {
}
```
See [unhead/types](https://github.com/unjs/unhead/blob/main/packages/unhead/src/types/schema/head.ts) for more detailed types.
See [@unhead/vue](https://github.com/unjs/unhead/blob/main/packages/vue/src/types/schema.ts) for more detailed types.
::note
The properties of `useHead` can be dynamic, accepting `ref`, `computed` and `reactive` properties. `meta` parameter can also accept a function returning an object to make the entire object reactive.

View File

@ -45,7 +45,7 @@
"@nuxt/schema": "workspace:*",
"@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*",
"@types/node": "22.13.1",
"@types/node": "22.13.2",
"@unhead/vue": "2.0.0-alpha.13",
"@vue/compiler-core": "3.5.13",
"@vue/compiler-dom": "3.5.13",
@ -81,14 +81,14 @@
"@testing-library/vue": "8.1.0",
"@types/babel__core": "7.20.5",
"@types/babel__helper-plugin-utils": "7.10.3",
"@types/node": "22.13.1",
"@types/node": "22.13.2",
"@types/semver": "7.5.8",
"@unhead/vue": "2.0.0-alpha.13",
"@vitest/coverage-v8": "3.0.5",
"@vue/test-utils": "2.4.6",
"acorn": "8.14.0",
"autoprefixer": "10.4.20",
"case-police": "0.7.2",
"case-police": "1.0.0",
"changelogen": "0.5.7",
"consola": "3.4.0",
"cssnano": "7.0.6",
@ -96,14 +96,14 @@
"devalue": "5.1.1",
"eslint": "9.20.1",
"eslint-plugin-no-only-tests": "3.3.0",
"eslint-plugin-perfectionist": "4.8.0",
"eslint-plugin-perfectionist": "4.9.0",
"eslint-typegen": "1.0.0",
"estree-walker": "3.0.3",
"h3": "npm:h3-nightly@1.14.0-20250122-114730-3f9e703",
"happy-dom": "17.0.4",
"happy-dom": "17.1.0",
"installed-check": "9.3.0",
"jiti": "2.4.2",
"knip": "5.44.0",
"knip": "5.44.1",
"magic-string": "0.30.17",
"markdownlint-cli": "0.44.0",
"memfs": "4.17.0",
@ -111,7 +111,7 @@
"nuxt": "workspace:*",
"nuxt-content-twoslash": "0.1.2",
"ofetch": "1.4.1",
"pathe": "2.0.2",
"pathe": "2.0.3",
"pkg-pr-new": "0.0.39",
"playwright-core": "1.50.1",
"rollup": "4.34.6",
@ -120,7 +120,7 @@
"std-env": "3.8.0",
"tinyexec": "0.3.2",
"tinyglobby": "0.2.10",
"ts-blank-space": "0.5.1",
"ts-blank-space": "0.6.0",
"typescript": "5.7.3",
"ufo": "1.5.4",
"unbuild": "3.3.1",

View File

@ -38,7 +38,7 @@
"klona": "^2.0.6",
"mlly": "^1.7.4",
"ohash": "^1.1.4",
"pathe": "^2.0.2",
"pathe": "^2.0.3",
"pkg-types": "^1.3.1",
"scule": "^1.3.0",
"semver": "^7.7.1",

View File

@ -2,6 +2,7 @@ import { pathToFileURL } from 'node:url'
import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
import type { Nuxt, NuxtConfig } from '@nuxt/schema'
import { resolve } from 'pathe'
import { withTrailingSlash } from 'ufo'
import { importModule, tryImportModule } from '../internal/esm'
import { runWithNuxtContext } from '../context'
import type { LoadNuxtConfigOptions } from './config'
@ -22,23 +23,23 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
// Apply dev as config override
opts.overrides.dev = !!opts.dev
const rootDir = withTrailingSlash(pathToFileURL(opts.cwd!).href)
const nearestNuxtPkg = await Promise.all(['nuxt-nightly', 'nuxt']
.map(pkg => resolvePackageJSON(pkg, { url: opts.cwd }).catch(() => null)))
.map(pkg => resolvePackageJSON(pkg, { url: rootDir }).catch(() => null)))
.then(r => (r.filter(Boolean) as string[]).sort((a, b) => b.length - a.length)[0])
if (!nearestNuxtPkg) {
throw new Error(`Cannot find any nuxt version from ${opts.cwd}`)
}
const pkg = await readPackageJSON(nearestNuxtPkg)
const rootDir = pathToFileURL(opts.cwd!).href
const { loadNuxt } = await importModule<typeof import('nuxt')>((pkg as any)._name || pkg.name, { paths: rootDir })
const nuxt = await loadNuxt(opts)
return nuxt
}
export async function buildNuxt (nuxt: Nuxt): Promise<any> {
const rootDir = pathToFileURL(nuxt.options.rootDir).href
const rootDir = withTrailingSlash(pathToFileURL(nuxt.options.rootDir).href)
const { build } = await tryImportModule<typeof import('nuxt')>('nuxt-nightly', { paths: rootDir }) || await importModule<typeof import('nuxt')>('nuxt', { paths: rootDir })
return runWithNuxtContext(nuxt, () => build(nuxt))

View File

@ -0,0 +1,33 @@
import { fileURLToPath } from 'node:url'
import { mkdir, rm, writeFile } from 'node:fs/promises'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { join, normalize } from 'pathe'
import { withoutTrailingSlash } from 'ufo'
import { x } from 'tinyexec'
import { loadNuxt } from '../src'
const repoRoot = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../', import.meta.url))))
describe('loadNuxt', () => {
const tempDir = join(repoRoot, 'temp')
beforeAll(async () => {
await mkdir(join(tempDir, 'nuxt'), { recursive: true })
await writeFile(join(tempDir, 'package.json'), '{"dependencies":{"nuxt":"file:./nuxt"}}')
await writeFile(join(tempDir, 'nuxt', 'package.json'), '{"name":"nuxt","type":"module","exports":{".":"./index.js"}}')
await writeFile(join(tempDir, 'nuxt', 'index.js'), 'export const loadNuxt = (opts) => ({ name: "it me" })')
await x('npm', ['install'], { nodeOptions: { cwd: tempDir } })
})
afterAll(async () => {
await rm(tempDir, { recursive: true, force: true })
})
it('respects correct directory', async () => {
const nuxt = await loadNuxt({ cwd: tempDir })
expect(nuxt).toStrictEqual({
name: 'it me',
})
})
})

View File

@ -102,7 +102,7 @@
"ofetch": "^1.4.1",
"ohash": "^1.1.4",
"on-change": "^5.0.1",
"pathe": "^2.0.2",
"pathe": "^2.0.3",
"perfect-debounce": "^1.0.0",
"pkg-types": "^1.3.1",
"radix3": "^1.1.2",

View File

@ -49,7 +49,19 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
.map(m => m.entryPath!),
)
const sharedDirs = new Set<string>()
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4
if (isNuxtV4 && (nuxt.options.nitro.imports !== false && nuxt.options.imports.scan !== false)) {
for (const layer of nuxt.options._layers) {
// Layer disabled scanning for itself
if (layer.config?.imports?.scan === false) {
continue
}
sharedDirs.add(resolve(layer.config.rootDir, 'shared', 'utils'))
sharedDirs.add(resolve(layer.config.rootDir, 'shared', 'types'))
}
}
const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
debug: nuxt.options.debug ? nuxt.options.debug.nitro : false,
@ -68,12 +80,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
},
imports: {
autoImport: nuxt.options.imports.autoImport as boolean,
dirs: isNuxtV4
? [
resolve(nuxt.options.rootDir, 'shared', 'utils'),
resolve(nuxt.options.rootDir, 'shared', 'types'),
]
: [],
dirs: [...sharedDirs],
imports: [
{
as: '__buildAssetsURL',

View File

@ -0,0 +1,57 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { normalize } from 'pathe'
import type { NuxtConfig } from '@nuxt/schema'
import { loadNuxt } from '../src'
const fixtureDir = normalize(fileURLToPath(new URL('../../../test/fixtures/basic', import.meta.url)))
describe('loadNuxt', () => {
it('does not add shared directories to nitro auto-imports in v3', async () => {
const importDirs = await getNitroImportDirs({ future: { compatibilityVersion: 3 as any } })
expect(normalizePaths(importDirs)).toMatchInlineSnapshot(`[]`)
})
it('adds shared directories for layers to nitro auto-imports in v4', async () => {
const importDirs = await getNitroImportDirs({ future: { compatibilityVersion: 4 } })
expect(normalizePaths(importDirs)).toMatchInlineSnapshot(`
[
"<rootDir>/shared/utils",
"<rootDir>/shared/types",
"<rootDir>/extends/bar/shared/utils",
"<rootDir>/extends/bar/shared/types",
"<rootDir>/extends/node_modules/foo/shared/utils",
"<rootDir>/extends/node_modules/foo/shared/types",
"<rootDir>/layers/bar/shared/utils",
"<rootDir>/layers/bar/shared/types",
]
`)
})
})
function normalizePaths (arr: unknown[]) {
const normalized = []
for (const dir of arr) {
normalized.push(typeof dir === 'string' ? dir.replace(fixtureDir, '<rootDir>') : dir)
}
return normalized
}
async function getNitroImportDirs (overrides?: NuxtConfig) {
const importDirs: unknown[] = []
const nuxt = await loadNuxt({
cwd: fixtureDir,
ready: true,
overrides: {
...overrides,
hooks: {
'nitro:config' (config) {
if (config.imports) {
importDirs.push(...config.imports.dirs || [])
}
},
},
},
})
await nuxt.close()
return importDirs
}

View File

@ -49,7 +49,7 @@
"magic-string": "^0.30.17",
"memfs": "^4.17.0",
"ohash": "^1.1.4",
"pathe": "^2.0.2",
"pathe": "^2.0.3",
"pify": "^6.1.0",
"postcss": "^8.5.2",
"postcss-import": "^16.1.0",

View File

@ -77,7 +77,7 @@
"dependencies": {
"consola": "^3.4.0",
"defu": "^6.1.4",
"pathe": "^2.0.2",
"pathe": "^2.0.3",
"std-env": "^3.8.0"
},
"engines": {

View File

@ -175,10 +175,10 @@ export default defineResolvers({
} satisfies NormalizedMetaObject)
// provides default charset and viewport if not set
if (!resolved.meta.find(m => m.charset)?.charset) {
if (!resolved.meta.find(m => m?.charset)?.charset) {
resolved.meta.unshift({ charset: resolved.charset || 'utf-8' })
}
if (!resolved.meta.find(m => m.name === 'viewport')?.content) {
if (!resolved.meta.find(m => m?.name === 'viewport')?.content) {
resolved.meta.unshift({ name: 'viewport', content: resolved.viewport || 'width=device-width, initial-scale=1' })
}

View File

@ -19,12 +19,12 @@
"devDependencies": {
"@unocss/reset": "65.4.3",
"beasties": "0.2.0",
"html-validate": "9.2.1",
"html-validate": "9.2.2",
"htmlnano": "2.1.1",
"jiti": "2.4.2",
"knitwork": "1.2.0",
"pathe": "2.0.2",
"prettier": "3.5.0",
"pathe": "2.0.3",
"prettier": "3.5.1",
"scule": "1.3.0",
"svgo": "3.3.2",
"tinyexec": "0.3.2",

View File

@ -47,7 +47,7 @@
"knitwork": "^1.2.0",
"magic-string": "^0.30.17",
"mlly": "^1.7.4",
"pathe": "^2.0.2",
"pathe": "^2.0.3",
"pkg-types": "^1.3.1",
"postcss": "^8.5.2",
"rollup-plugin-visualizer": "^5.14.0",

View File

@ -49,7 +49,7 @@
"memfs": "^4.17.0",
"mini-css-extract-plugin": "^2.9.2",
"ohash": "^1.1.4",
"pathe": "^2.0.2",
"pathe": "^2.0.3",
"pify": "^6.1.0",
"postcss": "^8.5.2",
"postcss-import": "^16.1.0",

File diff suppressed because it is too large Load Diff

View File

@ -145,6 +145,7 @@ export async function getContributors () {
'Authorization': `token ${process.env.GITHUB_TOKEN}`,
},
})
if (!author) { continue }
if (!contributors.some(c => c.username === author.login)) {
contributors.push({ name: commit.author.name, username: author.login })
}