Compare commits

...

12 Commits

Author SHA1 Message Date
renovate[bot]
2b95304bd4
chore(deps): update webpack 2025-02-18 10:15:24 +00:00
renovate[bot]
42fafca4cd
chore(deps): update all non-major dependencies (3.x) (#30984)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 11:13:46 +01:00
Daniel Roe
676447239e
test: add major version to unit test 2025-02-14 21:03:41 +01:00
xjccc
a85ca58382
feat(nuxt): allow forcing start/set in loading indicator (#30989) 2025-02-14 20:19:01 +01:00
Alex Liu
72159e0808
chore: use logical or assignment (#30992) 2025-02-14 20:19:00 +01:00
Daniel Roe
62cb4a6ec1
feat(kit,nuxt,vite): directoryToURL to normalise paths (#30986) 2025-02-14 20:18:59 +01:00
Connor Pearson
9ba51dba6f
fix(kit): ensure nuxt is loaded from cwd rather than parent dir (#30910) 2025-02-14 20:16:08 +01:00
Daniel Roe
0161985c0f
chore: handle undefined author 2025-02-14 20:14:28 +01:00
Horu
b71c6f1bf3
docs: add tips on how to override layers aliases (#30970) 2025-02-14 20:14:28 +01:00
Nolhan
05b020bedb
docs: fix typo (#30971) 2025-02-14 20:14:28 +01:00
Daniel Roe
a6b93f18ae
fix(nuxt): make shared/ directories available within layers (#30843) 2025-02-14 20:14:27 +01:00
renovate[bot]
616a5fb0b6
chore(deps): update all non-major dependencies (3.x) (#30967)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 22:21:56 +01:00
37 changed files with 1227 additions and 1163 deletions

View File

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

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. 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"} ::read-more{to="/docs/guide/going-further/layers"}

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. 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({ export default defineNuxtConfig({
hooks: { hooks: {
close: () => { } close: () => { }

View File

@ -40,7 +40,11 @@ It hooks into [`page:loading:start`](/docs/api/advanced/hooks#app-hooks-runtime)
### `start()` ### `start()`
Set `isLoading` to true and start to increase the `progress` value. Set `isLoading` to true and start to increase the `progress` value. `start` accepts a `{ force: true }` option to skip the interval and show the loading state immediately.
### `set()`
Set the `progress` value to a specific value. `set` accepts a `{ force: true }` option to skip the interval and show the loading state immediately.
### `finish()` ### `finish()`
@ -62,3 +66,12 @@ Used by `finish()`. Clear all timers and intervals used by the composable.
}) })
</script> </script>
``` ```
```vue
<script setup lang="ts">
const { start, set } = useLoadingIndicator()
// same as set(0, { force: true })
// set the progress to 0, and show loading immediately
start({ force: true })
</script>
```

View File

@ -37,7 +37,7 @@
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html" "typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html"
}, },
"resolutions": { "resolutions": {
"@babel/core": "7.26.8", "@babel/core": "7.26.9",
"@babel/helper-plugin-utils": "7.26.5", "@babel/helper-plugin-utils": "7.26.5",
"@nuxt/cli": "3.21.1", "@nuxt/cli": "3.21.1",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
@ -45,33 +45,33 @@
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@nuxt/vite-builder": "workspace:*", "@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*", "@nuxt/webpack-builder": "workspace:*",
"@types/node": "22.13.1", "@types/node": "22.13.4",
"@unhead/dom": "1.11.18", "@unhead/dom": "1.11.19",
"@unhead/schema": "1.11.18", "@unhead/schema": "1.11.19",
"@unhead/shared": "1.11.18", "@unhead/shared": "1.11.19",
"@unhead/ssr": "1.11.18", "@unhead/ssr": "1.11.19",
"@unhead/vue": "1.11.18", "@unhead/vue": "1.11.19",
"@vue/compiler-core": "3.5.13", "@vue/compiler-core": "3.5.13",
"@vue/compiler-dom": "3.5.13", "@vue/compiler-dom": "3.5.13",
"@vue/shared": "3.5.13", "@vue/shared": "3.5.13",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"memfs": "4.14.1", "memfs": "4.17.0",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"ohash": "1.1.4", "ohash": "1.1.4",
"postcss": "8.5.2", "postcss": "8.5.2",
"rollup": "4.34.6", "rollup": "4.34.8",
"send": ">=1.1.0", "send": ">=1.1.0",
"typescript": "5.7.3", "typescript": "5.7.3",
"ufo": "1.5.4", "ufo": "1.5.4",
"unhead": "1.11.18", "unhead": "1.11.19",
"unimport": "4.1.1", "unimport": "4.1.2",
"vite": "6.1.0", "vite": "6.1.0",
"vue": "3.5.13", "vue": "3.5.13",
"webpack": "5.96.1" "webpack": "5.98.0"
}, },
"devDependencies": { "devDependencies": {
"@arethetypeswrong/cli": "0.17.3", "@arethetypeswrong/cli": "0.17.3",
"@babel/core": "7.26.8", "@babel/core": "7.26.9",
"@babel/helper-plugin-utils": "7.26.5", "@babel/helper-plugin-utils": "7.26.5",
"@codspeed/vitest-plugin": "4.0.0", "@codspeed/vitest-plugin": "4.0.0",
"@nuxt/cli": "3.21.1", "@nuxt/cli": "3.21.1",
@ -83,10 +83,10 @@
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/babel__core": "7.20.5", "@types/babel__core": "7.20.5",
"@types/babel__helper-plugin-utils": "7.10.3", "@types/babel__helper-plugin-utils": "7.10.3",
"@types/node": "22.13.1", "@types/node": "22.13.4",
"@types/semver": "7.5.8", "@types/semver": "7.5.8",
"@unhead/schema": "1.11.18", "@unhead/schema": "1.11.19",
"@unhead/vue": "1.11.18", "@unhead/vue": "1.11.19",
"@vitest/coverage-v8": "3.0.5", "@vitest/coverage-v8": "3.0.5",
"@vue/test-utils": "2.4.6", "@vue/test-utils": "2.4.6",
"acorn": "8.14.0", "acorn": "8.14.0",
@ -99,17 +99,17 @@
"devalue": "5.1.1", "devalue": "5.1.1",
"eslint": "9.20.1", "eslint": "9.20.1",
"eslint-plugin-no-only-tests": "3.3.0", "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", "eslint-typegen": "1.0.0",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
"h3": "1.15.0", "h3": "1.15.0",
"happy-dom": "17.1.0", "happy-dom": "17.1.0",
"installed-check": "9.3.0", "installed-check": "9.3.0",
"jiti": "2.4.2", "jiti": "2.4.2",
"knip": "5.44.0", "knip": "5.44.1",
"magic-string": "0.30.17", "magic-string": "0.30.17",
"markdownlint-cli": "0.44.0", "markdownlint-cli": "0.44.0",
"memfs": "4.14.1", "memfs": "4.17.0",
"nitropack": "2.10.4", "nitropack": "2.10.4",
"nuxt": "workspace:*", "nuxt": "workspace:*",
"nuxt-content-twoslash": "0.1.2", "nuxt-content-twoslash": "0.1.2",
@ -117,22 +117,22 @@
"pathe": "2.0.3", "pathe": "2.0.3",
"pkg-pr-new": "0.0.39", "pkg-pr-new": "0.0.39",
"playwright-core": "1.50.1", "playwright-core": "1.50.1",
"rollup": "4.34.6", "rollup": "4.34.8",
"semver": "7.7.1", "semver": "7.7.1",
"sherif": "1.3.0", "sherif": "1.3.0",
"std-env": "3.8.0", "std-env": "3.8.0",
"tinyexec": "0.3.2", "tinyexec": "0.3.2",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.11",
"ts-blank-space": "0.5.1", "ts-blank-space": "0.6.0",
"typescript": "5.7.3", "typescript": "5.7.3",
"ufo": "1.5.4", "ufo": "1.5.4",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"vitest": "3.0.5", "vitest": "3.0.5",
"vitest-environment-nuxt": "1.0.1", "vitest-environment-nuxt": "1.0.1",
"vue": "3.5.13", "vue": "3.5.13",
"vue-tsc": "2.2.0", "vue-tsc": "2.2.2",
"webpack": "5.96.1" "webpack": "5.98.0"
}, },
"packageManager": "pnpm@10.3.0", "packageManager": "pnpm@10.4.1",
"version": "" "version": ""
} }

View File

@ -45,7 +45,7 @@
"std-env": "^3.8.0", "std-env": "^3.8.0",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"unctx": "^2.4.1", "unctx": "^2.4.1",
"unimport": "^4.1.1", "unimport": "^4.1.2",
"untyped": "^1.5.2" "untyped": "^1.5.2"
}, },
"devDependencies": { "devDependencies": {
@ -58,7 +58,7 @@
"unbuild": "latest", "unbuild": "latest",
"vite": "6.1.0", "vite": "6.1.0",
"vitest": "3.0.5", "vitest": "3.0.5",
"webpack": "5.96.1" "webpack": "5.98.0"
}, },
"engines": { "engines": {
"node": ">=18.12.0" "node": ">=18.12.0"

View File

@ -32,6 +32,6 @@ export { addTemplate, addServerTemplate, addTypeTemplate, normalizeTemplate, upd
export { logger, useLogger } from './logger' export { logger, useLogger } from './logger'
// Internal Utils // Internal Utils
export { resolveModule, tryResolveModule, importModule, tryImportModule, requireModule, tryRequireModule } from './internal/esm' export { directoryToURL, resolveModule, tryResolveModule, importModule, tryImportModule, requireModule, tryRequireModule } from './internal/esm'
export type { ImportModuleOptions, ResolveModuleOptions } from './internal/esm' export type { ImportModuleOptions, ResolveModuleOptions } from './internal/esm'
export * from './internal/template' export * from './internal/template'

View File

@ -3,7 +3,13 @@ import { interopDefault, resolvePath, resolvePathSync } from 'mlly'
import { createJiti } from 'jiti' import { createJiti } from 'jiti'
export interface ResolveModuleOptions { export interface ResolveModuleOptions {
/** @deprecated use `url` with URLs pointing at a file - never a directory */
paths?: string | string[] paths?: string | string[]
url?: URL | URL[]
}
export function directoryToURL (dir: string): URL {
return pathToFileURL(dir + '/')
} }
/** /**
@ -12,7 +18,10 @@ export interface ResolveModuleOptions {
* *
* @internal * @internal
*/ */
export async function tryResolveModule (id: string, url: string | string[] = import.meta.url) { export async function tryResolveModule (id: string, url: URL | URL[]): Promise<string | undefined>
/** @deprecated pass URLs pointing at files */
export async function tryResolveModule (id: string, url: string | string[]): Promise<string | undefined>
export async function tryResolveModule (id: string, url: string | string[] | URL | URL[] = import.meta.url) {
try { try {
return await resolvePath(id, { url }) return await resolvePath(id, { url })
} catch { } catch {
@ -21,7 +30,7 @@ export async function tryResolveModule (id: string, url: string | string[] = imp
} }
export function resolveModule (id: string, options?: ResolveModuleOptions) { export function resolveModule (id: string, options?: ResolveModuleOptions) {
return resolvePathSync(id, { url: options?.paths ?? [import.meta.url] }) return resolvePathSync(id, { url: options?.url ?? options?.paths ?? [import.meta.url] })
} }
export interface ImportModuleOptions extends ResolveModuleOptions { export interface ImportModuleOptions extends ResolveModuleOptions {
@ -30,8 +39,8 @@ export interface ImportModuleOptions extends ResolveModuleOptions {
} }
export async function importModule<T = unknown> (id: string, opts?: ImportModuleOptions) { export async function importModule<T = unknown> (id: string, opts?: ImportModuleOptions) {
const resolvedPath = await resolveModule(id, opts) const resolvedPath = resolveModule(id, opts)
return import(pathToFileURL(resolvedPath).href).then(r => opts?.interopDefault !== false ? interopDefault(r) : r) as Promise<T> return await import(pathToFileURL(resolvedPath).href).then(r => opts?.interopDefault !== false ? interopDefault(r) : r) as Promise<T>
} }
export function tryImportModule<T = unknown> (id: string, opts?: ImportModuleOptions) { export function tryImportModule<T = unknown> (id: string, opts?: ImportModuleOptions) {

View File

@ -9,7 +9,7 @@ import { globby } from 'globby'
import defu from 'defu' import defu from 'defu'
import { basename, join, relative } from 'pathe' import { basename, join, relative } from 'pathe'
import { isWindows } from 'std-env' import { isWindows } from 'std-env'
import { tryResolveModule } from '../internal/esm' import { directoryToURL, tryResolveModule } from '../internal/esm'
export interface LoadNuxtConfigOptions extends Omit<LoadConfigOptions<NuxtConfig>, 'overrides'> { export interface LoadNuxtConfigOptions extends Omit<LoadConfigOptions<NuxtConfig>, 'overrides'> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
@ -108,11 +108,12 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise<Nuxt
} }
async function loadNuxtSchema (cwd: string) { async function loadNuxtSchema (cwd: string) {
const paths = [cwd] const url = directoryToURL(cwd)
const nuxtPath = await tryResolveModule('nuxt', cwd) ?? await tryResolveModule('nuxt-nightly', cwd) const urls = [url]
const nuxtPath = await tryResolveModule('nuxt', url) ?? await tryResolveModule('nuxt-nightly', url)
if (nuxtPath) { if (nuxtPath) {
paths.unshift(nuxtPath) urls.unshift(pathToFileURL(nuxtPath))
} }
const schemaPath = await tryResolveModule('@nuxt/schema', paths) ?? '@nuxt/schema' const schemaPath = await tryResolveModule('@nuxt/schema', urls) ?? '@nuxt/schema'
return await import(isWindows ? pathToFileURL(schemaPath).href : schemaPath).then(r => r.NuxtConfigSchema) return await import(isWindows ? pathToFileURL(schemaPath).href : schemaPath).then(r => r.NuxtConfigSchema)
} }

View File

@ -1,7 +1,6 @@
import { pathToFileURL } from 'node:url'
import { readPackageJSON, resolvePackageJSON } from 'pkg-types' import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { importModule, tryImportModule } from '../internal/esm' import { directoryToURL, importModule, tryImportModule } from '../internal/esm'
import { runWithNuxtContext } from '../context' import { runWithNuxtContext } from '../context'
import type { LoadNuxtConfigOptions } from './config' import type { LoadNuxtConfigOptions } from './config'
@ -27,8 +26,10 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
// Apply dev as config override // Apply dev as config override
opts.overrides.dev = !!opts.dev opts.overrides.dev = !!opts.dev
const rootURL = directoryToURL(opts.cwd!)
const nearestNuxtPkg = await Promise.all(['nuxt-nightly', 'nuxt3', 'nuxt', 'nuxt-edge'] const nearestNuxtPkg = await Promise.all(['nuxt-nightly', 'nuxt3', 'nuxt', 'nuxt-edge']
.map(pkg => resolvePackageJSON(pkg, { url: opts.cwd }).catch(() => null))) .map(pkg => resolvePackageJSON(pkg, { url: rootURL }).catch(() => null)))
.then(r => (r.filter(Boolean) as string[]).sort((a, b) => b.length - a.length)[0]) .then(r => (r.filter(Boolean) as string[]).sort((a, b) => b.length - a.length)[0])
if (!nearestNuxtPkg) { if (!nearestNuxtPkg) {
throw new Error(`Cannot find any nuxt version from ${opts.cwd}`) throw new Error(`Cannot find any nuxt version from ${opts.cwd}`)
@ -36,17 +37,15 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
const pkg = await readPackageJSON(nearestNuxtPkg) const pkg = await readPackageJSON(nearestNuxtPkg)
const majorVersion = pkg.version ? Number.parseInt(pkg.version.split('.')[0]!) : '' const majorVersion = pkg.version ? Number.parseInt(pkg.version.split('.')[0]!) : ''
const rootDir = pathToFileURL(opts.cwd || process.cwd()).href
// Nuxt 3 // Nuxt 3
if (majorVersion && majorVersion >= 3) { if (majorVersion && majorVersion >= 3) {
const { loadNuxt } = await importModule<typeof import('nuxt')>((pkg as any)._name || pkg.name, { paths: rootDir }) const { loadNuxt } = await importModule<typeof import('nuxt')>((pkg as any)._name || pkg.name, { url: rootURL })
const nuxt = await loadNuxt(opts) const nuxt = await loadNuxt(opts)
return nuxt return nuxt
} }
// Nuxt 2 // Nuxt 2
const { loadNuxt } = await tryImportModule<{ loadNuxt: any }>('nuxt-edge', { paths: rootDir }) || await importModule<{ loadNuxt: any }>('nuxt', { paths: rootDir }) const { loadNuxt } = await tryImportModule<{ loadNuxt: any }>('nuxt-edge', { url: rootURL }) || await importModule<{ loadNuxt: any }>('nuxt', { url: rootURL })
const nuxt = await loadNuxt({ const nuxt = await loadNuxt({
rootDir: opts.cwd, rootDir: opts.cwd,
for: opts.dev ? 'dev' : 'build', for: opts.dev ? 'dev' : 'build',
@ -72,15 +71,15 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
} }
export async function buildNuxt (nuxt: Nuxt): Promise<any> { export async function buildNuxt (nuxt: Nuxt): Promise<any> {
const rootDir = pathToFileURL(nuxt.options.rootDir).href const rootURL = directoryToURL(nuxt.options.rootDir)
// Nuxt 3 // Nuxt 3
if (nuxt.options._majorVersion === 3) { if (nuxt.options._majorVersion === 3) {
const { build } = await tryImportModule<typeof import('nuxt')>('nuxt-nightly', { paths: rootDir }) || await tryImportModule<typeof import('nuxt')>('nuxt3', { paths: rootDir }) || await importModule<typeof import('nuxt')>('nuxt', { paths: rootDir }) const { build } = await tryImportModule<typeof import('nuxt')>('nuxt-nightly', { url: rootURL }) || await tryImportModule<typeof import('nuxt')>('nuxt3', { url: rootURL }) || await importModule<typeof import('nuxt')>('nuxt', { url: rootURL })
return runWithNuxtContext(nuxt, () => build(nuxt)) return runWithNuxtContext(nuxt, () => build(nuxt))
} }
// Nuxt 2 // Nuxt 2
const { build } = await tryImportModule<{ build: any }>('nuxt-edge', { paths: rootDir }) || await importModule<{ build: any }>('nuxt', { paths: rootDir }) const { build } = await tryImportModule<{ build: any }>('nuxt-edge', { url: rootURL }) || await importModule<{ build: any }>('nuxt', { url: rootURL })
return runWithNuxtContext(nuxt, () => build(nuxt)) return runWithNuxtContext(nuxt, () => build(nuxt))
} }

View File

@ -7,6 +7,7 @@ import { createJiti } from 'jiti'
import { parseNodeModulePath, resolve as resolveModule } from 'mlly' import { parseNodeModulePath, resolve as resolveModule } from 'mlly'
import { isRelative } from 'ufo' import { isRelative } from 'ufo'
import { isNuxt2 } from '../compatibility' import { isNuxt2 } from '../compatibility'
import { directoryToURL } from '../internal/esm'
import { useNuxt } from '../context' import { useNuxt } from '../context'
import { resolveAlias, resolvePath } from '../resolve' import { resolveAlias, resolvePath } from '../resolve'
import { logger } from '../logger' import { logger } from '../logger'
@ -107,7 +108,10 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
try { try {
const src = isAbsolute(path) const src = isAbsolute(path)
? pathToFileURL(await resolvePath(path, { fallbackToOriginal: false, extensions: nuxt.options.extensions })).href ? pathToFileURL(await resolvePath(path, { fallbackToOriginal: false, extensions: nuxt.options.extensions })).href
: await resolveModule(path, { url: nuxt.options.modulesDir.map(m => pathToFileURL(m.replace(/\/node_modules\/?$/, ''))), extensions: nuxt.options.extensions }) : await resolveModule(path, {
url: nuxt.options.modulesDir.map(m => directoryToURL(m.replace(/\/node_modules\/?$/, '/'))),
extensions: nuxt.options.extensions,
})
nuxtModule = await jiti.import(src, { default: true }) as NuxtModule nuxtModule = await jiti.import(src, { default: true }) as NuxtModule
resolvedModulePath = fileURLToPath(new URL(src)) resolvedModulePath = fileURLToPath(new URL(src))

View File

@ -4,6 +4,7 @@ import { basename, dirname, isAbsolute, join, normalize, resolve } from 'pathe'
import { globby } from 'globby' import { globby } from 'globby'
import { resolvePath as _resolvePath } from 'mlly' import { resolvePath as _resolvePath } from 'mlly'
import { resolveAlias as _resolveAlias } from 'pathe/utils' import { resolveAlias as _resolveAlias } from 'pathe/utils'
import { directoryToURL } from './internal/esm'
import { tryUseNuxt } from './context' import { tryUseNuxt } from './context'
import { isIgnored } from './ignore' import { isIgnored } from './ignore'
import { toArray } from './utils' import { toArray } from './utils'
@ -201,7 +202,7 @@ async function _resolvePathGranularly (path: string, opts: ResolvePathOptions =
} }
// Try to resolve as module id // Try to resolve as module id
const resolvedModulePath = await _resolvePath(_path, { url: [cwd, ...modulesDir] }).catch(() => null) const resolvedModulePath = await _resolvePath(_path, { url: [cwd, ...modulesDir].map(d => directoryToURL(d)) }).catch(() => null)
if (resolvedModulePath) { if (resolvedModulePath) {
return { return {
path: resolvedModulePath, path: resolvedModulePath,

View File

@ -9,7 +9,7 @@ import { gte } from 'semver'
import { readPackageJSON } from 'pkg-types' import { readPackageJSON } from 'pkg-types'
import { filterInPlace } from './utils' import { filterInPlace } from './utils'
import { tryResolveModule } from './internal/esm' import { directoryToURL, tryResolveModule } from './internal/esm'
import { getDirectory } from './module/install' import { getDirectory } from './module/install'
import { tryUseNuxt, useNuxt } from './context' import { tryUseNuxt, useNuxt } from './context'
import { resolveNuxtModule } from './resolve' import { resolveNuxtModule } from './resolve'
@ -244,6 +244,8 @@ export async function _generateTypes (nuxt: Nuxt) {
tsConfig.compilerOptions.paths ||= {} tsConfig.compilerOptions.paths ||= {}
tsConfig.include ||= [] tsConfig.include ||= []
const importPaths = nuxt.options.modulesDir.map(d => directoryToURL(d))
for (const alias in aliases) { for (const alias in aliases) {
if (excludedAlias.some(re => re.test(alias))) { if (excludedAlias.some(re => re.test(alias))) {
continue continue
@ -251,7 +253,7 @@ export async function _generateTypes (nuxt: Nuxt) {
let absolutePath = resolve(basePath, aliases[alias]!) let absolutePath = resolve(basePath, aliases[alias]!)
let stats = await fsp.stat(absolutePath).catch(() => null /* file does not exist */) let stats = await fsp.stat(absolutePath).catch(() => null /* file does not exist */)
if (!stats) { if (!stats) {
const resolvedModule = await tryResolveModule(aliases[alias]!, nuxt.options.modulesDir) const resolvedModule = await tryResolveModule(aliases[alias]!, importPaths)
if (resolvedModule) { if (resolvedModule) {
absolutePath = resolvedModule absolutePath = resolvedModule
stats = await fsp.stat(resolvedModule).catch(() => null) stats = await fsp.stat(resolvedModule).catch(() => null)

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","version":"3.0.0","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

@ -71,15 +71,15 @@
"dependencies": { "dependencies": {
"@nuxt/cli": "^3.21.1", "@nuxt/cli": "^3.21.1",
"@nuxt/devalue": "^2.0.2", "@nuxt/devalue": "^2.0.2",
"@nuxt/devtools": "^2.0.0", "@nuxt/devtools": "^2.1.0",
"@nuxt/kit": "workspace:*", "@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.6.5", "@nuxt/telemetry": "^2.6.5",
"@nuxt/vite-builder": "workspace:*", "@nuxt/vite-builder": "workspace:*",
"@unhead/dom": "^1.11.18", "@unhead/dom": "^1.11.19",
"@unhead/shared": "^1.11.18", "@unhead/shared": "^1.11.19",
"@unhead/ssr": "^1.11.18", "@unhead/ssr": "^1.11.19",
"@unhead/vue": "^1.11.18", "@unhead/vue": "^1.11.19",
"@vue/shared": "^3.5.13", "@vue/shared": "^3.5.13",
"acorn": "8.14.0", "acorn": "8.14.0",
"c12": "^2.0.2", "c12": "^2.0.2",
@ -118,14 +118,14 @@
"semver": "^7.7.1", "semver": "^7.7.1",
"std-env": "^3.8.0", "std-env": "^3.8.0",
"strip-literal": "^3.0.0", "strip-literal": "^3.0.0",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.11",
"ufo": "^1.5.4", "ufo": "^1.5.4",
"ultrahtml": "^1.5.3", "ultrahtml": "^1.5.3",
"uncrypto": "^0.1.3", "uncrypto": "^0.1.3",
"unctx": "^2.4.1", "unctx": "^2.4.1",
"unenv": "^1.10.0", "unenv": "^1.10.0",
"unhead": "^1.11.18", "unhead": "^1.11.19",
"unimport": "^4.1.1", "unimport": "^4.1.2",
"unplugin": "^2.2.0", "unplugin": "^2.2.0",
"unplugin-vue-router": "^0.11.2", "unplugin-vue-router": "^0.11.2",
"unstorage": "^1.14.4", "unstorage": "^1.14.4",
@ -136,7 +136,7 @@
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/scripts": "0.10.1", "@nuxt/scripts": "0.10.3",
"@parcel/watcher": "2.5.1", "@parcel/watcher": "2.5.1",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue": "5.2.1",

View File

@ -24,8 +24,8 @@ export type LoadingIndicator = {
progress: Ref<number> progress: Ref<number>
isLoading: Ref<boolean> isLoading: Ref<boolean>
error: Ref<boolean> error: Ref<boolean>
start: () => void start: (opts?: { force?: boolean }) => void
set: (value: number) => void set: (value: number, opts?: { force?: boolean }) => void
finish: (opts?: { force?: boolean, error?: boolean }) => void finish: (opts?: { force?: boolean, error?: boolean }) => void
clear: () => void clear: () => void
} }
@ -49,23 +49,24 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
let hideTimeout: number | NodeJS.Timeout let hideTimeout: number | NodeJS.Timeout
let resetTimeout: number | NodeJS.Timeout let resetTimeout: number | NodeJS.Timeout
const start = () => { const start = (opts: { force?: boolean } = {}) => {
error.value = false error.value = false
set(0) set(0, opts)
} }
function set (at = 0) { function set (at = 0, opts: { force?: boolean } = {}) {
if (nuxtApp.isHydrating) { if (nuxtApp.isHydrating) {
return return
} }
if (at >= 100) { return finish() } if (at >= 100) { return finish({ force: opts.force }) }
clear() clear()
progress.value = at < 0 ? 0 : at progress.value = at < 0 ? 0 : at
if (throttle && import.meta.client) { const throttleTime = opts.force ? 0 : throttle
if (throttleTime && import.meta.client) {
throttleTimeout = setTimeout(() => { throttleTimeout = setTimeout(() => {
isLoading.value = true isLoading.value = true
_startProgress() _startProgress()
}, throttle) }, throttleTime)
} else { } else {
isLoading.value = true isLoading.value = true
_startProgress() _startProgress()

View File

@ -523,7 +523,7 @@ export function tryUseNuxtApp (id?: string): NuxtApp | null {
nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt
} }
nuxtAppInstance = nuxtAppInstance || getNuxtAppCtx(id).tryUse() nuxtAppInstance ||= getNuxtAppCtx(id).tryUse()
return nuxtAppInstance || null return nuxtAppInstance || null
} }

View File

@ -52,7 +52,7 @@ function pageToClientOnly<T extends ComponentOptions> (component: T) {
if (isPromise(setupState)) { if (isPromise(setupState)) {
return Promise.resolve(setupState).then((setupState: any) => { return Promise.resolve(setupState).then((setupState: any) => {
if (typeof setupState !== 'function') { if (typeof setupState !== 'function') {
setupState = setupState || {} setupState ||= {}
setupState.mounted$ = mounted$ setupState.mounted$ = mounted$
return setupState return setupState
} }

View File

@ -1,7 +1,7 @@
import type { EventType } from '@parcel/watcher' import type { EventType } from '@parcel/watcher'
import type { FSWatcher } from 'chokidar' import type { FSWatcher } from 'chokidar'
import { watch as chokidarWatch } from 'chokidar' import { watch as chokidarWatch } from 'chokidar'
import { createIsIgnored, importModule, isIgnored, tryResolveModule, useNuxt } from '@nuxt/kit' import { createIsIgnored, directoryToURL, importModule, isIgnored, tryResolveModule, useNuxt } from '@nuxt/kit'
import { debounce } from 'perfect-debounce' import { debounce } from 'perfect-debounce'
import { normalize, relative, resolve } from 'pathe' import { normalize, relative, resolve } from 'pathe'
import type { Nuxt, NuxtBuilder } from 'nuxt/schema' import type { Nuxt, NuxtBuilder } from 'nuxt/schema'
@ -196,7 +196,7 @@ async function createParcelWatcher () {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.time('[nuxt] builder:parcel:watch') console.time('[nuxt] builder:parcel:watch')
} }
const watcherPath = await tryResolveModule('@parcel/watcher', [nuxt.options.rootDir, ...nuxt.options.modulesDir]) const watcherPath = await tryResolveModule('@parcel/watcher', [nuxt.options.rootDir, ...nuxt.options.modulesDir].map(d => directoryToURL(d)))
if (!watcherPath) { if (!watcherPath) {
logger.warn('Falling back to `chokidar-granular` as `@parcel/watcher` cannot be resolved in your project.') logger.warn('Falling back to `chokidar-granular` as `@parcel/watcher` cannot be resolved in your project.')
return false return false
@ -248,7 +248,7 @@ async function bundle (nuxt: Nuxt) {
} }
async function loadBuilder (nuxt: Nuxt, builder: string): Promise<NuxtBuilder> { async function loadBuilder (nuxt: Nuxt, builder: string): Promise<NuxtBuilder> {
const builderPath = await tryResolveModule(builder, [nuxt.options.rootDir, import.meta.url]) const builderPath = await tryResolveModule(builder, [directoryToURL(nuxt.options.rootDir), new URL(import.meta.url)])
if (!builderPath) { if (!builderPath) {
throw new Error(`Loading \`${builder}\` builder failed. You can read more about the nuxt \`builder\` option at: \`https://nuxt.com/docs/api/nuxt-config#builder\``) throw new Error(`Loading \`${builder}\` builder failed. You can read more about the nuxt \`builder\` option at: \`https://nuxt.com/docs/api/nuxt-config#builder\``)

View File

@ -49,7 +49,19 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
.map(m => m.entryPath!), .map(m => m.entryPath!),
) )
const sharedDirs = new Set<string>()
const isNuxtV4 = nuxt.options.future?.compatibilityVersion === 4 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, { const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
debug: nuxt.options.debug ? nuxt.options.debug.nitro : false, debug: nuxt.options.debug ? nuxt.options.debug.nitro : false,
@ -68,12 +80,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
}, },
imports: { imports: {
autoImport: nuxt.options.imports.autoImport as boolean, autoImport: nuxt.options.imports.autoImport as boolean,
dirs: isNuxtV4 dirs: [...sharedDirs],
? [
resolve(nuxt.options.rootDir, 'shared', 'utils'),
resolve(nuxt.options.rootDir, 'shared', 'types'),
]
: [],
imports: [ imports: [
{ {
as: '__buildAssetsURL', as: '__buildAssetsURL',

View File

@ -6,7 +6,7 @@ import { join, normalize, relative, resolve } from 'pathe'
import { createDebugger, createHooks } from 'hookable' import { createDebugger, createHooks } from 'hookable'
import ignore from 'ignore' import ignore from 'ignore'
import type { LoadNuxtOptions } from '@nuxt/kit' import type { LoadNuxtOptions } from '@nuxt/kit'
import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addTypeTemplate, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, runWithNuxtContext, tryResolveModule, useNitro } from '@nuxt/kit' import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addTypeTemplate, addVitePlugin, addWebpackPlugin, directoryToURL, installModule, loadNuxtConfig, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, runWithNuxtContext, tryResolveModule, useNitro } from '@nuxt/kit'
import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema' import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema'
import type { PackageJson } from 'pkg-types' import type { PackageJson } from 'pkg-types'
import { readPackageJSON } from 'pkg-types' import { readPackageJSON } from 'pkg-types'
@ -390,12 +390,13 @@ async function initNuxt (nuxt: Nuxt) {
} }
nuxt.hook('modules:done', async () => { nuxt.hook('modules:done', async () => {
const importPaths = nuxt.options.modulesDir.map(dir => directoryToURL((dir)))
// Add unctx transform // Add unctx transform
addBuildPlugin(UnctxTransformPlugin({ addBuildPlugin(UnctxTransformPlugin({
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
transformerOptions: { transformerOptions: {
...nuxt.options.optimization.asyncTransforms, ...nuxt.options.optimization.asyncTransforms,
helperModule: await tryResolveModule('unctx', nuxt.options.modulesDir) ?? 'unctx', helperModule: await tryResolveModule('unctx', importPaths) ?? 'unctx',
}, },
})) }))

View File

@ -68,7 +68,7 @@ export const ComposableKeysPlugin = (options: ComposableKeysOptions) => createUn
const name = node.callee.name const name = node.callee.name
if (!name || !keyedFunctions.has(name) || node.arguments.length >= maxLength) { return } if (!name || !keyedFunctions.has(name) || node.arguments.length >= maxLength) { return }
imports = imports || detectImportNames(script, composableMeta) imports ||= detectImportNames(script, composableMeta)
if (imports.has(name)) { return } if (imports.has(name)) { return }
const meta = composableMeta[name] const meta = composableMeta[name]

View File

@ -1,7 +1,7 @@
import { parseNodeModulePath, resolvePath } from 'mlly' import { parseNodeModulePath, resolvePath } from 'mlly'
import { isAbsolute, normalize } from 'pathe' import { isAbsolute, normalize } from 'pathe'
import type { Plugin } from 'vite' import type { Plugin } from 'vite'
import { resolveAlias } from '@nuxt/kit' import { directoryToURL, resolveAlias } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { pkgDir } from '../../dirs' import { pkgDir } from '../../dirs'
@ -37,7 +37,7 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
const dir = parseNodeModulePath(normalisedImporter).dir || pkgDir const dir = parseNodeModulePath(normalisedImporter).dir || pkgDir
return await this.resolve?.(normalisedId, dir, { skipSelf: true }) ?? await resolvePath(id, { return await this.resolve?.(normalisedId, dir, { skipSelf: true }) ?? await resolvePath(id, {
url: [dir, ...nuxt.options.modulesDir], url: [dir, ...nuxt.options.modulesDir].map(d => directoryToURL(d)),
conditions, conditions,
}).catch(() => { }).catch(() => {
logger.debug('Could not resolve id', id, importer) logger.debug('Could not resolve id', id, importer)

View File

@ -5,7 +5,7 @@ import { resolve } from 'pathe'
import { watch } from 'chokidar' import { watch } from 'chokidar'
import { defu } from 'defu' import { defu } from 'defu'
import { debounce } from 'perfect-debounce' import { debounce } from 'perfect-debounce'
import { createIsIgnored, createResolver, defineNuxtModule, importModule, tryResolveModule } from '@nuxt/kit' import { createIsIgnored, createResolver, defineNuxtModule, directoryToURL, importModule, tryResolveModule } from '@nuxt/kit'
import { generateTypes, resolveSchema as resolveUntypedSchema } from 'untyped' import { generateTypes, resolveSchema as resolveUntypedSchema } from 'untyped'
import type { Schema, SchemaDefinition } from 'untyped' import type { Schema, SchemaDefinition } from 'untyped'
import untypedPlugin from 'untyped/babel-plugin' import untypedPlugin from 'untyped/babel-plugin'
@ -57,7 +57,7 @@ export default defineNuxtModule({
}) })
if (nuxt.options.experimental.watcher === 'parcel') { if (nuxt.options.experimental.watcher === 'parcel') {
const watcherPath = await tryResolveModule('@parcel/watcher', [nuxt.options.rootDir, ...nuxt.options.modulesDir]) const watcherPath = await tryResolveModule('@parcel/watcher', [nuxt.options.rootDir, ...nuxt.options.modulesDir].map(dir => directoryToURL(dir)))
if (watcherPath) { if (watcherPath) {
const { subscribe } = await importModule<typeof import('@parcel/watcher')>(watcherPath) const { subscribe } = await importModule<typeof import('@parcel/watcher')>(watcherPath)
for (const layer of nuxt.options._layers) { for (const layer of nuxt.options._layers) {

View File

@ -1,11 +1,11 @@
import { resolvePackageJSON } from 'pkg-types' import { resolvePackageJSON } from 'pkg-types'
import { resolvePath as _resolvePath } from 'mlly' import { resolvePath as _resolvePath } from 'mlly'
import { dirname } from 'pathe' import { dirname } from 'pathe'
import { tryUseNuxt } from '@nuxt/kit' import { directoryToURL, tryUseNuxt } from '@nuxt/kit'
export async function resolveTypePath (path: string, subpath: string, searchPaths = tryUseNuxt()?.options.modulesDir) { export async function resolveTypePath (path: string, subpath: string, searchPaths = tryUseNuxt()?.options.modulesDir) {
try { try {
const r = await _resolvePath(path, { url: searchPaths, conditions: ['types', 'import', 'require'] }) const r = await _resolvePath(path, { url: searchPaths?.map(d => directoryToURL(d)), conditions: ['types', 'import', 'require'] })
if (subpath) { if (subpath) {
return r.replace(/(?:\.d)?\.[mc]?[jt]s$/, '') return r.replace(/(?:\.d)?\.[mc]?[jt]s$/, '')
} }

View File

@ -1,5 +1,5 @@
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { addComponent, addImportsSources, addPlugin, addTemplate, defineNuxtModule, tryResolveModule } from '@nuxt/kit' import { addComponent, addImportsSources, addPlugin, addTemplate, defineNuxtModule, directoryToURL, tryResolveModule } from '@nuxt/kit'
import type { NuxtOptions } from '@nuxt/schema' import type { NuxtOptions } from '@nuxt/schema'
import { distDir } from '../dirs' import { distDir } from '../dirs'
@ -52,7 +52,8 @@ export default defineNuxtModule<NuxtOptions['unhead']>({
}) })
// Opt-out feature allowing dependencies using @vueuse/head to work // Opt-out feature allowing dependencies using @vueuse/head to work
const unheadVue = await tryResolveModule('@unhead/vue', nuxt.options.modulesDir) || '@unhead/vue' const importPaths = nuxt.options.modulesDir.map(d => directoryToURL(d))
const unheadVue = await tryResolveModule('@unhead/vue', importPaths) || '@unhead/vue'
if (nuxt.options.experimental.polyfillVueUseHead) { if (nuxt.options.experimental.polyfillVueUseHead) {
// backwards compatibility // backwards compatibility
nuxt.options.alias['@vueuse/head'] = unheadVue nuxt.options.alias['@vueuse/head'] = unheadVue

View File

@ -1,5 +1,5 @@
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { addBuildPlugin, addTemplate, addTypeTemplate, createIsIgnored, defineNuxtModule, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit' import { addBuildPlugin, addTemplate, addTypeTemplate, createIsIgnored, defineNuxtModule, directoryToURL, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit'
import { isAbsolute, join, normalize, relative, resolve } from 'pathe' import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
import type { Import, Unimport } from 'unimport' import type { Import, Unimport } from 'unimport'
import { createUnimport, scanDirExports, toExports } from 'unimport' import { createUnimport, scanDirExports, toExports } from 'unimport'
@ -181,6 +181,8 @@ function addDeclarationTemplates (ctx: Unimport, options: Partial<ImportsOptions
const SUPPORTED_EXTENSION_RE = new RegExp(`\\.(${nuxt.options.extensions.map(i => i.replace('.', '')).join('|')})$`) const SUPPORTED_EXTENSION_RE = new RegExp(`\\.(${nuxt.options.extensions.map(i => i.replace('.', '')).join('|')})$`)
const importPaths = nuxt.options.modulesDir.map(dir => directoryToURL(dir))
async function cacheImportPaths (imports: Import[]) { async function cacheImportPaths (imports: Import[]) {
const importSource = Array.from(new Set(imports.map(i => i.from))) const importSource = Array.from(new Set(imports.map(i => i.from)))
// skip relative import paths for node_modules that are explicitly installed // skip relative import paths for node_modules that are explicitly installed
@ -190,7 +192,7 @@ function addDeclarationTemplates (ctx: Unimport, options: Partial<ImportsOptions
} }
let path = resolveAlias(from) let path = resolveAlias(from)
if (!isAbsolute(path)) { if (!isAbsolute(path)) {
path = await tryResolveModule(from, nuxt.options.modulesDir).then(async (r) => { path = await tryResolveModule(from, importPaths).then(async (r) => {
if (!r) { return r } if (!r) { return r }
const { dir, name } = parseNodeModulePath(r) const { dir, name } = parseNodeModulePath(r)

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

@ -47,7 +47,7 @@
"jiti": "^2.4.2", "jiti": "^2.4.2",
"knitwork": "^1.2.0", "knitwork": "^1.2.0",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"memfs": "^4.14.1", "memfs": "^4.17.0",
"ohash": "^1.1.4", "ohash": "^1.1.4",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"pify": "^6.1.0", "pify": "^6.1.0",
@ -75,7 +75,7 @@
"@types/pify": "5.0.4", "@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0", "@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9", "@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.34.6", "rollup": "4.34.8",
"unbuild": "latest", "unbuild": "latest",
"vue": "3.5.13" "vue": "3.5.13"
}, },

View File

@ -40,12 +40,12 @@
"@types/rollup-plugin-visualizer": "4.2.4", "@types/rollup-plugin-visualizer": "4.2.4",
"@types/webpack-bundle-analyzer": "4.7.0", "@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9", "@types/webpack-hot-middleware": "2.25.9",
"@unhead/schema": "1.11.18", "@unhead/schema": "1.11.19",
"@vitejs/plugin-vue": "5.2.1", "@vitejs/plugin-vue": "5.2.1",
"@vitejs/plugin-vue-jsx": "4.1.1", "@vitejs/plugin-vue-jsx": "4.1.1",
"@vue/compiler-core": "3.5.13", "@vue/compiler-core": "3.5.13",
"@vue/compiler-sfc": "3.5.13", "@vue/compiler-sfc": "3.5.13",
"@vue/language-core": "2.2.0", "@vue/language-core": "2.2.2",
"c12": "2.0.2", "c12": "2.0.2",
"chokidar": "4.0.3", "chokidar": "4.0.3",
"compatx": "0.1.8", "compatx": "0.1.8",
@ -61,18 +61,18 @@
"ofetch": "1.4.1", "ofetch": "1.4.1",
"pkg-types": "1.3.1", "pkg-types": "1.3.1",
"postcss": "8.5.2", "postcss": "8.5.2",
"sass-loader": "16.0.4", "sass-loader": "16.0.5",
"scule": "1.3.0", "scule": "1.3.0",
"unbuild": "3.3.1", "unbuild": "3.3.1",
"unctx": "2.4.1", "unctx": "2.4.1",
"unimport": "4.1.1", "unimport": "4.1.2",
"untyped": "1.5.2", "untyped": "1.5.2",
"vite": "6.1.0", "vite": "6.1.0",
"vue": "3.5.13", "vue": "3.5.13",
"vue-bundle-renderer": "2.1.1", "vue-bundle-renderer": "2.1.1",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"vue-router": "4.5.0", "vue-router": "4.5.0",
"webpack": "5.96.1", "webpack": "5.98.0",
"webpack-dev-middleware": "7.4.2" "webpack-dev-middleware": "7.4.2"
}, },
"dependencies": { "dependencies": {

View File

@ -18,20 +18,20 @@
}, },
"devDependencies": { "devDependencies": {
"@types/lodash-es": "4.17.12", "@types/lodash-es": "4.17.12",
"@unocss/reset": "65.4.3", "@unocss/reset": "65.5.0",
"beasties": "0.2.0", "beasties": "0.2.0",
"html-validate": "9.2.2", "html-validate": "9.3.0",
"htmlnano": "2.1.1", "htmlnano": "2.1.1",
"jiti": "2.4.2", "jiti": "2.4.2",
"knitwork": "1.2.0", "knitwork": "1.2.0",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"pathe": "2.0.3", "pathe": "2.0.3",
"prettier": "3.5.0", "prettier": "3.5.1",
"scule": "1.3.0", "scule": "1.3.0",
"svgo": "3.3.2", "svgo": "3.3.2",
"tinyexec": "0.3.2", "tinyexec": "0.3.2",
"tinyglobby": "0.2.10", "tinyglobby": "0.2.11",
"unocss": "65.4.3", "unocss": "65.5.0",
"vite": "6.1.0" "vite": "6.1.0"
} }
} }

View File

@ -26,7 +26,7 @@
}, },
"devDependencies": { "devDependencies": {
"@nuxt/schema": "workspace:*", "@nuxt/schema": "workspace:*",
"rollup": "4.34.6", "rollup": "4.34.8",
"unbuild": "latest", "unbuild": "latest",
"vue": "3.5.13" "vue": "3.5.13"
}, },

View File

@ -2,7 +2,7 @@ import { resolve } from 'pathe'
import * as vite from 'vite' import * as vite from 'vite'
import vuePlugin from '@vitejs/plugin-vue' import vuePlugin from '@vitejs/plugin-vue'
import viteJsxPlugin from '@vitejs/plugin-vue-jsx' import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
import { logger, resolvePath, tryImportModule } from '@nuxt/kit' import { directoryToURL, logger, resolvePath, tryImportModule } from '@nuxt/kit'
import { joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo' import { joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo'
import type { ViteConfig } from '@nuxt/schema' import type { ViteConfig } from '@nuxt/schema'
import type { PackageJson } from 'pkg-types' import type { PackageJson } from 'pkg-types'
@ -117,7 +117,7 @@ export async function buildServer (ctx: ViteBuildContext) {
if (!ctx.nuxt.options.dev) { if (!ctx.nuxt.options.dev) {
const runtimeDependencies = await tryImportModule<PackageJson>('nitropack/package.json', { const runtimeDependencies = await tryImportModule<PackageJson>('nitropack/package.json', {
paths: ctx.nuxt.options.modulesDir, url: ctx.nuxt.options.modulesDir.map(d => directoryToURL(d)),
})?.then(r => r?.dependencies ? Object.keys(r.dependencies) : []).catch(() => []) || [] })?.then(r => r?.dependencies ? Object.keys(r.dependencies) : []).catch(() => []) || []
if (Array.isArray(serverConfig.ssr!.external)) { if (Array.isArray(serverConfig.ssr!.external)) {
serverConfig.ssr!.external.push( serverConfig.ssr!.external.push(

View File

@ -44,7 +44,7 @@
"h3": "^1.15.0", "h3": "^1.15.0",
"jiti": "^2.4.2", "jiti": "^2.4.2",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"memfs": "^4.14.1", "memfs": "^4.17.0",
"mini-css-extract-plugin": "^2.9.2", "mini-css-extract-plugin": "^2.9.2",
"ohash": "^1.1.4", "ohash": "^1.1.4",
"pathe": "^2.0.3", "pathe": "^2.0.3",
@ -63,7 +63,7 @@
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vue-bundle-renderer": "^2.1.1", "vue-bundle-renderer": "^2.1.1",
"vue-loader": "^17.4.2", "vue-loader": "^17.4.2",
"webpack": "^5.96.1", "webpack": "^5.98.0",
"webpack-bundle-analyzer": "^4.10.2", "webpack-bundle-analyzer": "^4.10.2",
"webpack-dev-middleware": "^7.4.2", "webpack-dev-middleware": "^7.4.2",
"webpack-hot-middleware": "^2.26.1", "webpack-hot-middleware": "^2.26.1",

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -530,6 +530,23 @@ describe('loading state', () => {
}) })
}) })
describe('loading state', () => {
it('expect state from set opts: { force: true }', async () => {
vi.stubGlobal('setTimeout', vi.fn((cb: () => void) => cb()))
const nuxtApp = useNuxtApp()
const { isLoading, start, finish, set } = useLoadingIndicator()
await nuxtApp.callHook('page:loading:start')
start({ force: true })
expect(isLoading.value).toBeTruthy()
finish()
expect(isLoading.value).toBeFalsy()
set(0, { force: true })
expect(isLoading.value).toBeTruthy()
set(100, { force: true })
expect(isLoading.value).toBeFalsy()
})
})
describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', () => { describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', () => {
it('getAppManifest', async () => { it('getAppManifest', async () => {
const manifest = await getAppManifest() const manifest = await getAppManifest()