Compare commits

...

31 Commits

Author SHA1 Message Date
David Nahodyl 4e4c1334b0
Merge 93f5fe07aa into 73accb5ac1 2024-09-21 16:21:32 +03:00
Daniel Roe 73accb5ac1
fix(kit,vite,webpack): resolve postcss paths from each modules dir (#29096) 2024-09-21 13:37:49 +01:00
Daniel Roe 097105fb2b
fix(kit): try resolving module path from each node_modules dir 2024-09-21 13:37:48 +01:00
Daniel Roe 33e2d0a5f3
fix(kit): handle passing 'bare' relative paths to modules 2024-09-21 13:37:48 +01:00
renovate[bot] 01e00963bc
chore(deps): update all non-major dependencies (main) (#29101) 2024-09-21 12:10:59 +01:00
Matej Černý d970da6da5
docs: add a section about `useRequestFetch` and `event.$fetch` (#29099) 2024-09-21 11:39:54 +01:00
renovate[bot] 61b5ef7850
chore(deps): update all non-major dependencies (main) (#29093) 2024-09-21 09:32:18 +01:00
Matej Černý 00ad2b8233
docs: move custom directories note to the correct place (#29100) 2024-09-21 09:32:00 +01:00
Daniel Roe 93f5fe07aa
Update 4.sessions-and-authentication.md
Co-authored-by: Sébastien Chopin <seb@nuxtlabs.com>
2024-07-19 15:47:41 +01:00
Sébastien Chopin 95dabb13ad
Update docs/2.guide/4.recipes/4.sessions-and-authentication.md 2024-07-19 16:08:02 +02:00
David Nahodyl 4c26a95794
Merge branch 'main' into main 2024-07-15 12:10:34 -04:00
David Nahodyl a831877cc7
grammar suggestions from GalacticHypernova 2024-06-16 13:30:26 -04:00
David Nahodyl 2037207e02
Victims of the great "data is/are" debate 2024-06-16 13:24:46 -04:00
David Nahodyl 5047501be4
fixed module name for consistency. 2024-06-16 13:15:39 -04:00
David Nahodyl f7ac32f509
updated based on feedback from GalacticHypernova 2024-06-16 13:04:59 -04:00
autofix-ci[bot] e9e6c1b142
[autofix.ci] apply automated fixes 2024-06-16 17:03:10 +00:00
David Nahodyl f38cf6b65f
Merge branch 'nuxt:main' into main 2024-06-16 12:56:32 -04:00
David Nahodyl 7501792b91
Merge branch 'nuxt:main' into main 2024-06-14 09:11:28 -04:00
David Nahodyl 6f82ebce3c
Merge branch 'main' into main 2024-06-10 19:26:38 -04:00
David Nahodyl fd435ca06b
removed unnecessary utility function, improvements to the back-half of the guide 2024-06-10 19:26:24 -04:00
David Nahodyl 4024329635
better clarification, fixed some examples 2024-06-10 19:16:31 -04:00
David Nahodyl 6037c7af55
more details 2024-06-10 17:26:43 -04:00
David Nahodyl b97b4a083a
more details 2024-06-10 17:24:49 -04:00
David Nahodyl b30c465a57
Merge branch 'main' into main 2024-06-05 14:49:34 -04:00
David Nahodyl cc354886f2
improved login process to use fetch() without full page reload and comments 2024-06-05 14:27:21 -04:00
David Nahodyl fb2c03a0b9
Changed to Nuxt module install instead of package install
Co-authored-by: Sébastien Chopin <seb@nuxtlabs.com>
2024-05-22 19:40:14 -04:00
David Nahodyl b896daf0aa
Update docs/2.guide/4.recipes/4.sessions-and-authentication.md
Changed to relative link

Co-authored-by: Sébastien Chopin <seb@nuxtlabs.com>
2024-05-22 19:39:10 -04:00
Smef 7664825a8a
Merge branch 'main' into main 2024-05-21 13:23:48 -04:00
autofix-ci[bot] 40d9df5038
[autofix.ci] apply automated fixes 2024-05-21 08:24:51 +00:00
David Nahodyl 51450631cd
clarified warning 2024-05-20 20:16:04 -04:00
David Nahodyl 056bc94743
Added session recipe 2024-05-20 20:10:00 -04:00
18 changed files with 1142 additions and 713 deletions

View File

@ -166,6 +166,10 @@ export default defineNuxtConfig({
})
```
::note
Any nested directories need to be added first as they are scanned in order.
::
## npm Packages
If you want to auto-import components from an npm package, you can use [`addComponent`](/docs/api/kit/components#addcomponent) in a [local module](/docs/guide/directory-structure/modules) to register them.
@ -198,10 +202,6 @@ export default defineNuxtModule({
::
::note
Any nested directories need to be added first as they are scanned in order.
::
## Component Extensions
By default, any file with an extension specified in the [extensions key of `nuxt.config.ts`](/docs/api/nuxt-config#extensions) is treated as a component.

View File

@ -347,6 +347,22 @@ export default defineEventHandler((event) => {
})
```
### Forwarding Context & Headers
By default, neither the headers from the incoming request nor the request context are forwarded when
making fetch requests in server routes. You can use `event.$fetch` to forward the request context and headers when making fetch requests in server routes.
```ts [server/api/forward.ts]
export default defineEventHandler((event) => {
return event.$fetch('/api/forwarded')
})
```
::note
Headers that are **not meant to be forwarded** will **not be included** in the request. These headers include, for example:
`transfer-encoding`, `connection`, `keep-alive`, `upgrade`, `expect`, `host`, `accept`
::
## Advanced Usage
### Nitro Config

View File

@ -0,0 +1,398 @@
---
title: 'Sessions and Authentication'
description: "Authentication is an extremely common requirement in web apps. This recipe will show you how to implement basic user registration and authentication in your Nuxt app."
---
## Introduction
In this recipe we'll be setting up user registration, login, sessions, and authentication in a full-stack Nuxt app using [Nuxt Auth Utils](https://github.com/Atinux/nuxt-auth-utils) which provides convenient utilities for managing client-side and server-side session data. We'll install and use this to get the core session management functionality we're going to need to manage user logins. For the database ORM we'll be using [Drizzle](https://orm.drizzle.team/) with the built-in [Nitro SQL database](https://nitro.unjs.io/guide/database), but you can use any ORM or database connection strategy you prefer.
You'll need a `users` table in your database with the following columns:
- `id` (int, primary key, auto increment)
- `email` (varchar)
- `password` (varchar)
## Steps
### 1. Install nuxt-auth-utils
Install the [nuxt-auth-utils](https://github.com/Atinux/nuxt-auth-utils) module using the `nuxi` CLI.
```bash
npx nuxi@latest module add auth-utils
```
### 1a. (Optional) Add a session encryption key
Session cookies are encrypted using a key from the `.env` file. This key will be added to your `.env` automatically when running in development mode for the first time. However, you'll need to add this to your production environment before deploying.
```dotenv [.env]
NUXT_SESSION_PASSWORD=password-with-at-least-32-characters
```
### 2. Create a registration page
The first page we'll need is a page for users to register and create new accounts. Create a new Vue page in your Nuxt app at `/pages/register.vue` for user registration. This page should have a form with fields for email and password. We'll intercept the form submission using `@submit.prevent` and use the [`$fetch`](/docs/getting-started/data-fetching#fetch) utility to post the data to `/api/register`. This form POST will be received by Nuxt in an API route which we will set up next.
If the request is successful, we'll navigate to the (soon to be created) `/users` page, which will be guarded and only visible to logged in users.
Here's an example registration form for reference:
```vue [pages/register.vue]
<script setup lang="ts">
const form = ref({
name: "",
email: "",
password: "",
});
const error = ref(null);
async function submitForm() {
// clear any previous errors
error.value = null;
// perform the login
try {
await $fetch("/api/register", {
method: "POST",
body: form.value,
});
} catch (e) {
// if there's an error, set the error message and return early
error.value = "Error Registering User";
return;
}
// Refresh the session status now that the user is logged in
// This is a fetch() function from auth-utils to refresh session data in the client, and is not the same as $fetch
const { fetch } = useUserSession();
await fetch();
// you may want to use something like Pinia to manage global state of the logged-in user
// update Pinia state here...
// take the user to the auth-only users index page now that they're logged in
await navigateTo("/users");
// Alternative - Don't use Nuxt Router here so that we can easily trigger a whole page load and get the whole UI refreshed now that the user is logged in.
// window.location.href = "/users";
}
</script>
<template>
<div>
<form class="space-y-2" @submit.prevent="submitForm">
<div>
<label for="name" class="block text-sm uppercase">Name</label>
<input id="name" name="name" v-model="form.name" class="rounded-md px-2 py-1" required />
</div>
<div>
<label for="email" class="block text-sm uppercase">Email</label>
<input id="email" name="email" v-model="form.email" class="rounded-md px-2 py-1" required />
</div>
<div>
<label for="password" class="block text-sm uppercase">password</label>
<input
id="password"
name="password"
v-model="form.password"
class="rounded-md px-2 py-1"
type="password"
required
/>
</div>
<div class="pt-2">
<button type="submit">Register</button>
</div>
<div v-if="error">{{ error }}</div>
</form>
</div>
</template>
```
### 3. Create an API route for registration
With the user interface created we'll need to add a route to receive the registration form data. This route should accept a POST request with the email and password in the request body. It should hash the password and insert the user into the database. This route will only accept POST requests, so we'll follow the instructions for [API route methods](https://nuxt.com/docs/guide/directory-structure/server#matching-http-method) and name the file with `*.post.ts` to restrict the endpoint to only accept POST.
After we've successfully registered the user and stored the record in the database we can log them in by calling the `replaceUserSession` utility function from auth-utils. This utility function is automatically imported by the auth-utils module. We're using `replaceUserSession` here to make sure that any existing session data is cleared and replaced with the user login we're performing now.
The example file below is a very simple example of registration. You would probably want to add some error handling and nice response messages.
```typescript [/server/api/register.post.ts]
import users from "~/database/schema/users";
import getDatabase from "~/database/database";
import bcrypt from "bcrypt";
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const db = await getDatabase();
// has the password before creating the user record
const passwordHash = bcrypt.hashSync(body.password, 12);
await db.insert(users).values({
name: body.name,
email: body.email,
password: passwordHash,
});
// get the user record we just created
const user = (await db.select().from(users).where(eq(users.email, body.email)).limit(1))[0];
// log the user in as the user that was just created
await replaceUserSession(event, {
user: {
id: user.id,
name: user.name,
},
loggedInAt: new Date(),
});
await auth.login(event, user);
});
```
### 4. Create a login page
When registered users return to your site they'll need to be able to log back in. Create a new page at `/pages/login.vue` in your Nuxt app for user login. This page should have a form with fields for email and password and should submit a POST request to `/api/login`.
Like the registration page, we'll intercept the form submission using `@submit.prevent` and use [`$fetch`](https://nuxt.com/docs/getting-started/data-fetching#fetch) to post the data to `/api/login`.
This is a very simple login form example, so you'd definitely want to add more validation and error checking in a real-world application.
```vue [/pages/login.vue]
<script setup lang="ts">
import { ref } from "vue";
const form = ref({
email: "",
password: "",
});
const error = ref(null);
async function submitForm() {
// clear any previous errors
error.value = null;
// perform the login
try {
await $fetch("/api/auth/login", {
method: "POST",
body: loginForm.value,
});
} catch (e) {
// if there's an error, set the error message and return early
error.value = "Error logging in";
return;
}
// Refresh the session status now that the user is logged in
// This is a fetch() function from auth-utils to refresh session data in the client, and is not the same as $fetch
const { fetch } = useUserSession();
await fetch();
// you may want to use something like Pinia to manage global state of the logged-in user
// update Pinia state here...
// take the user to the auth-only users index page now that they're logged in
await navigateTo("/users");
// Alternative - Don't use Nuxt Router here so that we can easily trigger a whole page load and get the whole UI refreshed now that the user is logged in. This will perform a full-page load.
// window.location.href = "/users";
}
</script>
<template>
<div>
<form class="space-y-2" @submit.prevent="submitForm">
<div>
<label for="email" class="block text-sm uppercase">Email</label>
<input id="email" name="email" v-model="form.email" class="rounded-md px-2 py-1" required />
</div>
<div>
<label for="password" class="block text-sm uppercase">password</label>
<input
id="password"
name="password"
v-model="form.password"
class="rounded-md px-2 py-1"
type="password"
required
/>
</div>
<div class="pt-2">
<button type="submit">Login</button>
</div>
<div v-if="error">{{ error }}</div>
</form>
</div>
</template>
```
### 5. Create an API route for login
With the login form created, we need to create an API route to handle the login request. This route should accept a POST request with the email and password in the request body and check the email and password against the database. If the user and password match, we'll set a session cookie to log the user in.
This server API route should be at `/server/api/auth/login.post.ts`. Just like the registration form endpoint, suffixing the filename in `.post.ts.` means that this handler will only respond to post requests.
```typescript [/server/api/auth/login.post.ts]
import users from "~/database/schema/users";
import getDatabase from "~/database/database";
import { eq } from "drizzle-orm";
import bcrypt from "bcrypt";
export default defineEventHandler(async (event) => {
const db = await getDatabase();
const foundUser = (
await db
.select({ id: users.id, name: users.name, email: users.email, password: users.password })
.from(users)
.where(eq(users.email, email))
.limit(1)
)?.[0];
// compare the password hash
if (!foundUser || !bcrypt.compareSync(password, foundUser.password)) {
// return an error if the user is not found or the password doesn't match
throw createError({
statusCode: 401,
statusMessage: "Invalid email or password",
});
}
// log in as the selected user
await replaceUserSession(event, {
user: {
id: user.id,
name: user.name,
},
loggedInAt: new Date(),
});
});
```
The user should now be logged in! With the session set, we can get the current user session in any API route or page by calling `getUserSession(event)` which is auto-imported as a util function from the `nuxt-auth-utils` package.
### 6. Create a logout API route
Users need to be able to log out, so we should create an API route to allow them to do this. This should require post request as well, just like login. We'll clear the session when this endpoint is called.
We'll use the `clearUserSession` from the `auth-utils` module to log the user out and clear the session data. This function is automatically imported from the module by Nuxt, so we don't need to manually import it.
```typescript [server/api/auth/logout.post.ts]
export default defineEventHandler(async (event) => {
// Clear the current user session
await clearUserSession(event);
});
```
### 7. Protect your server route
Protecting server routes is key to making sure your data is safe. Client-side middleware is helpful for the user, but without server-side protection your data can still be accessed. Because of this, it is critical that we protect any API routes with sensitive data. For these sensitive routes, we should return a 401 error if the user is not logged in.
The `auth-utils` module provides the `requireUserSession` utility function to help make sure that users are logged in and have an active session. We can use this to protect our different endpoints. Like many of the other utilities from the auth module, it is automatically imported in our server endpoints.
In the example below, we use the `requireUserSession` utility function to protect the `/server/api/users.get.ts` server route. This route will only be accessible to logged-in users.
```typescript [/server/api/users.get.ts]
import getDatabase from "~/database/database";
import users from "~/database/schema/users";
import requireUserLoggedIn from "~/server/utils/requireUserLoggedIn";
export default defineEventHandler(async (event) => {
// make sure the user is logged in
// This will throw a 401 error if the request doesn't come from a valid user session
await requireUserSession(event);
// If we make it here, the user is authenticated. It's safe to fetch and return data
const db = await getDatabase();
// Send back the list of users
const userList = await db.select({ name: users.name, id: users.id }).from(users).limit(10);
return userList;
});
```
### 8. Create a client-side middleware to protect routes
Our data is safe with the server-side route in place, but without doing anything else, unauthenticated users would probably get some odd data when trying to access the `/users` page. We should create a [client-side middleware](https://nuxt.com/docs/guide/directory-structure/middleware) to protect the route on the client side and redirect users to the login page.
`nuxt-auth-utils` provides a convenient `useUserSession` composable which we'll use to check if the user is logged in, and redirect them if they are not.
We'll create a middleware in the `/middleware` directory. Unlike on the server, client-side middleware is not automatically applied to all endpoints, and we'll need to specify where we want it applied.
```typescript [/middleware/RedirectIfNotAuthenticated.ts]
export default defineNuxtRouteMiddleware(() => {
// check if the user is logged in
const { loggedIn } = useUserSession();
// redirect the user to the login screen if they're not authenticated
if (!loggedIn.value) {
return navigateTo("/login");
}
return null;
});
```
### 9. Protect a route with the client-side middleware
Now that we have the client-side middleware to protect client-side routes, we can use it in any page to ensure that only logged-in users can access the route. Users will be redirected to the login page if they are not authenticated.
We'll use [`definePageMeta`](https://nuxt.com/docs/api/utils/define-page-meta) to apply the middleware to the route that we want to protect.
::important
:warning: Remember that your data isn't really secure without server-side protection! Always secure your data server-side first before worrying about the client-side.
::
```vue [pages/users/index.vue]
<script setup lang="ts">
definePageMeta({
middleware: ["redirect-if-not-authenticated"],
});
const { data: users } = await useFetch("/api/users");
</script>
<template>
<div>
<div class="pb-4 text-sm font-semibold uppercase">Users List</div>
<div>
<ul class="space-y-4">
<li v-for="user in users" :key="user.id">
<NuxtLink :to="`/users/${user.id}`">
<div class="px-4 py-2 hover:bg-gray-600">{{ user.name }}</div>
</NuxtLink>
</li>
</ul>
</div>
</div>
</template>
```
### Complete
We've successfully set up user registration and authentication in our Nuxt app. Users can now register, log in, and log out. We've also protected sensitive routes on the server and client side to ensure that only authenticated users can access them.

View File

@ -0,0 +1,52 @@
---
title: 'useRequestFetch'
description: 'Forward the request context and headers for server-side fetch requests with the useRequestFetch composable.'
links:
- label: Source
icon: i-simple-icons-github
to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/ssr.ts
size: xs
---
You can use `useRequestFetch` to forward the request context and headers when making server-side fetch requests.
When making a client-side fetch request, the browser automatically sends the necessary headers.
However, when making a request during server-side rendering, because the request is made on the server, we need to forward the headers manually.
::note
Headers that are **not meant to be forwarded** will **not be included** in the request. These headers include, for example:
`transfer-encoding`, `connection`, `keep-alive`, `upgrade`, `expect`, `host`, `accept`
::
::tip
The [`useFetch`](/docs/api/composables/use-fetch) composable uses `useRequestFetch` under the hood to automatically forward the request context and headers.
::
::code-group
```vue [pages/index.vue]
<script setup lang="ts">
// This will forward the user's headers to the `/api/foo` event handler
// Result: { cookies: { foo: 'bar' } }
const requestFetch = useRequestFetch()
const { data: forwarded } = await useAsyncData(() => requestFetch('/api/cookies'))
// This will NOT forward anything
// Result: { cookies: {} }
const { data: notForwarded } = await useAsyncData(() => $fetch('/api/cookies'))
</script>
```
```ts [server/api/cookies.ts]
export default defineEventHandler((event) => {
const cookies = parseCookies(event)
return { cookies }
})
```
::
::tip
In the browser during client-side navigation, `useRequestFetch` will behave just like regular [`$fetch`](/docs/api/utils/dollarfetch).
::

View File

@ -39,9 +39,9 @@
"@nuxt/ui-templates": "workspace:*",
"@nuxt/vite-builder": "workspace:*",
"@nuxt/webpack-builder": "workspace:*",
"@vue/compiler-core": "3.5.6",
"@vue/compiler-dom": "3.5.6",
"@vue/shared": "3.5.6",
"@vue/compiler-core": "3.5.7",
"@vue/compiler-dom": "3.5.7",
"@vue/shared": "3.5.7",
"@types/node": "20.16.5",
"c12": "2.0.0-beta.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
@ -51,16 +51,16 @@
"nuxt": "workspace:*",
"ohash": "1.1.4",
"postcss": "8.4.47",
"rollup": "4.22.2",
"rollup": "4.22.4",
"send": ">=0.19.0",
"typescript": "5.6.2",
"ufo": "1.5.4",
"unbuild": "3.0.0-rc.7",
"vite": "5.4.6",
"vue": "3.5.6"
"vite": "5.4.7",
"vue": "3.5.7"
},
"devDependencies": {
"@eslint/js": "9.10.0",
"@eslint/js": "9.11.0",
"@nuxt/eslint-config": "0.5.7",
"@nuxt/kit": "workspace:*",
"@nuxt/test-utils": "3.14.2",
@ -81,7 +81,7 @@
"cssnano": "7.0.6",
"destr": "2.0.3",
"devalue": "5.0.0",
"eslint": "9.10.0",
"eslint": "9.11.0",
"eslint-plugin-no-only-tests": "3.3.0",
"eslint-plugin-perfectionist": "3.6.0",
"eslint-typegen": "0.3.2",
@ -93,9 +93,9 @@
"nuxi": "3.13.2",
"nuxt": "workspace:*",
"nuxt-content-twoslash": "0.1.1",
"ofetch": "1.3.4",
"ofetch": "1.4.0",
"pathe": "1.1.2",
"playwright-core": "1.47.1",
"playwright-core": "1.47.2",
"rimraf": "6.0.1",
"semver": "7.6.3",
"sherif": "1.0.0",
@ -106,18 +106,13 @@
"ufo": "1.5.4",
"vitest": "2.1.1",
"vitest-environment-nuxt": "1.0.1",
"vue": "3.5.6",
"vue": "3.5.7",
"vue-router": "4.4.5",
"vue-tsc": "2.1.6"
},
"packageManager": "pnpm@9.10.0",
"packageManager": "pnpm@9.11.0",
"engines": {
"node": "^16.10.0 || >=18.0.0"
},
"version": "",
"pnpm": {
"patchedDependencies": {
"ofetch@1.3.4": "patches/ofetch@1.3.4.patch"
}
}
"version": ""
}

View File

@ -52,7 +52,7 @@
"@types/semver": "7.5.8",
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"unbuild": "3.0.0-rc.7",
"vite": "5.4.6",
"vite": "5.4.7",
"vitest": "2.1.1",
"webpack": "5.94.0"
},

View File

@ -79,27 +79,30 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
// 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 = jiti.esmResolve(path)
nuxtModule = await jiti.import(src) as NuxtModule
const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule, join(nuxt.options.rootDir, nuxtModule)]
// nuxt-module-builder generates a module.json with metadata including the version
const moduleMetadataPath = join(dirname(src), 'module.json')
if (existsSync(moduleMetadataPath)) {
buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8'))
for (const parentURL of nuxt.options.modulesDir) {
for (const path of paths) {
try {
const src = jiti.esmResolve(path, { parentURL })
nuxtModule = await jiti.import(src) as NuxtModule
// nuxt-module-builder generates a module.json with metadata including the version
const moduleMetadataPath = join(dirname(src), 'module.json')
if (existsSync(moduleMetadataPath)) {
buildTimeModuleMeta = JSON.parse(await fsp.readFile(moduleMetadataPath, 'utf-8'))
}
break
} catch (error: unknown) {
const code = (error as Error & { code?: string }).code
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
continue
}
logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
throw error
}
break
} catch (_err: unknown) {
error = _err
continue
}
}
if (typeof nuxtModule !== 'function' && error) {
logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`)
throw error
if (typeof nuxtModule !== 'string') { break }
}
}

View File

@ -60,7 +60,7 @@
},
"dependencies": {
"@nuxt/devalue": "^2.0.2",
"@nuxt/devtools": "^1.4.2",
"@nuxt/devtools": "^1.5.0",
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.6.0",
@ -69,7 +69,7 @@
"@unhead/shared": "^1.11.6",
"@unhead/ssr": "^1.11.6",
"@unhead/vue": "^1.11.6",
"@vue/shared": "^3.5.6",
"@vue/shared": "^3.5.7",
"acorn": "8.12.1",
"c12": "^2.0.0-beta.2",
"chokidar": "^3.6.0",
@ -97,7 +97,7 @@
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"nuxi": "^3.13.2",
"nypm": "^0.3.11",
"ofetch": "^1.3.4",
"ofetch": "^1.4.0",
"ohash": "^1.1.4",
"pathe": "^1.1.2",
"perfect-debounce": "^1.0.0",
@ -119,7 +119,7 @@
"unplugin-vue-router": "^0.10.8",
"unstorage": "^1.12.0",
"untyped": "^1.4.2",
"vue": "^3.5.6",
"vue": "^3.5.7",
"vue-bundle-renderer": "^2.1.0",
"vue-devtools-stub": "^0.1.0",
"vue-router": "^4.4.5"
@ -130,9 +130,9 @@
"@parcel/watcher": "2.4.1",
"@types/estree": "1.0.6",
"@vitejs/plugin-vue": "5.1.4",
"@vue/compiler-sfc": "3.5.6",
"@vue/compiler-sfc": "3.5.7",
"unbuild": "3.0.0-rc.7",
"vite": "5.4.6",
"vite": "5.4.7",
"vitest": "2.1.1"
},
"peerDependencies": {

View File

@ -42,20 +42,20 @@
"@unhead/schema": "1.11.6",
"@vitejs/plugin-vue": "5.1.4",
"@vitejs/plugin-vue-jsx": "4.0.1",
"@vue/compiler-core": "3.5.6",
"@vue/compiler-sfc": "3.5.6",
"@vue/compiler-core": "3.5.7",
"@vue/compiler-sfc": "3.5.7",
"@vue/language-core": "2.1.6",
"c12": "2.0.0-beta.2",
"esbuild-loader": "4.2.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"ignore": "6.0.2",
"nitro": "npm:nitro-nightly@3.0.0-beta-28665895.e727afda",
"ofetch": "1.3.4",
"ofetch": "1.4.0",
"unbuild": "3.0.0-rc.7",
"unctx": "2.3.1",
"unenv": "1.10.0",
"vite": "5.4.6",
"vue": "3.5.6",
"vite": "5.4.7",
"vue": "3.5.7",
"vue-bundle-renderer": "2.1.0",
"vue-loader": "17.4.2",
"vue-router": "4.4.5",

View File

@ -30,6 +30,6 @@
"tinyexec": "0.3.0",
"tinyglobby": "0.2.6",
"unocss": "0.62.4",
"vite": "5.4.6"
"vite": "5.4.7"
}
}

View File

@ -27,9 +27,9 @@
"@nuxt/schema": "workspace:*",
"@types/clear": "0.1.4",
"@types/estree": "1.0.6",
"rollup": "4.22.2",
"rollup": "4.22.4",
"unbuild": "3.0.0-rc.7",
"vue": "3.5.6"
"vue": "3.5.7"
},
"dependencies": {
"@nuxt/kit": "workspace:*",
@ -62,7 +62,7 @@
"ufo": "^1.5.4",
"unenv": "^1.10.0",
"unplugin": "^1.14.1",
"vite": "^5.4.6",
"vite": "^5.4.7",
"vite-node": "^2.1.1",
"vite-plugin-checker": "^0.8.0",
"vue-bundle-renderer": "^2.1.0"

View File

@ -27,11 +27,16 @@ export async function resolveCSSOptions (nuxt: Nuxt): Promise<ViteConfig['css']>
const pluginOptions = postcssOptions.plugins[pluginName]
if (!pluginOptions) { continue }
const path = jiti.esmResolve(pluginName)
const pluginFn = (await jiti.import(path)) as (opts: Record<string, any>) => Plugin
if (typeof pluginFn === 'function') {
css.postcss.plugins.push(pluginFn(pluginOptions))
} else {
let pluginFn: ((opts: Record<string, any>) => Plugin) | undefined
for (const parentURL of nuxt.options.modulesDir) {
pluginFn = await jiti.import(pluginName, { parentURL, try: true }) as (opts: Record<string, any>) => Plugin
if (typeof pluginFn === 'function') {
css.postcss.plugins.push(pluginFn(pluginOptions))
break
}
}
if (typeof pluginFn !== 'function') {
console.warn(`[nuxt] could not import postcss plugin \`${pluginName}\`. Please report this as a bug.`)
}
}

View File

@ -7,7 +7,6 @@ export const viteNodeOptions = JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS ||
export const viteNodeFetch = $fetch.create({
baseURL: viteNodeOptions.baseURL,
// @ts-expect-error https://github.com/node-fetch/node-fetch#custom-agent
agent: viteNodeOptions.baseURL.startsWith('https://')
? new HTTPSAgent({ rejectUnauthorized: false })
: null,

View File

@ -78,9 +78,9 @@
"@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.22.2",
"rollup": "4.22.4",
"unbuild": "3.0.0-rc.7",
"vue": "3.5.6"
"vue": "3.5.7"
},
"peerDependencies": {
"vue": "^3.3.4"

View File

@ -49,11 +49,16 @@ export async function getPostcssConfig (nuxt: Nuxt) {
const pluginOptions = postcssOptions.plugins[pluginName]
if (!pluginOptions) { continue }
const path = jiti.esmResolve(pluginName)
const pluginFn = (await jiti.import(path)) as (opts: Record<string, any>) => Plugin
if (typeof pluginFn === 'function') {
plugins.push(pluginFn(pluginOptions))
} else {
let pluginFn: ((opts: Record<string, any>) => Plugin) | undefined
for (const parentURL of nuxt.options.modulesDir) {
pluginFn = await jiti.import(pluginName, { parentURL, try: true }) as (opts: Record<string, any>) => Plugin
if (typeof pluginFn === 'function') {
plugins.push(pluginFn(pluginOptions))
break
}
}
if (typeof pluginFn !== 'function') {
console.warn(`[nuxt] could not import postcss plugin \`${pluginName}\`. Please report this as a bug.`)
}
}

View File

@ -1,33 +0,0 @@
diff --git a/dist/node.d.cts b/dist/node.d.cts
index d3a39ff53717d267ff4581af714533ff7229799c..4e3db1f3d6defb7b0c40d11589c0ff6cb8391ad5 100644
--- a/dist/node.d.cts
+++ b/dist/node.d.cts
@@ -1,5 +1,5 @@
import { $ as $Fetch } from './shared/ofetch.8459ad38.cjs';
-export { F as FetchError, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.cjs';
+export { C as CreateFetchOptions, g as Fetch, b as FetchContext, F as FetchError, d as FetchOptions, h as FetchRequest, f as FetchResponse, G as GlobalOptions, I as IFetchError, M as MappedResponseType, R as ResponseMap, e as ResponseType, S as SearchParameters, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
declare function createNodeFetch(): (input: RequestInfo, init?: RequestInit) => any;
declare const fetch: typeof globalThis.fetch;
diff --git a/dist/node.d.mts b/dist/node.d.mts
index 3d8b330375ce60178c05292179ec8bac764ae516..bdcc322bd8554fc7e61d5d9760cb9991560560eb 100644
--- a/dist/node.d.mts
+++ b/dist/node.d.mts
@@ -1,5 +1,5 @@
import { $ as $Fetch } from './shared/ofetch.8459ad38.mjs';
-export { F as FetchError, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
+export { C as CreateFetchOptions, g as Fetch, b as FetchContext, F as FetchError, d as FetchOptions, h as FetchRequest, f as FetchResponse, G as GlobalOptions, I as IFetchError, M as MappedResponseType, R as ResponseMap, e as ResponseType, S as SearchParameters, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
declare function createNodeFetch(): (input: RequestInfo, init?: RequestInit) => any;
declare const fetch: typeof globalThis.fetch;
diff --git a/dist/node.d.ts b/dist/node.d.ts
index 6a5419d1939000a15958b362f44bf49fb1800207..4b319d2c3051e966274268670e243c5f99e2904d 100644
--- a/dist/node.d.ts
+++ b/dist/node.d.ts
@@ -1,5 +1,5 @@
import { $ as $Fetch } from './shared/ofetch.8459ad38.js';
-export { F as FetchError, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.js';
+export { C as CreateFetchOptions, g as Fetch, b as FetchContext, F as FetchError, d as FetchOptions, h as FetchRequest, f as FetchResponse, G as GlobalOptions, I as IFetchError, M as MappedResponseType, R as ResponseMap, e as ResponseType, S as SearchParameters, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
declare function createNodeFetch(): (input: RequestInfo, init?: RequestInit) => any;
declare const fetch: typeof globalThis.fetch;

File diff suppressed because it is too large Load Diff

View File

@ -37,10 +37,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"207k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"208k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1387k"`)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1388k"`)
const packages = modules.files
.filter(m => m.endsWith('package.json'))
@ -78,7 +78,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"555k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"557k"`)
const modules = await analyzeSizes(['node_modules/**/*'], serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"88.2k"`)