diff --git a/docs/3.api/2.composables/use-nuxt-app.md b/docs/3.api/2.composables/use-nuxt-app.md
index 860cb89c22..6b915edf00 100644
--- a/docs/3.api/2.composables/use-nuxt-app.md
+++ b/docs/3.api/2.composables/use-nuxt-app.md
@@ -8,7 +8,7 @@ links:
size: xs
---
-`useNuxtApp` is a built-in composable that provides a way to access shared runtime context of Nuxt, also known as the [Nuxt context](/docs/guide/going-further/nuxt-app#the-nuxt-context), which is available on both client and server side. It helps you access the Vue app instance, runtime hooks, runtime config variables and internal states, such as `ssrContext` and `payload`.
+`useNuxtApp` is a built-in composable that provides a way to access shared runtime context of Nuxt, also known as the [Nuxt context](/docs/guide/going-further/nuxt-app#the-nuxt-context), which is available on both client and server side (but not within Nitro routes). It helps you access the Vue app instance, runtime hooks, runtime config variables and internal states, such as `ssrContext` and `payload`.
```vue [app.vue]
+```
+
+```ts [server/api/cookies.ts]
+export default defineEventHandler((event) => {
+ const cookies = parseCookies(event)
+
+ return { cookies }
+})
+```
+
+::
+
+::tip
+In the browser during client-side navigation, `useRequestFetch` will behave just like regular [`$fetch`](/docs/api/utils/dollarfetch).
+::
diff --git a/docs/3.api/2.composables/use-route-announcer.md b/docs/3.api/2.composables/use-route-announcer.md
index c3272631fe..cdaa4408d9 100644
--- a/docs/3.api/2.composables/use-route-announcer.md
+++ b/docs/3.api/2.composables/use-route-announcer.md
@@ -11,7 +11,7 @@ links:
---
::important
-This composable will be available in Nuxt v3.12 or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
+This composable is available in Nuxt v3.12+.
::
## Description
@@ -51,7 +51,7 @@ Sets the message with `politeness = "assertive"`
## Example
-```ts
+```vue [pages/index.vue]
- {{ pending ? 'Loading' : count }}
+ {{ status === 'pending' ? 'Loading' : count }}
Refresh
diff --git a/docs/3.api/3.utils/reload-nuxt-app.md b/docs/3.api/3.utils/reload-nuxt-app.md
index 3b25a95905..0244c78b9c 100644
--- a/docs/3.api/3.utils/reload-nuxt-app.md
+++ b/docs/3.api/3.utils/reload-nuxt-app.md
@@ -14,7 +14,7 @@ links:
By default, it will also save the current `state` of your app (that is, any state you could access with `useState`).
-::read-more{to="/docs/guide/going-further/experimental-features#restorestate" icon="i-ph-star-duotone"}
+::read-more{to="/docs/guide/going-further/experimental-features#restorestate" icon="i-ph-star"}
You can enable experimental restoration of this state by enabling the `experimental.restoreState` option in your `nuxt.config` file.
::
diff --git a/docs/3.api/4.commands/_dir.yml b/docs/3.api/4.commands/_dir.yml
index b1123168e0..00af2f6eb1 100644
--- a/docs/3.api/4.commands/_dir.yml
+++ b/docs/3.api/4.commands/_dir.yml
@@ -1,3 +1,3 @@
title: 'Commands'
-icon: i-ph-terminal-window-duotone
+icon: i-ph-terminal-window
titleTemplate: '%s ยท Nuxt Commands'
diff --git a/docs/3.api/4.commands/upgrade.md b/docs/3.api/4.commands/upgrade.md
index 187e915635..23958ea72f 100644
--- a/docs/3.api/4.commands/upgrade.md
+++ b/docs/3.api/4.commands/upgrade.md
@@ -1,6 +1,6 @@
---
title: "nuxi upgrade"
-description: The upgrade command upgrades Nuxt 3 to the latest version.
+description: The upgrade command upgrades Nuxt to the latest version.
links:
- label: Source
icon: i-simple-icons-github
@@ -12,7 +12,7 @@ links:
npx nuxi upgrade [--force|-f]
```
-The `upgrade` command upgrades Nuxt 3 to the latest version.
+The `upgrade` command upgrades Nuxt to the latest version.
Option | Default | Description
-------------------------|-----------------|------------------
diff --git a/docs/3.api/5.kit/11.nitro.md b/docs/3.api/5.kit/11.nitro.md
index 65c0c97437..9ca5785754 100644
--- a/docs/3.api/5.kit/11.nitro.md
+++ b/docs/3.api/5.kit/11.nitro.md
@@ -8,7 +8,7 @@ links:
size: xs
---
-Nitro is an open source TypeScript framework to build ultra-fast web servers. Nuxt 3 (and, optionally, Nuxt Bridge) uses Nitro as its server engine. You can use `useNitro` to access the Nitro instance, `addServerHandler` to add a server handler, `addDevServerHandler` to add a server handler to be used only in development mode, `addServerPlugin` to add a plugin to extend Nitro's runtime behavior, and `addPrerenderRoutes` to add routes to be prerendered by Nitro.
+Nitro is an open source TypeScript framework to build ultra-fast web servers. Nuxt uses Nitro as its server engine. You can use `useNitro` to access the Nitro instance, `addServerHandler` to add a server handler, `addDevServerHandler` to add a server handler to be used only in development mode, `addServerPlugin` to add a plugin to extend Nitro's runtime behavior, and `addPrerenderRoutes` to add routes to be prerendered by Nitro.
## `addServerHandler`
diff --git a/docs/3.api/5.kit/12.resolving.md b/docs/3.api/5.kit/12.resolving.md
index bb455d471e..8218ecfb0d 100644
--- a/docs/3.api/5.kit/12.resolving.md
+++ b/docs/3.api/5.kit/12.resolving.md
@@ -211,7 +211,7 @@ Type of path to resolve. If set to `'file'`, the function will try to resolve a
Creates resolver relative to base path.
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/resolving-paths-and-injecting-assets-to-the-app" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/resolving-paths-and-injecting-assets-to-the-app?friend=nuxt" target="_blank"}
Watch Vue School video about createResolver.
::
diff --git a/docs/3.api/5.kit/4.autoimports.md b/docs/3.api/5.kit/4.autoimports.md
index 2116b92d37..4aa9aac211 100644
--- a/docs/3.api/5.kit/4.autoimports.md
+++ b/docs/3.api/5.kit/4.autoimports.md
@@ -18,7 +18,7 @@ These functions are designed for registering your own utils, composables and Vue
Nuxt auto-imports helper functions, composables and Vue APIs to use across your application without explicitly importing them. Based on the directory structure, every Nuxt application can also use auto-imports for its own composables and plugins. Composables or plugins can use these functions.
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/expanding-nuxt-s-auto-imports" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/expanding-nuxt-s-auto-imports?friend=nuxt" target="_blank"}
Watch Vue School video about Auto-imports Nuxt Kit utilities.
::
diff --git a/docs/3.api/5.kit/5.components.md b/docs/3.api/5.kit/5.components.md
index 0740f0963a..3d41667d31 100644
--- a/docs/3.api/5.kit/5.components.md
+++ b/docs/3.api/5.kit/5.components.md
@@ -10,7 +10,7 @@ links:
Components are the building blocks of your Nuxt application. They are reusable Vue instances that can be used to create a user interface. In Nuxt, components from the components directory are automatically imported by default. However, if you need to import components from an alternative directory or wish to selectively import them as needed, `@nuxt/kit` provides the `addComponentsDir` and `addComponent` methods. These utils allow you to customize the component configuration to better suit your needs.
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/injecting-components-and-component-directories" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/injecting-components-and-component-directories?friend=nuxt" target="_blank"}
Watch Vue School video about injecting components.
::
diff --git a/docs/3.api/5.kit/7.pages.md b/docs/3.api/5.kit/7.pages.md
index 19692ddb28..8c1d91a685 100644
--- a/docs/3.api/5.kit/7.pages.md
+++ b/docs/3.api/5.kit/7.pages.md
@@ -10,9 +10,9 @@ links:
## `extendPages`
-In Nuxt 3, routes are automatically generated based on the structure of the files in the `pages` directory. However, there may be scenarios where you'd want to customize these routes. For instance, you might need to add a route for a dynamic page not generated by Nuxt, remove an existing route, or modify the configuration of a route. For such customizations, Nuxt 3 offers the `extendPages` feature, which allows you to extend and alter the pages configuration.
+In Nuxt 3, routes are automatically generated based on the structure of the files in the `pages` directory. However, there may be scenarios where you'd want to customize these routes. For instance, you might need to add a route for a dynamic page not generated by Nuxt, remove an existing route, or modify the configuration of a route. For such customizations, Nuxt offers the `extendPages` feature, which allows you to extend and alter the pages configuration.
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/extend-and-alter-nuxt-pages" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/extend-and-alter-nuxt-pages?friend=nuxt" target="_blank"}
Watch Vue School video about extendPages.
::
@@ -71,7 +71,7 @@ Nuxt is powered by the [Nitro](https://nitro.unjs.io) server engine. With Nitro,
You can read more about Nitro route rules in the [Nitro documentation](https://nitro.unjs.io/guide/routing#route-rules).
::
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares?friend=nuxt" target="_blank"}
Watch Vue School video about adding route rules and route middelwares.
::
@@ -192,7 +192,7 @@ Route middlewares can be also defined in plugins via [`addRouteMiddleware`](/doc
Read more about route middlewares in the [Route middleware documentation](/docs/getting-started/routing#route-middleware).
::
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/adding-route-rules-and-route-middlewares?friend=nuxt" target="_blank"}
Watch Vue School video about adding route rules and route middelwares.
::
@@ -209,6 +209,7 @@ type NuxtMiddleware = {
interface AddRouteMiddlewareOptions {
override?: boolean
+ prepend?: boolean
}
```
@@ -246,7 +247,21 @@ A middleware object or an array of middleware objects with the following propert
**Default**: `{}`
-Options to pass to the middleware. If `override` is set to `true`, it will override the existing middleware with the same name.
+- `override` (optional)
+
+ **Type**: `boolean`
+
+ **Default**: `false`
+
+ If enabled, overrides the existing middleware with the same name.
+
+- `prepend` (optional)
+
+ **Type**: `boolean`
+
+ **Default**: `false`
+
+ If enabled, prepends the middleware to the list of existing middleware.
### Examples
@@ -272,7 +287,7 @@ export default defineNuxtModule({
name: 'auth',
path: resolver.resolve('runtime/auth.ts'),
global: true
- })
+ }, { prepend: true })
}
})
```
diff --git a/docs/3.api/5.kit/8.layout.md b/docs/3.api/5.kit/8.layout.md
index dd6de0f6aa..9bf3ef78d2 100644
--- a/docs/3.api/5.kit/8.layout.md
+++ b/docs/3.api/5.kit/8.layout.md
@@ -15,7 +15,7 @@ Layouts is used to be a wrapper around your pages. It can be used to wrap your p
Register template as layout and add it to the layouts.
::note
-In Nuxt 2 `error` layout can also be registered using this utility. In Nuxt 3 `error` layout [replaced](/docs/getting-started/error-handling#rendering-an-error-page) with `error.vue` page in project root.
+In Nuxt 2 `error` layout can also be registered using this utility. In Nuxt 3+ `error` layout [replaced](/docs/getting-started/error-handling#rendering-an-error-page) with `error.vue` page in project root.
::
### Type
diff --git a/docs/3.api/5.kit/9.plugins.md b/docs/3.api/5.kit/9.plugins.md
index aec172f0ec..e2f09cfc76 100644
--- a/docs/3.api/5.kit/9.plugins.md
+++ b/docs/3.api/5.kit/9.plugins.md
@@ -14,7 +14,7 @@ Plugins are self-contained code that usually add app-level functionality to Vue.
Registers a Nuxt plugin and to the plugins array.
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/injecting-plugins" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/injecting-plugins?friend=nuxt" target="_blank"}
Watch Vue School video about addPlugin.
::
@@ -114,7 +114,7 @@ export default defineNuxtPlugin((nuxtApp) => {
Adds a template and registers as a nuxt plugin. This is useful for plugins that need to generate code at build time.
-::tip{icon="i-ph-video-duotone" to="https://vueschool.io/lessons/injecting-plugin-templates" target="_blank"}
+::tip{icon="i-ph-video" to="https://vueschool.io/lessons/injecting-plugin-templates?friend=nuxt" target="_blank"}
Watch Vue School video about addPluginTemplate.
::
diff --git a/docs/3.api/5.kit/_dir.yml b/docs/3.api/5.kit/_dir.yml
index dda66db56e..86a5d387a4 100644
--- a/docs/3.api/5.kit/_dir.yml
+++ b/docs/3.api/5.kit/_dir.yml
@@ -1,3 +1,3 @@
title: Nuxt Kit
-navigation.icon: i-ph-toolbox-duotone
+navigation.icon: i-ph-toolbox
titleTemplate: '%s ยท Nuxt Kit'
diff --git a/docs/3.api/6.advanced/1.hooks.md b/docs/3.api/6.advanced/1.hooks.md
index 0980575a37..894f104728 100644
--- a/docs/3.api/6.advanced/1.hooks.md
+++ b/docs/3.api/6.advanced/1.hooks.md
@@ -7,7 +7,7 @@ description: Nuxt provides a powerful hooking system to expand almost every aspe
## App Hooks (runtime)
-Check the [app source code](https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/nuxt.ts#L27) for all available hooks.
+Check the [app source code](https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/nuxt.ts#L37) for all available hooks.
Hook | Arguments | Environment | Description
-----------------------|---------------------|-----------------|-------------
@@ -30,11 +30,11 @@ Hook | Arguments | Environment | Description
`page:loading:end` | - | Client | Called after `page:finish`
`page:transition:finish`| `pageComponent?` | Client | After page transition [onAfterLeave](https://vuejs.org/guide/built-ins/transition.html#javascript-hooks) event.
`dev:ssr-logs` | `logs` | Client | Called with an array of server-side logs that have been passed to the client (if `features.devLogs` is enabled).
-`page:view-transition:start` | `transition` | Client | Called after `document.startViewTransition` is called when [experimental viewTransition support is enabled](https://nuxt.com/docs/getting-started/transitions#view-transitions-api-experimental).
+`page:view-transition:start` | `transition` | Client | Called after `document.startViewTransition` is called when [experimental viewTransition support is enabled](/docs/getting-started/transitions#view-transitions-api-experimental).
## Nuxt Hooks (build time)
-Check the [schema source code](https://github.com/nuxt/nuxt/blob/main/packages/schema/src/types/hooks.ts#L53) for all available hooks.
+Check the [schema source code](https://github.com/nuxt/nuxt/blob/main/packages/schema/src/types/hooks.ts#L83) for all available hooks.
Hook | Arguments | Description
-------------------------|----------------------------|-------------
@@ -98,7 +98,7 @@ Hook | Arguments | Description
`render:html` | `html, { event }` | Called before constructing the HTML. | [html](https://github.com/nuxt/nuxt/blob/71ef8bd3ff207fd51c2ca18d5a8c7140476780c7/packages/nuxt/src/core/runtime/nitro/renderer.ts#L15), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
`render:island` | `islandResponse, { event, islandContext }` | Called before constructing the island HTML. | [islandResponse](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L28), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), [islandContext](https://github.com/nuxt/nuxt/blob/e50cabfed1984c341af0d0c056a325a8aec26980/packages/nuxt/src/core/runtime/nitro/renderer.ts#L38)
`close` | - | Called when Nitro is closed. | -
-`error` | `error, { event? }` | Called when an error occurs. | [error](https://github.com/unjs/nitro/blob/main/src/runtime/types.ts#L24), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
+`error` | `error, { event? }` | Called when an error occurs. | [error](https://github.com/unjs/nitro/blob/d20ffcbd16fc4003b774445e1a01e698c2bb078a/src/types/runtime/nitro.ts#L48), [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
`request` | `event` | Called when a request is received. | [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38)
`beforeResponse` | `event, { body }` | Called before sending the response. | [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), unknown
`afterResponse` | `event, { body }` | Called after sending the response. | [event](https://github.com/unjs/h3/blob/f6ceb5581043dc4d8b6eab91e9be4531e0c30f8e/src/types.ts#L38), unknown
diff --git a/docs/3.api/6.advanced/2.import-meta.md b/docs/3.api/6.advanced/2.import-meta.md
index 97d291ee98..633e0461fd 100644
--- a/docs/3.api/6.advanced/2.import-meta.md
+++ b/docs/3.api/6.advanced/2.import-meta.md
@@ -10,7 +10,9 @@ This is done through `import.meta`, which is an object that provides your code w
Throughout the Nuxt documentation you may see snippets that use this already to figure out whether the
code is currently running on the client or server side.
-:read-more{to="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta"}
+::read-more{to="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta"}
+Read more about `import.meta`.
+::
## Runtime (App) Properties
diff --git a/docs/3.api/6.advanced/_dir.yml b/docs/3.api/6.advanced/_dir.yml
index b8a90804b7..e0a580e33c 100644
--- a/docs/3.api/6.advanced/_dir.yml
+++ b/docs/3.api/6.advanced/_dir.yml
@@ -1 +1 @@
-icon: i-ph-brain-duotone
+icon: i-ph-brain
diff --git a/docs/3.api/6.nuxt-config.md b/docs/3.api/6.nuxt-config.md
index 7f915757f6..cad9250920 100644
--- a/docs/3.api/6.nuxt-config.md
+++ b/docs/3.api/6.nuxt-config.md
@@ -2,7 +2,7 @@
title: Nuxt Configuration
titleTemplate: '%s'
description: Discover all the options you can use in your nuxt.config.ts file.
-navigation.icon: i-ph-gear-duotone
+navigation.icon: i-ph-gear
---
::note{icon="i-simple-icons-github" color="gray" to="https://github.com/nuxt/nuxt/tree/main/packages/schema/src/config" target="_blank"}
diff --git a/docs/3.api/index.md b/docs/3.api/index.md
index f0b12b7425..7e4970a2ae 100644
--- a/docs/3.api/index.md
+++ b/docs/3.api/index.md
@@ -7,25 +7,25 @@ surround: false
---
::card-group
- ::card{icon="i-ph-cube-duotone" title="Components" to="/docs/api/components/client-only"}
+ ::card{icon="i-ph-cube" title="Components" to="/docs/api/components/client-only"}
Explore Nuxt built-in components for pages, layouts, head, and more.
::
- ::card{icon="i-ph-arrows-left-right-duotone" title="Composables" to="/docs/api/composables/use-app-config"}
+ ::card{icon="i-ph-arrows-left-right" title="Composables" to="/docs/api/composables/use-app-config"}
Discover Nuxt composable functions for data-fetching, head management and more.
::
- ::card{icon="i-ph-function-duotone" title="Utils" to="/docs/api/utils/dollarfetch"}
+ ::card{icon="i-ph-function" title="Utils" to="/docs/api/utils/dollarfetch"}
Learn about Nuxt utility functions for navigation, error handling and more.
::
- ::card{icon="i-ph-terminal-window-duotone" title="Commands" to="/docs/api/commands/add"}
+ ::card{icon="i-ph-terminal-window" title="Commands" to="/docs/api/commands/add"}
List of Nuxt CLI commands to init, analyze, build, and preview your application.
::
- ::card{icon="i-ph-toolbox-duotone" title="Nuxt Kit" to="/docs/api/kit/modules"}
+ ::card{icon="i-ph-toolbox" title="Nuxt Kit" to="/docs/api/kit/modules"}
Understand Nuxt Kit utilities to create modules and control Nuxt.
::
- ::card{icon="i-ph-brain-duotone" title="Advanced" to="/docs/api/advanced/hooks"}
+ ::card{icon="i-ph-brain" title="Advanced" to="/docs/api/advanced/hooks"}
Go deep in Nuxt internals with Nuxt lifecycle hooks.
::
- ::card{icon="i-ph-gear-duotone" title="Nuxt Configuration" to="/docs/api/nuxt-config"}
+ ::card{icon="i-ph-gear" title="Nuxt Configuration" to="/docs/api/nuxt-config"}
Explore all Nuxt configuration options to customize your application.
::
::
diff --git a/docs/5.community/2.getting-help.md b/docs/5.community/2.getting-help.md
index 70ab6946fc..ded7e1292a 100644
--- a/docs/5.community/2.getting-help.md
+++ b/docs/5.community/2.getting-help.md
@@ -1,7 +1,8 @@
---
-title: 'Getting Help'
-description: "We're a friendly community of developers and we'd love to help."
-navigation.icon: i-ph-lifebuoy-duotone
+title: Getting Help
+description: We're a friendly community of developers and we'd love to help.
+navigation:
+ icon: i-ph-lifebuoy
---
At some point, you may find that there's an issue you need some help with.
@@ -16,13 +17,10 @@ Please don't feel embarrassed about asking a question that you think is easy - w
Everyone you'll encounter is helping out because they care, not because they are paid to do so. The kindest thing to do is make it easy for them to help you. Here are some ideas:
-* _Explain what your objective is, not just the problem you're facing._ "I need to ensure my form inputs are accessible, so I'm trying to get the ids to match between server and client."
-
-* _Make sure you've first read the docs and used your favorite search engine_. Let people know by saying something like "I've Googled for 'nuxt script setup' but I couldn't find code examples anywhere."
-
-* _Explain what you've tried._ Tell people the kind of solutions you've experimented with, and why. Often this can make people's advice more relevant to your situation.
-
-* _Share your code._ People probably won't be able to help if they just see an error message or a screenshot - but that all changes if you share your code in a copy/pasteable format - preferably in the form of a minimal reproduction like a CodeSandbox.
+- _Explain what your objective is, not just the problem you're facing._ "I need to ensure my form inputs are accessible, so I'm trying to get the ids to match between server and client."
+- _Make sure you've first read the docs and used your favorite search engine_. Let people know by saying something like "I've Googled for 'nuxt script setup' but I couldn't find code examples anywhere."
+- _Explain what you've tried._ Tell people the kind of solutions you've experimented with, and why. Often this can make people's advice more relevant to your situation.
+- _Share your code._ People probably won't be able to help if they just see an error message or a screenshot - but that all changes if you share your code in a copy/pasteable format - preferably in the form of a minimal reproduction like a CodeSandbox.
And finally, just ask the question! There's no need to [ask permission to ask a question](https://dontasktoask.com) or [wait for someone to reply to your 'hello'](https://www.nohello.com). If you do, you might not get a response because people are waiting for the whole question before engaging.
@@ -30,4 +28,12 @@ And finally, just ask the question! There's no need to [ask permission to ask a
Something isn't working the way that the docs say that it should. You're not sure if it's a bug. You've searched through the [open issues](https://github.com/nuxt/nuxt/issues) and [discussions](https://github.com/nuxt/nuxt/discussions) but you can't find anything. (if there is a closed issue, please create a new one)
-We recommend taking a look at [how to report bugs](/docs/community/reporting-bugs). Nuxt 3 is still in active development, and every issue helps make it better.
+We recommend taking a look at [how to report bugs](/docs/community/reporting-bugs). Nuxt is still in active development, and every issue helps make it better.
+
+## "I need professional help"
+
+If the community couldn't provide the help you need in the time-frame you have, NuxtLabs offers professional support with the [Nuxt Experts](https://nuxt.com/enterprise/support).
+
+The objective of the Nuxt Expert is to provide support to the Vue ecosystem, while also creating freelance opportunities for those contributing to open-source solutions, thus helping to maintain the sustainability of the ecosystem.
+
+The Nuxt experts are Vue, Nuxt and Vite chosen contributors providing professional support and consulting services.
diff --git a/docs/5.community/3.reporting-bugs.md b/docs/5.community/3.reporting-bugs.md
index 815617c2e8..30b60f3386 100644
--- a/docs/5.community/3.reporting-bugs.md
+++ b/docs/5.community/3.reporting-bugs.md
@@ -1,7 +1,7 @@
---
title: 'Reporting Bugs'
description: 'One of the most valuable roles in open source is taking the time to report bugs helpfully.'
-navigation.icon: i-ph-bug-duotone
+navigation.icon: i-ph-bug
---
Try as we might, we will never completely eliminate bugs.
@@ -22,31 +22,25 @@ Search through the [open issues](https://github.com/nuxt/nuxt/issues) and [discu
It's important to be able to reproduce the bug reliably - in a minimal way and apart from the rest of your project. This narrows down what could be causing the issue and makes it possible for someone not only to find the cause, but also to test a potential solution.
-Start with the Nuxt 3 or Nuxt Bridge sandbox and add the **minimum** amount of code necessary to reproduce the bug you're experiencing.
+Start with the Nuxt sandbox and add the **minimum** amount of code necessary to reproduce the bug you're experiencing.
::note
-If your issue concerns Vue 3 or Vite, please try to reproduce it first with the Vue 3 SSR starter.
+If your issue concerns Vue or Vite, please try to reproduce it first with the Vue SSR starter.
::
-**Nuxt 3**:
+**Nuxt**:
::card-group
- :card{title="Nuxt 3 on StackBlitz" icon="i-simple-icons-stackblitz" to="https://nuxt.new/s/v3" target="_blank"}
- :card{title="Nuxt 3 on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://nuxt.new/c/v3" target="_blank"}
+ :card{title="Nuxt on StackBlitz" icon="i-simple-icons-stackblitz" to="https://nuxt.new/s/v3" target="_blank"}
+ :card{title="Nuxt on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://nuxt.new/c/v3" target="_blank"}
::
-**Nuxt Bridge**:
+**Vue**:
::card-group
- :card{title="Nuxt Bridge on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://codesandbox.io/s/github/nuxt/starter/v2-bridge-codesandbox" target="_blank"}
-::
-
-**Vue 3**:
-
-::card-group
- :card{title="Vue 3 SSR on StackBlitz" icon="i-simple-icons-stackblitz" to="https://stackblitz.com/github/nuxt-contrib/vue3-ssr-starter/tree/main?terminal=dev" target="_blank"}
- :card{title="Vue 3 SSR on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://codesandbox.io/s/github/nuxt-contrib/vue3-ssr-starter/main" target="_blank"}
- :card{title="Vue 3 SSR Template on GitHub" icon="i-simple-icons-github" to="https://github.com/nuxt-contrib/vue3-ssr-starter/generate" target="_blank"}
+ :card{title="Vue SSR on StackBlitz" icon="i-simple-icons-stackblitz" to="https://stackblitz.com/github/nuxt-contrib/vue3-ssr-starter/tree/main?terminal=dev" target="_blank"}
+ :card{title="Vue SSR on CodeSandbox" icon="i-simple-icons-codesandbox" to="https://codesandbox.io/s/github/nuxt-contrib/vue3-ssr-starter/main" target="_blank"}
+ :card{title="Vue SSR Template on GitHub" icon="i-simple-icons-github" to="https://github.com/nuxt-contrib/vue3-ssr-starter/generate" target="_blank"}
::
Once you've reproduced the issue, remove as much code from your reproduction as you can (while still recreating the bug). The time spent making the reproduction as minimal as possible will make a huge difference to whoever sets out to fix the issue.
diff --git a/docs/5.community/4.contribution.md b/docs/5.community/4.contribution.md
index a601e4a51d..d8e58c24dc 100644
--- a/docs/5.community/4.contribution.md
+++ b/docs/5.community/4.contribution.md
@@ -1,7 +1,7 @@
---
title: 'Contribution'
description: 'Nuxt is a community project - and so we love contributions of all kinds! โค๏ธ'
-navigation.icon: i-ph-git-pull-request-duotone
+navigation.icon: i-ph-git-pull-request
---
There is a range of different ways you might be able to contribute to the Nuxt ecosystem.
@@ -32,10 +32,6 @@ We'll do our best to follow our [internal issue decision making flowchart](https
### Send a Pull Request
-::Tip
-On windows, you need to clone the repository with `git clone -c core.symlinks=true https://github.com/nuxt/nuxt.git` to make symlinks work.
-::
-
We always welcome pull requests! โค๏ธ
#### Before You Start
@@ -188,21 +184,21 @@ Here are some tips that may help improve your documentation:
Keep in mind your readers can have different backgrounds and experiences. Therefore, these words don't convey meaning and can be harmful.
- ::caution{ icon="i-ph-x-circle-duotone"}
+ ::caution{ icon="i-ph-x-circle"}
Simply make sure the function returns a promise.
::
- ::tip{icon="i-ph-check-circle-duotone"}
+ ::tip{icon="i-ph-check-circle"}
Make sure the function returns a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
::
* Prefer [active voice](https://developers.google.com/tech-writing/one/active-voice).
- ::caution{icon="i-ph-x-circle-duotone"}
+ ::caution{icon="i-ph-x-circle"}
An error will be thrown by Nuxt.
::
- ::tip{icon="i-ph-check-circle-duotone"}
+ ::tip{icon="i-ph-check-circle"}
Nuxt will throw an error.
::
diff --git a/docs/5.community/5.framework-contribution.md b/docs/5.community/5.framework-contribution.md
index 2f70b77f98..7c9315fa3a 100644
--- a/docs/5.community/5.framework-contribution.md
+++ b/docs/5.community/5.framework-contribution.md
@@ -1,6 +1,6 @@
---
title: 'Framework'
-navigation.icon: i-ph-github-logo-duotone
+navigation.icon: i-ph-github-logo
description: Some specific points about contributions to the framework repository.
---
@@ -25,9 +25,9 @@ To contribute to Nuxt, you need to set up a local environment.
```bash [Terminal]
corepack enable
```
-4. Run `pnpm install` to Install the dependencies with pnpm:
+4. Run `pnpm install --frozen-lockfile` to Install the dependencies with pnpm:
```bash [Terminal]
- pnpm install
+ pnpm install --frozen-lockfile
```
::note
If you are adding a dependency, please use `pnpm add`. :br
diff --git a/docs/5.community/6.roadmap.md b/docs/5.community/6.roadmap.md
index f485be077d..df987f8bfb 100644
--- a/docs/5.community/6.roadmap.md
+++ b/docs/5.community/6.roadmap.md
@@ -1,7 +1,7 @@
---
title: 'Roadmap'
description: 'Nuxt is constantly evolving, with new features and modules being added all the time.'
-navigation.icon: i-ph-map-trifold-duotone
+navigation.icon: i-ph-map-trifold
---
::read-more{to="/blog"}
@@ -32,7 +32,7 @@ Milestone | Expected date | Notes
-------------|---------------|------------------------------------------------------------------------|-----------------------
SEO & PWA | 2024 | [nuxt/nuxt#18395](https://github.com/nuxt/nuxt/discussions/18395) | Migrating from [nuxt-community/pwa-module](https://github.com/nuxt-community/pwa-module) for built-in SEO utils and service worker support
Assets | 2024 | [nuxt/nuxt#22012](https://github.com/nuxt/nuxt/discussions/22012) | Allow developers and modules to handle loading third-party assets.
-Translations | - | [nuxt/translations#4](https://github.com/nuxt/translations/discussions/4) ([request access](https://github.com/nuxt/nuxt/discussions/16054)) | A collaborative project for a stable translation process for Nuxt 3 docs. Currently pending for ideas and documentation tooling support (content v2 with remote sources).
+Translations | - | [nuxt/translations#4](https://github.com/nuxt/translations/discussions/4) ([request access](https://github.com/nuxt/nuxt/discussions/16054)) | A collaborative project for a stable translation process for Nuxt docs. Currently pending for ideas and documentation tooling support (content v2 with remote sources).
## Core Modules Roadmap
@@ -40,9 +40,9 @@ In addition to the Nuxt framework, there are modules that are vital for the ecos
Module | Status | Nuxt Support | Repository | Description
------------------------------------|---------------------|--------------|------------|-------------------
-[Scripts](https://scripts.nuxt.com) | Public Preview | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management.
+[Scripts](https://scripts.nuxt.com) | Public Beta | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management.
A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255)
-Auth | Planned | 3.x | `nuxt/auth` to be announced | Nuxt 3 support is planned after session support.
+Auth | Planned | 3.x | `nuxt/auth` to be announced | Support is planned after session support.
Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices.
## Release Cycle
@@ -59,13 +59,13 @@ The current active version of [Nuxt](https://nuxt.com) is **v3** which is availa
Nuxt 2 is in maintenance mode and is available on npm with the `2x` tag. It will reach End of Life (EOL) on June 30, 2024.
-Each active version has its own nightly releases which are generated automatically. For more about enabling the Nuxt 3 nightly release channel, see [the nightly release channel docs](/docs/guide/going-further/nightly-release-channel).
+Each active version has its own nightly releases which are generated automatically. For more about enabling the Nuxt nightly release channel, see [the nightly release channel docs](/docs/guide/going-further/nightly-release-channel).
Release | | Initial release | End Of Life | Docs
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
-**4.x** (scheduled) | | 2024 Q2 | |
+**4.x** (scheduled) | | 2024 Q3 | |
**3.x** (stable) |
| 2022-11-16 | TBA | [nuxt.com](/docs)
-**2.x** (maintenance) |
| 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
+**2.x** (unsupported) |
| 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
**1.x** (unsupported) |
| 2018-01-08 | 2019-09-21 |
### Support Status
diff --git a/docs/5.community/7.changelog.md b/docs/5.community/7.changelog.md
index 5daf444f3b..942ec3e32b 100644
--- a/docs/5.community/7.changelog.md
+++ b/docs/5.community/7.changelog.md
@@ -1,7 +1,7 @@
---
title: 'Releases'
description: Discover the latest releases of Nuxt & Nuxt official modules.
-navigation.icon: i-ph-notification-duotone
+navigation.icon: i-ph-notification
---
::card-group
@@ -73,7 +73,7 @@ navigation.icon: i-ph-notification-duotone
target: _blank
ui.icon.base: text-black dark:text-white
---
- Nuxt Scripts releases. (Public Preview)
+ Nuxt Scripts releases.
::
::card
---
diff --git a/docs/5.community/_dir.yml b/docs/5.community/_dir.yml
index 1330352c11..de92f13d6f 100644
--- a/docs/5.community/_dir.yml
+++ b/docs/5.community/_dir.yml
@@ -1,3 +1,3 @@
title: 'Community'
titleTemplate: '%s ยท Nuxt Community'
-icon: i-ph-chats-teardrop-duotone
+icon: i-ph-chats-teardrop
diff --git a/docs/6.bridge/1.overview.md b/docs/6.bridge/1.overview.md
index 8f3a018b24..e37a64a8af 100644
--- a/docs/6.bridge/1.overview.md
+++ b/docs/6.bridge/1.overview.md
@@ -28,16 +28,15 @@ Make sure your dev server (`nuxt dev`) isn't running, remove any package lock fi
Then, reinstall your dependencies:
-::code-group
-
-```bash [yarn]
-yarn install
-```
+::package-managers
```bash [npm]
npm install
```
+```bash [yarn]
+yarn install
+```
::
::note
@@ -48,16 +47,16 @@ Once the installation is complete, make sure both development and production bui
Install `@nuxt/bridge` and `nuxi` as development dependencies:
-::code-group
-
-```bash [Yarn]
-yarn add --dev @nuxt/bridge nuxi
-```
+::package-managers
```bash [npm]
npm install -D @nuxt/bridge nuxi
```
+```bash [yarn]
+yarn add --dev @nuxt/bridge nuxi
+```
+
::
### Update `nuxt.config`
diff --git a/docs/6.bridge/4.plugins-and-middleware.md b/docs/6.bridge/4.plugins-and-middleware.md
index 2250b8bad5..de59c68649 100644
--- a/docs/6.bridge/4.plugins-and-middleware.md
+++ b/docs/6.bridge/4.plugins-and-middleware.md
@@ -44,7 +44,7 @@ Use of `defineNuxtRouteMiddleware` is not supported outside of the middleware di
## definePageMeta
-You can also use [`definePageMeta`](https://nuxt.com/docs/api/utils/define-page-meta) in Nuxt Bridge.
+You can also use [`definePageMeta`](/docs/api/utils/define-page-meta) in Nuxt Bridge.
You can be enabled with the `macros.pageMeta` option in your configuration file
diff --git a/docs/6.bridge/6.meta.md b/docs/6.bridge/6.meta.md
index 6903a774e6..e29ca7995f 100644
--- a/docs/6.bridge/6.meta.md
+++ b/docs/6.bridge/6.meta.md
@@ -98,6 +98,10 @@ export default defineNuxtComponent({
```
+::warning
+Possible breaking change: `head` receives the nuxt app but cannot access the component instance. If the code in your `head` tries to access the data object through `this` or `this.$data`, you will need to migrate to the `useHead` composable.
+::
+
## Title Template
If you want to use a function (for full control), then this cannot be set in your nuxt.config, and it is recommended instead to set it within your `/layouts` directory.
diff --git a/docs/6.bridge/8.nitro.md b/docs/6.bridge/8.nitro.md
index 920b37374a..664ab52427 100644
--- a/docs/6.bridge/8.nitro.md
+++ b/docs/6.bridge/8.nitro.md
@@ -27,16 +27,16 @@ You will also need to update your scripts within your `package.json` to reflect
Install `nuxi` as a development dependency:
-::code-group
-
-```bash [yarn]
-yarn add --dev nuxi
-```
+::package-managers
```bash [npm]
npm install -D nuxi
```
+```bash [yarn]
+yarn add --dev nuxi
+```
+
::
### Nuxi
diff --git a/docs/6.bridge/_dir.yml b/docs/6.bridge/_dir.yml
index f2a37c2daa..f7db65f48d 100644
--- a/docs/6.bridge/_dir.yml
+++ b/docs/6.bridge/_dir.yml
@@ -1,3 +1,3 @@
titleTemplate: 'Migrate to Nuxt Bridge: %s'
title: 'Migrate to Nuxt Bridge'
-icon: i-ph-bridge-duotone
+icon: i-ph-bridge
diff --git a/docs/7.migration/20.module-authors.md b/docs/7.migration/20.module-authors.md
index ff42347c12..abe8fc67c1 100644
--- a/docs/7.migration/20.module-authors.md
+++ b/docs/7.migration/20.module-authors.md
@@ -9,7 +9,7 @@ Nuxt 3 has a basic backward compatibility layer for Nuxt 2 modules using `@nuxt/
We have prepared a [Dedicated Guide](/docs/guide/going-further/modules) for authoring Nuxt 3 ready modules using `@nuxt/kit`. Currently best migration path is to follow it and rewrite your modules. Rest of this guide includes preparation steps if you prefer to avoid a full rewrite yet making modules compatible with Nuxt 3.
-::tip{icon="i-ph-puzzle-piece-duotone" to="/modules"}
+::tip{icon="i-ph-puzzle-piece" to="/modules"}
Explore Nuxt 3 compatible modules.
::
diff --git a/docs/7.migration/7.component-options.md b/docs/7.migration/7.component-options.md
index ced74743cb..ac710b0a63 100644
--- a/docs/7.migration/7.component-options.md
+++ b/docs/7.migration/7.component-options.md
@@ -103,7 +103,7 @@ This feature is not yet supported in Nuxt 3.
## `scrollToTop`
-This feature is not yet supported in Nuxt 3. If you want to overwrite the default scroll behavior of `vue-router`, you can do so in `~/app/router.options.ts` (see [docs](/docs/guide/going-further/custom-routing#router-options)) for more info.
+This feature is not yet supported in Nuxt 3. If you want to overwrite the default scroll behavior of `vue-router`, you can do so in `~/app/router.options.ts` (see [docs](/docs/guide/recipes/custom-routing#router-options)) for more info.
Similar to `key`, specify it within the [`definePageMeta`](/docs/api/utils/define-page-meta) compiler macro.
```diff [pages/index.vue]
diff --git a/docs/7.migration/_dir.yml b/docs/7.migration/_dir.yml
index 54585df393..a880111684 100644
--- a/docs/7.migration/_dir.yml
+++ b/docs/7.migration/_dir.yml
@@ -1,3 +1,3 @@
titleTemplate: 'Migrate to Nuxt 3: %s'
title: 'Migrate to Nuxt 3'
-icon: i-ph-arrow-circle-up-duotone
+icon: i-ph-arrow-circle-up
diff --git a/docs/_dir.yml b/docs/_dir.yml
index 6639eb3a35..18fdf7dc26 100644
--- a/docs/_dir.yml
+++ b/docs/_dir.yml
@@ -1,2 +1,2 @@
title: Docs
-icon: i-ph-book-bookmark-duotone
+icon: i-ph-book-bookmark
diff --git a/eslint.config.mjs b/eslint.config.mjs
index c4e282b673..a6ad15a72e 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -3,7 +3,6 @@ import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
// @ts-expect-error missing types
import noOnlyTests from 'eslint-plugin-no-only-tests'
import typegen from 'eslint-typegen'
-// @ts-expect-error missing types
import perfectionist from 'eslint-plugin-perfectionist'
export default createConfigForNuxt({
@@ -148,17 +147,6 @@ export default createConfigForNuxt({
],
},
],
- 'import/order': [
- 'error',
- {
- pathGroups: [
- {
- group: 'external',
- pattern: '#vue-router',
- },
- ],
- },
- ],
'jsdoc/check-tag-names': [
'error',
{
@@ -200,6 +188,7 @@ export default createConfigForNuxt({
},
},
// Sort rule keys in eslint config
+ // @ts-expect-error incorrect types ๐ค
{
files: ['**/eslint.config.mjs'],
name: 'local/sort-eslint-config',
diff --git a/examples/README.md b/examples/README.md
index ba22dfb66e..da61500f7c 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,4 +1,4 @@
-# Nuxt 3 Examples
+# Nuxt Examples
- ๐ See examples in your browser at https://nuxt.com/docs/examples
- ๐ View on GitHub at https://github.com/nuxt/examples
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 694788ebb0..54f547026e 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,16 +1,21 @@
// For pnpm typecheck:docs to generate correct types
-import { addPluginTemplate } from 'nuxt/kit'
+import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit'
export default defineNuxtConfig({
typescript: { shim: process.env.DOCS_TYPECHECK === 'true' },
pages: process.env.DOCS_TYPECHECK === 'true',
modules: [
function () {
+ if (!process.env.DOCS_TYPECHECK) { return }
addPluginTemplate({
filename: 'plugins/my-plugin.mjs',
getContents: () => 'export default defineNuxtPlugin({ name: \'my-plugin\' })',
})
+ addRouteMiddleware({
+ name: 'auth',
+ path: '#build/auth.js',
+ })
},
],
})
diff --git a/package.json b/package.json
index 6dd9b42cd8..f34ee7e6b6 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"build:stub": "pnpm dev:prepare",
"cleanup": "rimraf 'packages/**/node_modules' 'playground/node_modules' 'node_modules'",
"dev": "pnpm play",
- "dev:prepare": "pnpm --filter './packages/**' prepack --stub",
+ "dev:prepare": "pnpm --filter './packages/**' prepack --stub && pnpm --filter './packages/ui-templates' build",
"lint": "eslint . --cache",
"lint:fix": "eslint . --cache --fix",
"lint:docs": "markdownlint ./docs && case-police 'docs/**/*.md' *.md",
@@ -31,7 +31,7 @@
"test:types": "pnpm --filter './test/fixtures/**' test:types",
"test:unit": "vitest run packages/",
"typecheck": "tsc --noEmit",
- "typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs"
+ "typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs --languages html"
},
"resolutions": {
"@nuxt/kit": "workspace:*",
@@ -39,59 +39,78 @@
"@nuxt/ui-templates": "workspace:*",
"@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*",
- "magic-string": "^0.30.10",
+ "@vue/compiler-core": "3.5.8",
+ "@vue/compiler-dom": "3.5.8",
+ "@vue/shared": "3.5.8",
+ "@types/node": "20.16.6",
+ "c12": "2.0.0-beta.2",
+ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
+ "jiti": "2.0.0-rc.1",
+ "magic-string": "^0.30.11",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxt": "workspace:*",
- "rollup": "^4.18.0",
- "vite": "5.2.12",
- "vue": "3.4.27"
+ "ohash": "1.1.4",
+ "postcss": "8.4.47",
+ "rollup": "4.22.4",
+ "send": ">=0.19.0",
+ "typescript": "5.6.2",
+ "ufo": "1.5.4",
+ "unbuild": "3.0.0-rc.7",
+ "vite": "5.4.7",
+ "vue": "3.5.8"
},
"devDependencies": {
- "@eslint/js": "9.3.0",
- "@nuxt/eslint-config": "0.3.13",
+ "@eslint/js": "9.11.1",
+ "@nuxt/eslint-config": "0.5.7",
"@nuxt/kit": "workspace:*",
- "@nuxt/test-utils": "3.13.1",
+ "@nuxt/test-utils": "3.14.2",
"@nuxt/webpack-builder": "workspace:*",
"@testing-library/vue": "8.1.0",
"@types/eslint__js": "8.42.3",
- "@types/fs-extra": "11.0.4",
- "@types/node": "20.12.13",
+ "@types/node": "20.16.6",
"@types/semver": "7.5.8",
- "@vitest/coverage-v8": "1.6.0",
+ "@unhead/schema": "1.11.6",
+ "@unhead/vue": "1.11.6",
+ "@vitejs/plugin-vue": "5.1.4",
+ "@vitest/coverage-v8": "2.1.1",
"@vue/test-utils": "2.4.6",
- "case-police": "0.6.1",
- "changelogen": "0.5.5",
+ "autoprefixer": "10.4.20",
+ "case-police": "0.7.0",
+ "changelogen": "0.5.7",
"consola": "3.2.3",
+ "cssnano": "7.0.6",
+ "destr": "2.0.3",
"devalue": "5.0.0",
- "eslint": "9.3.0",
- "eslint-plugin-no-only-tests": "3.1.0",
- "eslint-plugin-perfectionist": "2.10.0",
- "eslint-typegen": "0.2.4",
- "execa": "9.1.0",
- "fs-extra": "11.2.0",
- "globby": "14.0.1",
- "h3": "1.11.1",
- "happy-dom": "14.12.0",
- "jiti": "1.21.0",
- "markdownlint-cli": "0.41.0",
- "nitropack": "2.9.6",
- "nuxi": "3.11.1",
+ "eslint": "9.11.1",
+ "eslint-plugin-no-only-tests": "3.3.0",
+ "eslint-plugin-perfectionist": "3.7.0",
+ "eslint-typegen": "0.3.2",
+ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
+ "happy-dom": "15.7.4",
+ "jiti": "2.0.0-rc.1",
+ "markdownlint-cli": "0.42.0",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
+ "nuxi": "3.13.2",
"nuxt": "workspace:*",
- "nuxt-content-twoslash": "0.0.10",
- "ofetch": "1.3.4",
+ "nuxt-content-twoslash": "0.1.1",
+ "ofetch": "1.4.0",
"pathe": "1.1.2",
- "playwright-core": "1.44.1",
- "rimraf": "5.0.7",
- "semver": "7.6.2",
+ "playwright-core": "1.47.2",
+ "rimraf": "6.0.1",
+ "semver": "7.6.3",
+ "sherif": "1.0.0",
"std-env": "3.7.0",
- "typescript": "5.4.5",
- "ufo": "1.5.3",
- "vitest": "1.6.0",
- "vitest-environment-nuxt": "1.0.0",
- "vue": "3.4.27",
- "vue-router": "4.3.2",
- "vue-tsc": "2.0.19"
+ "tinyexec": "0.3.0",
+ "tinyglobby": "0.2.6",
+ "typescript": "5.6.2",
+ "ufo": "1.5.4",
+ "vitest": "2.1.1",
+ "vitest-environment-nuxt": "1.0.1",
+ "vue": "3.5.8",
+ "vue-router": "4.4.5",
+ "vue-tsc": "2.1.6"
},
- "packageManager": "pnpm@9.1.3",
+ "packageManager": "pnpm@9.11.0",
"engines": {
"node": "^16.10.0 || >=18.0.0"
},
diff --git a/packages/kit/build.config.ts b/packages/kit/build.config.ts
index 10db295927..87a8ccb072 100644
--- a/packages/kit/build.config.ts
+++ b/packages/kit/build.config.ts
@@ -8,9 +8,9 @@ export default defineBuildConfig({
externals: [
'@nuxt/schema',
'nitropack',
+ 'nitro',
'webpack',
'vite',
'h3',
],
- failOnWarn: false,
})
diff --git a/packages/kit/index.d.ts b/packages/kit/index.d.ts
deleted file mode 100644
index 43b479fd69..0000000000
--- a/packages/kit/index.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-disable no-var */
-declare global {
- var __NUXT_PREPATHS__: string[] | string | undefined
- var __NUXT_PATHS__: string[] | string | undefined
-}
-
-export {}
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 62d145b029..b763fd1258 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
{
"name": "@nuxt/kit",
- "version": "3.11.2",
+ "version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@@ -27,36 +27,34 @@
},
"dependencies": {
"@nuxt/schema": "workspace:*",
- "c12": "^1.10.0",
+ "c12": "^2.0.0-beta.2",
"consola": "^3.2.3",
"defu": "^6.1.4",
"destr": "^2.0.3",
- "globby": "^14.0.1",
+ "errx": "^0.1.0",
+ "globby": "^14.0.2",
"hash-sum": "^2.0.0",
- "ignore": "^5.3.1",
- "jiti": "^1.21.0",
+ "ignore": "^6.0.1",
+ "jiti": "^2.0.0-rc.1",
"klona": "^2.0.6",
- "knitwork": "^1.1.0",
- "mlly": "^1.7.0",
+ "mlly": "^1.7.1",
"pathe": "^1.1.2",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.2.0",
"scule": "^1.3.0",
- "semver": "^7.6.2",
- "ufo": "^1.5.3",
+ "semver": "^7.6.3",
+ "ufo": "^1.5.4",
"unctx": "^2.3.1",
- "unimport": "^3.7.2",
+ "unimport": "^3.12.0",
"untyped": "^1.4.2"
},
"devDependencies": {
"@types/hash-sum": "1.0.2",
- "@types/lodash-es": "4.17.12",
"@types/semver": "7.5.8",
- "lodash-es": "4.17.21",
- "nitropack": "2.9.6",
- "unbuild": "latest",
- "vite": "5.2.12",
- "vitest": "1.6.0",
- "webpack": "5.91.0"
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
+ "unbuild": "3.0.0-rc.7",
+ "vite": "5.4.7",
+ "vitest": "2.1.1",
+ "webpack": "5.94.0"
},
"engines": {
"node": "^14.18.0 || >=16.10.0"
diff --git a/packages/kit/src/build.ts b/packages/kit/src/build.ts
index 0a186f63b7..fab8c45228 100644
--- a/packages/kit/src/build.ts
+++ b/packages/kit/src/build.ts
@@ -30,8 +30,10 @@ export interface ExtendConfigOptions {
prepend?: boolean
}
+// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ExtendWebpackConfigOptions extends ExtendConfigOptions {}
+// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ExtendViteConfigOptions extends ExtendConfigOptions {}
/**
diff --git a/packages/kit/src/compatibility.ts b/packages/kit/src/compatibility.ts
index b2720f05c9..00ad74b67c 100644
--- a/packages/kit/src/compatibility.ts
+++ b/packages/kit/src/compatibility.ts
@@ -29,23 +29,6 @@ export async function checkNuxtCompatibility (constraints: NuxtCompatibility, nu
}
}
- // Bridge compatibility check
- if (isNuxt2(nuxt)) {
- const bridgeRequirement = constraints.bridge
- const hasBridge = !!(nuxt.options as any).bridge
- if (bridgeRequirement === true && !hasBridge) {
- issues.push({
- name: 'bridge',
- message: 'Nuxt bridge is required',
- })
- } else if (bridgeRequirement === false && hasBridge) {
- issues.push({
- name: 'bridge',
- message: 'Nuxt bridge is not supported',
- })
- }
- }
-
// Builder compatibility check
if (constraints.builder && typeof nuxt.options.builder === 'string') {
const currentBuilder = builderMap[nuxt.options.builder] || nuxt.options.builder
@@ -98,19 +81,26 @@ export async function hasNuxtCompatibility (constraints: NuxtCompatibility, nuxt
}
/**
- * Check if current nuxt instance is version 2 legacy
+ * Check if current Nuxt instance is of specified major version
*/
-export function isNuxt2 (nuxt: Nuxt = useNuxt()) {
+export function isNuxtMajorVersion (majorVersion: 2 | 3 | 4, nuxt: Nuxt = useNuxt()) {
const version = getNuxtVersion(nuxt)
- return version[0] === '2' && version[1] === '.'
+
+ return version[0] === majorVersion.toString() && version[1] === '.'
}
/**
- * Check if current nuxt instance is version 3
+ * @deprecated Use `isNuxtMajorVersion(2, nuxt)` instead. This may be removed in \@nuxt/kit v5 or a future major version.
+ */
+export function isNuxt2 (nuxt: Nuxt = useNuxt()) {
+ return isNuxtMajorVersion(2, nuxt)
+}
+
+/**
+ * @deprecated Use `isNuxtMajorVersion(3, nuxt)` instead. This may be removed in \@nuxt/kit v5 or a future major version.
*/
export function isNuxt3 (nuxt: Nuxt = useNuxt()) {
- const version = getNuxtVersion(nuxt)
- return version[0] === '3' && version[1] === '.'
+ return isNuxtMajorVersion(3, nuxt)
}
/**
@@ -118,8 +108,8 @@ export function isNuxt3 (nuxt: Nuxt = useNuxt()) {
*/
export function getNuxtVersion (nuxt: Nuxt | any = useNuxt() /* TODO: LegacyNuxt */) {
const rawVersion = nuxt?._version || nuxt?.version || nuxt?.constructor?.version
- if (!rawVersion) {
- throw new Error('Cannot determine nuxt version! Is current instance passed?')
+ if (typeof rawVersion !== 'string') {
+ throw new TypeError('Cannot determine nuxt version! Is current instance passed?')
}
return rawVersion.replace(/^v/g, '')
}
diff --git a/packages/kit/src/components.ts b/packages/kit/src/components.ts
index 49bc6d91e2..669d6ce410 100644
--- a/packages/kit/src/components.ts
+++ b/packages/kit/src/components.ts
@@ -6,8 +6,6 @@ import { logger } from './logger'
/**
* Register a directory to be scanned for components and imported only when used.
- *
- * Requires Nuxt 2.13+
*/
export async function addComponentsDir (dir: ComponentsDir, opts: { prepend?: boolean } = {}) {
const nuxt = useNuxt()
@@ -23,8 +21,6 @@ export type AddComponentOptions = { name: string, filePath: string } & Partial
{
const existingComponentIndex = components.findIndex(c => (c.pascalName === component.pascalName || c.kebabName === component.kebabName) && c.mode === component.mode)
if (existingComponentIndex !== -1) {
- const existingComponent = components[existingComponentIndex]
+ const existingComponent = components[existingComponentIndex]!
const existingPriority = existingComponent.priority ?? 0
const newPriority = component.priority ?? 0
diff --git a/packages/kit/src/ignore.ts b/packages/kit/src/ignore.ts
index 9dcdc32cf2..dc18508a06 100644
--- a/packages/kit/src/ignore.ts
+++ b/packages/kit/src/ignore.ts
@@ -50,7 +50,7 @@ export function resolveIgnorePatterns (relativePath?: string): string[] {
// Map ignore patterns based on if they start with * or !*
return ignorePatterns.map((p) => {
const [_, negation = '', pattern] = p.match(NEGATION_RE) || []
- if (pattern[0] === '*') {
+ if (pattern && pattern[0] === '*') {
return p
}
return negation + relative(relativePath, resolve(nuxt.options.rootDir, pattern || p))
@@ -73,7 +73,7 @@ export function resolveGroupSyntax (group: string): string[] {
groups = groups.flatMap((group) => {
const [head, ...tail] = group.split('{')
if (tail.length) {
- const [body, ...rest] = tail.join('{').split('}')
+ const [body = '', ...rest] = tail.join('{').split('}')
return body.split(',').map(part => `${head}${part}${rest.join('')}`)
}
diff --git a/packages/kit/src/imports.ts b/packages/kit/src/imports.ts
index 530f4f6f96..4aa9649833 100644
--- a/packages/kit/src/imports.ts
+++ b/packages/kit/src/imports.ts
@@ -1,20 +1,15 @@
import type { Import } from 'unimport'
import type { ImportPresetWithDeprecation } from '@nuxt/schema'
import { useNuxt } from './context'
-import { assertNuxtCompatibility } from './compatibility'
import { toArray } from './utils'
export function addImports (imports: Import | Import[]) {
- assertNuxtCompatibility({ bridge: true })
-
useNuxt().hook('imports:extend', (_imports) => {
_imports.push(...toArray(imports))
})
}
export function addImportsDir (dirs: string | string[], opts: { prepend?: boolean } = {}) {
- assertNuxtCompatibility({ bridge: true })
-
useNuxt().hook('imports:dirs', (_dirs: string[]) => {
for (const dir of toArray(dirs)) {
_dirs[opts.prepend ? 'unshift' : 'push'](dir)
@@ -22,8 +17,6 @@ export function addImportsDir (dirs: string | string[], opts: { prepend?: boolea
})
}
export function addImportsSources (presets: ImportPresetWithDeprecation | ImportPresetWithDeprecation[]) {
- assertNuxtCompatibility({ bridge: true })
-
useNuxt().hook('imports:sources', (_presets: ImportPresetWithDeprecation[]) => {
for (const preset of toArray(presets)) {
_presets.push(preset)
diff --git a/packages/kit/src/index.ts b/packages/kit/src/index.ts
index 405de8606a..bde038e6fb 100644
--- a/packages/kit/src/index.ts
+++ b/packages/kit/src/index.ts
@@ -1,38 +1,36 @@
// Module
-export * from './module/define'
-export * from './module/install'
-export * from './module/compatibility'
+export { defineNuxtModule } from './module/define'
+export { getDirectory, installModule, loadNuxtModuleInstance, normalizeModuleTranspilePath } from './module/install'
+export { getNuxtModuleVersion, hasNuxtModule, hasNuxtModuleCompatibility } from './module/compatibility'
// Loader
-export * from './loader/config'
-export * from './loader/schema'
-export * from './loader/nuxt'
+export { loadNuxtConfig } from './loader/config'
+export type { LoadNuxtConfigOptions } from './loader/config'
+export { extendNuxtSchema } from './loader/schema'
+export { buildNuxt, loadNuxt } from './loader/nuxt'
+export type { LoadNuxtOptions } from './loader/nuxt'
// Utils
-export * from './imports'
+export { addImports, addImportsDir, addImportsSources } from './imports'
export { updateRuntimeConfig, useRuntimeConfig } from './runtime-config'
-export * from './build'
-export * from './compatibility'
-export * from './components'
-export * from './context'
+export { addBuildPlugin, addVitePlugin, addWebpackPlugin, extendViteConfig, extendWebpackConfig } from './build'
+export type { ExtendConfigOptions, ExtendViteConfigOptions, ExtendWebpackConfigOptions } from './build'
+export { assertNuxtCompatibility, checkNuxtCompatibility, getNuxtVersion, hasNuxtCompatibility, isNuxtMajorVersion, normalizeSemanticVersion, isNuxt2, isNuxt3 } from './compatibility'
+export { addComponent, addComponentsDir } from './components'
+export type { AddComponentOptions } from './components'
+export { nuxtCtx, tryUseNuxt, useNuxt } from './context'
export { isIgnored, resolveIgnorePatterns } from './ignore'
-export * from './layout'
-export * from './pages'
-export * from './plugin'
-export * from './resolve'
-export * from './nitro'
+export { addLayout } from './layout'
+export { addRouteMiddleware, extendPages, extendRouteRules } from './pages'
+export type { AddRouteMiddlewareOptions, ExtendRouteRulesOptions } from './pages'
+export { addPlugin, addPluginTemplate, normalizePlugin } from './plugin'
+export type { AddPluginOptions } from './plugin'
+export { createResolver, findPath, resolveAlias, resolveFiles, resolveNuxtModule, resolvePath } from './resolve'
+export type { ResolvePathOptions, Resolver } from './resolve'
+export { addServerHandler, addDevServerHandler, addServerPlugin, addPrerenderRoutes, useNitro, addServerImports, addServerImportsDir, addServerScanDir } from './nitro'
export { addTemplate, addTypeTemplate, normalizeTemplate, updateTemplates, writeTypes } from './template'
-export * from './logger'
+export { logger, useLogger } from './logger'
// Internal Utils
-// TODO
-export {
- resolveModule,
- requireModule,
- importModule,
- tryImportModule,
- tryRequireModule,
-} from './internal/cjs'
-export type { ResolveModuleOptions, RequireModuleOptions } from './internal/cjs'
-export { tryResolveModule } from './internal/esm'
-export * from './internal/template'
+export { resolveModule, tryResolveModule, importModule, tryImportModule, requireModule, tryRequireModule } from './internal/esm'
+export type { ImportModuleOptions, ResolveModuleOptions } from './internal/esm'
diff --git a/packages/kit/src/internal/cjs.ts b/packages/kit/src/internal/cjs.ts
deleted file mode 100644
index 7e30acfec7..0000000000
--- a/packages/kit/src/internal/cjs.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { pathToFileURL } from 'node:url'
-import { normalize } from 'pathe'
-import { interopDefault } from 'mlly'
-import jiti from 'jiti'
-
-// TODO: use create-require for jest environment
-const _require = jiti(process.cwd(), { interopDefault: true, esmResolve: true })
-
-/** @deprecated Do not use CJS utils */
-export interface ResolveModuleOptions {
- paths?: string | string[]
-}
-
-/** @deprecated Do not use CJS utils */
-export interface RequireModuleOptions extends ResolveModuleOptions {
- // TODO: use create-require for jest environment
- // native?: boolean
- /** Clear the require cache (force fresh require) but only if not within `node_modules` */
- clearCache?: boolean
-
- /** Automatically de-default the result of requiring the module. */
- interopDefault?: boolean
-}
-
-/** @deprecated Do not use CJS utils */
-function isNodeModules (id: string) {
- // TODO: Follow symlinks
- return /[/\\]node_modules[/\\]/.test(id)
-}
-
-/** @deprecated Do not use CJS utils */
-function clearRequireCache (id: string) {
- if (isNodeModules(id)) {
- return
- }
-
- const entry = getRequireCacheItem(id)
-
- if (!entry) {
- delete _require.cache[id]
- return
- }
-
- if (entry.parent) {
- entry.parent.children = entry.parent.children.filter(e => e.id !== id)
- }
-
- for (const child of entry.children) {
- clearRequireCache(child.id)
- }
-
- delete _require.cache[id]
-}
-
-/** @deprecated Do not use CJS utils */
-function getRequireCacheItem (id: string) {
- try {
- return _require.cache[id]
- } catch (e) {
- // ignore issues accessing require.cache
- }
-}
-
-export function getModulePaths (paths?: string[] | string) {
- return ([] as Array).concat(
- global.__NUXT_PREPATHS__,
- paths || [],
- process.cwd(),
- global.__NUXT_PATHS__,
- ).filter(Boolean) as string[]
-}
-
-/** @deprecated Do not use CJS utils */
-export function resolveModule (id: string, opts: ResolveModuleOptions = {}) {
- return normalize(_require.resolve(id, {
- paths: getModulePaths(opts.paths),
- }))
-}
-
-/** @deprecated Do not use CJS utils */
-export function requireModule (id: string, opts: RequireModuleOptions = {}) {
- // Resolve id
- const resolvedPath = resolveModule(id, opts)
-
- // Clear require cache if necessary
- if (opts.clearCache && !isNodeModules(id)) {
- clearRequireCache(resolvedPath)
- }
-
- // Try to require
- const requiredModule = _require(resolvedPath)
-
- return requiredModule
-}
-
-/** @deprecated Do not use CJS utils */
-export function importModule (id: string, opts: RequireModuleOptions = {}) {
- const resolvedPath = resolveModule(id, opts)
- if (opts.interopDefault !== false) {
- return import(pathToFileURL(resolvedPath).href).then(interopDefault)
- }
- return import(pathToFileURL(resolvedPath).href)
-}
-
-/** @deprecated Do not use CJS utils */
-export function tryImportModule (id: string, opts: RequireModuleOptions = {}) {
- try {
- return importModule(id, opts).catch(() => undefined)
- } catch {
- // intentionally empty as this is a `try-` function
- }
-}
-
-/** @deprecated Do not use CJS utils */
-export function tryRequireModule (id: string, opts: RequireModuleOptions = {}) {
- try {
- return requireModule(id, opts)
- } catch {
- // intentionally empty as this is a `try-` function
- }
-}
diff --git a/packages/kit/src/internal/esm.ts b/packages/kit/src/internal/esm.ts
index 54e4524a73..dabfa78d15 100644
--- a/packages/kit/src/internal/esm.ts
+++ b/packages/kit/src/internal/esm.ts
@@ -1,5 +1,11 @@
-import { pathToFileURL } from 'node:url'
-import { interopDefault, resolvePath } from 'mlly'
+import { fileURLToPath, pathToFileURL } from 'node:url'
+import { interopDefault, resolvePath, resolvePathSync } from 'mlly'
+import { createJiti } from 'jiti'
+import { captureStackTrace } from 'errx'
+
+export interface ResolveModuleOptions {
+ paths?: string | string[]
+}
/**
* Resolve a module from a given root path using an algorithm patterned on
@@ -15,14 +21,54 @@ export async function tryResolveModule (id: string, url: string | string[] = imp
}
}
-export async function importModule (id: string, url: string | string[] = import.meta.url) {
- const resolvedPath = await resolvePath(id, { url })
- return import(pathToFileURL(resolvedPath).href).then(interopDefault)
+export function resolveModule (id: string, options?: ResolveModuleOptions) {
+ return resolvePathSync(id, { url: options?.paths ?? [import.meta.url] })
}
-export function tryImportModule (id: string, url = import.meta.url) {
+export interface ImportModuleOptions extends ResolveModuleOptions {
+ /** Automatically de-default the result of requiring the module. */
+ interopDefault?: boolean
+}
+
+export async function importModule (id: string, opts?: ImportModuleOptions) {
+ const resolvedPath = await resolveModule(id, opts)
+ return import(pathToFileURL(resolvedPath).href).then(r => opts?.interopDefault !== false ? interopDefault(r) : r) as Promise
+}
+
+export function tryImportModule (id: string, opts?: ImportModuleOptions) {
try {
- return importModule(id, url).catch(() => undefined)
+ return importModule(id, opts).catch(() => undefined)
+ } catch {
+ // intentionally empty as this is a `try-` function
+ }
+}
+
+const warnings = new Set()
+
+/**
+ * @deprecated Please use `importModule` instead.
+ */
+export function requireModule (id: string, opts?: ImportModuleOptions) {
+ const { source, line, column } = captureStackTrace().find(entry => entry.source !== import.meta.url) ?? {}
+ const explanation = source ? ` (used at \`${fileURLToPath(source)}:${line}:${column}\`)` : ''
+ const warning = `[@nuxt/kit] \`requireModule\` is deprecated${explanation}. Please use \`importModule\` instead.`
+ if (!warnings.has(warning)) {
+ console.warn(warning)
+ warnings.add(warning)
+ }
+ const resolvedPath = resolveModule(id, opts)
+ const jiti = createJiti(import.meta.url, {
+ interopDefault: opts?.interopDefault !== false,
+ })
+ return jiti(pathToFileURL(resolvedPath).href) as T
+}
+
+/**
+ * @deprecated Please use `tryImportModule` instead.
+ */
+export function tryRequireModule (id: string, opts?: ImportModuleOptions) {
+ try {
+ return requireModule(id, opts)
} catch {
// intentionally empty as this is a `try-` function
}
diff --git a/packages/kit/src/internal/template.ts b/packages/kit/src/internal/template.ts
deleted file mode 100644
index 5b40a2a214..0000000000
--- a/packages/kit/src/internal/template.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { promises as fsp } from 'node:fs'
-// TODO: swap out when https://github.com/lodash/lodash/pull/5649 is merged
-import { template as lodashTemplate } from 'lodash-es'
-import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
-
-import type { NuxtTemplate } from '@nuxt/schema'
-import { logger } from '../logger'
-import { toArray } from '../utils'
-
-/** @deprecated */
-// TODO: Remove support for compiling ejs templates in v4
-export async function compileTemplate (template: NuxtTemplate, ctx: any) {
- const data = { ...ctx, options: template.options }
- if (template.src) {
- try {
- const srcContents = await fsp.readFile(template.src, 'utf-8')
- return lodashTemplate(srcContents, {})(data)
- } catch (err) {
- logger.error('Error compiling template: ', template)
- throw err
- }
- }
- if (template.getContents) {
- return template.getContents(data)
- }
- throw new Error('Invalid template: ' + JSON.stringify(template))
-}
-
-/** @deprecated */
-const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"\{(.+)\}"(?=,?$)/gm, r => JSON.parse(r).replace(/^\{(.*)\}$/, '$1'))
-
-/** @deprecated */
-const importSources = (sources: string | string[], { lazy = false } = {}) => {
- return toArray(sources).map((src) => {
- if (lazy) {
- return `const ${genSafeVariableName(src)} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
- }
- return genImport(src, genSafeVariableName(src))
- }).join('\n')
-}
-
-/** @deprecated */
-const importName = genSafeVariableName
-
-/** @deprecated */
-export const templateUtils = { serialize, importName, importSources }
diff --git a/packages/kit/src/layout.ts b/packages/kit/src/layout.ts
index bf86c443c9..65fd183163 100644
--- a/packages/kit/src/layout.ts
+++ b/packages/kit/src/layout.ts
@@ -1,35 +1,19 @@
import type { NuxtTemplate } from '@nuxt/schema'
import { join, parse, relative } from 'pathe'
import { kebabCase } from 'scule'
-import { isNuxt2 } from './compatibility'
import { useNuxt } from './context'
import { logger } from './logger'
import { addTemplate } from './template'
-export function addLayout (this: any, template: NuxtTemplate | string, name?: string) {
+export function addLayout (template: NuxtTemplate | string, name?: string) {
const nuxt = useNuxt()
const { filename, src } = addTemplate(template)
const layoutName = kebabCase(name || parse(filename).name).replace(/["']/g, '')
- if (isNuxt2(nuxt)) {
- // Nuxt 2 adds layouts in options
- const layout = (nuxt.options as any).layouts[layoutName]
- if (layout) {
- return logger.warn(
- `Not overriding \`${layoutName}\` (provided by \`${layout}\`) with \`${src || filename}\`.`,
- )
- }
- (nuxt.options as any).layouts[layoutName] = `./${filename}`
- if (name === 'error') {
- this.addErrorLayout(filename)
- }
- return
- }
-
// Nuxt 3 adds layouts on app
nuxt.hook('app:templates', (app) => {
if (layoutName in app.layouts) {
- const relativePath = relative(nuxt.options.srcDir, app.layouts[layoutName].file)
+ const relativePath = relative(nuxt.options.srcDir, app.layouts[layoutName]!.file)
return logger.warn(
`Not overriding \`${layoutName}\` (provided by \`~/${relativePath}\`) with \`${src || filename}\`.`,
)
diff --git a/packages/kit/src/loader/config.ts b/packages/kit/src/loader/config.ts
index fab118bf6c..80139fb015 100644
--- a/packages/kit/src/loader/config.ts
+++ b/packages/kit/src/loader/config.ts
@@ -1,3 +1,4 @@
+import { existsSync } from 'node:fs'
import type { JSValue } from 'untyped'
import { applyDefaults } from 'untyped'
import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12'
@@ -6,11 +7,20 @@ import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
import { NuxtConfigSchema } from '@nuxt/schema'
import { globby } from 'globby'
import defu from 'defu'
+import { join } from 'pathe'
-export interface LoadNuxtConfigOptions extends LoadConfigOptions {}
+export interface LoadNuxtConfigOptions extends Omit, 'overrides'> {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
+ overrides?: Exclude['overrides'], Promise | Function>
+}
-const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir']
-const layerSchema = Object.fromEntries(Object.entries(NuxtConfigSchema).filter(([key]) => layerSchemaKeys.includes(key)))
+const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'serverDir', 'dir']
+const layerSchema = Object.create(null)
+for (const key of layerSchemaKeys) {
+ if (key in NuxtConfigSchema) {
+ layerSchema[key] = NuxtConfigSchema[key]
+ }
+}
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise {
// Automatically detect and import layers from `~~/layers/` directory
@@ -39,11 +49,21 @@ export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise[] = []
+ const processedLayers = new Set()
for (const layer of layers) {
// Resolve `rootDir` & `srcDir` of layers
layer.config = layer.config || {}
- layer.config.rootDir = layer.config.rootDir ?? layer.cwd
+ layer.config.rootDir = layer.config.rootDir ?? layer.cwd!
+
+ // Only process/resolve layers once
+ if (processedLayers.has(layer.config.rootDir)) { continue }
+ processedLayers.add(layer.config.rootDir)
// Normalise layer directories
layer.config = await applyDefaults(layerSchema, layer.config as NuxtConfig & Record) as unknown as NuxtConfig
diff --git a/packages/kit/src/loader/nuxt.ts b/packages/kit/src/loader/nuxt.ts
index 85c4498313..892286f5b6 100644
--- a/packages/kit/src/loader/nuxt.ts
+++ b/packages/kit/src/loader/nuxt.ts
@@ -1,6 +1,7 @@
import { pathToFileURL } from 'node:url'
import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
-import type { Nuxt } from '@nuxt/schema'
+import type { Nuxt, NuxtConfig } from '@nuxt/schema'
+import { resolve } from 'pathe'
import { importModule, tryImportModule } from '../internal/esm'
import type { LoadNuxtConfigOptions } from './config'
@@ -10,76 +11,34 @@ export interface LoadNuxtOptions extends LoadNuxtConfigOptions {
/** Use lazy initialization of nuxt if set to false */
ready?: boolean
-
- /** @deprecated Use cwd option */
- rootDir?: LoadNuxtConfigOptions['cwd']
-
- /** @deprecated use overrides option */
- config?: LoadNuxtConfigOptions['overrides']
}
export async function loadNuxt (opts: LoadNuxtOptions): Promise {
// Backward compatibility
- opts.cwd = opts.cwd || opts.rootDir
- opts.overrides = opts.overrides || opts.config || {}
+ opts.cwd = resolve(opts.cwd || (opts as any).rootDir /* backwards compat */ || '.')
+ opts.overrides = opts.overrides || (opts as any).config as NuxtConfig /* backwards compat */ || {}
// Apply dev as config override
opts.overrides.dev = !!opts.dev
- const nearestNuxtPkg = await Promise.all(['nuxt-nightly', 'nuxt3', 'nuxt', 'nuxt-edge']
+ const nearestNuxtPkg = await Promise.all(['nuxt-nightly', 'nuxt']
.map(pkg => resolvePackageJSON(pkg, { url: opts.cwd }).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 majorVersion = pkg.version ? Number.parseInt(pkg.version.split('.')[0]) : ''
- const rootDir = pathToFileURL(opts.cwd || process.cwd()).href
+ const rootDir = pathToFileURL(opts.cwd!).href
- // Nuxt 3
- if (majorVersion === 3) {
- const { loadNuxt } = await importModule((pkg as any)._name || pkg.name, rootDir)
- const nuxt = await loadNuxt(opts)
- return nuxt
- }
-
- // Nuxt 2
- const { loadNuxt } = await tryImportModule('nuxt-edge', rootDir) || await importModule('nuxt', rootDir)
- const nuxt = await loadNuxt({
- rootDir: opts.cwd,
- for: opts.dev ? 'dev' : 'build',
- configOverrides: opts.overrides,
- ready: opts.ready,
- envConfig: opts.dotenv, // TODO: Backward format conversion
- })
-
- // Mock new hookable methods
- nuxt.removeHook ||= nuxt.clearHook.bind(nuxt)
- nuxt.removeAllHooks ||= nuxt.clearHooks.bind(nuxt)
- nuxt.hookOnce ||= (name: string, fn: (...args: any[]) => any, ...hookArgs: any[]) => {
- const unsub = nuxt.hook(name, (...args: any[]) => {
- unsub()
- return fn(...args)
- }, ...hookArgs)
- return unsub
- }
- // https://github.com/nuxt/nuxt/tree/main/packages/kit/src/module/define.ts#L111-L113
- nuxt.hooks ||= nuxt
-
- return nuxt as Nuxt
+ const { loadNuxt } = await importModule((pkg as any)._name || pkg.name, { paths: rootDir })
+ const nuxt = await loadNuxt(opts)
+ return nuxt
}
export async function buildNuxt (nuxt: Nuxt): Promise {
const rootDir = pathToFileURL(nuxt.options.rootDir).href
- // Nuxt 3
- if (nuxt.options._majorVersion === 3) {
- const { build } = await tryImportModule('nuxt-nightly', rootDir) || await tryImportModule('nuxt3', rootDir) || await importModule('nuxt', rootDir)
- return build(nuxt)
- }
-
- // Nuxt 2
- const { build } = await tryImportModule('nuxt-edge', rootDir) || await importModule('nuxt', rootDir)
+ const { build } = await tryImportModule('nuxt-nightly', { paths: rootDir }) || await importModule('nuxt', { paths: rootDir })
return build(nuxt)
}
diff --git a/packages/kit/src/module/compatibility.ts b/packages/kit/src/module/compatibility.ts
index 446e7e3fce..5bccbf8654 100644
--- a/packages/kit/src/module/compatibility.ts
+++ b/packages/kit/src/module/compatibility.ts
@@ -51,11 +51,10 @@ export async function getNuxtModuleVersion (module: string | NuxtModule, nuxt: N
// need a name from here
if (!moduleMeta.name) { return false }
// maybe the version got attached within the installed module instance?
- const version = nuxt.options._installedModules
- // @ts-expect-error _installedModules is not typed
- .filter(m => m.meta.name === moduleMeta.name).map(m => m.meta.version)?.[0]
- if (version) {
- return version
+ for (const m of nuxt.options._installedModules) {
+ if (m.meta.name === moduleMeta.name && m.meta.version) {
+ return m.meta.version
+ }
}
// it's possible that the module will be installed, it just hasn't been done yet, preemptively load the instance
if (hasNuxtModule(moduleMeta.name)) {
diff --git a/packages/kit/src/module/define.ts b/packages/kit/src/module/define.ts
index ff2a56d2d4..33ac6d015a 100644
--- a/packages/kit/src/module/define.ts
+++ b/packages/kit/src/module/define.ts
@@ -1,42 +1,87 @@
-import { promises as fsp } from 'node:fs'
import { performance } from 'node:perf_hooks'
import { defu } from 'defu'
import { applyDefaults } from 'untyped'
-import { dirname } from 'pathe'
-import type { ModuleDefinition, ModuleOptions, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
+import type { ModuleDefinition, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedModuleOptions } from '@nuxt/schema'
import { logger } from '../logger'
-import { nuxtCtx, tryUseNuxt, useNuxt } from '../context'
-import { checkNuxtCompatibility, isNuxt2 } from '../compatibility'
-import { compileTemplate, templateUtils } from '../internal/template'
+import { tryUseNuxt, useNuxt } from '../context'
+import { checkNuxtCompatibility } from '../compatibility'
/**
* Define a Nuxt module, automatically merging defaults with user provided options, installing
* any hooks that are provided, and calling an optional setup function for full control.
*/
-export function defineNuxtModule (definition: ModuleDefinition | NuxtModule): NuxtModule {
- if (typeof definition === 'function') { return defineNuxtModule({ setup: definition }) }
+export function defineNuxtModule (
+ definition: ModuleDefinition, false> | NuxtModule, false>
+): NuxtModule
- // Normalize definition and meta
- const module: ModuleDefinition & Required, 'meta'>> = defu(definition, { meta: {} })
- if (module.meta.configKey === undefined) {
- module.meta.configKey = module.meta.name
+export function defineNuxtModule (): {
+ with: > (
+ definition: ModuleDefinition | NuxtModule
+ ) => NuxtModule
+}
+
+export function defineNuxtModule (
+ definition?: ModuleDefinition, false> | NuxtModule, false>,
+) {
+ if (definition) {
+ return _defineNuxtModule(definition)
}
+ return {
+ with: >(
+ definition: ModuleDefinition | NuxtModule,
+ ) => _defineNuxtModule(definition),
+ }
+}
+
+function _defineNuxtModule<
+ TOptions extends ModuleOptions,
+ TOptionsDefaults extends Partial,
+ TWith extends boolean,
+> (
+ definition: ModuleDefinition | NuxtModule,
+): NuxtModule {
+ if (typeof definition === 'function') {
+ return _defineNuxtModule({ setup: definition })
+ }
+
+ // Normalize definition and meta
+ const module: ModuleDefinition & Required, 'meta'>> = defu(definition, { meta: {} })
+
+ module.meta.configKey ||= module.meta.name
+
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
- async function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
- const configKey = module.meta.configKey || module.meta.name!
- const _defaults = module.defaults instanceof Function ? module.defaults(nuxt) : module.defaults
- let _options = defu(inlineOptions, nuxt.options[configKey as keyof NuxtOptions], _defaults) as OptionsT
+ async function getOptions (
+ inlineOptions?: Partial,
+ nuxt: Nuxt = useNuxt(),
+ ): Promise<
+ TWith extends true
+ ? ResolvedModuleOptions
+ : TOptions
+ > {
+ const nuxtConfigOptionsKey = module.meta.configKey || module.meta.name
+
+ const nuxtConfigOptions: Partial = nuxtConfigOptionsKey && nuxtConfigOptionsKey in nuxt.options ? nuxt.options[ nuxtConfigOptionsKey] : {}
+
+ const optionsDefaults: TOptionsDefaults =
+ module.defaults instanceof Function
+ ? module.defaults(nuxt)
+ : module.defaults ?? {}
+
+ let options = defu(inlineOptions, nuxtConfigOptions, optionsDefaults)
+
if (module.schema) {
- _options = await applyDefaults(module.schema, _options) as OptionsT
+ options = await applyDefaults(module.schema, options) as any
}
- return Promise.resolve(_options)
+
+ // @ts-expect-error ignore type mismatch when calling `defineNuxtModule` without `.with()`
+ return Promise.resolve(options)
}
// Module format is always a simple function
- async function normalizedModule (this: any, inlineOptions: OptionsT, nuxt: Nuxt) {
+ async function normalizedModule (inlineOptions: Partial, nuxt = tryUseNuxt()!): Promise {
if (!nuxt) {
- nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
+ throw new TypeError('Cannot use module outside of Nuxt context')
}
// Avoid duplicate installs
@@ -58,9 +103,6 @@ export function defineNuxtModule (definition: Mo
}
}
- // Prepare
- nuxt2Shims(nuxt)
-
// Resolve module and options
const _options = await getOptions(inlineOptions, nuxt)
@@ -70,11 +112,10 @@ export function defineNuxtModule (definition: Mo
}
// Call setup
- const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
- const mark = performance.mark(key)
+ const start = performance.now()
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
- const perf = performance.measure(key, mark.name)
- const setupTime = Math.round((perf.duration * 100)) / 100
+ const perf = performance.now() - start
+ const setupTime = Math.round((perf * 100)) / 100
// Measure setup time
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
@@ -87,7 +128,7 @@ export function defineNuxtModule (definition: Mo
if (res === false) { return false }
// Return module install result
- return defu(res, {
+ return defu(res, {
timings: {
setup: setupTime,
},
@@ -98,55 +139,5 @@ export function defineNuxtModule (definition: Mo
normalizedModule.getMeta = () => Promise.resolve(module.meta)
normalizedModule.getOptions = getOptions
- return normalizedModule as NuxtModule
-}
-
-// -- Nuxt 2 compatibility shims --
-const NUXT2_SHIMS_KEY = '__nuxt2_shims_key__'
-function nuxt2Shims (nuxt: Nuxt) {
- // Avoid duplicate install and only apply to Nuxt2
- if (!isNuxt2(nuxt) || nuxt[NUXT2_SHIMS_KEY as keyof Nuxt]) { return }
- nuxt[NUXT2_SHIMS_KEY as keyof Nuxt] = true
-
- // Allow using nuxt.hooks
- // @ts-expect-error Nuxt 2 extends hookable
- nuxt.hooks = nuxt
-
- // Allow using useNuxt()
- if (!nuxtCtx.tryUse()) {
- nuxtCtx.set(nuxt)
- nuxt.hook('close', () => nuxtCtx.unset())
- }
-
- // Support virtual templates with getContents() by writing them to .nuxt directory
- let virtualTemplates: ResolvedNuxtTemplate[]
- // @ts-expect-error Nuxt 2 hook
- nuxt.hook('builder:prepared', (_builder, buildOptions) => {
- virtualTemplates = buildOptions.templates.filter((t: any) => t.getContents)
- for (const template of virtualTemplates) {
- buildOptions.templates.splice(buildOptions.templates.indexOf(template), 1)
- }
- })
- // @ts-expect-error Nuxt 2 hook
- nuxt.hook('build:templates', async (templates) => {
- const context = {
- nuxt,
- utils: templateUtils,
- app: {
- dir: nuxt.options.srcDir,
- extensions: nuxt.options.extensions,
- plugins: nuxt.options.plugins,
- templates: [
- ...templates.templatesFiles,
- ...virtualTemplates,
- ],
- templateVars: templates.templateVars,
- },
- }
- for await (const template of virtualTemplates) {
- const contents = await compileTemplate({ ...template, src: '' }, context)
- await fsp.mkdir(dirname(template.dst), { recursive: true })
- await fsp.writeFile(template.dst, contents)
- }
- })
+ return > normalizedModule
}
diff --git a/packages/kit/src/module/install.ts b/packages/kit/src/module/install.ts
index b17cee47ca..1149fc9d94 100644
--- a/packages/kit/src/module/install.ts
+++ b/packages/kit/src/module/install.ts
@@ -1,18 +1,19 @@
import { existsSync, promises as fsp, lstatSync } from 'node:fs'
-import type { ModuleMeta, Nuxt, NuxtModule } from '@nuxt/schema'
+import type { ModuleMeta, Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema'
import { dirname, isAbsolute, join, resolve } from 'pathe'
import { defu } from 'defu'
-import { isNuxt2 } from '../compatibility'
+import { createJiti } from 'jiti'
import { useNuxt } from '../context'
-import { requireModule } from '../internal/cjs'
-import { importModule } from '../internal/esm'
-import { resolveAlias, resolvePath } from '../resolve'
+import { resolveAlias } from '../resolve'
import { logger } from '../logger'
const NODE_MODULES_RE = /[/\\]node_modules[/\\]/
/** Installs a module on a Nuxt instance. */
-export async function installModule (moduleToInstall: string | NuxtModule, inlineOptions?: any, nuxt: Nuxt = useNuxt()) {
+export async function installModule<
+ T extends string | NuxtModule,
+ Config extends Extract[number], [T, any]>,
+> (moduleToInstall: T, inlineOptions?: [Config] extends [never] ? any : Config[1], nuxt: Nuxt = useNuxt()) {
const { nuxtModule, buildTimeModuleMeta } = await loadNuxtModuleInstance(moduleToInstall, nuxt)
const localLayerModuleDirs = new Set()
@@ -24,12 +25,7 @@ export async function installModule (moduleToInstall: string | NuxtModule, inlin
}
// Call module
- const res = (
- isNuxt2()
- // @ts-expect-error Nuxt 2 `moduleContainer` is not typed
- ? await nuxtModule.call(nuxt.moduleContainer, inlineOptions, nuxt)
- : await nuxtModule(inlineOptions, nuxt)
- ) ?? {}
+ const res = await nuxtModule(inlineOptions || {}, nuxt) ?? {}
if (res === false /* setup aborted */) {
return
}
@@ -43,10 +39,16 @@ export async function installModule (moduleToInstall: string | NuxtModule, inlin
}
nuxt.options._installedModules = nuxt.options._installedModules || []
+ const entryPath = typeof moduleToInstall === 'string' ? resolveAlias(moduleToInstall) : undefined
+
+ if (typeof moduleToInstall === 'string' && entryPath !== moduleToInstall) {
+ buildTimeModuleMeta.rawPath = moduleToInstall
+ }
+
nuxt.options._installedModules.push({
meta: defu(await nuxtModule.getMeta?.(), buildTimeModuleMeta),
timings: res.timings,
- entryPath: typeof moduleToInstall === 'string' ? resolveAlias(moduleToInstall) : undefined,
+ entryPath,
})
}
@@ -57,7 +59,7 @@ export function getDirectory (p: string) {
// we need to target directories instead of module file paths themselves
// /home/user/project/node_modules/module/index.js -> /home/user/project/node_modules/module
return isAbsolute(p) && lstatSync(p).isFile() ? dirname(p) : p
- } catch (e) {
+ } catch {
// maybe the path is absolute but does not exist, allow this to bubble up
}
return p
@@ -69,30 +71,38 @@ export const normalizeModuleTranspilePath = (p: string) => {
export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, nuxt: Nuxt = useNuxt()) {
let buildTimeModuleMeta: ModuleMeta = {}
+
+ const jiti = createJiti(nuxt.options.rootDir, {
+ interopDefault: true,
+ alias: nuxt.options.alias,
+ })
+
// Import if input is string
if (typeof nuxtModule === 'string') {
- const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule]
- let error: unknown
- for (const path of paths) {
- try {
- const src = await resolvePath(path)
- // Prefer ESM resolution if possible
- nuxtModule = await importModule(src, nuxt.options.modulesDir).catch(() => null) ?? requireModule(src, { paths: nuxt.options.modulesDir })
+ const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule, join(nuxt.options.rootDir, nuxtModule)]
- // nuxt-module-builder generates a module.json with metadata including the version
- const moduleMetadataPath = join(dirname(src), 'module.json')
- if (existsSync(moduleMetadataPath)) {
- buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8'))
+ for (const parentURL of nuxt.options.modulesDir) {
+ for (const path of paths) {
+ try {
+ const src = jiti.esmResolve(path, { parentURL: parentURL.replace(/\/node_modules\/?$/, '') })
+ nuxtModule = await jiti.import(src) as NuxtModule
+
+ // nuxt-module-builder generates a module.json with metadata including the version
+ const moduleMetadataPath = join(dirname(src), 'module.json')
+ if (existsSync(moduleMetadataPath)) {
+ buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8'))
+ }
+ break
+ } catch (error: unknown) {
+ const code = (error as Error & { code?: string }).code
+ if (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
+ continue
+ }
+ logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
+ throw error
}
- break
- } catch (_err: unknown) {
- error = _err
- continue
}
- }
- if (typeof nuxtModule !== 'function' && error) {
- logger.error(`Error while requiring module \`${nuxtModule}\`: ${error}`)
- throw error
+ if (typeof nuxtModule !== 'string') { break }
}
}
diff --git a/packages/kit/src/nitro.ts b/packages/kit/src/nitro.ts
index 72b3ca93f3..29b0b44981 100644
--- a/packages/kit/src/nitro.ts
+++ b/packages/kit/src/nitro.ts
@@ -1,4 +1,4 @@
-import type { Nitro, NitroDevEventHandler, NitroEventHandler } from 'nitropack'
+import type { Nitro, NitroDevEventHandler, NitroEventHandler } from 'nitro/types'
import type { Import } from 'unimport'
import { normalize } from 'pathe'
import { useNuxt } from './context'
@@ -12,7 +12,7 @@ function normalizeHandlerMethod (handler: NitroEventHandler) {
// retrieve method from handler file name
const [, method = undefined] = handler.handler.match(/\.(get|head|patch|post|put|delete|connect|options|trace)(\.\w+)*$/) || []
return {
- method,
+ method: method as 'get' | 'head' | 'patch' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace' | undefined,
...handler,
handler: normalize(handler.handler),
}
diff --git a/packages/kit/src/pages.ts b/packages/kit/src/pages.ts
index a5f7fc1d3e..35c73fae83 100644
--- a/packages/kit/src/pages.ts
+++ b/packages/kit/src/pages.ts
@@ -1,19 +1,12 @@
import type { NuxtHooks, NuxtMiddleware } from '@nuxt/schema'
-import type { NitroRouteConfig } from 'nitropack'
+import type { NitroRouteConfig } from 'nitro/types'
import { defu } from 'defu'
import { useNuxt } from './context'
-import { isNuxt2 } from './compatibility'
import { logger } from './logger'
import { toArray } from './utils'
export function extendPages (cb: NuxtHooks['pages:extend']) {
- const nuxt = useNuxt()
- if (isNuxt2(nuxt)) {
- // @ts-expect-error TODO: Nuxt 2 hook
- nuxt.hook('build:extendRoutes', cb)
- } else {
- nuxt.hook('pages:extend', cb)
- }
+ useNuxt().hook('pages:extend', cb)
}
export interface ExtendRouteRulesOptions {
@@ -42,6 +35,11 @@ export interface AddRouteMiddlewareOptions {
* @default false
*/
override?: boolean
+ /**
+ * Prepend middleware to the list
+ * @default false
+ */
+ prepend?: boolean
}
export function addRouteMiddleware (input: NuxtMiddleware | NuxtMiddleware[], options: AddRouteMiddlewareOptions = {}) {
@@ -51,12 +49,15 @@ export function addRouteMiddleware (input: NuxtMiddleware | NuxtMiddleware[], op
for (const middleware of middlewares) {
const find = app.middleware.findIndex(item => item.name === middleware.name)
if (find >= 0) {
- if (app.middleware[find].path === middleware.path) { continue }
+ const foundPath = app.middleware[find]!.path
+ if (foundPath === middleware.path) { continue }
if (options.override === true) {
app.middleware[find] = { ...middleware }
} else {
- logger.warn(`'${middleware.name}' middleware already exists at '${app.middleware[find].path}'. You can set \`override: true\` to replace it.`)
+ logger.warn(`'${middleware.name}' middleware already exists at '${foundPath}'. You can set \`override: true\` to replace it.`)
}
+ } else if (options.prepend === true) {
+ app.middleware.unshift({ ...middleware })
} else {
app.middleware.push({ ...middleware })
}
diff --git a/packages/kit/src/plugin.ts b/packages/kit/src/plugin.ts
index 8905c11274..aadcdbc718 100644
--- a/packages/kit/src/plugin.ts
+++ b/packages/kit/src/plugin.ts
@@ -43,8 +43,11 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
* Note: By default plugin is prepended to the plugins array. You can use second argument to append (push) instead.
* @example
* ```js
+ * import { createResolver } from '@nuxt/kit'
+ * const resolver = createResolver(import.meta.url)
+ *
* addPlugin({
- * src: path.resolve(__dirname, 'templates/foo.js'),
+ * src: resolver.resolve('templates/foo.js'),
* filename: 'foo.server.js' // [optional] only include in server bundle
* })
* ```
diff --git a/packages/kit/src/resolve.ts b/packages/kit/src/resolve.ts
index 8bdf0693c3..31ae08d2ed 100644
--- a/packages/kit/src/resolve.ts
+++ b/packages/kit/src/resolve.ts
@@ -23,6 +23,13 @@ export interface ResolvePathOptions {
* @default false
*/
virtual?: boolean
+
+ /**
+ * Whether to fallback to the original path if the resolved path does not exist instead of returning the normalized input path.
+ *
+ * @default false
+ */
+ fallbackToOriginal?: boolean
}
/**
@@ -99,7 +106,7 @@ export async function resolvePath (path: string, opts: ResolvePathOptions = {}):
}
// Return normalized input
- return path
+ return opts.fallbackToOriginal ? _path : path
}
/**
@@ -161,13 +168,13 @@ export function createResolver (base: string | URL): Resolver {
}
}
-export async function resolveNuxtModule (base: string, paths: string[]) {
- const resolved = []
+export async function resolveNuxtModule (base: string, paths: string[]): Promise {
+ const resolved: string[] = []
const resolver = createResolver(base)
for (const path of paths) {
if (path.startsWith(base)) {
- resolved.push(path.split('/index.ts')[0])
+ resolved.push(path.split('/index.ts')[0]!)
} else {
const resolvedPath = await resolver.resolvePath(path)
resolved.push(resolvedPath.slice(0, resolvedPath.lastIndexOf(path) + path.length))
@@ -202,6 +209,12 @@ function existsInVFS (path: string, nuxt = tryUseNuxt()) {
}
export async function resolveFiles (path: string, pattern: string | string[], opts: { followSymbolicLinks?: boolean } = {}) {
- const files = await globby(pattern, { cwd: path, followSymbolicLinks: opts.followSymbolicLinks ?? true })
- return files.map(p => resolve(path, p)).filter(p => !isIgnored(p)).sort()
+ const files: string[] = []
+ for (const file of await globby(pattern, { cwd: path, followSymbolicLinks: opts.followSymbolicLinks ?? true })) {
+ const p = resolve(path, file)
+ if (!isIgnored(p)) {
+ files.push(p)
+ }
+ }
+ return files.sort()
}
diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts
index 4aa9ec147b..903308497f 100644
--- a/packages/kit/src/template.ts
+++ b/packages/kit/src/template.ts
@@ -5,12 +5,12 @@ import type { Nuxt, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSRefe
import { withTrailingSlash } from 'ufo'
import { defu } from 'defu'
import type { TSConfig } from 'pkg-types'
+import { gte } from 'semver'
import { readPackageJSON } from 'pkg-types'
import { tryResolveModule } from './internal/esm'
import { getDirectory } from './module/install'
import { tryUseNuxt, useNuxt } from './context'
-import { getModulePaths } from './internal/cjs'
import { resolveNuxtModule } from './resolve'
/**
@@ -112,56 +112,103 @@ export async function updateTemplates (options?: { filter?: (template: ResolvedN
}
export async function _generateTypes (nuxt: Nuxt) {
- const nodeModulePaths = getModulePaths(nuxt.options.modulesDir)
-
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir)
+ const relativeRootDir = relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir)
- const modulePaths = await resolveNuxtModule(rootDirWithSlash,
- nuxt.options._installedModules
- .filter(m => m.entryPath)
- .map(m => getDirectory(m.entryPath)),
- )
+ const include = new Set([
+ './nuxt.d.ts',
+ join(relativeRootDir, '.config/nuxt.*'),
+ join(relativeRootDir, '**/*'),
+ ])
+ if (nuxt.options.srcDir !== nuxt.options.rootDir) {
+ include.add(join(relative(nuxt.options.buildDir, nuxt.options.srcDir), '**/*'))
+ }
+
+ if (nuxt.options.typescript.includeWorkspace && nuxt.options.workspaceDir !== nuxt.options.rootDir) {
+ include.add(join(relative(nuxt.options.buildDir, nuxt.options.workspaceDir), '**/*'))
+ }
+
+ for (const layer of nuxt.options._layers) {
+ const srcOrCwd = layer.config.srcDir ?? layer.cwd
+ if (!srcOrCwd.startsWith(rootDirWithSlash) || srcOrCwd.includes('node_modules')) {
+ include.add(join(relative(nuxt.options.buildDir, srcOrCwd), '**/*'))
+ }
+ }
+
+ const exclude = new Set([
+ // nitro generate output: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/nitro.ts#L186
+ relativeWithDot(nuxt.options.buildDir, resolve(nuxt.options.rootDir, 'dist')),
+ ])
+
+ for (const dir of nuxt.options.modulesDir) {
+ exclude.add(relativeWithDot(nuxt.options.buildDir, dir))
+ }
+
+ const moduleEntryPaths: string[] = []
+ for (const m of nuxt.options._installedModules) {
+ if (m.entryPath) {
+ moduleEntryPaths.push(getDirectory(m.entryPath))
+ }
+ }
+
+ const modulePaths = await resolveNuxtModule(rootDirWithSlash, moduleEntryPaths)
+
+ for (const path of modulePaths) {
+ const relative = relativeWithDot(nuxt.options.buildDir, path)
+ include.add(join(relative, 'runtime'))
+ exclude.add(join(relative, 'runtime/server'))
+ include.add(join(relative, 'dist/runtime'))
+ exclude.add(join(relative, 'dist/runtime/server'))
+ }
+
+ const isV4 = nuxt.options.future?.compatibilityVersion === 4
+ const hasTypescriptVersionWithModulePreserve = await readPackageJSON('typescript', { url: nuxt.options.modulesDir })
+ .then(r => r?.version && gte(r.version, '5.4.0'))
+ .catch(() => isV4)
+
+ // https://www.totaltypescript.com/tsconfig-cheat-sheet
const tsConfig: TSConfig = defu(nuxt.options.typescript?.tsConfig, {
compilerOptions: {
+ /* Base options: */
+ esModuleInterop: true,
+ skipLibCheck: true,
+ target: 'ESNext',
+ allowJs: true,
+ resolveJsonModule: true,
+ moduleDetection: 'force',
+ isolatedModules: true,
+ verbatimModuleSyntax: true,
+ /* Strictness */
+ strict: nuxt.options.typescript?.strict ?? true,
+ noUncheckedIndexedAccess: isV4,
forceConsistentCasingInFileNames: true,
+ noImplicitOverride: true,
+ /* If NOT transpiling with TypeScript: */
+ module: hasTypescriptVersionWithModulePreserve ? 'preserve' : 'ESNext',
+ noEmit: true,
+ /* If your code runs in the DOM: */
+ lib: [
+ 'ESNext',
+ 'dom',
+ 'dom.iterable',
+ 'webworker',
+ ],
+ /* JSX support for Vue */
jsx: 'preserve',
jsxImportSource: 'vue',
- target: 'ESNext',
- module: 'ESNext',
- moduleDetection: 'force',
- moduleResolution: nuxt.options.future?.typescriptBundlerResolution || (nuxt.options.experimental as any)?.typescriptBundlerResolution ? 'Bundler' : 'Node',
- skipLibCheck: true,
- isolatedModules: true,
- useDefineForClassFields: true,
- strict: nuxt.options.typescript?.strict ?? true,
- noImplicitThis: true,
- esModuleInterop: true,
+ /* remove auto-scanning for types */
types: [],
- verbatimModuleSyntax: true,
- allowJs: true,
- noEmit: true,
- resolveJsonModule: true,
- allowSyntheticDefaultImports: true,
+ /* add paths object for filling-in later */
paths: {},
+ /* Possibly consider removing the following in future */
+ moduleResolution: nuxt.options.future?.typescriptBundlerResolution || (nuxt.options.experimental as any)?.typescriptBundlerResolution ? 'Bundler' : 'Node', /* implied by module: preserve */
+ useDefineForClassFields: true, /* implied by target: es2022+ */
+ noImplicitThis: true, /* enabled with `strict` */
+ allowSyntheticDefaultImports: true,
},
- include: [
- './nuxt.d.ts',
- join(relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir), '.config/nuxt.*'),
- join(relativeWithDot(nuxt.options.buildDir, nuxt.options.rootDir), '**/*'),
- ...nuxt.options.srcDir !== nuxt.options.rootDir ? [join(relative(nuxt.options.buildDir, nuxt.options.srcDir), '**/*')] : [],
- ...nuxt.options._layers.map(layer => layer.config.srcDir ?? layer.cwd)
- .filter(srcOrCwd => !srcOrCwd.startsWith(rootDirWithSlash) || srcOrCwd.includes('node_modules'))
- .map(srcOrCwd => join(relative(nuxt.options.buildDir, srcOrCwd), '**/*')),
- ...nuxt.options.typescript.includeWorkspace && nuxt.options.workspaceDir !== nuxt.options.rootDir ? [join(relative(nuxt.options.buildDir, nuxt.options.workspaceDir), '**/*')] : [],
- ...modulePaths.map(m => join(relativeWithDot(nuxt.options.buildDir, m), 'runtime')),
- ],
- exclude: [
- ...nuxt.options.modulesDir.map(m => relativeWithDot(nuxt.options.buildDir, m)),
- ...modulePaths.map(m => join(relativeWithDot(nuxt.options.buildDir, m), 'runtime/server')),
- // nitro generate output: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/core/nitro.ts#L186
- relativeWithDot(nuxt.options.buildDir, resolve(nuxt.options.rootDir, 'dist')),
- ],
+ include: [...include],
+ exclude: [...exclude],
} satisfies TSConfig)
const aliases: Record = {
@@ -172,7 +219,9 @@ export async function _generateTypes (nuxt: Nuxt) {
// Exclude bridge alias types to support Volar
const excludedAlias = [/^@vue\/.*$/]
- const basePath = tsConfig.compilerOptions!.baseUrl ? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl) : nuxt.options.buildDir
+ const basePath = tsConfig.compilerOptions!.baseUrl
+ ? resolve(nuxt.options.buildDir, tsConfig.compilerOptions!.baseUrl)
+ : nuxt.options.buildDir
tsConfig.compilerOptions = tsConfig.compilerOptions || {}
tsConfig.include = tsConfig.include || []
@@ -181,10 +230,10 @@ export async function _generateTypes (nuxt: Nuxt) {
if (excludedAlias.some(re => re.test(alias))) {
continue
}
- let absolutePath = resolve(basePath, aliases[alias])
+ let absolutePath = resolve(basePath, aliases[alias]!)
let stats = await fsp.stat(absolutePath).catch(() => null /* file does not exist */)
if (!stats) {
- const resolvedModule = await tryResolveModule(aliases[alias], nuxt.options.modulesDir)
+ const resolvedModule = await tryResolveModule(aliases[alias]!, nuxt.options.modulesDir)
if (resolvedModule) {
absolutePath = resolvedModule
stats = await fsp.stat(resolvedModule).catch(() => null)
@@ -204,7 +253,7 @@ export async function _generateTypes (nuxt: Nuxt) {
// remove extension
? relativePath.replace(/\b\.\w+$/g, '')
// non-existent file probably shouldn't be resolved
- : aliases[alias]
+ : aliases[alias]!
tsConfig.compilerOptions.paths[alias] = [path]
@@ -214,12 +263,13 @@ export async function _generateTypes (nuxt: Nuxt) {
}
}
- const references: TSReference[] = await Promise.all([
- ...nuxt.options.modules,
- ...nuxt.options._modules,
- ]
- .filter(f => typeof f === 'string')
- .map(async id => ({ types: (await readPackageJSON(id, { url: nodeModulePaths }).catch(() => null))?.name || id })))
+ const references: TSReference[] = []
+ await Promise.all([...nuxt.options.modules, ...nuxt.options._modules].map(async (id) => {
+ if (typeof id !== 'string') { return }
+
+ const pkg = await readPackageJSON(id, { url: nuxt.options.modulesDir }).catch(() => null)
+ references.push(({ types: pkg?.name || id }))
+ }))
const declarations: string[] = []
@@ -270,19 +320,18 @@ export async function writeTypes (nuxt: Nuxt) {
await fsp.writeFile(declarationPath, GeneratedBy + '\n' + declaration)
}
- // This is needed for Nuxt 2 which clears the build directory again before building
- // https://github.com/nuxt/nuxt/blob/2.x/packages/builder/src/builder.js#L144
- // @ts-expect-error TODO: Nuxt 2 hook
- nuxt.hook('builder:prepared', writeFile)
-
await writeFile()
}
function renderAttrs (obj: Record) {
- return Object.entries(obj).map(e => renderAttr(e[0], e[1])).join(' ')
+ const attrs: string[] = []
+ for (const key in obj) {
+ attrs.push(renderAttr(key, obj[key]))
+ }
+ return attrs.join(' ')
}
-function renderAttr (key: string, value: string) {
+function renderAttr (key: string, value?: string) {
return value ? `${key}="${value}"` : ''
}
diff --git a/packages/kit/test/generate-types.spec.ts b/packages/kit/test/generate-types.spec.ts
index 7ac10ba8d1..b5bcc9a6bb 100644
--- a/packages/kit/test/generate-types.spec.ts
+++ b/packages/kit/test/generate-types.spec.ts
@@ -53,12 +53,12 @@ describe('tsConfig generation', () => {
}))
expect(tsConfig.exclude).toMatchInlineSnapshot(`
[
+ "../dist",
"../modules/test/node_modules",
"../modules/node_modules",
"../node_modules/@some/module/node_modules",
"../node_modules",
"../../node_modules",
- "../dist",
]
`)
})
diff --git a/packages/nuxt/.gitignore b/packages/nuxt/.gitignore
new file mode 100644
index 0000000000..95c0486757
--- /dev/null
+++ b/packages/nuxt/.gitignore
@@ -0,0 +1,6 @@
+src/app/components/error-404.vue
+src/app/components/error-500.vue
+src/app/components/error-dev.vue
+src/app/components/welcome.vue
+src/core/runtime/nitro/error-500.ts
+src/core/runtime/nitro/error-dev.ts
diff --git a/packages/nuxt/config.d.ts b/packages/nuxt/config.d.ts
index a9272be362..3160b60bd0 100644
--- a/packages/nuxt/config.d.ts
+++ b/packages/nuxt/config.d.ts
@@ -3,5 +3,6 @@ import type { ConfigLayerMeta, DefineConfig } from 'c12'
export { NuxtConfig } from 'nuxt/schema'
+// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface DefineNuxtConfig extends DefineConfig {}
export declare const defineNuxtConfig: DefineNuxtConfig
diff --git a/packages/nuxt/index.d.ts b/packages/nuxt/index.d.ts
index 2256348248..5630f9aeb0 100644
--- a/packages/nuxt/index.d.ts
+++ b/packages/nuxt/index.d.ts
@@ -2,8 +2,6 @@
declare global {
var __NUXT_VERSION__: string
var __NUXT_ASYNC_CONTEXT__: boolean
- var __NUXT_PREPATHS__: string[] | string | undefined
- var __NUXT_PATHS__: string[] | string | undefined
interface Navigator {
connection?: {
@@ -14,7 +12,8 @@ declare global {
interface Window {
cookieStore?: {
- onchange: (event: any) => void
+ addEventListener: (type: 'change', listener: (event: any) => void) => void
+ removeEventListener: (type: 'change', listener: (event: any) => void) => void
}
}
}
diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json
index 403b6c4d87..4f184fff56 100644
--- a/packages/nuxt/package.json
+++ b/packages/nuxt/package.json
@@ -1,6 +1,6 @@
{
"name": "nuxt",
- "version": "3.11.2",
+ "version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@@ -60,72 +60,80 @@
},
"dependencies": {
"@nuxt/devalue": "^2.0.2",
- "@nuxt/devtools": "^1.3.2",
+ "@nuxt/devtools": "^1.5.1",
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
- "@nuxt/telemetry": "^2.5.4",
+ "@nuxt/telemetry": "^2.6.0",
"@nuxt/vite-builder": "workspace:*",
- "@unhead/dom": "^1.9.11",
- "@unhead/ssr": "^1.9.11",
- "@unhead/vue": "^1.9.11",
- "@vue/shared": "^3.4.27",
- "acorn": "8.11.3",
- "c12": "^1.10.0",
+ "@unhead/dom": "^1.11.6",
+ "@unhead/shared": "^1.11.6",
+ "@unhead/ssr": "^1.11.6",
+ "@unhead/vue": "^1.11.6",
+ "@vue/shared": "^3.5.8",
+ "acorn": "8.12.1",
+ "c12": "^2.0.0-beta.2",
"chokidar": "^3.6.0",
- "cookie-es": "^1.1.0",
+ "compatx": "^0.1.8",
+ "consola": "^3.2.3",
+ "cookie-es": "^1.2.2",
"defu": "^6.1.4",
"destr": "^2.0.3",
"devalue": "^5.0.0",
- "esbuild": "^0.21.4",
+ "errx": "^0.1.0",
+ "esbuild": "^0.24.0",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
- "fs-extra": "^11.2.0",
- "globby": "^14.0.1",
- "h3": "^1.11.1",
+ "globby": "^14.0.2",
+ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"hookable": "^5.5.3",
- "ignore": "^5.3.1",
- "jiti": "^1.21.0",
+ "ignore": "^6.0.1",
+ "impound": "^0.1.0",
+ "jiti": "^2.0.0-rc.1",
"klona": "^2.0.6",
"knitwork": "^1.1.0",
- "magic-string": "^0.30.10",
- "mlly": "^1.7.0",
- "nitropack": "^2.9.6",
- "nuxi": "^3.11.1",
- "nypm": "^0.3.8",
- "ofetch": "^1.3.4",
- "ohash": "^1.1.3",
+ "magic-string": "^0.30.11",
+ "mlly": "^1.7.1",
+ "nanotar": "^0.1.1",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
+ "nuxi": "^3.13.2",
+ "nypm": "^0.3.11",
+ "ofetch": "^1.4.0",
+ "ohash": "^1.1.4",
"pathe": "^1.1.2",
"perfect-debounce": "^1.0.0",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.2.0",
"radix3": "^1.1.2",
"scule": "^1.3.0",
- "semver": "^7.6.2",
+ "semver": "^7.6.3",
"std-env": "^3.7.0",
"strip-literal": "^2.1.0",
- "ufo": "^1.5.3",
+ "tinyglobby": "0.2.6",
+ "ufo": "^1.5.4",
"ultrahtml": "^1.5.3",
"uncrypto": "^0.1.3",
"unctx": "^2.3.1",
- "unenv": "^1.9.0",
- "unimport": "^3.7.2",
- "unplugin": "^1.10.1",
- "unplugin-vue-router": "^0.7.0",
- "unstorage": "^1.10.2",
+ "unenv": "^1.10.0",
+ "unhead": "^1.11.6",
+ "unimport": "^3.12.0",
+ "unplugin": "^1.14.1",
+ "unplugin-vue-router": "^0.10.8",
+ "unstorage": "^1.12.0",
"untyped": "^1.4.2",
- "vue": "^3.4.27",
- "vue-bundle-renderer": "^2.1.0",
+ "vue": "^3.5.8",
+ "vue-bundle-renderer": "^2.1.1",
"vue-devtools-stub": "^0.1.0",
- "vue-router": "^4.3.2"
+ "vue-router": "^4.4.5"
},
"devDependencies": {
+ "@nuxt/scripts": "0.9.3",
"@nuxt/ui-templates": "1.3.4",
"@parcel/watcher": "2.4.1",
- "@types/estree": "1.0.5",
- "@types/fs-extra": "11.0.4",
- "@vitejs/plugin-vue": "5.0.4",
- "unbuild": "latest",
- "vite": "5.2.12",
- "vitest": "1.6.0"
+ "@types/estree": "1.0.6",
+ "@vitejs/plugin-vue": "5.1.4",
+ "@vue/compiler-sfc": "3.5.8",
+ "unbuild": "3.0.0-rc.7",
+ "vite": "5.4.7",
+ "vitest": "2.1.1"
},
"peerDependencies": {
"@parcel/watcher": "^2.1.0",
diff --git a/packages/nuxt/src/app/components/client-fallback.server.ts b/packages/nuxt/src/app/components/client-fallback.server.ts
index c1bc3c3e02..dd4e0cdb28 100644
--- a/packages/nuxt/src/app/components/client-fallback.server.ts
+++ b/packages/nuxt/src/app/components/client-fallback.server.ts
@@ -54,8 +54,10 @@ const NuxtClientFallbackServer = defineComponent({
const defaultSlot = ctx.slots.default?.()
const ssrVNodes = createBuffer()
- for (let i = 0; i < (defaultSlot?.length || 0); i++) {
- ssrRenderVNode(ssrVNodes.push, defaultSlot![i], vm!)
+ if (defaultSlot) {
+ for (let i = 0; i < defaultSlot.length; i++) {
+ ssrRenderVNode(ssrVNodes.push, defaultSlot[i]!, vm!)
+ }
}
const buffer = ssrVNodes.getBuffer()
diff --git a/packages/nuxt/src/app/components/client-only.ts b/packages/nuxt/src/app/components/client-only.ts
index 5483dbe0c6..ab56eb4ab2 100644
--- a/packages/nuxt/src/app/components/client-only.ts
+++ b/packages/nuxt/src/app/components/client-only.ts
@@ -1,7 +1,9 @@
import { cloneVNode, createElementBlock, createStaticVNode, defineComponent, getCurrentInstance, h, onMounted, provide, ref } from 'vue'
import type { ComponentInternalInstance, ComponentOptions, InjectionKey } from 'vue'
+import { isPromise } from '@vue/shared'
import { useNuxtApp } from '../nuxt'
import { getFragmentHTML } from './utils'
+import ServerPlaceholder from './server-placeholder'
export const clientOnlySymbol: InjectionKey = Symbol.for('nuxt:client-only')
@@ -35,6 +37,9 @@ const cache = new WeakMap()
/* @__NO_SIDE_EFFECTS__ */
export function createClientOnly (component: T) {
+ if (import.meta.server) {
+ return ServerPlaceholder
+ }
if (cache.has(component)) {
return cache.get(component)
}
@@ -42,7 +47,7 @@ export function createClientOnly (component: T) {
const clone = { ...component }
if (clone.render) {
- // override the component render (non script setup component)
+ // override the component render (non script setup component) or dev mode
clone.render = (ctx: any, cache: any, $props: any, $setup: any, $data: any, $options: any) => {
if ($setup.mounted$ ?? ctx.mounted$) {
const res = component.render?.bind(ctx)(ctx, cache, $props, $setup, $data, $options)
@@ -51,7 +56,7 @@ export function createClientOnly (component: T) {
: h(res)
} else {
const fragment = getFragmentHTML(ctx._.vnode.el ?? null) ?? ['
']
- return import.meta.client ? createStaticVNode(fragment.join(''), fragment.length) : h('div', ctx.$attrs ?? ctx._.attrs)
+ return createStaticVNode(fragment.join(''), fragment.length)
}
}
} else if (clone.template) {
@@ -63,43 +68,61 @@ export function createClientOnly (component: T) {
}
clone.setup = (props, ctx) => {
+ const nuxtApp = useNuxtApp()
+ const mounted$ = ref(nuxtApp.isHydrating === false)
const instance = getCurrentInstance()!
- const attrs = { ...instance.attrs }
+ if (nuxtApp.isHydrating) {
+ const attrs = { ...instance.attrs }
+ // remove existing directives during hydration
+ const directives = extractDirectives(instance)
+ // prevent attrs inheritance since a staticVNode is rendered before hydration
+ for (const key in attrs) {
+ delete instance.attrs[key]
+ }
- // remove existing directives during hydration
- const directives = extractDirectives(instance)
- // prevent attrs inheritance since a staticVNode is rendered before hydration
- for (const key in attrs) {
- delete instance.attrs[key]
+ onMounted(() => {
+ Object.assign(instance.attrs, attrs)
+ instance.vnode.dirs = directives
+ })
}
- const mounted$ = ref(false)
onMounted(() => {
- Object.assign(instance.attrs, attrs)
- instance.vnode.dirs = directives
mounted$.value = true
})
+ const setupState = component.setup?.(props, ctx) || {}
- return Promise.resolve(component.setup?.(props, ctx) || {})
- .then((setupState) => {
+ if (isPromise(setupState)) {
+ return Promise.resolve(setupState).then((setupState) => {
if (typeof setupState !== 'function') {
setupState = setupState || {}
setupState.mounted$ = mounted$
return setupState
}
return (...args: any[]) => {
- if (mounted$.value) {
+ if (mounted$.value || !nuxtApp.isHydrating) {
const res = setupState(...args)
return (res.children === null || typeof res.children === 'string')
? cloneVNode(res)
: h(res)
} else {
const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['
']
- return import.meta.client ? createStaticVNode(fragment.join(''), fragment.length) : h('div', ctx.attrs)
+ return createStaticVNode(fragment.join(''), fragment.length)
}
}
})
+ } else {
+ if (typeof setupState === 'function') {
+ return (...args: any[]) => {
+ if (mounted$.value) {
+ return h(setupState(...args), ctx.attrs)
+ }
+ const fragment = getFragmentHTML(instance?.vnode.el ?? null) ?? ['
']
+ return createStaticVNode(fragment.join(''), fragment.length)
+ }
+ }
+ return Object.assign(setupState, { mounted$ })
+ }
}
cache.set(component, clone)
diff --git a/packages/nuxt/src/app/components/dev-only.ts b/packages/nuxt/src/app/components/dev-only.ts
index a1446dd3dd..94b5ae84e3 100644
--- a/packages/nuxt/src/app/components/dev-only.ts
+++ b/packages/nuxt/src/app/components/dev-only.ts
@@ -2,6 +2,7 @@ import { defineComponent } from 'vue'
export default defineComponent({
name: 'DevOnly',
+ inheritAttrs: false,
setup (_, props) {
if (import.meta.dev) {
return () => props.slots.default?.()
diff --git a/packages/nuxt/src/app/components/error-404.vue b/packages/nuxt/src/app/components/error-404.vue
deleted file mode 120000
index c2b73ea4bf..0000000000
--- a/packages/nuxt/src/app/components/error-404.vue
+++ /dev/null
@@ -1 +0,0 @@
-../../../../ui-templates/dist/templates/error-404.vue
\ No newline at end of file
diff --git a/packages/nuxt/src/app/components/error-500.vue b/packages/nuxt/src/app/components/error-500.vue
deleted file mode 120000
index 8d3d1c8b9d..0000000000
--- a/packages/nuxt/src/app/components/error-500.vue
+++ /dev/null
@@ -1 +0,0 @@
-../../../../ui-templates/dist/templates/error-500.vue
\ No newline at end of file
diff --git a/packages/nuxt/src/app/components/error-dev.vue b/packages/nuxt/src/app/components/error-dev.vue
deleted file mode 120000
index b793df3c2f..0000000000
--- a/packages/nuxt/src/app/components/error-dev.vue
+++ /dev/null
@@ -1 +0,0 @@
-../../../../ui-templates/dist/templates/error-dev.vue
\ No newline at end of file
diff --git a/packages/nuxt/src/app/components/island-renderer.ts b/packages/nuxt/src/app/components/island-renderer.ts
index 59e767f867..189c980e50 100644
--- a/packages/nuxt/src/app/components/island-renderer.ts
+++ b/packages/nuxt/src/app/components/island-renderer.ts
@@ -1,12 +1,14 @@
import type { defineAsyncComponent } from 'vue'
import { createVNode, defineComponent, onErrorCaptured } from 'vue'
+import { injectHead } from '@unhead/vue'
import { createError } from '../composables/error'
// @ts-expect-error virtual file
import { islandComponents } from '#build/components.islands.mjs'
export default defineComponent({
+ name: 'IslandRenderer',
props: {
context: {
type: Object as () => { name: string, props?: Record },
@@ -14,6 +16,10 @@ export default defineComponent({
},
},
setup (props) {
+ // reset head - we don't want to have any head tags from plugin or anywhere else.
+ const head = injectHead()
+ head.headEntries().splice(0, head.headEntries().length)
+
const component = islandComponents[props.context.name] as ReturnType
if (!component) {
diff --git a/packages/nuxt/src/app/components/layout.ts b/packages/nuxt/src/app/components/layout.ts
deleted file mode 100644
index b88def1a94..0000000000
--- a/packages/nuxt/src/app/components/layout.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// TODO: remove in 4.x
-export { default } from './nuxt-layout'
diff --git a/packages/nuxt/src/app/components/nuxt-error-boundary.ts b/packages/nuxt/src/app/components/nuxt-error-boundary.ts
index ea54ff8393..8fb88d830a 100644
--- a/packages/nuxt/src/app/components/nuxt-error-boundary.ts
+++ b/packages/nuxt/src/app/components/nuxt-error-boundary.ts
@@ -2,6 +2,8 @@ import { defineComponent, onErrorCaptured, ref } from 'vue'
import { useNuxtApp } from '../nuxt'
export default defineComponent({
+ name: 'NuxtErrorBoundary',
+ inheritAttrs: false,
emits: {
error (_error: unknown) {
return true
@@ -11,14 +13,16 @@ export default defineComponent({
const error = ref(null)
const nuxtApp = useNuxtApp()
- onErrorCaptured((err, target, info) => {
- if (import.meta.client && (!nuxtApp.isHydrating || !nuxtApp.payload.serverRendered)) {
- emit('error', err)
- nuxtApp.hooks.callHook('vue:error', err, target, info)
- error.value = err
- return false
- }
- })
+ if (import.meta.client) {
+ onErrorCaptured((err, target, info) => {
+ if (!nuxtApp.isHydrating || !nuxtApp.payload.serverRendered) {
+ emit('error', err)
+ nuxtApp.hooks.callHook('vue:error', err, target, info)
+ error.value = err
+ return false
+ }
+ })
+ }
function clearError () {
error.value = null
diff --git a/packages/nuxt/src/app/components/nuxt-error-page.vue b/packages/nuxt/src/app/components/nuxt-error-page.vue
index 14feb22ae0..c41743972b 100644
--- a/packages/nuxt/src/app/components/nuxt-error-page.vue
+++ b/packages/nuxt/src/app/components/nuxt-error-page.vue
@@ -40,10 +40,10 @@ const description = _error.message || _error.toString()
const stack = import.meta.dev && !is404 ? _error.description || `${stacktrace} ` : undefined
// TODO: Investigate side-effect issue with imports
-const _Error404 = defineAsyncComponent(() => import('./error-404.vue').then(r => r.default || r))
+const _Error404 = defineAsyncComponent(() => import('./error-404.vue'))
const _Error = import.meta.dev
- ? defineAsyncComponent(() => import('./error-dev.vue').then(r => r.default || r))
- : defineAsyncComponent(() => import('./error-500.vue').then(r => r.default || r))
+ ? defineAsyncComponent(() => import('./error-dev.vue'))
+ : defineAsyncComponent(() => import('./error-500.vue'))
const ErrorTemplate = is404 ? _Error404 : _Error
diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts
index 503d3e34a8..c6bb6d8657 100644
--- a/packages/nuxt/src/app/components/nuxt-island.ts
+++ b/packages/nuxt/src/app/components/nuxt-island.ts
@@ -1,9 +1,9 @@
-import type { Component } from 'vue'
+import type { Component, PropType, VNode } from 'vue'
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue'
import { debounce } from 'perfect-debounce'
import { hash } from 'ohash'
import { appendResponseHeader } from 'h3'
-import { useHead } from '@unhead/vue'
+import { injectHead } from '@unhead/vue'
import { randomUUID } from 'uncrypto'
import { joinURL, withQuery } from 'ufo'
import type { FetchResponse } from 'ofetch'
@@ -29,22 +29,26 @@ const getId = import.meta.client ? () => (id++).toString() : randomUUID
const components = import.meta.client ? new Map() : undefined
async function loadComponents (source = appBaseURL, paths: NuxtIslandResponse['components']) {
- const promises = []
+ if (!paths) { return }
- for (const component in paths) {
+ const promises: Array> = []
+
+ for (const [component, item] of Object.entries(paths)) {
if (!(components!.has(component))) {
promises.push((async () => {
- const chunkSource = join(source, paths[component].chunk)
+ const chunkSource = join(source, item.chunk)
const c = await import(/* @vite-ignore */ chunkSource).then(m => m.default || m)
components!.set(component, c)
})())
}
}
+
await Promise.all(promises)
}
export default defineComponent({
name: 'NuxtIsland',
+ inheritAttrs: false,
props: {
name: {
type: String,
@@ -59,6 +63,10 @@ export default defineComponent({
type: Object,
default: () => ({}),
},
+ scopeId: {
+ type: String as PropType,
+ default: () => undefined,
+ },
source: {
type: String,
default: () => undefined,
@@ -92,7 +100,7 @@ export default defineComponent({
if (result.props) { toRevive.props = result.props }
if (result.slots) { toRevive.slots = result.slots }
if (result.components) { toRevive.components = result.components }
-
+ if (result.head) { toRevive.head = result.head }
nuxtApp.payload.data[key] = {
__nuxt_island: {
key,
@@ -131,6 +139,10 @@ export default defineComponent({
const currentSlots = Object.keys(slots)
let html = ssrHTML.value
+ if (props.scopeId) {
+ html = html.replace(/^<[^> ]*/, full => full + ' ' + props.scopeId)
+ }
+
if (import.meta.client && !canLoadClientComponent.value) {
for (const [key, value] of Object.entries(payloads.components || {})) {
html = html.replace(new RegExp(` data-island-uid="${uid.value}" data-island-component="${key}"[^>]*>`), (full) => {
@@ -150,8 +162,7 @@ export default defineComponent({
return html
})
- const cHead = ref>>>({ link: [], style: [] })
- useHead(cHead)
+ const head = injectHead()
async function _fetchComponent (force = false) {
const key = `${props.name}_${hashId.value}`
@@ -191,8 +202,7 @@ export default defineComponent({
}
try {
const res: NuxtIslandResponse = await nuxtApp[pKey][uid.value]
- cHead.value.link = res.head.link
- cHead.value.style = res.head.style
+
ssrHTML.value = res.html.replaceAll(DATA_ISLAND_UID_RE, `data-island-uid="${uid.value}"`)
key.value++
error.value = null
@@ -240,6 +250,14 @@ export default defineComponent({
await loadComponents(props.source, payloads.components)
}
+ if (import.meta.server || nuxtApp.isHydrating) {
+ // re-push head into active head instance
+ const responseHead = (nuxtApp.payload.data[`${props.name}_${hashId.value}`] as NuxtIslandResponse)?.head
+ if (responseHead) {
+ head.push(responseHead)
+ }
+ }
+
return (_ctx: any, _cache: any) => {
if (!html.value || error.value) {
return [slots.fallback?.({ error: error.value }) ?? createVNode('div')]
@@ -251,7 +269,7 @@ export default defineComponent({
// should away be triggered ONE tick after re-rendering the static node
withMemo([teleportKey.value], () => {
- const teleports = []
+ const teleports: Array = []
// this is used to force trigger Teleport when vue makes the diff between old and new node
const isKeyOdd = teleportKey.value === 0 || !!(teleportKey.value && !(teleportKey.value % 2))
@@ -261,7 +279,7 @@ export default defineComponent({
teleports.push(createVNode(Teleport,
// use different selectors for even and odd teleportKey to force trigger the teleport
{ to: import.meta.client ? `${isKeyOdd ? 'div' : ''}[data-island-uid="${uid.value}"][data-island-slot="${slot}"]` : `uid=${uid.value};slot=${slot}` },
- { default: () => (payloads.slots?.[slot].props?.length ? payloads.slots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) }),
+ { default: () => (payloads.slots?.[slot]?.props?.length ? payloads.slots[slot].props : [{}]).map((data: any) => slots[slot]?.(data)) }),
)
}
}
diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts
index 16116ebcfd..f38ddbd777 100644
--- a/packages/nuxt/src/app/components/nuxt-link.ts
+++ b/packages/nuxt/src/app/components/nuxt-link.ts
@@ -7,11 +7,11 @@ import type {
VNodeProps,
} from 'vue'
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, resolveComponent } from 'vue'
-import type { RouteLocation, RouteLocationRaw, Router, RouterLink, RouterLinkProps, useLink } from '#vue-router'
+import type { RouteLocation, RouteLocationRaw, Router, RouterLink, RouterLinkProps, useLink } from 'vue-router'
import { hasProtocol, joinURL, parseQuery, withTrailingSlash, withoutTrailingSlash } from 'ufo'
import { preloadRouteComponents } from '../composables/preload'
import { onNuxtReady } from '../composables/ready'
-import { navigateTo, useRouter } from '../composables/router'
+import { navigateTo, resolveRouteObject, useRouter } from '../composables/router'
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
import { cancelIdleCallback, requestIdleCallback } from '../compat/idle-callback'
@@ -59,6 +59,13 @@ export interface NuxtLinkProps extends Omit {
* When enabled will prefetch middleware, layouts and payloads of links in the viewport.
*/
prefetch?: boolean
+ /**
+ * Allows controlling when to prefetch links. By default, prefetch is triggered only on visibility.
+ */
+ prefetchOn?: 'visibility' | 'interaction' | Partial<{
+ visibility: boolean
+ interaction: boolean
+ }>
/**
* Escape hatch to disable `prefetch` attribute.
*/
@@ -70,8 +77,8 @@ export interface NuxtLinkProps extends Omit {
* @see https://nuxt.com/docs/api/components/nuxt-link
*/
export interface NuxtLinkOptions extends
- Pick,
- Pick {
+ Partial>,
+ Partial> {
/**
* The name of the component.
* @default "NuxtLink"
@@ -86,6 +93,11 @@ export interface NuxtLinkOptions extends
* If unset or not matching the valid values `append` or `remove`, it will be ignored.
*/
trailingSlash?: 'append' | 'remove'
+
+ /**
+ * Allows controlling default setting for when to prefetch links. By default, prefetch is triggered only on visibility.
+ */
+ prefetchOn?: Exclude
}
/* @__NO_SIDE_EFFECTS__ */
@@ -124,34 +136,17 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
const router = useRouter()
const config = useRuntimeConfig()
- // Resolving `to` value from `to` and `href` props
- const to: ComputedRef = computed(() => {
- checkPropConflicts(props, 'to', 'href')
- const path = props.to || props.href || '' // Defaults to empty string (won't render any `href` attribute)
- return resolveTrailingSlashBehavior(path, router.resolve)
- })
+ const hasTarget = computed(() => !!props.target && props.target !== '_self')
// Lazily check whether to.value has a protocol
- const isAbsoluteUrl = computed(() => typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true }))
-
- // Resolves `to` value if it's a route location object
- const href = computed(() => (typeof to.value === 'object'
- ? router.resolve(to.value)?.href ?? null
- : (to.value && !props.external && !isAbsoluteUrl.value)
- ? resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve) as string
- : to.value
- ))
+ const isAbsoluteUrl = computed(() => {
+ const path = props.to || props.href || ''
+ return typeof path === 'string' && hasProtocol(path, { acceptRelative: true })
+ })
const builtinRouterLink = resolveComponent('RouterLink') as string | typeof RouterLink
const useBuiltinLink = builtinRouterLink && typeof builtinRouterLink !== 'string' ? builtinRouterLink.useLink : undefined
- const link = useBuiltinLink?.({
- ...props,
- to: to.value,
- })
-
- const hasTarget = computed(() => props.target && props.target !== '_self')
-
// Resolving link type
const isExternal = computed(() => {
// External prop is explicitly set
@@ -159,17 +154,42 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
return true
}
- // When `target` prop is set, link is external
- if (hasTarget.value) {
- return true
- }
+ const path = props.to || props.href || ''
// When `to` is a route object then it's an internal link
- if (typeof to.value === 'object') {
+ if (typeof path === 'object') {
return false
}
- return to.value === '' || isAbsoluteUrl.value
+ return path === '' || isAbsoluteUrl.value
+ })
+
+ // Resolving `to` value from `to` and `href` props
+ const to: ComputedRef = computed(() => {
+ checkPropConflicts(props, 'to', 'href')
+ const path = props.to || props.href || '' // Defaults to empty string (won't render any `href` attribute)
+ if (isExternal.value) { return path }
+ return resolveTrailingSlashBehavior(path, router.resolve)
+ })
+
+ const link = isExternal.value ? undefined : useBuiltinLink?.({ ...props, to })
+
+ // Resolves `to` value if it's a route location object
+ const href = computed(() => {
+ if (!to.value || isAbsoluteUrl.value) { return to.value as string }
+
+ if (isExternal.value) {
+ const path = typeof to.value === 'object' && 'path' in to.value ? resolveRouteObject(to.value) : to.value
+ // separately resolve route objects with a 'name' property and without 'path'
+ const href = typeof path === 'object' ? router.resolve(path).href : path
+ return resolveTrailingSlashBehavior(href, router.resolve /* will not be called */) as string
+ }
+
+ if (typeof to.value === 'object') {
+ return router.resolve(to.value)?.href ?? null
+ }
+
+ return resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve /* will not be called */)
})
return {
@@ -183,10 +203,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
isExactActive: link?.isExactActive ?? computed(() => to.value === router.currentRoute.value.path),
route: link?.route ?? computed(() => router.resolve(to.value)),
async navigate () {
- await navigateTo(href.value, { replace: props.replace, external: props.external })
+ await navigateTo(href.value, { replace: props.replace, external: isExternal.value || hasTarget.value })
},
} satisfies ReturnType & {
- to: ComputedRef
+ to: ComputedRef
hasTarget: ComputedRef
isAbsoluteUrl: ComputedRef
isExternal: ComputedRef
@@ -231,6 +251,11 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
default: undefined,
required: false,
},
+ prefetchOn: {
+ type: [String, Object] as PropType,
+ default: undefined,
+ required: false,
+ },
noPrefetch: {
type: Boolean as PropType,
default: undefined,
@@ -291,10 +316,27 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
const el = import.meta.server ? undefined : ref(null)
const elRef = import.meta.server ? undefined : (ref: any) => { el!.value = props.custom ? ref?.$el?.nextElementSibling : ref?.$el }
+ function shouldPrefetch (mode: 'visibility' | 'interaction') {
+ return !prefetched.value && (typeof props.prefetchOn === 'string' ? props.prefetchOn === mode : (props.prefetchOn?.[mode] ?? options.prefetchOn?.[mode])) && (props.prefetch ?? options.prefetch) !== false && props.noPrefetch !== true && props.target !== '_blank' && !isSlowConnection()
+ }
+
+ async function prefetch (nuxtApp = useNuxtApp()) {
+ if (prefetched.value) { return }
+
+ prefetched.value = true
+
+ const path = typeof to.value === 'string'
+ ? to.value
+ : isExternal.value ? resolveRouteObject(to.value) : router.resolve(to.value).fullPath
+ await Promise.all([
+ nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}),
+ !isExternal.value && !hasTarget.value && preloadRouteComponents(to.value as string, router).catch(() => {}),
+ ])
+ }
+
if (import.meta.client) {
checkPropConflicts(props, 'prefetch', 'noPrefetch')
- const shouldPrefetch = props.prefetch !== false && props.noPrefetch !== true && props.target !== '_blank' && !isSlowConnection()
- if (shouldPrefetch) {
+ if (shouldPrefetch('visibility')) {
const nuxtApp = useNuxtApp()
let idleId: number
let unobserve: (() => void) | null = null
@@ -306,13 +348,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
unobserve = observer!.observe(el.value as HTMLElement, async () => {
unobserve?.()
unobserve = null
-
- const path = typeof to.value === 'string' ? to.value : router.resolve(to.value).fullPath
- await Promise.all([
- nuxtApp.hooks.callHook('link:prefetch', path).catch(() => {}),
- !isExternal.value && preloadRouteComponents(to.value as string, router).catch(() => {}),
- ])
- prefetched.value = true
+ await prefetch(nuxtApp)
})
}
})
@@ -336,7 +372,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
}
return () => {
- if (!isExternal.value) {
+ if (!isExternal.value && !hasTarget.value) {
const routerLinkProps: RouterLinkProps & VNodeProps & AllowedComponentProps & AnchorHTMLAttributes = {
ref: elRef,
to: to.value,
@@ -350,6 +386,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
// `custom` API cannot support fallthrough attributes as the slot
// may render fragment or text root nodes (#14897, #19375)
if (!props.custom) {
+ if (shouldPrefetch('interaction')) {
+ routerLinkProps.onPointerenter = prefetch.bind(null, undefined)
+ routerLinkProps.onFocus = prefetch.bind(null, undefined)
+ }
if (prefetched.value) {
routerLinkProps.class = props.prefetchedClass || options.prefetchedClass
}
@@ -389,6 +429,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
return slots.default({
href: href.value,
navigate,
+ prefetch,
get route () {
if (!href.value) { return undefined }
@@ -408,7 +449,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
},
rel,
target,
- isExternal: isExternal.value,
+ isExternal: isExternal.value || hasTarget.value,
isActive: false,
isExactActive: false,
})
diff --git a/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts b/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts
index 31b2c0718b..b8ef06f9c4 100644
--- a/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts
+++ b/packages/nuxt/src/app/components/nuxt-teleport-island-component.ts
@@ -18,6 +18,7 @@ export const NuxtTeleportIslandSymbol = Symbol('NuxtTeleportIslandComponent') as
/* @__PURE__ */
export default defineComponent({
name: 'NuxtTeleportIslandComponent',
+ inheritAttrs: false,
props: {
to: {
type: String,
@@ -38,12 +39,12 @@ export default defineComponent({
const islandContext = nuxtApp.ssrContext!.islandContext!
return () => {
- const slot = slots.default!()[0]
+ const slot = slots.default!()[0]!
const slotType = slot.type as ExtendedComponent
const name = (slotType.__name || slotType.name) as string
islandContext.components[props.to] = {
- chunk: import.meta.dev ? '_nuxt/' + paths[name] : paths[name],
+ chunk: import.meta.dev ? nuxtApp.$config.app.buildAssetsDir + paths[name] : paths[name],
props: slot.props || {},
}
diff --git a/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts b/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts
index 9f9fefc8b1..7db8042735 100644
--- a/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts
+++ b/packages/nuxt/src/app/components/nuxt-teleport-island-slot.ts
@@ -9,6 +9,7 @@ import { NuxtTeleportIslandSymbol } from './nuxt-teleport-island-component'
/* @__PURE__ */
export default defineComponent({
name: 'NuxtTeleportIslandSlot',
+ inheritAttrs: false,
props: {
name: {
type: String,
diff --git a/packages/nuxt/src/app/components/route-provider.ts b/packages/nuxt/src/app/components/route-provider.ts
index de4ae64a47..16ac724bc7 100644
--- a/packages/nuxt/src/app/components/route-provider.ts
+++ b/packages/nuxt/src/app/components/route-provider.ts
@@ -1,6 +1,6 @@
import { defineComponent, h, nextTick, onMounted, provide, shallowReactive } from 'vue'
import type { Ref, VNode } from 'vue'
-import type { RouteLocation, RouteLocationNormalizedLoaded } from '#vue-router'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { PageRouteSymbol } from './injections'
export const RouteProvider = defineComponent({
@@ -23,10 +23,11 @@ export const RouteProvider = defineComponent({
const previousRoute = props.route
// Provide a reactive route within the page
- const route = {} as RouteLocation
+ const route = {} as RouteLocationNormalizedLoaded
for (const key in props.route) {
Object.defineProperty(route, key, {
get: () => previousKey === props.renderKey ? props.route[key as keyof RouteLocationNormalizedLoaded] : previousRoute[key as keyof RouteLocationNormalizedLoaded],
+ enumerable: true,
})
}
diff --git a/packages/nuxt/src/app/components/test-component-wrapper.ts b/packages/nuxt/src/app/components/test-component-wrapper.ts
index 8b0da24381..b2de69d8f5 100644
--- a/packages/nuxt/src/app/components/test-component-wrapper.ts
+++ b/packages/nuxt/src/app/components/test-component-wrapper.ts
@@ -7,7 +7,7 @@ import { devRootDir } from '#build/nuxt.config.mjs'
export default (url: string) => defineComponent({
name: 'NuxtTestComponentWrapper',
-
+ inheritAttrs: false,
async setup (props, { attrs }) {
const query = parseQuery(new URL(url, 'http://localhost').search)
const urlProps = query.props ? destr>(query.props as string) : {}
diff --git a/packages/nuxt/src/app/components/utils.ts b/packages/nuxt/src/app/components/utils.ts
index a5e918a89d..0bde127ec5 100644
--- a/packages/nuxt/src/app/components/utils.ts
+++ b/packages/nuxt/src/app/components/utils.ts
@@ -2,7 +2,7 @@ import { h } from 'vue'
import type { Component, RendererNode } from 'vue'
// eslint-disable-next-line
import { isString, isPromise, isArray, isObject } from '@vue/shared'
-import type { RouteLocationNormalized } from '#vue-router'
+import type { RouteLocationNormalized } from 'vue-router'
// @ts-expect-error virtual file
import { START_LOCATION } from '#build/pages'
@@ -86,7 +86,7 @@ export function vforToArray (source: any): any[] {
if (import.meta.dev && !Number.isInteger(source)) {
console.warn(`The v-for range expect an integer value but got ${source}.`)
}
- const array = []
+ const array: number[] = []
for (let i = 0; i < source; i++) {
array[i] = i
}
@@ -100,7 +100,7 @@ export function vforToArray (source: any): any[] {
const keys = Object.keys(source)
const array = new Array(keys.length)
for (let i = 0, l = keys.length; i < l; i++) {
- const key = keys[i]
+ const key = keys[i]!
array[i] = source[key]
}
return array
diff --git a/packages/nuxt/src/app/components/welcome.vue b/packages/nuxt/src/app/components/welcome.vue
deleted file mode 120000
index e2e9ab51e4..0000000000
--- a/packages/nuxt/src/app/components/welcome.vue
+++ /dev/null
@@ -1 +0,0 @@
-../../../../ui-templates/dist/templates/welcome.vue
\ No newline at end of file
diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts
index aafb729e14..2081b96307 100644
--- a/packages/nuxt/src/app/composables/asyncData.ts
+++ b/packages/nuxt/src/app/composables/asyncData.ts
@@ -1,5 +1,5 @@
import { computed, getCurrentInstance, getCurrentScope, onBeforeMount, onScopeDispose, onServerPrefetch, onUnmounted, ref, shallowRef, toRef, unref, watch } from 'vue'
-import type { Ref, WatchSource } from 'vue'
+import type { MultiWatchSources, Ref } from 'vue'
import type { NuxtApp } from '../nuxt'
import { useNuxtApp } from '../nuxt'
import { toArray } from '../utils'
@@ -8,10 +8,7 @@ import { createError } from './error'
import { onNuxtReady } from './ready'
// @ts-expect-error virtual file
-import { asyncDataDefaults, resetAsyncDataToUndefined } from '#build/nuxt.config.mjs'
-
-// TODO: temporary module for backwards compatibility
-import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
+import { asyncDataDefaults } from '#build/nuxt.config.mjs'
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
@@ -37,7 +34,7 @@ export type KeysOf = Array<
export type KeyOfRes = KeysOf>
-export type MultiWatchSources = (WatchSource | object)[]
+export type { MultiWatchSources }
export type NoInfer = [T][T extends any ? 0 : never]
@@ -45,7 +42,7 @@ export interface AsyncDataOptions<
ResT,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> {
/**
* Whether to fetch on the server side.
@@ -63,10 +60,10 @@ export interface AsyncDataOptions<
default?: () => DefaultT | Ref
/**
* Provide a function which returns cached data.
- * A `null` or `undefined` return value will trigger a fetch.
+ * An `undefined` return value will trigger a fetch.
* Default is `key => nuxt.isHydrating ? nuxt.payload.data[key] : nuxt.static.data[key]` which only caches data when payloadExtraction is enabled.
*/
- getCachedData?: (key: string, nuxtApp: NuxtApp) => NoInfer
+ getCachedData?: (key: string, nuxtApp: NuxtApp) => NoInfer | undefined
/**
* A function that can be used to alter handler function result after resolving.
* Do not use it along with the `pick` option.
@@ -87,7 +84,7 @@ export interface AsyncDataOptions<
*/
immediate?: boolean
/**
- * Return data in a deep ref object (it is true by default). It can be set to false to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive.
+ * Return data in a deep ref object (it is false by default). It can be set to false to return data in a shallow ref object, which can improve performance if your data does not need to be deeply reactive.
*/
deep?: boolean
/**
@@ -99,16 +96,12 @@ export interface AsyncDataOptions<
export interface AsyncDataExecuteOptions {
_initial?: boolean
- // TODO: remove boolean option in Nuxt 4
/**
* Force a refresh, even if there is already a pending request. Previous requests will
* not be cancelled, but their result will not affect the data/pending state - and any
* previously awaited promises will not resolve until this new request resolves.
- *
- * Instead of using `boolean` values, use `cancel` for `true` and `defer` for `false`.
- * Boolean values will be removed in a future release.
*/
- dedupe?: boolean | 'cancel' | 'defer'
+ dedupe?: 'cancel' | 'defer'
}
export interface _AsyncData {
@@ -120,15 +113,12 @@ export interface _AsyncData {
refresh: (opts?: AsyncDataExecuteOptions) => Promise
execute: (opts?: AsyncDataExecuteOptions) => Promise
clear: () => void
- error: Ref
+ error: Ref
status: Ref
}
export type AsyncData = _AsyncData & Promise<_AsyncData>
-// TODO: remove boolean option in Nuxt 4
-const isDefer = (dedupe?: boolean | 'cancel' | 'defer') => dedupe === 'defer' || dedupe === false
-
/**
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
@@ -141,11 +131,11 @@ export function useAsyncData<
NuxtErrorDataT = unknown,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | undefined>
/**
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
@@ -161,7 +151,7 @@ export function useAsyncData<
> (
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | undefined>
/**
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
@@ -174,17 +164,17 @@ export function useAsyncData<
NuxtErrorDataT = unknown,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | undefined>
/**
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
* @param key A unique key to ensure that data fetching can be properly de-duplicated across requests.
- * @param handler An asynchronous function that must return a truthy value (for example, it should not be `undefined` or `null`) or the request may be duplicated on the client side.
+ * @param handler An asynchronous function that must return a value (it should not be `undefined`) or the request may be duplicated on the client side.
* @param options customize the behavior of useAsyncData
*/
export function useAsyncData<
@@ -197,14 +187,14 @@ export function useAsyncData<
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | undefined>
export function useAsyncData<
ResT,
NuxtErrorDataT = unknown,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
-> (...args: any[]): AsyncData, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue> {
+ DefaultT = undefined,
+> (...args: any[]): AsyncData, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | undefined> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
@@ -236,7 +226,7 @@ export function useAsyncData<
}
// Used to get default values
- const getDefault = () => asyncDataDefaults.value
+ const getDefault = () => undefined
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
// Apply defaults
@@ -249,22 +239,17 @@ export function useAsyncData<
options.deep = options.deep ?? asyncDataDefaults.deep
options.dedupe = options.dedupe ?? 'cancel'
- if (import.meta.dev && typeof options.dedupe === 'boolean') {
- console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
- }
-
- // TODO: make more precise when v4 lands
- const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
-
// Create or use a shared asyncData entity
+ const initialCachedData = options.getCachedData!(key, nuxtApp)
+ const hasCachedData = typeof initialCachedData !== 'undefined'
+
if (!nuxtApp._asyncData[key] || !options.immediate) {
- nuxtApp.payload._errors[key] ??= asyncDataDefaults.errorValue
+ nuxtApp.payload._errors[key] ??= undefined
const _ref = options.deep ? ref : shallowRef
-
nuxtApp._asyncData[key] = {
- data: _ref(options.getCachedData!(key, nuxtApp) ?? options.default!()),
- pending: ref(!hasCachedData()),
+ data: _ref(hasCachedData ? initialCachedData : options.default!()),
+ pending: ref(!hasCachedData),
error: toRef(nuxtApp.payload._errors, key),
status: ref('idle'),
_default: options.default!,
@@ -279,15 +264,18 @@ export function useAsyncData<
asyncData.refresh = asyncData.execute = (opts = {}) => {
if (nuxtApp._asyncDataPromises[key]) {
- if (isDefer(opts.dedupe ?? options.dedupe)) {
+ if ((opts.dedupe ?? options.dedupe) === 'defer') {
// Avoid fetching same key more than once at a time
return nuxtApp._asyncDataPromises[key]!
}
(nuxtApp._asyncDataPromises[key] as any).cancelled = true
}
// Avoid fetching same key that is already fetched
- if ((opts._initial || (nuxtApp.isHydrating && opts._initial !== false)) && hasCachedData()) {
- return Promise.resolve(options.getCachedData!(key, nuxtApp))
+ if ((opts._initial || (nuxtApp.isHydrating && opts._initial !== false))) {
+ const cachedData = opts._initial ? initialCachedData : options.getCachedData!(key, nuxtApp)
+ if (typeof cachedData !== 'undefined') {
+ return Promise.resolve(cachedData)
+ }
}
asyncData.pending.value = true
asyncData.status.value = 'pending'
@@ -312,10 +300,15 @@ export function useAsyncData<
result = pick(result as any, options.pick) as DataT
}
+ if (import.meta.dev && import.meta.server && typeof result === 'undefined') {
+ // @ts-expect-error private property
+ console.warn(`[nuxt] \`${options._functionName || 'useAsyncData'}\` must return a value (it should not be \`undefined\`) or the request may be duplicated on the client side.`)
+ }
+
nuxtApp.payload.data[key] = result
asyncData.data.value = result
- asyncData.error.value = asyncDataDefaults.errorValue
+ asyncData.error.value = undefined
asyncData.status.value = 'success'
})
.catch((error: any) => {
@@ -357,7 +350,7 @@ export function useAsyncData<
if (import.meta.client) {
// Setup hook callbacks once per instance
const instance = getCurrentInstance()
- if (import.meta.dev && !nuxtApp.isHydrating && (!instance || instance?.isMounted)) {
+ if (import.meta.dev && !nuxtApp.isHydrating && !nuxtApp._processingMiddleware /* internal flag */ && (!instance || instance?.isMounted)) {
// @ts-expect-error private property
console.warn(`[nuxt] [${options._functionName || 'useAsyncData'}] Component is already mounted, please use $fetch instead. See https://nuxt.com/docs/getting-started/data-fetching`)
}
@@ -371,7 +364,7 @@ export function useAsyncData<
onUnmounted(() => cbs.splice(0, cbs.length))
}
- if (fetchOnServer && nuxtApp.isHydrating && (asyncData.error.value || hasCachedData())) {
+ if (fetchOnServer && nuxtApp.isHydrating && (asyncData.error.value || typeof initialCachedData !== 'undefined')) {
// 1. Hydration (server: true): no fetch
asyncData.pending.value = false
asyncData.status.value = asyncData.error.value ? 'error' : 'success'
@@ -412,11 +405,11 @@ export function useLazyAsyncData<
DataE = Error,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
+): AsyncData | DefaultT, DataE | undefined>
export function useLazyAsyncData<
ResT,
DataE = Error,
@@ -426,18 +419,18 @@ export function useLazyAsyncData<
> (
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
+): AsyncData | DefaultT, DataE | undefined>
export function useLazyAsyncData<
ResT,
DataE = Error,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
+): AsyncData | DefaultT, DataE | undefined>
export function useLazyAsyncData<
ResT,
DataE = Error,
@@ -448,15 +441,15 @@ export function useLazyAsyncData<
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
+): AsyncData | DefaultT, DataE | undefined>
export function useLazyAsyncData<
ResT,
DataE = Error,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
-> (...args: any[]): AsyncData | DefaultT, DataE | DefaultAsyncDataValue> {
+ DefaultT = undefined,
+> (...args: any[]): AsyncData | DefaultT, DataE | undefined> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions]
@@ -471,12 +464,12 @@ export function useLazyAsyncData<
}
/** @since 3.1.0 */
-export function useNuxtData (key: string): { data: Ref } {
+export function useNuxtData (key: string): { data: Ref } {
const nuxtApp = useNuxtApp()
// Initialize value when key is not already set
if (!(key in nuxtApp.payload.data)) {
- nuxtApp.payload.data[key] = asyncDataDefaults.value
+ nuxtApp.payload.data[key] = undefined
}
return {
@@ -528,18 +521,21 @@ function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
}
if (key in nuxtApp.payload._errors) {
- nuxtApp.payload._errors[key] = asyncDataDefaults.errorValue
+ nuxtApp.payload._errors[key] = undefined
}
if (nuxtApp._asyncData[key]) {
- nuxtApp._asyncData[key]!.data.value = resetAsyncDataToUndefined ? undefined : nuxtApp._asyncData[key]!._default()
- nuxtApp._asyncData[key]!.error.value = asyncDataDefaults.errorValue
+ nuxtApp._asyncData[key]!.data.value = nuxtApp._asyncData[key]!._default()
+ nuxtApp._asyncData[key]!.error.value = undefined
nuxtApp._asyncData[key]!.pending.value = false
nuxtApp._asyncData[key]!.status.value = 'idle'
}
if (key in nuxtApp._asyncDataPromises) {
- (nuxtApp._asyncDataPromises[key] as any).cancelled = true
+ if (nuxtApp._asyncDataPromises[key]) {
+ (nuxtApp._asyncDataPromises[key] as any).cancelled = true
+ }
+
nuxtApp._asyncDataPromises[key] = undefined
}
}
diff --git a/packages/nuxt/src/app/composables/component.ts b/packages/nuxt/src/app/composables/component.ts
index ebc234226c..e8bd93e20c 100644
--- a/packages/nuxt/src/app/composables/component.ts
+++ b/packages/nuxt/src/app/composables/component.ts
@@ -16,7 +16,7 @@ async function runLegacyAsyncData (res: Record | Promise '') : fetchKey) ||
([_fetchKeyBase, route.fullPath, route.matched.findIndex(r => Object.values(r.components || {}).includes(vm.type))].join(':'))
- const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => nuxtApp.runWithContext(() => fn(nuxtApp)))
+ const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => import.meta.server ? nuxtApp.runWithContext(() => fn(nuxtApp)) : fn(nuxtApp))
if (error.value) {
throw createError(error.value)
}
diff --git a/packages/nuxt/src/app/composables/cookie.ts b/packages/nuxt/src/app/composables/cookie.ts
index 7f5b6cd267..47a9299673 100644
--- a/packages/nuxt/src/app/composables/cookie.ts
+++ b/packages/nuxt/src/app/composables/cookie.ts
@@ -23,6 +23,7 @@ export interface CookieOptions extends _CookieOptions {
readonly?: boolean
}
+// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface CookieRef extends Ref {}
const CookieDefaults = {
@@ -39,6 +40,7 @@ export function useCookie (name: string, _opts?:
export function useCookie (name: string, _opts: CookieOptions & { readonly: true }): Readonly>
export function useCookie (name: string, _opts?: CookieOptions): CookieRef {
const opts = { ...CookieDefaults, ..._opts }
+ opts.filter ??= key => key === name
const cookies = readRawCookies(opts) || {}
let delay: number | undefined
@@ -83,13 +85,16 @@ export function useCookie (name: string, _opts?:
const handleChange = (data: { value?: any, refresh?: boolean }) => {
const value = data.refresh ? readRawCookies(opts)?.[name] : opts.decode(data.value)
watchPaused = true
- cookies[name] = cookie.value = value
+ cookie.value = value
+ cookies[name] = klona(value)
nextTick(() => { watchPaused = false })
}
let watchPaused = false
- if (getCurrentScope()) {
+ const hasScope = !!getCurrentScope()
+
+ if (hasScope) {
onScopeDispose(() => {
watchPaused = true
callback()
@@ -98,9 +103,22 @@ export function useCookie