通过 100 多个技巧学习 Nuxt!

precognition
nuxt-precognition

在 Nitro 中实现 Laravel Precognition 协议的 Nuxt 模块

Nuxt Precognition

npm versionnpm downloadsLicenseNuxt

这是 nuxt-laravel-precognition 的新版本。它提供相同的功能,但不再依赖 Laravel。

它不只支持 $fetchLaravel,还可以使用简单的 promise,针对任何实现了基本 Precognition 协议的后端。这些 promise 将接收到 payload 形式和协议 Headers

示例

interface User = {
  email: string
  password: string
}

const form = useForm(
  (): User => ({ email: '', password: '' }),
  (body, headers) => $fetch('/api/login', { method: 'POST', headers, body })
)

此模块带有原生的 Nitro 集成,但也适用于其他后端。

您只使用 Lambda 吗? Lambda Precognition 将满足您的需求!!

它支持任何验证库 (谁说 Zod??) 服务器端或客户端。您只需配置特定的 错误解析器

功能

  •  兼容 Laravel
  •  与验证库无关
  •  客户端和服务器端验证
  •  最佳 Typescript 支持
  •  高度可定制

工作原理

一切都围绕 errorParsers 展开(用户定义函数,用于从 Error payload 读取验证错误

type ValidationErrors = Record<string, string | string[]>

interface ValidationErrorsData {
  message: string
  errors: ValidationErrors
}

type ValidationErrorParser = (error: Error) => ValidationErrorsData | undefined | null

您可以在全局(在 Nuxt Plugin 或自定义 eventHandler 中)或每个 form 实例中定义它们。

假设您正在使用 Zod
只需创建一个 nuxt 插件 并定义“Zod 错误解析器

// plugins/precognition.ts

export default defineNuxtPlugin(() => {
  const { $precognition } = useNuxtApp()

  $precognition.errorParsers.push(
    (error) => {
      if (error instanceof ZodError) {
        const errors = {} as Record<string, string[]>
        error.errors.forEach((e) => {
          const key = e.path.join('.')
          if (key in errors) {
            errors[key].push(e.message)
            return
          }
          errors[key] = [e.message]
        })
        return { errors, message: 'Validation error' }
      }
      return null
    },
  )
})

从现在开始,每次 useForm 捕获错误时,它都会运行我们的解析器,并捕获和分配任何验证错误。

如果您想在多个页面上重复使用相同的选项,您可以通过 useForm.create 工厂函数创建您自己的 自定义组合

服务器端怎么样

相同的想法,创建一个 nitro 插件

// server/plugins/precognition.ts

import { ZodError } from 'zod'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    event.context.$precognition.errorParsers = [
      (error) => {
        if (error instanceof ZodError) {
          const errors: Record<string, string[]> = {}
          error.errors.forEach((e) => {
            const key = e.path.join('.')
            if (key in errors) {
              errors[key].push(e.message)
              return
            }
            errors[key] = [e.message]
          })
          const message = error.errors.at(0)?.message ?? 'Validation error'
          return { errors, message }
        }
      },
    ]
  })
})

如果您不喜欢挂钩每个请求,您可以通过 definePrecognitiveEventHandler.create 工厂函数创建您自己的自定义 eventHandler。

definePrecognitiveEventHandleronRequest 处理程序中设置您的验证逻辑。

// server/api/login.post.ts
import { z } from 'zod'
import { definePrecognitiveEventHandler, readBody } from '#imports'

const loginSchema = z.object({
  email: z.string().email().refine(_email => // Check for email uniqueness
    true, { message: 'Email is already in use' },
  ),
  password: z.string(),
}).refine((_data) => {
  // Check for email and password match
  // ...
  return true
},
{ message: 'invalid credentials', path: ['email'] },
)

export default definePrecognitiveEventHandler({
  async onRequest(event) {
    const body = await readBody(event)
    loginSchema.parse(body)
  },
  handler: () => {
    return {
      status: 200,
      body: {
        message: 'Success',
      },
    }
  },
})

这次错误将转换为 NuxtServerValidationError,如果我们在 nuxt 配置文件中启用预定义的解析器,则客户端将被捕获

// nuxt.config.ts

export default defineNuxtConfig({
  modules: ['nuxt-precognitiion'],
  precognition: {
    backendValidation: true,
    enableNuxtClientErrorParser: true,
  }
})

请记住仅在 onRequest 处理程序(使用 对象表示法)中抛出 ValidationError.
基础 handler 中的任何逻辑都不会在 precognitiveRequests 期间被处理。

  • 每个 event.context 还包括一个标志({ precognitive: boolean }),指示请求是否为预知请求,具体取决于是否存在 Precognitive header

预知协议

