@@ -34,7 +34,7 @@ It provides a number of features that make it easy to build fast, SEO-friendly,
- 🏠 [Local Development](#local-development)
- ⛰️ [Nuxt 2](#nuxt-2)
- 🛟 [Professional Support](#professional-support)
-- 🔗 [Follow us](#follow-us)
+- 🔗 [Follow Us](#follow-us)
- ⚖️ [License](#license)
---
@@ -101,18 +101,12 @@ Here are a few ways you can get involved:
Follow the docs to [Set Up Your Local Development Environment](https://nuxt.com/docs/community/framework-contribution#setup) to contribute to the framework and documentation.
-## ⛰️ Nuxt 2
-
-You can find the code for Nuxt 2 on the [`2.x` branch](https://github.com/nuxt/nuxt/tree/2.x) and the documentation at [v2.nuxt.com](https://v2.nuxt.com).
-
-If you expect to be using Nuxt 2 beyond the EOL (End of Life) date (June 30, 2024), and still need a maintained version that can satisfy security and browser compatibility requirements, make sure to check out [HeroDevs’ NES (Never-Ending Support) Nuxt 2](https://www.herodevs.com/support/nuxt-nes?utm_source=nuxt-github&utm_medium=nuxt-readme).
-
## 🛟 Professional Support
- Technical audit & consulting: [Nuxt Experts](https://nuxt.com/enterprise/support)
- Custom development & more: [Nuxt Agencies Partners](https://nuxt.com/enterprise/agencies)
-## 🔗 Follow us
+## 🔗 Follow Us
@@ -120,4 +114,4 @@ If you expect to be using Nuxt 2 beyond the EOL (End of Life) date (June 30, 202
## ⚖️ License
-[MIT](./LICENSE)
+[MIT](https://github.com/nuxt/nuxt/tree/main/LICENSE)
diff --git a/docs/1.getting-started/1.introduction.md b/docs/1.getting-started/1.introduction.md
index 47bfdb0bc4..d092c50579 100644
--- a/docs/1.getting-started/1.introduction.md
+++ b/docs/1.getting-started/1.introduction.md
@@ -76,7 +76,6 @@ Nuxt is composed of different [core packages](https://github.com/nuxt/nuxt/tree/
- Command line interface: [nuxi](https://github.com/nuxt/nuxt/tree/main/packages/nuxi)
- Server engine: [nitro](https://github.com/unjs/nitro)
- Development kit: [@nuxt/kit](https://github.com/nuxt/nuxt/tree/main/packages/kit)
-- Nuxt 2 Bridge: [@nuxt/bridge](https://github.com/nuxt/bridge)
We recommend reading each concept to have a full vision of Nuxt capabilities and the scope of each package.
diff --git a/docs/1.getting-started/10.deployment.md b/docs/1.getting-started/10.deployment.md
index 91a4a71cc0..9043f0035c 100644
--- a/docs/1.getting-started/10.deployment.md
+++ b/docs/1.getting-started/10.deployment.md
@@ -85,13 +85,13 @@ export default defineNuxtConfig({
## Hosting Providers
-Nuxt 3 can be deployed to several cloud providers with a minimal amount of configuration:
+Nuxt can be deployed to several cloud providers with a minimal amount of configuration:
:read-more{to="/deploy"}
## Presets
-In addition to Node.js servers and static hosting services, a Nuxt 3 project can be deployed with several well-tested presets and minimal amount of configuration.
+In addition to Node.js servers and static hosting services, a Nuxt project can be deployed with several well-tested presets and minimal amount of configuration.
You can explicitly set the desired preset in the [`nuxt.config.ts`](/docs/guide/directory-structure/nuxt-config) file:
@@ -117,10 +117,13 @@ In most cases, Nuxt can work with third-party content that is not generated or c
Accordingly, you should make sure that the following options are unchecked / disabled in Cloudflare. Otherwise, unnecessary re-rendering or hydration errors could impact your production application.
-1. Speed > Optimization > Auto Minify: Uncheck JavaScript, CSS and HTML
-2. Speed > Optimization > Disable "Rocket Loader™"
-3. Speed > Optimization > Disable "Mirage"
+1. Speed > Optimization > Content Optimization > Auto Minify: Uncheck JavaScript, CSS and HTML
+2. Speed > Optimization > Content Optimization > Disable "Rocket Loader™"
+3. Speed > Optimization > Image Optimization > Disable "Mirage"
4. Scrape Shield > Disable "Email Address Obfuscation"
-5. Scrape Shield > Disable "Server-side Excludes"
With these settings, you can be sure that Cloudflare won't inject scripts into your Nuxt application that may cause unwanted side effects.
+
+::tip
+Their location on the Cloudflare dashboard sometimes changes so don't hesitate to look around.
+::
diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md
index 3017305c77..ffeb075ab2 100644
--- a/docs/1.getting-started/12.upgrade.md
+++ b/docs/1.getting-started/12.upgrade.md
@@ -11,19 +11,41 @@ navigation.icon: i-ph-arrow-circle-up-duotone
To upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command.
-```bash [Terminal]
+::code-group
+
+```bash [npm]
npx nuxi upgrade
```
+```bash [yarn]
+yarn dlx nuxi upgrade
+```
+
+```bash [pnpm]
+pnpm dlx nuxi upgrade
+```
+
+```bash [bun]
+bunx nuxi upgrade
+```
+
+::
+
### Nightly Release Channel
To use the latest Nuxt build and test features before their release, read about the [nightly release channel](/docs/guide/going-further/nightly-release-channel) guide.
+::alert{type="warning"}
+The nightly release channel `latest` tag is currently tracking the Nuxt v4 branch, meaning that it is particularly likely to have breaking changes right now - be careful!
+
+You can opt in to the 3.x branch nightly releases with `"nuxt": "npm:nuxt-nightly@3x"`.
+::
+
## Testing Nuxt 4
Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date).
-Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12 or via the nightly release channel.
+Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12+.
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"}
Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already.
@@ -31,7 +53,7 @@ Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking
### Opting in to Nuxt 4
-First, opt in to the nightly release channel [following these steps](/docs/guide/going-further/nightly-release-channel#opting-in).
+First, upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases).
Then you can set your `compatibilityVersion` to match Nuxt 4 behavior:
@@ -87,7 +109,8 @@ Nuxt now defaults to a new directory structure, with backwards compatibility (so
* the new Nuxt default `srcDir` is `app/` by default, and most things are resolved from there.
* `serverDir` now defaults to `/server` rather than `/server`
-* `modules` and `public` are resolved relative to `` by default
+* `layers/`, `modules/` and `public/` are resolved relative to `` by default
+* if using [Nuxt Content v2.13+](https://github.com/nuxt/content/pull/2649), `content/` is resolved relative to ``
* a new `dir.app` is added, which is the directory we look for `router.options.ts` and `spa-loading-template.html` - this defaults to `/`
@@ -109,6 +132,8 @@ app/
app.config.ts
app.vue
router.options.ts
+content/
+layers/
modules/
node_modules/
public/
@@ -134,7 +159,7 @@ nuxt.config.ts
1. Create a new directory called `app/`.
1. Move your `assets/`, `components/`, `composables/`, `layouts/`, `middleware/`, `pages/`, `plugins/` and `utils/` folders under it, as well as `app.vue`, `error.vue`, `app.config.ts`. If you have an `app/router-options.ts` or `app/spa-loading-template.html`, these paths remain the same.
-1. Make sure your `nuxt.config.ts`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project.
+1. Make sure your `nuxt.config.ts`, `content/`, `layers/`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project.
However, migration is _not required_. If you wish to keep your current folder structure, Nuxt should auto-detect it. (If it does not, please raise an issue.) The one exception is that if you _already_ have a custom `srcDir`. In this case, you should be aware that your `modules/`, `public/` and `server/` folders will be resolved from your `rootDir` rather than from your custom `srcDir`. You can override this by configuring `dir.modules`, `dir.public` and `serverDir` if you need to.
@@ -209,6 +234,7 @@ Previously `data` was initialized to `null` but reset in `clearNuxtData` to `und
If you encounter any issues you can revert back to the previous behavior with:
```ts twoslash [nuxt.config.ts]
+// @errors: 2353
export default defineNuxtConfig({
experimental: {
defaults: {
@@ -232,6 +258,7 @@ Please report an issue if you are doing this, as we do not plan to keep this as
Previously it was possible to pass `dedupe: boolean` to `refresh`. These were aliases of `cancel` (`true`) and `defer` (`false`).
```ts twoslash [app.vue]
+// @errors: 2322
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
async function refreshData () {
@@ -280,6 +307,7 @@ Often users set an appropriately empty value, such as an empty array, to avoid t
If you encounter any issues you can revert back to the previous behavior, for now, with:
```ts twoslash [nuxt.config.ts]
+// @errors: 2353
export default defineNuxtConfig({
experimental: {
resetAsyncDataToUndefined: true,
@@ -352,6 +380,25 @@ However, if you are a module author using the `builder:watch` hook and wishing t
})
```
+#### Removal of `window.__NUXT__` object
+
+##### What Changed
+
+We are removing the global `window.__NUXT__` object after the app finishes hydration.
+
+##### Reasons for Change
+
+This opens the way to multi-app patterns ([#21635](https://github.com/nuxt/nuxt/issues/21635)) and enables us to focus on a single way to access Nuxt app data - `useNuxtApp()`.
+
+##### Migration Steps
+
+The data is still available, but can be accessed with `useNuxtApp().payload`:
+
+```diff
+- console.log(window.__NUXT__)
++ console.log(useNuxtApp().payload)
+```
+
#### Directory index scanning
🚦 **Impact Level**: Medium
@@ -448,10 +495,11 @@ const importName = genSafeVariableName
Four experimental features are no longer configurable in Nuxt 4:
-* `treeshakeClientOnly` will be `true` (default since v3.0)
-* `configSchema` will be `true` (default since v3.3)
-* `polyfillVueUseHead` will be `false` (default since v3.4)
-* `respectNoSSRHeader` will be `false` (default since v3.4)
+* `experimental.treeshakeClientOnly` will be `true` (default since v3.0)
+* `experimental.configSchema` will be `true` (default since v3.3)
+* `experimental.polyfillVueUseHead` will be `false` (default since v3.4)
+* `experimental.respectNoSSRHeader` will be `false` (default since v3.4)
+* `vite.devBundler` is no longer configurable - it will use `vite-node` by default
##### Reasons for Change
@@ -463,11 +511,11 @@ These options have been set to their current values for some time and we do not
* `respectNoSSRHeader`is implementable in user-land with [server middleware](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/no-ssr.ts#L8-L9)
-## Nuxt 2 vs Nuxt 3
+## Nuxt 2 vs Nuxt 3+
In the table below, there is a quick comparison between 3 versions of Nuxt:
-Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3
+Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3+
-------------------------|-----------------|------------------|---------
Vue | 2 | 2 | 3
Stability | 😊 Stable | 😊 Stable | 😊 Stable
@@ -485,9 +533,9 @@ Vite | ⚠️ Partial | 🚧 Partial | ✅
Nuxi CLI | ❌ Old | ✅ nuxi | ✅ nuxi
Static sites | ✅ | ✅ | ✅
-## Nuxt 2 to Nuxt 3
+## Nuxt 2 to Nuxt 3+
-The migration guide provides a step-by-step comparison of Nuxt 2 features to Nuxt 3 features and guidance to adapt your current application.
+The migration guide provides a step-by-step comparison of Nuxt 2 features to Nuxt 3+ features and guidance to adapt your current application.
::read-more{to="/docs/migration/overview"}
Check out the **guide to migrating from Nuxt 2 to Nuxt 3**.
@@ -495,7 +543,7 @@ Check out the **guide to migrating from Nuxt 2 to Nuxt 3**.
## Nuxt 2 to Nuxt Bridge
-If you prefer to progressively migrate your Nuxt 2 application to Nuxt 3, you can use Nuxt Bridge. Nuxt Bridge is a compatibility layer that allows you to use Nuxt 3 features in Nuxt 2 with an opt-in mechanism.
+If you prefer to progressively migrate your Nuxt 2 application to Nuxt 3, you can use Nuxt Bridge. Nuxt Bridge is a compatibility layer that allows you to use Nuxt 3+ features in Nuxt 2 with an opt-in mechanism.
::read-more{to="/docs/bridge/overview"}
**Migrate from Nuxt 2 to Nuxt Bridge**
diff --git a/docs/1.getting-started/2.installation.md b/docs/1.getting-started/2.installation.md
index 1af58ad887..32e8eb7f29 100644
--- a/docs/1.getting-started/2.installation.md
+++ b/docs/1.getting-started/2.installation.md
@@ -37,10 +37,14 @@ Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.
::code-group
-```bash [npx]
+```bash [npm]
npx nuxi@latest init
```
+```bash [yarn]
+yarn dlx nuxi@latest init
+```
+
```bash [pnpm]
pnpm dlx nuxi@latest init
```
@@ -96,6 +100,6 @@ Well done! A browser window should automatically open for value`) over computed (`computed(() => value)`).
-
::code-group
```vue twoslash [useHead]
diff --git a/docs/1.getting-started/5.transitions.md b/docs/1.getting-started/5.transitions.md
index 6123b64789..ccabe0ed6d 100644
--- a/docs/1.getting-started/5.transitions.md
+++ b/docs/1.getting-started/5.transitions.md
@@ -328,7 +328,7 @@ definePageMeta({
},
middleware (to, from) {
if (to.meta.pageTransition && typeof to.meta.pageTransition !== 'boolean')
- to.meta.pageTransition.name = +to.params.id > +from.params.id ? 'slide-left' : 'slide-right'
+ to.meta.pageTransition.name = +to.params.id! > +from.params.id! ? 'slide-left' : 'slide-right'
}
})
@@ -392,7 +392,7 @@ The page now applies the `slide-left` transition when going to the next id and `
## Transition with NuxtPage
-When `` is used in `app.vue`, transition-props can be passed directly as a component props to activate global transition.
+When `` is used in `app.vue`, transitions can be configured with the `transition` prop to activate transitions globally.
```vue [app.vue]
@@ -470,6 +470,4 @@ export default defineNuxtRouteMiddleware(to => {
### Known issues
-- View transitions may not work as expected with nested pages/layouts/async components owing to this upstream Vue bug: . If you make use of this pattern, you may need to delay adopting this experimental feature or implement it yourself. Feedback is very welcome.
-
- If you perform data fetching within your page setup functions, that you may wish to reconsider using this feature for the moment. (By design, View Transitions completely freeze DOM updates whilst they are taking place.) We're looking at restrict the View Transition to the final moments before `` resolves, but in the interim you may want to consider carefully whether to adopt this feature if this describes you.
diff --git a/docs/1.getting-started/6.data-fetching.md b/docs/1.getting-started/6.data-fetching.md
index 2297412b99..1b6fc9bb34 100644
--- a/docs/1.getting-started/6.data-fetching.md
+++ b/docs/1.getting-started/6.data-fetching.md
@@ -134,7 +134,7 @@ The `useAsyncData` composable is a great way to wrap and wait for multiple `$fet
```vue
-
+
Loading ...
@@ -204,7 +203,7 @@ You can alternatively use [`useLazyFetch`](/docs/api/composables/use-lazy-fetch)
```vue twoslash
```
@@ -227,7 +226,7 @@ Combined with the `lazy` option, this can be useful for data that is not needed
const articles = await useFetch('/api/article')
/* This call will only be performed on the client */
-const { pending, data: posts } = useFetch('/api/comments', {
+const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false
})
@@ -355,7 +354,7 @@ Sometimes you may need to compute an URL from reactive values, and refresh the d
@@ -406,7 +407,7 @@ With that, you will need both the `status` to handle the fetch lifecycle, and `e
```vue
@@ -416,7 +417,7 @@ const { data, error, execute, pending, status } = await useLazyFetch('/api/comme
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..eac13cab4f 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-duotone" 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..6a0b0a08a5 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-duotone" 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..b112c84962 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-duotone" 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..9e6ffab3bc 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-duotone" 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-duotone" 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-duotone" 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.
::
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..4ee2eda5af 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-duotone" 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-duotone" to="https://vueschool.io/lessons/injecting-plugin-templates?friend=nuxt" target="_blank"}
Watch Vue School video about addPluginTemplate.
::
diff --git a/docs/3.api/6.advanced/1.hooks.md b/docs/3.api/6.advanced/1.hooks.md
index 30204db7e4..0c0436f1a3 100644
--- a/docs/3.api/6.advanced/1.hooks.md
+++ b/docs/3.api/6.advanced/1.hooks.md
@@ -30,7 +30,7 @@ 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)
diff --git a/docs/5.community/2.getting-help.md b/docs/5.community/2.getting-help.md
index 70ab6946fc..8f4e038906 100644
--- a/docs/5.community/2.getting-help.md
+++ b/docs/5.community/2.getting-help.md
@@ -30,4 +30,4 @@ 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.
diff --git a/docs/5.community/3.reporting-bugs.md b/docs/5.community/3.reporting-bugs.md
index 815617c2e8..c82a6d8ba6 100644
--- a/docs/5.community/3.reporting-bugs.md
+++ b/docs/5.community/3.reporting-bugs.md
@@ -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..3702d845a8 100644
--- a/docs/5.community/4.contribution.md
+++ b/docs/5.community/4.contribution.md
@@ -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
diff --git a/docs/5.community/6.roadmap.md b/docs/5.community/6.roadmap.md
index f485be077d..b0209259b7 100644
--- a/docs/5.community/6.roadmap.md
+++ b/docs/5.community/6.roadmap.md
@@ -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
@@ -42,7 +42,7 @@ Module | Status | Nuxt Support | Repos
------------------------------------|---------------------|--------------|------------|-------------------
[Scripts](https://scripts.nuxt.com) | Public Preview | 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/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/eslint.config.mjs b/eslint.config.mjs
index c4e282b673..9ca63411de 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -148,17 +148,6 @@ export default createConfigForNuxt({
],
},
],
- 'import/order': [
- 'error',
- {
- pathGroups: [
- {
- group: 'external',
- pattern: '#vue-router',
- },
- ],
- },
- ],
'jsdoc/check-tag-names': [
'error',
{
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/package.json b/package.json
index 03e833fefe..2b69f0b164 100644
--- a/package.json
+++ b/package.json
@@ -39,63 +39,74 @@
"@nuxt/ui-templates": "workspace:*",
"@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*",
+ "c12": "2.0.0-beta.1",
+ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
+ "jiti": "2.0.0-beta.3",
"magic-string": "^0.30.10",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxt": "workspace:*",
- "rollup": "^4.18.0",
- "vite": "5.3.0",
- "vue": "3.4.27"
+ "rollup": "^4.18.1",
+ "typescript": "5.5.3",
+ "unbuild": "3.0.0-rc.6",
+ "vite": "5.3.4",
+ "vue": "3.4.31"
},
"devDependencies": {
- "@eslint/js": "9.4.0",
+ "@eslint/js": "9.7.0",
"@nuxt/eslint-config": "0.3.13",
"@nuxt/kit": "workspace:*",
"@nuxt/test-utils": "3.13.1",
"@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.14.2",
+ "@types/node": "20.14.10",
"@types/semver": "7.5.8",
- "@unhead/schema": "1.9.13",
- "@vitejs/plugin-vue": "5.0.4",
- "@vitest/coverage-v8": "1.6.0",
+ "@unhead/schema": "1.9.16",
+ "@vitejs/plugin-vue": "5.0.5",
+ "@vitest/coverage-v8": "2.0.3",
"@vue/test-utils": "2.4.6",
+ "autoprefixer": "10.4.19",
"case-police": "0.6.1",
"changelogen": "0.5.5",
"consola": "3.2.3",
+ "cssnano": "7.0.4",
"devalue": "5.0.0",
- "eslint": "9.4.0",
+ "eslint": "9.7.0",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-perfectionist": "2.11.0",
"eslint-typegen": "0.2.4",
- "execa": "9.2.0",
- "fs-extra": "11.2.0",
- "globby": "14.0.1",
- "h3": "1.11.1",
- "happy-dom": "14.12.0",
- "jiti": "1.21.6",
+ "execa": "9.3.0",
+ "globby": "14.0.2",
+ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
+ "happy-dom": "14.12.3",
+ "jiti": "2.0.0-beta.3",
"markdownlint-cli": "0.41.0",
- "nitropack": "2.9.6",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxi": "3.12.0",
"nuxt": "workspace:*",
- "nuxt-content-twoslash": "0.0.10",
+ "nuxt-content-twoslash": "0.1.0",
"ofetch": "1.3.4",
"pathe": "1.1.2",
- "playwright-core": "1.44.1",
- "rimraf": "5.0.7",
+ "playwright-core": "1.45.2",
+ "rimraf": "6.0.1",
"semver": "7.6.2",
"std-env": "3.7.0",
- "typescript": "5.4.5",
+ "typescript": "5.5.3",
"ufo": "1.5.3",
- "vitest": "1.6.0",
+ "vitest": "2.0.3",
"vitest-environment-nuxt": "1.0.0",
- "vue": "3.4.27",
- "vue-router": "4.3.3",
- "vue-tsc": "2.0.21"
+ "vue": "3.4.31",
+ "vue-router": "4.4.0",
+ "vue-tsc": "2.0.26"
},
- "packageManager": "pnpm@9.3.0",
+ "packageManager": "pnpm@9.5.0",
"engines": {
"node": "^16.10.0 || >=18.0.0"
},
- "version": ""
+ "version": "",
+ "pnpm": {
+ "patchedDependencies": {
+ "ofetch@1.3.4": "patches/ofetch@1.3.4.patch"
+ }
+ }
}
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 310931589b..aa1951e9ed 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
{
"name": "@nuxt/kit",
- "version": "3.12.1",
+ "version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@@ -27,36 +27,33 @@
},
"dependencies": {
"@nuxt/schema": "workspace:*",
- "c12": "^1.11.1",
+ "c12": "^2.0.0-beta.1",
"consola": "^3.2.3",
"defu": "^6.1.4",
"destr": "^2.0.3",
- "globby": "^14.0.1",
+ "globby": "^14.0.2",
"hash-sum": "^2.0.0",
"ignore": "^5.3.1",
- "jiti": "^1.21.6",
+ "jiti": "^2.0.0-beta.3",
"klona": "^2.0.6",
- "knitwork": "^1.1.0",
"mlly": "^1.7.1",
"pathe": "^1.1.2",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.1.3",
"scule": "^1.3.0",
"semver": "^7.6.2",
"ufo": "^1.5.3",
"unctx": "^2.3.1",
- "unimport": "^3.7.2",
+ "unimport": "^3.8.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.3.0",
- "vitest": "1.6.0",
- "webpack": "5.92.0"
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
+ "unbuild": "3.0.0-rc.6",
+ "vite": "5.3.4",
+ "vitest": "2.0.3",
+ "webpack": "5.93.0"
},
"engines": {
"node": "^14.18.0 || >=16.10.0"
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..1257a6a68d 100644
--- a/packages/kit/src/components.ts
+++ b/packages/kit/src/components.ts
@@ -55,7 +55,7 @@ export async function addComponent (opts: AddComponentOptions) {
nuxt.hook('components:extend', (components: Component[]) => {
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/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..36f6843404 100644
--- a/packages/kit/src/internal/esm.ts
+++ b/packages/kit/src/internal/esm.ts
@@ -1,5 +1,10 @@
import { pathToFileURL } from 'node:url'
-import { interopDefault, resolvePath } from 'mlly'
+import { interopDefault, resolvePath, resolvePathSync } from 'mlly'
+import { createJiti } from 'jiti'
+
+export interface ResolveModuleOptions {
+ paths?: string | string[]
+}
/**
* Resolve a module from a given root path using an algorithm patterned on
@@ -15,14 +20,52 @@ 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) {
+ if (!warnings.has(id)) {
+ // TODO: add more information on stack trace
+ console.warn('[@nuxt/kit] `requireModule` is deprecated. Please use `importModule` instead.')
+ warnings.add(id)
+ }
+ 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..3116d421dd 100644
--- a/packages/kit/src/layout.ts
+++ b/packages/kit/src/layout.ts
@@ -1,7 +1,6 @@
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'
@@ -11,25 +10,10 @@ export function addLayout (this: any, template: NuxtTemplate | string, name?: st
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 b2c4d1b883..b0c2c04f67 100644
--- a/packages/kit/src/loader/config.ts
+++ b/packages/kit/src/loader/config.ts
@@ -11,8 +11,13 @@ export interface LoadNuxtConfigOptions extends Omit['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
diff --git a/packages/kit/src/loader/nuxt.ts b/packages/kit/src/loader/nuxt.ts
index 85c4498313..0c4727ffab 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 { 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 {} /* 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..2069539107 100644
--- a/packages/kit/src/module/define.ts
+++ b/packages/kit/src/module/define.ts
@@ -1,40 +1,85 @@
-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 (this: any, inlineOptions: Partial, nuxt: Nuxt): Promise {
if (!nuxt) {
nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
}
@@ -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 beecd62aef..536b18ca86 100644
--- a/packages/kit/src/module/install.ts
+++ b/packages/kit/src/module/install.ts
@@ -2,11 +2,9 @@ import { existsSync, promises as fsp, lstatSync } from 'node:fs'
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[/\\]/
@@ -27,12 +25,7 @@ export async function installModule<
}
// 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
}
@@ -78,15 +71,20 @@ 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, { fallbackToOriginal: true })
- // Prefer ESM resolution if possible
- nuxtModule = await importModule(src, nuxt.options.modulesDir).catch(() => null) ?? requireModule(src, { paths: nuxt.options.modulesDir })
+ const src = jiti.esmResolve(path)
+ 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')
@@ -100,7 +98,7 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
}
}
if (typeof nuxtModule !== 'function' && error) {
- logger.error(`Error while requiring module \`${nuxtModule}\`: ${error}`)
+ logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
throw error
}
}
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..b2fda348e3 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 {
@@ -51,11 +44,12 @@ 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 {
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 392dec86d8..31ae08d2ed 100644
--- a/packages/kit/src/resolve.ts
+++ b/packages/kit/src/resolve.ts
@@ -168,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))
@@ -209,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 1816ccf536..8820a73094 100644
--- a/packages/kit/src/template.ts
+++ b/packages/kit/src/template.ts
@@ -11,7 +11,6 @@ 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'
/**
@@ -113,18 +112,55 @@ 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'))
+ }
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)
@@ -154,6 +190,7 @@ export async function _generateTypes (nuxt: Nuxt) {
'ESNext',
'dom',
'dom.iterable',
+ 'webworker',
],
/* JSX support for Vue */
jsx: 'preserve',
@@ -168,23 +205,8 @@ export async function _generateTypes (nuxt: Nuxt) {
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 = {
@@ -195,7 +217,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 || []
@@ -204,10 +228,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)
@@ -227,7 +251,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]
@@ -237,12 +261,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[] = []
@@ -302,10 +327,14 @@ export async function writeTypes (nuxt: Nuxt) {
}
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/index.d.ts b/packages/nuxt/index.d.ts
index 2256348248..db90cd08a0 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?: {
diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json
index bc9c2b3c14..8484974eed 100644
--- a/packages/nuxt/package.json
+++ b/packages/nuxt/package.json
@@ -1,6 +1,6 @@
{
"name": "nuxt",
- "version": "3.12.1",
+ "version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@@ -60,43 +60,44 @@
},
"dependencies": {
"@nuxt/devalue": "^2.0.2",
- "@nuxt/devtools": "^1.3.3",
+ "@nuxt/devtools": "^1.3.9",
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.5.4",
"@nuxt/vite-builder": "workspace:*",
- "@unhead/dom": "^1.9.13",
- "@unhead/ssr": "^1.9.13",
- "@unhead/vue": "^1.9.13",
- "@vue/shared": "^3.4.27",
- "acorn": "8.11.3",
- "c12": "^1.11.1",
+ "@unhead/dom": "^1.9.16",
+ "@unhead/ssr": "^1.9.16",
+ "@unhead/vue": "^1.9.16",
+ "@vue/shared": "^3.4.31",
+ "acorn": "8.12.1",
+ "c12": "^2.0.0-beta.1",
"chokidar": "^3.6.0",
+ "compatx": "^0.1.8",
+ "consola": "^3.2.3",
"cookie-es": "^1.1.0",
"defu": "^6.1.4",
"destr": "^2.0.3",
"devalue": "^5.0.0",
- "esbuild": "^0.21.5",
+ "esbuild": "^0.23.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.6",
+ "jiti": "^2.0.0-beta.3",
"klona": "^2.0.6",
"knitwork": "^1.1.0",
"magic-string": "^0.30.10",
"mlly": "^1.7.1",
- "nitropack": "^2.9.6",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxi": "^3.12.0",
- "nypm": "^0.3.8",
+ "nypm": "^0.3.9",
"ofetch": "^1.3.4",
"ohash": "^1.1.3",
"pathe": "^1.1.2",
"perfect-debounce": "^1.0.0",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.1.3",
"radix3": "^1.1.2",
"scule": "^1.3.0",
"semver": "^7.6.2",
@@ -107,27 +108,26 @@
"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",
+ "unimport": "^3.8.0",
+ "unplugin": "^1.11.0",
+ "unplugin-vue-router": "^0.10.0",
"unstorage": "^1.10.2",
"untyped": "^1.4.2",
- "vue": "^3.4.27",
+ "vue": "^3.4.31",
"vue-bundle-renderer": "^2.1.0",
"vue-devtools-stub": "^0.1.0",
- "vue-router": "^4.3.3"
+ "vue-router": "^4.4.0"
},
"devDependencies": {
- "@nuxt/scripts": "0.5.1",
+ "@nuxt/scripts": "0.6.4",
"@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",
- "@vue/compiler-sfc": "3.4.27",
- "unbuild": "latest",
- "vite": "5.3.0",
- "vitest": "1.6.0"
+ "@vitejs/plugin-vue": "5.0.5",
+ "@vue/compiler-sfc": "3.4.31",
+ "unbuild": "3.0.0-rc.6",
+ "vite": "5.3.4",
+ "vitest": "2.0.3"
},
"peerDependencies": {
"@parcel/watcher": "^2.1.0",
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-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts
index d8d1c748d6..df480b0ea5 100644
--- a/packages/nuxt/src/app/components/nuxt-island.ts
+++ b/packages/nuxt/src/app/components/nuxt-island.ts
@@ -1,4 +1,4 @@
-import type { Component, PropType } 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'
@@ -29,7 +29,7 @@ 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 = []
+ const promises: Array> = []
for (const component in paths) {
if (!(components!.has(component))) {
@@ -267,7 +267,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))
diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts
index e4fa62d8a4..0b5d1c6511 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 { hasProtocol, joinURL, parseQuery, withQuery, withTrailingSlash, withoutTrailingSlash } from 'ufo'
+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'
@@ -167,8 +167,10 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
if (!to.value || isAbsoluteUrl.value) { return to.value as string }
if (isExternal.value) {
- const path = typeof to.value === 'object' ? resolveRouteObject(to.value) : to.value
- return resolveTrailingSlashBehavior(path, router.resolve /* will not be called */) as string
+ 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') {
@@ -495,7 +497,3 @@ function isSlowConnection () {
if (cn && (cn.saveData || /2g/.test(cn.effectiveType))) { return true }
return false
}
-
-function resolveRouteObject (to: Exclude) {
- return withQuery(to.path || '', to.query || {}) + (to.hash ? '#' + to.hash : '')
-}
diff --git a/packages/nuxt/src/app/components/route-provider.ts b/packages/nuxt/src/app/components/route-provider.ts
index de4ae64a47..3f81d028a9 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,7 +23,7 @@ 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],
diff --git a/packages/nuxt/src/app/components/utils.ts b/packages/nuxt/src/app/components/utils.ts
index a5e918a89d..bb16708588 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
}
diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts
index 8ca773a338..66c6d10a81 100644
--- a/packages/nuxt/src/app/composables/asyncData.ts
+++ b/packages/nuxt/src/app/composables/asyncData.ts
@@ -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 { DedupeOption, DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
+import { asyncDataDefaults } from '#build/nuxt.config.mjs'
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
@@ -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.
@@ -103,11 +100,8 @@ export interface AsyncDataExecuteOptions {
* 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?: DedupeOption
+ dedupe?: 'cancel' | 'defer'
}
export interface _AsyncData {
@@ -119,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}
@@ -140,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}
@@ -160,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}
@@ -173,12 +164,12 @@ 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}
@@ -196,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) }
@@ -235,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
@@ -248,21 +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
+ const hasCachedData = () => options.getCachedData!(key, nuxtApp) !== undefined
// Create or use a shared asyncData entity
if (!nuxtApp._asyncData[key] || !options.immediate) {
- nuxtApp.payload._errors[key] ??= asyncDataDefaults.errorValue
+ nuxtApp.payload._errors[key] ??= undefined
const _ref = options.deep ? ref : shallowRef
-
+ const cachedData = options.getCachedData!(key, nuxtApp)
nuxtApp._asyncData[key] = {
- data: _ref(options.getCachedData!(key, nuxtApp) ?? options.default!()),
+ data: _ref(typeof cachedData !== 'undefined' ? cachedData : options.default!()),
pending: ref(!hasCachedData()),
error: toRef(nuxtApp.payload._errors, key),
status: ref('idle'),
@@ -278,7 +265,7 @@ 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]!
}
@@ -311,10 +298,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) => {
@@ -411,11 +403,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,
@@ -425,18 +417,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,
@@ -447,15 +439,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]
@@ -470,12 +462,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 {
@@ -527,18 +519,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/cookie.ts b/packages/nuxt/src/app/composables/cookie.ts
index 7f5b6cd267..bdc9d93782 100644
--- a/packages/nuxt/src/app/composables/cookie.ts
+++ b/packages/nuxt/src/app/composables/cookie.ts
@@ -198,6 +198,7 @@ function cookieRef (value: T | undefined, delay: number, shouldWatch: boolean
if (shouldWatch) { unsubscribe = watch(internalRef, trigger) }
function createExpirationTimeout () {
+ elapsed = 0
clearTimeout(timeout)
const timeRemaining = delay - elapsed
const timeoutLength = timeRemaining < MAX_TIMEOUT_DELAY ? timeRemaining : MAX_TIMEOUT_DELAY
diff --git a/packages/nuxt/src/app/composables/error.ts b/packages/nuxt/src/app/composables/error.ts
index 69b56e1c2b..8e9752479f 100644
--- a/packages/nuxt/src/app/composables/error.ts
+++ b/packages/nuxt/src/app/composables/error.ts
@@ -4,9 +4,6 @@ import { toRef } from 'vue'
import { useNuxtApp } from '../nuxt'
import { useRouter } from './router'
-// @ts-expect-error virtual file
-import { nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
-
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
/** @since 3.0.0 */
@@ -50,7 +47,7 @@ export const clearError = async (options: { redirect?: string } = {}) => {
await useRouter().replace(options.redirect)
}
- error.value = nuxtDefaultErrorValue
+ error.value = undefined
}
/** @since 3.0.0 */
diff --git a/packages/nuxt/src/app/composables/fetch.ts b/packages/nuxt/src/app/composables/fetch.ts
index a083c47789..5908d42bfb 100644
--- a/packages/nuxt/src/app/composables/fetch.ts
+++ b/packages/nuxt/src/app/composables/fetch.ts
@@ -1,5 +1,5 @@
import type { FetchError, FetchOptions } from 'ofetch'
-import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod as _AvailableRouterMethod } from 'nitropack'
+import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod as _AvailableRouterMethod } from 'nitro/types'
import type { MaybeRef, Ref } from 'vue'
import { computed, reactive, toValue } from 'vue'
import { hash } from 'ohash'
@@ -8,9 +8,6 @@ import { useRequestFetch } from './ssr'
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
import { useAsyncData } from './asyncData'
-// TODO: temporary module for backwards compatibility
-import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
-
// @ts-expect-error virtual file
import { fetchDefaults } from '#build/nuxt.config.mjs'
@@ -33,7 +30,7 @@ export interface UseFetchOptions<
ResT,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
R extends NitroFetchRequest = string & {},
M extends AvailableRouterMethod = AvailableRouterMethod,
> extends Omit, 'watch'>, ComputedFetchOptions {
@@ -57,11 +54,11 @@ export function useFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
request: Ref | ReqT | (() => ReqT),
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
-): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, ErrorT | undefined>
/**
* Fetch data from an API endpoint with an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
@@ -80,7 +77,7 @@ export function useFetch<
> (
request: Ref | ReqT | (() => ReqT),
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
-): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, ErrorT | undefined>
export function useFetch<
ResT = void,
ErrorT = FetchError,
@@ -89,7 +86,7 @@ export function useFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
request: Ref | ReqT | (() => ReqT),
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
@@ -195,11 +192,11 @@ export function useLazyFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
request: Ref | ReqT | (() => ReqT),
opts?: Omit, 'lazy'>
-): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, ErrorT | undefined>
export function useLazyFetch<
ResT = void,
ErrorT = FetchError,
@@ -212,7 +209,7 @@ export function useLazyFetch<
> (
request: Ref | ReqT | (() => ReqT),
opts?: Omit, 'lazy'>
-): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
+): AsyncData | DefaultT, ErrorT | undefined>
export function useLazyFetch<
ResT = void,
ErrorT = FetchError,
@@ -221,7 +218,7 @@ export function useLazyFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = DefaultAsyncDataValue,
+ DefaultT = undefined,
> (
request: Ref | ReqT | (() => ReqT),
arg1?: string | Omit, 'lazy'>,
diff --git a/packages/nuxt/src/app/composables/preload.ts b/packages/nuxt/src/app/composables/preload.ts
index d7b747b279..c89e82cca1 100644
--- a/packages/nuxt/src/app/composables/preload.ts
+++ b/packages/nuxt/src/app/composables/preload.ts
@@ -1,5 +1,5 @@
import type { Component } from 'vue'
-import type { RouteLocationRaw, Router } from '#vue-router'
+import type { RouteLocationRaw, Router } from 'vue-router'
import { useNuxtApp } from '../nuxt'
import { toArray } from '../utils'
import { useRouter } from './router'
@@ -23,6 +23,8 @@ export const preloadComponents = async (components: string | string[]) => {
* @since 3.0.0
*/
export const prefetchComponents = (components: string | string[]) => {
+ if (import.meta.server) { return }
+
// TODO
return preloadComponents(components)
}
diff --git a/packages/nuxt/src/app/composables/route-announcer.ts b/packages/nuxt/src/app/composables/route-announcer.ts
index ae250bb266..9a6e27741f 100644
--- a/packages/nuxt/src/app/composables/route-announcer.ts
+++ b/packages/nuxt/src/app/composables/route-announcer.ts
@@ -1,7 +1,7 @@
import type { Ref } from 'vue'
import { getCurrentScope, onScopeDispose, ref } from 'vue'
import { injectHead } from '@unhead/vue'
-import { useNuxtApp } from '#app'
+import { useNuxtApp } from '../nuxt'
export type Politeness = 'assertive' | 'polite' | 'off'
diff --git a/packages/nuxt/src/app/composables/router.ts b/packages/nuxt/src/app/composables/router.ts
index d6395e5832..093102a223 100644
--- a/packages/nuxt/src/app/composables/router.ts
+++ b/packages/nuxt/src/app/composables/router.ts
@@ -1,6 +1,6 @@
import { getCurrentInstance, hasInjectionContext, inject, onScopeDispose } from 'vue'
import type { Ref } from 'vue'
-import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationPathRaw, RouteLocationRaw, Router, useRoute as _useRoute, useRouter as _useRouter } from '#vue-router'
+import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, Router, useRoute as _useRoute, useRouter as _useRouter } from 'vue-router'
import { sanitizeStatusCode } from 'h3'
import { hasProtocol, isScriptProtocol, joinURL, withQuery } from 'ufo'
@@ -120,7 +120,7 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
to = '/'
}
- const toPath = typeof to === 'string' ? to : (withQuery((to as RouteLocationPathRaw).path || '/', to.query || {}) + (to.hash || ''))
+ const toPath = typeof to === 'string' ? to : 'path' in to ? resolveRouteObject(to) : useRouter().resolve(to).href
// Early open handler
if (import.meta.client && options?.open) {
@@ -135,7 +135,8 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
return Promise.resolve()
}
- const isExternal = options?.external || hasProtocol(toPath, { acceptRelative: true })
+ const isExternalHost = hasProtocol(toPath, { acceptRelative: true })
+ const isExternal = options?.external || isExternalHost
if (isExternal) {
if (!options?.external) {
throw new Error('Navigating to an external URL is not allowed by default. Use `navigateTo(url, { external: true })`.')
@@ -166,10 +167,12 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
// TODO: consider deprecating in favour of `app:rendered` and removing
await nuxtApp.callHook('app:redirected')
const encodedLoc = location.replace(/"/g, '%22')
+ const encodedHeader = encodeURL(location, isExternalHost)
+
nuxtApp.ssrContext!._renderResponse = {
statusCode: sanitizeStatusCode(options?.redirectCode || 302, 302),
body: ``,
- headers: { location: encodeURI(location) },
+ headers: { location: encodedHeader },
}
return response
}
@@ -252,3 +255,24 @@ export const setPageLayout = (layout: unknown extends PageMeta['layout'] ? strin
useRoute().meta.layout = layout as Exclude
}
}
+
+/**
+ * @internal
+ */
+export function resolveRouteObject (to: Exclude) {
+ return withQuery(to.path || '', to.query || {}) + (to.hash || '')
+}
+
+/**
+ * @internal
+ */
+export function encodeURL (location: string, isExternalHost = false) {
+ const url = new URL(location, 'http://localhost')
+ if (!isExternalHost) {
+ return url.pathname + url.search + url.hash
+ }
+ if (location.startsWith('//')) {
+ return url.toString().replace(url.protocol, '')
+ }
+ return url.toString()
+}
diff --git a/packages/nuxt/src/app/composables/script-stubs.ts b/packages/nuxt/src/app/composables/script-stubs.ts
index 6fcaf114b2..469b1619ee 100644
--- a/packages/nuxt/src/app/composables/script-stubs.ts
+++ b/packages/nuxt/src/app/composables/script-stubs.ts
@@ -60,9 +60,15 @@ export function useScriptGoogleTagManager (...args: unknown[]) {
export function useScriptSegment (...args: unknown[]) {
renderStubMessage('useScriptSegment')
}
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export function useScriptFacebookPixel (...args: unknown[]) {
- renderStubMessage('useScriptFacebookPixel')
+export function useScriptClarity (...args: unknown[]) {
+ renderStubMessage('useScriptClarity')
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function useScriptMetaPixel (...args: unknown[]) {
+ renderStubMessage('useScriptMetaPixel')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function useScriptXPixel (...args: unknown[]) {
@@ -100,3 +106,13 @@ export function useScriptGoogleMaps (...args: unknown[]) {
export function useScriptNpm (...args: unknown[]) {
renderStubMessage('useScriptNpm')
}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function useScriptGoogleAdsense (...args: unknown[]) {
+ renderStubMessage('useScriptGoogleAdsense')
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function useScriptYouTubePlayer (...args: unknown[]) {
+ renderStubMessage('useScriptYouTubePlayer')
+}
diff --git a/packages/nuxt/src/app/defaults.ts b/packages/nuxt/src/app/defaults.ts
deleted file mode 100644
index f0dd26ea72..0000000000
--- a/packages/nuxt/src/app/defaults.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// TODO: temporary module for backwards compatibility
-
-export type DefaultAsyncDataErrorValue = null
-export type DefaultAsyncDataValue = null
-export type DefaultErrorValue = null
-export type DedupeOption = boolean | 'cancel' | 'defer'
-
-export {}
diff --git a/packages/nuxt/src/app/index.ts b/packages/nuxt/src/app/index.ts
index 9f8fc9103e..363c72d1bf 100644
--- a/packages/nuxt/src/app/index.ts
+++ b/packages/nuxt/src/app/index.ts
@@ -1,13 +1,14 @@
-///
+export { applyPlugin, applyPlugins, callWithNuxt, createNuxtApp, defineAppConfig, defineNuxtPlugin, definePayloadPlugin, isNuxtPlugin, registerPluginHooks, tryUseNuxtApp, useNuxtApp, useRuntimeConfig } from './nuxt'
+export type { CreateOptions, NuxtApp, NuxtPayload, NuxtPluginIndicator, NuxtSSRContext, ObjectPlugin, Plugin, PluginEnvContext, PluginMeta, ResolvedPluginMeta, RuntimeNuxtHooks } from './nuxt'
-export * from './nuxt'
+export { defineNuxtComponent, useAsyncData, useLazyAsyncData, useNuxtData, refreshNuxtData, clearNuxtData, useHydration, callOnce, useState, clearNuxtState, clearError, createError, isNuxtError, showError, useError, useFetch, useLazyFetch, useCookie, refreshCookie, onPrehydrate, prerenderRoutes, useRequestHeaders, useRequestEvent, useRequestFetch, setResponseStatus, onNuxtReady, abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter, preloadComponents, prefetchComponents, preloadRouteComponents, isPrerendered, loadPayload, preloadPayload, definePayloadReducer, definePayloadReviver, getAppManifest, getRouteRules, reloadNuxtApp, useRequestURL, usePreviewMode, useId, useRouteAnnouncer, useHead, useSeoMeta, useServerSeoMeta } from './composables/index'
+export type { AddRouteMiddlewareOptions, AsyncData, AsyncDataOptions, AsyncDataRequestStatus, CookieOptions, CookieRef, FetchResult, NuxtAppManifest, NuxtAppManifestMeta, NuxtError, ReloadNuxtAppOptions, RouteMiddleware, UseFetchOptions } from './composables/index'
-export * from './composables/index'
-
-export * from './components/index'
-export * from './config'
-export * from './compat/idle-callback'
-export * from './types'
+export { defineNuxtLink } from './components/index'
+export type { NuxtLinkOptions, NuxtLinkProps } from './components/index'
+export { _getAppConfig, updateAppConfig, useAppConfig } from './config'
+export { cancelIdleCallback, requestIdleCallback } from './compat/idle-callback'
+export type { NuxtAppLiterals, NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext, PageMeta } from './types'
export const isVue2 = false
export const isVue3 = true
diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts
index 75ce824a47..fd8e33b3e6 100644
--- a/packages/nuxt/src/app/nuxt.ts
+++ b/packages/nuxt/src/app/nuxt.ts
@@ -1,13 +1,13 @@
import { effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, reactive, shallowReactive } from 'vue'
import type { App, EffectScope, Ref, VNode, onErrorCaptured } from 'vue'
-import type { RouteLocationNormalizedLoaded } from '#vue-router'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
import type { HookCallback, Hookable } from 'hookable'
import { createHooks } from 'hookable'
import { getContext } from 'unctx'
import type { SSRContext, createRenderer } from 'vue-bundle-renderer/runtime'
import type { EventHandlerRequest, H3Event } from 'h3'
import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema'
-import type { RenderResponse } from 'nitropack'
+import type { RenderResponse } from 'nitro/types'
import type { LogObject } from 'consola'
import type { MergeHead, VueHeadClient } from '@unhead/vue'
@@ -23,8 +23,6 @@ import type { ViewTransition } from './plugins/view-transitions.client'
// @ts-expect-error virtual file
import { appId } from '#build/nuxt.config.mjs'
-// TODO: temporary module for backwards compatibility
-import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from '#app/defaults'
import type { NuxtAppLiterals } from '#app'
function getNuxtAppCtx (appName = appId || 'nuxt-app') {
@@ -94,8 +92,8 @@ export interface NuxtPayload {
state: Record
once: Set
config?: Pick
- error?: NuxtError | DefaultErrorValue
- _errors: Record
+ error?: NuxtError | undefined
+ _errors: Record
[key: string]: unknown
}
@@ -123,8 +121,11 @@ interface _NuxtApp {
/** @internal */
_asyncData: Record
+ /**
+ * @deprecated This may be removed in a future major version.
+ */
pending: Ref
- error: Ref
+ error: Ref
status: Ref
/** @internal */
_default: () => unknown
diff --git a/packages/nuxt/src/app/plugins/navigation-repaint.client.ts b/packages/nuxt/src/app/plugins/navigation-repaint.client.ts
new file mode 100644
index 0000000000..fa368bf593
--- /dev/null
+++ b/packages/nuxt/src/app/plugins/navigation-repaint.client.ts
@@ -0,0 +1,23 @@
+import { defineNuxtPlugin } from '../nuxt'
+import { onNuxtReady } from '../composables/ready'
+import { useRouter } from '../composables/router'
+
+export default defineNuxtPlugin(() => {
+ const router = useRouter()
+ onNuxtReady(() => {
+ router.beforeResolve(async () => {
+ /**
+ * This gives an opportunity for the browser to repaint, acknowledging user interaction.
+ * It can reduce INP when navigating on prerendered routes.
+ *
+ * @see https://github.com/nuxt/nuxt/issues/26271#issuecomment-2178582037
+ * @see https://vercel.com/blog/demystifying-inp-new-tools-and-actionable-insights
+ */
+ await new Promise((resolve) => {
+ // Ensure we always resolve, even if the animation frame never fires
+ setTimeout(resolve, 100)
+ requestAnimationFrame(() => { setTimeout(resolve, 0) })
+ })
+ })
+ })
+})
diff --git a/packages/nuxt/src/app/plugins/preload.server.ts b/packages/nuxt/src/app/plugins/preload.server.ts
index 18c8931f08..44f17795dc 100644
--- a/packages/nuxt/src/app/plugins/preload.server.ts
+++ b/packages/nuxt/src/app/plugins/preload.server.ts
@@ -5,9 +5,9 @@ export default defineNuxtPlugin({
setup (nuxtApp) {
nuxtApp.vueApp.mixin({
beforeCreate () {
- const { _registeredComponents } = this.$nuxt.ssrContext
+ const { modules } = this.$nuxt.ssrContext
const { __moduleIdentifier } = this.$options
- _registeredComponents.add(__moduleIdentifier)
+ modules.add(__moduleIdentifier)
},
})
},
diff --git a/packages/nuxt/src/app/plugins/revive-payload.client.ts b/packages/nuxt/src/app/plugins/revive-payload.client.ts
index 7deec66618..24452437b6 100644
--- a/packages/nuxt/src/app/plugins/revive-payload.client.ts
+++ b/packages/nuxt/src/app/plugins/revive-payload.client.ts
@@ -49,7 +49,6 @@ export default defineNuxtPlugin({
definePayloadReviver(reviver, revivers[reviver as keyof typeof revivers])
}
Object.assign(nuxtApp.payload, await nuxtApp.runWithContext(getNuxtClientPayload))
- // For backwards compatibility - TODO: remove later
- window.__NUXT__ = nuxtApp.payload
+ delete window.__NUXT__
},
})
diff --git a/packages/nuxt/src/app/plugins/router.ts b/packages/nuxt/src/app/plugins/router.ts
index 8fc32754b2..399f6e8e87 100644
--- a/packages/nuxt/src/app/plugins/router.ts
+++ b/packages/nuxt/src/app/plugins/router.ts
@@ -226,6 +226,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
})
}
+ // @ts-expect-error vue-router types diverge from our Route type above
nuxtApp._route = route
// Handle middleware
diff --git a/packages/nuxt/src/components/islandsTransform.ts b/packages/nuxt/src/components/islandsTransform.ts
index ac5a954932..e10f1cd957 100644
--- a/packages/nuxt/src/components/islandsTransform.ts
+++ b/packages/nuxt/src/components/islandsTransform.ts
@@ -28,7 +28,7 @@ const SCRIPT_RE = /
+
+
+
+
+
+
+`, 'hello.server.vue')
+
+ expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
+ "
+
+
+
+
+
+
+ "
+ `)
+ })
+
it('expect slot fallback transform to match inline snapshot', async () => {
const result = await viteTransform(`
@@ -103,6 +140,7 @@ describe('islandTransform - server and island components', () => {
@@ -351,6 +395,7 @@ describe('islandTransform - server and island components', () => {
expect(result).toMatchInlineSnapshot(`
"
@@ -396,6 +441,7 @@ describe('islandTransform - server and island components', () => {
+ `, filePath)
+
+ expect(meta).toMatchInlineSnapshot(`
+ {
+ "meta": {
+ "__nuxt_dynamic_meta_key": Set {
+ "meta",
+ },
+ },
+ }
+ `)
+ })
})
describe('normalizeRoutes', () => {
diff --git a/packages/nuxt/test/scan-components.test.ts b/packages/nuxt/test/scan-components.test.ts
index a4e8f172da..17d26f03dd 100644
--- a/packages/nuxt/test/scan-components.test.ts
+++ b/packages/nuxt/test/scan-components.test.ts
@@ -1,10 +1,11 @@
+import { fileURLToPath } from 'node:url'
import { resolve } from 'pathe'
import { expect, it, vi } from 'vitest'
import type { ComponentsDir } from 'nuxt/schema'
import { scanComponents } from '../src/components/scan'
-const fixtureDir = resolve(__dirname, 'fixture')
+const fixtureDir = fileURLToPath(new URL('fixture', import.meta.url))
const rFixture = (...p: string[]) => resolve(fixtureDir, ...p)
vi.mock('@nuxt/kit', () => ({
diff --git a/packages/nuxt/test/treeshake-client.test.ts b/packages/nuxt/test/treeshake-client.test.ts
index e0b076d950..27a54baf87 100644
--- a/packages/nuxt/test/treeshake-client.test.ts
+++ b/packages/nuxt/test/treeshake-client.test.ts
@@ -67,10 +67,19 @@ const treeshake = async (source: string): Promise => {
}
async function SFCCompile (name: string, source: string, options: Options, ssr = false): Promise {
- const result = await (vuePlugin({
+ const plugin = vuePlugin({
compiler: VueCompilerSFC,
...options,
- }).transform! as Function).call({
+ })
+ // @ts-expect-error Types are not correct as they are too generic
+ plugin.configResolved!({
+ isProduction: options.isProduction,
+ command: 'build',
+ root: process.cwd(),
+ build: { sourcemap: false },
+ define: {},
+ })
+ const result = await (plugin.transform! as Function).call({
parse: (code: string, opts: any = {}) => Parser.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
@@ -84,14 +93,16 @@ async function SFCCompile (name: string, source: string, options: Options, ssr =
return typeof result === 'string' ? result : result?.code
}
-const stateToTest: { name: string, options: Partial }[] = [
+const stateToTest: { index: number, name: string, options: Partial }[] = [
{
+ index: 0,
name: 'prod',
options: {
isProduction: true,
},
},
{
+ index: 1,
name: 'dev',
options: {
isProduction: false,
@@ -107,93 +118,91 @@ const stateToTest: { name: string, options: Partial {
vi.spyOn(process, 'cwd').mockImplementation(() => '')
- for (const [index, state] of stateToTest.entries()) {
- it(`should treeshake ClientOnly correctly in ${state.name}`, async () => {
- // add index to avoid using vite vue plugin cache
- const clientResult = await SFCCompile(`SomeComponent${index}.vue`, WithClientOnly, state.options)
+ it.each(stateToTest)(`should treeshake ClientOnly correctly in $name`, async (state) => {
+ // add index to avoid using vite vue plugin cache
+ const clientResult = await SFCCompile(`SomeComponent${state.index}.vue`, WithClientOnly, state.options)
- const ssrResult = await SFCCompile(`SomeComponent${index}.vue`, WithClientOnly, state.options, true)
+ const ssrResult = await SFCCompile(`SomeComponent${state.index}.vue`, WithClientOnly, state.options, true)
- const treeshaken = await treeshake(ssrResult)
- const [_, scopeId] = clientResult.match(/_pushScopeId\("(.*)"\)/)!
+ const treeshaken = await treeshake(ssrResult)
+ const [_, scopeId] = clientResult.match(/_pushScopeId\("(.*)"\)/)!
- // ensure the id is correctly passed between server and client
- expect(clientResult).toContain(`pushScopeId("${scopeId}")`)
- expect(treeshaken).toContain(`
`)
+ // ensure the id is correctly passed between server and client
+ expect(clientResult).toContain(`pushScopeId("${scopeId}")`)
+ expect(treeshaken).toContain(`
`)
- expect(clientResult).toContain('should-be-treeshaken')
- expect(treeshaken).not.toContain('should-be-treeshaken')
+ expect(clientResult).toContain('should-be-treeshaken')
+ expect(treeshaken).not.toContain('should-be-treeshaken')
- expect(treeshaken).not.toContain('import HelloWorld from \'../HelloWorld.vue\'')
- expect(clientResult).toContain('import HelloWorld from \'../HelloWorld.vue\'')
+ expect(treeshaken).not.toContain('import HelloWorld from \'../HelloWorld.vue\'')
+ expect(clientResult).toContain('import HelloWorld from \'../HelloWorld.vue\'')
- expect(treeshaken).not.toContain('import { Treeshaken } from \'somepath\'')
- expect(clientResult).toContain('import { Treeshaken } from \'somepath\'')
+ expect(treeshaken).not.toContain('import { Treeshaken } from \'somepath\'')
+ expect(clientResult).toContain('import { Treeshaken } from \'somepath\'')
- // remove resolved import
- expect(treeshaken).not.toContain('const _component_ResolvedImport =')
- expect(clientResult).toContain('const _component_ResolvedImport =')
+ // remove resolved import
+ expect(treeshaken).not.toContain('const _component_ResolvedImport =')
+ expect(clientResult).toContain('const _component_ResolvedImport =')
- // treeshake multi line variable declaration
- expect(clientResult).toContain('const SomeIsland = defineAsyncComponent(async () => {')
- expect(treeshaken).not.toContain('const SomeIsland = defineAsyncComponent(async () => {')
- expect(treeshaken).not.toContain('return (await import(\'./../some.island.vue\'))')
- expect(treeshaken).toContain('const NotToBeTreeShaken = defineAsyncComponent(async () => {')
+ // treeshake multi line variable declaration
+ expect(clientResult).toContain('const SomeIsland = defineAsyncComponent(async () => {')
+ expect(treeshaken).not.toContain('const SomeIsland = defineAsyncComponent(async () => {')
+ expect(treeshaken).not.toContain('return (await import(\'./../some.island.vue\'))')
+ expect(treeshaken).toContain('const NotToBeTreeShaken = defineAsyncComponent(async () => {')
- // treeshake object and array declaration
- expect(treeshaken).not.toContain('const { ObjectPattern } = await import(\'nuxt.com\')')
- expect(treeshaken).not.toContain('const { ObjectPattern: ObjectPatternDeclaration } = await import(\'nuxt.com\')')
- expect(treeshaken).toContain('const { ButShouldNotBeTreeShaken } = defineAsyncComponent(async () => {')
- expect(treeshaken).toContain('const [ { Dont, }, That] = defineAsyncComponent(async () => {')
+ // treeshake object and array declaration
+ expect(treeshaken).not.toContain('const { ObjectPattern } = await import(\'nuxt.com\')')
+ expect(treeshaken).not.toContain('const { ObjectPattern: ObjectPatternDeclaration } = await import(\'nuxt.com\')')
+ expect(treeshaken).toContain('const { ButShouldNotBeTreeShaken } = defineAsyncComponent(async () => {')
+ expect(treeshaken).toContain('const [ { Dont, }, That] = defineAsyncComponent(async () => {')
- // treeshake object that has an assignment pattern
- expect(treeshaken).toContain('const { woooooo, } = defineAsyncComponent(async () => {')
- expect(treeshaken).not.toContain('const { Deep, assignment: { Pattern = ofComponent } } = defineAsyncComponent(async () => {')
+ // treeshake object that has an assignment pattern
+ expect(treeshaken).toContain('const { woooooo, } = defineAsyncComponent(async () => {')
+ expect(treeshaken).not.toContain('const { Deep, assignment: { Pattern = ofComponent } } = defineAsyncComponent(async () => {')
- // expect no empty ObjectPattern on treeshaking
- expect(treeshaken).not.toContain('const { } = defineAsyncComponent')
- expect(treeshaken).not.toContain('import { } from')
+ // expect no empty ObjectPattern on treeshaking
+ expect(treeshaken).not.toContain('const { } = defineAsyncComponent')
+ expect(treeshaken).not.toContain('import { } from')
- // expect components used in setup to not be removed
- expect(treeshaken).toContain('import DontRemoveThisSinceItIsUsedInSetup from \'./ComponentWithProps.vue\'')
+ // expect components used in setup to not be removed
+ expect(treeshaken).toContain('import DontRemoveThisSinceItIsUsedInSetup from \'./ComponentWithProps.vue\'')
- // expect import of ClientImport to be treeshaken but not Glob since it is also used outside
- expect(treeshaken).not.toContain('ClientImport')
- expect(treeshaken).toContain('import { Glob } from \'#components\'')
+ // expect import of ClientImport to be treeshaken but not Glob since it is also used outside
+ expect(treeshaken).not.toContain('ClientImport')
+ expect(treeshaken).toContain('import { Glob } from \'#components\'')
- // treeshake .client slot
- expect(treeshaken).not.toContain('ByeBye')
- // don't treeshake variables that has the same name as .client components
- expect(treeshaken).toContain('NotDotClientComponent')
- expect(treeshaken).not.toContain('(DotClientComponent')
+ // treeshake .client slot
+ expect(treeshaken).not.toContain('ByeBye')
+ // don't treeshake variables that has the same name as .client components
+ expect(treeshaken).toContain('NotDotClientComponent')
+ expect(treeshaken).not.toContain('(DotClientComponent')
- expect(treeshaken).not.toContain('AutoImportedComponent')
- expect(treeshaken).toContain('AutoImportedNotTreeShakenComponent')
+ expect(treeshaken).not.toContain('AutoImportedComponent')
+ expect(treeshaken).toContain('AutoImportedNotTreeShakenComponent')
- expect(treeshaken).not.toContain('Both')
- expect(treeshaken).not.toContain('AreTreeshaken')
+ expect(treeshaken).not.toContain('Both')
+ expect(treeshaken).not.toContain('AreTreeshaken')
- if (state.options.isProduction === false) {
- // treeshake at inlined template
- expect(treeshaken).not.toContain('ssrRenderComponent($setup["HelloWorld"]')
- expect(treeshaken).toContain('ssrRenderComponent($setup["Glob"]')
- } else {
- // treeshake unref
- expect(treeshaken).not.toContain('ssrRenderComponent(_unref(HelloWorld')
- expect(treeshaken).toContain('ssrRenderComponent(_unref(Glob')
- }
- expect(treeshaken.replace(/data-v-\w{8}/g, 'data-v-one-hash').replace(/scoped=\w{8}/g, 'scoped=one-hash')).toMatchSnapshot()
- })
- }
+ if (state.options.isProduction === false) {
+ // treeshake at inlined template
+ expect(treeshaken).not.toContain('ssrRenderComponent($setup["HelloWorld"]')
+ expect(treeshaken).toContain('ssrRenderComponent($setup["Glob"]')
+ } else {
+ // treeshake unref
+ expect(treeshaken).not.toContain('ssrRenderComponent(_unref(HelloWorld')
+ expect(treeshaken).toContain('ssrRenderComponent(_unref(Glob')
+ }
+ expect(treeshaken.replace(/data-v-\w{8}/g, 'data-v-one-hash').replace(/scoped=\w{8}/g, 'scoped=one-hash')).toMatchSnapshot()
+ })
it('should not treeshake reused component #26137', async () => {
const treeshaken = await treeshake(`import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from "vue"
import { ssrRenderComponent as _ssrRenderComponent, ssrRenderAttrs as _ssrRenderAttrs } from "vue/server-renderer"
-
+
export function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_AppIcon = _resolveComponent("AppIcon")
const _component_ClientOnly = _resolveComponent("ClientOnly")
-
+
_push(\`
\`)
_push(_ssrRenderComponent(_component_AppIcon, { name: "caret-left" }, null, _parent))
_push(_ssrRenderComponent(_component_ClientOnly, null, {
diff --git a/packages/nuxt/test/utils.ts b/packages/nuxt/test/utils.ts
index 9d295ac1ec..a8ea696f22 100644
--- a/packages/nuxt/test/utils.ts
+++ b/packages/nuxt/test/utils.ts
@@ -1,6 +1,6 @@
-import { resolve } from 'pathe'
+import { fileURLToPath } from 'node:url'
-export const fixtureDir = resolve(__dirname, 'fixture')
+export const fixtureDir = fileURLToPath(new URL('fixture', import.meta.url))
export function normalizeLineEndings (str: string, normalized = '\n') {
return str.replace(/\r?\n/g, normalized)
diff --git a/packages/nuxt/types.d.mts b/packages/nuxt/types.d.mts
index b1b7ec3047..f027f93b6a 100644
--- a/packages/nuxt/types.d.mts
+++ b/packages/nuxt/types.d.mts
@@ -1,4 +1,5 @@
-///
+///
+///
import type { DefineNuxtConfig } from 'nuxt/config'
import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema'
@@ -14,7 +15,28 @@ declare global {
}
// Note: Keep in sync with packages/nuxt/src/core/templates.ts
-declare module 'nitropack' {
+declare module 'nitro/types' {
+ interface NitroRuntimeConfigApp {
+ buildAssetsDir: string
+ cdnURL: string
+ }
+ interface NitroRuntimeConfig extends RuntimeConfig {}
+ interface NitroRouteConfig {
+ ssr?: boolean
+ experimentalNoScripts?: boolean
+ }
+ interface NitroRouteRules {
+ ssr?: boolean
+ experimentalNoScripts?: boolean
+ appMiddleware?: Record
+ }
+ interface NitroRuntimeHooks {
+ 'dev:ssr-logs': (ctx: { logs: LogObject[], path: string }) => void | Promise
+ 'render:html': (htmlContext: NuxtRenderHTMLContext, context: { event: H3Event }) => void | Promise
+ 'render:island': (islandResponse: NuxtIslandResponse, context: { event: H3Event, islandContext: NuxtIslandContext }) => void | Promise
+ }
+}
+declare module 'nitropack/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string
diff --git a/packages/nuxt/types.d.ts b/packages/nuxt/types.d.ts
index 1733bdec62..2ecb9dc72e 100644
--- a/packages/nuxt/types.d.ts
+++ b/packages/nuxt/types.d.ts
@@ -1,4 +1,6 @@
-///
+///
+///
+
import type { DefineNuxtConfig } from 'nuxt/config'
import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema'
import type { H3Event } from 'h3'
@@ -13,7 +15,28 @@ declare global {
}
// Note: Keep in sync with packages/nuxt/src/core/templates.ts
-declare module 'nitropack' {
+declare module 'nitro/types' {
+ interface NitroRuntimeConfigApp {
+ buildAssetsDir: string
+ cdnURL: string
+ }
+ interface NitroRuntimeConfig extends RuntimeConfig {}
+ interface NitroRouteConfig {
+ ssr?: boolean
+ experimentalNoScripts?: boolean
+ }
+ interface NitroRouteRules {
+ ssr?: boolean
+ experimentalNoScripts?: boolean
+ appMiddleware?: Record
+ }
+ interface NitroRuntimeHooks {
+ 'dev:ssr-logs': (ctx: { logs: LogObject[], path: string }) => void | Promise
+ 'render:html': (htmlContext: NuxtRenderHTMLContext, context: { event: H3Event }) => void | Promise
+ 'render:island': (islandResponse: NuxtIslandResponse, context: { event: H3Event, islandContext: NuxtIslandContext }) => void | Promise
+ }
+}
+declare module 'nitropack/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string
diff --git a/packages/schema/build.config.ts b/packages/schema/build.config.ts
index e7179a174f..22f17cbef8 100644
--- a/packages/schema/build.config.ts
+++ b/packages/schema/build.config.ts
@@ -23,6 +23,8 @@ export default defineBuildConfig({
externals: [
// Type imports
'#app/components/nuxt-link',
+ 'cssnano',
+ 'autoprefixer',
'ofetch',
'vue-router',
'@nuxt/telemetry',
@@ -31,6 +33,7 @@ export default defineBuildConfig({
'vue',
'unctx',
'hookable',
+ 'nitro',
'nitropack',
'webpack',
'webpack-bundle-analyzer',
diff --git a/packages/schema/package.json b/packages/schema/package.json
index d295d4e3df..fc2eb54c6b 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -1,6 +1,6 @@
{
"name": "@nuxt/schema",
- "version": "3.12.1",
+ "version": "3.12.2",
"repository": {
"type": "git",
"url": "git+https://github.com/nuxt/nuxt.git",
@@ -39,27 +39,27 @@
"@types/file-loader": "5.0.4",
"@types/pug": "2.0.10",
"@types/sass-loader": "8.0.8",
- "@unhead/schema": "1.9.13",
- "@vitejs/plugin-vue": "5.0.4",
+ "@unhead/schema": "1.9.16",
+ "@vitejs/plugin-vue": "5.0.5",
"@vitejs/plugin-vue-jsx": "4.0.0",
- "@vue/compiler-core": "3.4.27",
- "@vue/compiler-sfc": "3.4.27",
- "@vue/language-core": "2.0.21",
- "c12": "1.11.1",
- "esbuild-loader": "4.1.0",
- "h3": "1.11.1",
+ "@vue/compiler-core": "3.4.31",
+ "@vue/compiler-sfc": "3.4.31",
+ "@vue/language-core": "2.0.26",
+ "c12": "2.0.0-beta.1",
+ "esbuild-loader": "4.2.2",
+ "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"ignore": "5.3.1",
- "nitropack": "2.9.6",
+ "nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"ofetch": "1.3.4",
- "unbuild": "latest",
+ "unbuild": "3.0.0-rc.6",
"unctx": "2.3.1",
"unenv": "1.9.0",
- "vite": "5.3.0",
- "vue": "3.4.27",
+ "vite": "5.3.4",
+ "vue": "3.4.31",
"vue-bundle-renderer": "2.1.0",
"vue-loader": "17.4.2",
- "vue-router": "4.3.3",
- "webpack": "5.92.0",
+ "vue-router": "4.4.0",
+ "webpack": "5.93.0",
"webpack-dev-middleware": "7.2.1"
},
"dependencies": {
@@ -68,12 +68,12 @@
"defu": "^6.1.4",
"hookable": "^5.5.3",
"pathe": "^1.1.2",
- "pkg-types": "^1.1.1",
+ "pkg-types": "^1.1.3",
"scule": "^1.3.0",
"std-env": "^3.7.0",
"ufo": "^1.5.3",
"uncrypto": "^0.1.3",
- "unimport": "^3.7.2",
+ "unimport": "^3.8.0",
"untyped": "^1.4.2"
},
"engines": {
diff --git a/packages/schema/src/config/adhoc.ts b/packages/schema/src/config/adhoc.ts
index b6a849b853..4328909a32 100644
--- a/packages/schema/src/config/adhoc.ts
+++ b/packages/schema/src/config/adhoc.ts
@@ -23,7 +23,7 @@ export default defineUntypedSchema({
/**
* Configure how Nuxt auto-imports composables into your application.
- * @see [Nuxt 3 documentation](https://nuxt.com/docs/guide/directory-structure/composables)
+ * @see [Nuxt documentation](https://nuxt.com/docs/guide/directory-structure/composables)
* @type {typeof import('../src/types/imports').ImportsOptions}
*/
imports: {
diff --git a/packages/schema/src/config/common.ts b/packages/schema/src/config/common.ts
index 152a76a3ca..2173318721 100644
--- a/packages/schema/src/config/common.ts
+++ b/packages/schema/src/config/common.ts
@@ -1,7 +1,7 @@
import { existsSync } from 'node:fs'
import { readdir } from 'node:fs/promises'
import { defineUntypedSchema } from 'untyped'
-import { join, relative, resolve } from 'pathe'
+import { basename, join, relative, resolve } from 'pathe'
import { isDebug, isDevelopment, isTest } from 'std-env'
import { defu } from 'defu'
import { findWorkspaceDir } from 'pkg-types'
@@ -118,13 +118,15 @@ export default defineUntypedSchema({
}
const srcDir = resolve(rootDir, 'app')
+ if (!existsSync(srcDir)) {
+ return rootDir
+ }
+
const srcDirFiles = new Set()
- if (existsSync(srcDir)) {
- const files = await readdir(srcDir).catch(() => [])
- for (const file of files) {
- if (file !== 'spa-loading-template.html' && !file.startsWith('router.options')) {
- srcDirFiles.add(file)
- }
+ const files = await readdir(srcDir).catch(() => [])
+ for (const file of files) {
+ if (file !== 'spa-loading-template.html' && !file.startsWith('router.options')) {
+ srcDirFiles.add(file)
}
}
if (srcDirFiles.size === 0) {
@@ -301,7 +303,8 @@ export default defineUntypedSchema({
$resolve: async (val: string | undefined, get) => {
const isV4 = (await get('future') as Record).compatibilityVersion === 4
if (isV4) {
- return resolve(await get('srcDir') as string, val || '.')
+ const [srcDir, rootDir] = await Promise.all([get('srcDir') as Promise, get('rootDir') as Promise])
+ return resolve(await get('srcDir') as string, val || (srcDir === rootDir ? 'app' : '.'))
}
return val || 'app'
},
@@ -419,8 +422,8 @@ export default defineUntypedSchema({
'@': srcDir,
'~~': rootDir,
'@@': rootDir,
- [assetsDir]: join(srcDir, assetsDir),
- [publicDir]: join(srcDir, publicDir),
+ [basename(assetsDir)]: join(srcDir, assetsDir),
+ [basename(publicDir)]: resolve(srcDir, publicDir),
...val,
}
},
diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts
index b80f0beca1..c00a4de820 100644
--- a/packages/schema/src/config/experimental.ts
+++ b/packages/schema/src/config/experimental.ts
@@ -7,45 +7,12 @@ export default defineUntypedSchema({
*/
future: {
/**
- * Enable early access to Nuxt v4 features or flags.
+ * Enable early access to future features or flags.
*
- * Setting `compatibilityVersion` to `4` changes defaults throughout your
- * Nuxt configuration, but you can granularly re-enable Nuxt v3 behaviour
- * when testing (see example). Please file issues if so, so that we can
- * address in Nuxt or in the ecosystem.
- *
- * @example
- * ```ts
- * export default defineNuxtConfig({
- * future: {
- * compatibilityVersion: 4,
- * },
- * // To re-enable _all_ Nuxt v3 behaviour, set the following options:
- * srcDir: '.',
- * dir: {
- * app: 'app'
- * },
- * experimental: {
- * compileTemplate: true,
- * templateUtils: true,
- * relativeWatchPaths: true,
- * resetAsyncDataToUndefined: true,
- * defaults: {
- * useAsyncData: {
- * deep: true
- * }
- * }
- * },
- * unhead: {
- * renderSSRHeadOptions: {
- * omitLineBreaks: false
- * }
- * }
- * })
- * ```
- * @type {3 | 4}
+ * It is currently not configurable but may be in future.
+ * @type {4}
*/
- compatibilityVersion: 3,
+ compatibilityVersion: 4,
/**
* This enables early access to the experimental multi-app support.
* @see [Nuxt Issue #21635](https://github.com/nuxt/nuxt/issues/21635)
@@ -140,21 +107,11 @@ export default defineUntypedSchema({
externalVue: true,
/**
- * Tree shakes contents of client-only components from server bundle.
- * @see [Nuxt PR #5750](https://github.com/nuxt/framework/pull/5750)
- * @deprecated This option will no longer be configurable in Nuxt v4
+ * Enable accessing `appConfig` from server routes.
+ *
+ * @deprecated This option is not recommended.
*/
- treeshakeClientOnly: {
- async $resolve (val, get) {
- const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
- if (isV4 && val === false) {
- console.warn('Enabling `experimental.treeshakeClientOnly` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
- return true
- }
- return val ?? true
- },
- },
-
+ serverAppConfig: false,
/**
* Emit `app:chunkError` hook when there is an error loading vite/webpack
* chunks.
@@ -259,55 +216,6 @@ export default defineUntypedSchema({
},
},
- /**
- * Config schema support
- * @see [Nuxt Issue #15592](https://github.com/nuxt/nuxt/issues/15592)
- * @deprecated This option will no longer be configurable in Nuxt v4
- */
- configSchema: {
- async $resolve (val, get) {
- const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
- if (isV4 && val === false) {
- console.warn('Enabling `experimental.configSchema` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
- return true
- }
- return val ?? true
- },
- },
-
- /**
- * Whether or not to add a compatibility layer for modules, plugins or user code relying on the old
- * `@vueuse/head` API.
- *
- * This is disabled to reduce the client-side bundle by ~0.5kb.
- * @deprecated This feature will be removed in Nuxt v4.
- */
- polyfillVueUseHead: {
- async $resolve (val, get) {
- const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
- if (isV4 && val === true) {
- console.warn('Disabling `experimental.polyfillVueUseHead` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
- return false
- }
- return val ?? false
- },
- },
-
- /**
- * Allow disabling Nuxt SSR responses by setting the `x-nuxt-no-ssr` header.
- * @deprecated This feature will be removed in Nuxt v4.
- */
- respectNoSSRHeader: {
- async $resolve (val, get) {
- const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
- if (isV4 && val === true) {
- console.warn('Disabling `experimental.respectNoSSRHeader` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
- return false
- }
- return val ?? false
- },
- },
-
/** Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories. */
localLayerAliases: true,
@@ -330,8 +238,9 @@ export default defineUntypedSchema({
/**
* Set an alternative watcher that will be used as the watching service for Nuxt.
*
- * Nuxt uses 'chokidar-granular' by default, which will ignore top-level directories
- * (like `node_modules` and `.git`) that are excluded from watching.
+ * Nuxt uses 'chokidar-granular' if your source directory is the same as your root
+ * directory . This will ignore top-level directories (like `node_modules` and `.git`)
+ * that are excluded from watching.
*
* You can set this instead to `parcel` to use `@parcel/watcher`, which may improve
* performance in large projects or on Windows platforms.
@@ -341,7 +250,18 @@ export default defineUntypedSchema({
* @see [Parcel watcher](https://github.com/parcel-bundler/watcher)
* @type {'chokidar' | 'parcel' | 'chokidar-granular'}
*/
- watcher: 'chokidar-granular',
+ watcher: {
+ $resolve: async (val, get) => {
+ if (val) {
+ return val
+ }
+ const [srcDir, rootDir] = await Promise.all([get('srcDir'), get('rootDir')]) as [string, string]
+ if (srcDir === rootDir) {
+ return 'chokidar-granular'
+ }
+ return 'chokidar'
+ },
+ },
/**
* Enable native async context to be accessible for nested composables
@@ -430,23 +350,7 @@ export default defineUntypedSchema({
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
*/
useAsyncData: {
- /** @type {'undefined' | 'null'} */
- value: {
- async $resolve (val, get) {
- return val ?? ((await get('future') as Record).compatibilityVersion === 4 ? 'undefined' : 'null')
- },
- },
- /** @type {'undefined' | 'null'} */
- errorValue: {
- async $resolve (val, get) {
- return val ?? ((await get('future') as Record).compatibilityVersion === 4 ? 'undefined' : 'null')
- },
- },
- deep: {
- async $resolve (val, get) {
- return val ?? !((await get('future') as Record