如果您需要在 nitro 之外定义自己的后端逻辑(AWS Lamba),请遵守以下要求列表。

  • 预知请求必须具有
    1. 预知标头 { 'Precognitive': 'true' }
  • 要验证特定变量,必须在 ValidateOnly 标头中指定每个键,以逗号分隔并利用点表示法 { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }
  • 要验证整个表单,应省略 ValidateOnly 标头或将其定义为空字符串。
  • 成功验证响应必须具有
    1. 预知标头 { 'Precognitive': 'true' }
    2. 预知成功标头 { 'Precognition-Success': 'true' }
    3. 预知成功状态代码:204
  • 错误验证响应必须具有
    1. 预知标头 { 'Precognitive': 'true' }
    2. 如果需要,使用 ValidationOnly 标头 { 'Precognition-Validate-Only': 'name,age,address.street,address.number' }
    3. 验证错误状态代码:422
    4. 验证错误和消息将按照您定义的逻辑进行解析,或使用标准的 errorParsers
      • NuxtErrorParsers:NuxtPrecognitiveErrorResponseResponse & { _data: { data: ValidationErrorsData }}
      • LaravelErrorParsers:LaravelPrecognitiveErrorResponseResponse & { _data: ValidationErrorsData }

快速设置

使用一个命令将模块安装到您的 Nuxt 应用程序

npx nuxi module add nuxt-precognition

配置

名称类型默认值描述
validationTimeout数字1500两次预知验证请求之间的去抖动时间,以毫秒为单位。
backendValidation布尔值false用于启用预知验证的标志。
validateFiles布尔值false用于在预知请求上启用文件验证的标志。
enableNuxtClientErrorParser布尔值false用于启用客户端(在 form.validateform.submit 中)的 nuxtErrorParsers 的标志。
enableLaravelClientErrorParser布尔值false用于启用客户端(在 form.validateform.submit 中)的 laravelErrorParsers 的标志。
enableLaravelServerErrorParser布尔值false用于启用客户端(在 definePrecognitiveEventHandler 中)的 laravelErrorParsers 的标志。

状态处理程序

官方软件包一样,您可以全局或在实例级别为特定错误代码定义自定义处理程序

// plugins/precognition.ts

export default defineNuxtPlugin(() => {
  const { $precognition } = useNuxtApp()

  $precognition.statusHandlers = {
    401: async (error, form) => {
      form.error = createError('Unauthorized')
      await navigateTo('/login')
    },
    403: async (error, form) => {
      form.error = createError('Forbidden')
    },
  }
})

就这样!您现在可以在您的 Nuxt 应用程序中使用 Nuxt Precognition ✨

使用 Laravel

  1. 像这样定义一个插件
// plugins/api.ts

export default defineNuxtPlugin((app) => {
  const { $precognition } = useNuxtApp()
  const token = useCookie('XSRF-TOKEN')

  const api = $fetch.create({
    baseURL: 'https://127.0.0.1',
    credentials: 'include',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
    onRequest: ({ options }) => {
      // Setup csrf protection for every requests if available
      if (token.value) {
        const headers = new Headers(options.headers)
        headers.set('X-XSRF-TOKEN', token.value)
        options.headers = headers
      }
    },
    onResponse: (context) => {
      // ensure that all precognitive requests will receive precognitive responses
      $precognition.assertSuccessfulPrecognitiveResponses(context)
    },
  })

  async function fetchSanctumToken() {
    try {
      await api('/sanctum/csrf-cookie')
      token.value = useCookie('XSRF-TOKEN').value

      if (!token.value) {
        throw new Error('Failed to get CSRF token')
      }
    }
    catch (e) {
      console.error(e)
    }
  }

  app.hook('app:mounted', fetchSanctumToken)

  return {
    provide: {
      api,
      sanctum: {
        fetchToken: fetchSanctumToken,
        token,
      },
    },
  }
})
  1. 启用后端验证和原生 Laravel 错误解析器客户端或服务器端
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-precognition'],
  precognition: {
    backendValidation: true,
    enableLaravelClientErrorParser: true,
  },
  /*
  ...
  */
})

* 如果您 enableLaravelServerErrorParser,您还必须 enableNuxtClientErrorParser

  1. 设置 Laravel Cors 配置文件
// config/cors.php

return [

    /*
    |--------------------------------------------------------------------------
    | Cross-Origin Resource Sharing (CORS) Configuration
    |--------------------------------------------------------------------------
    |
    */

    'paths' => ['*'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [env('FRONTEND_URL', 'https://127.0.0.1:3000')],

    'allowed_headers' => ['*'],

    'exposed_headers' => ['Precognition', 'Precognition-Success'],

    'max_age' => 0,

    'supports_credentials' => true,

];
  1. 在需要的地方启用 Precognition 中间件
// routes/api.php

Route::middleware('precognitive')->group(function () {
    Route::apiResource('posts', \App\Http\Controllers\PostController::class);
});

贡献

本地开发
# Install dependencies
npm install

# Generate type stubs
npm run dev:prepare

# Develop with the playground
npm run dev

# Build the playground
npm run dev:build

# Run ESLint
npm run lint

# Run Vitest
npm run test
npm run test:watch

# Release new version
npm run release