透過超過 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??)伺服器端或用戶端。您只需要設定特定的 Error parsers

功能

  •  相容 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 工廠函式建立您的 自訂 composable

伺服器端如何?

相同的概念,建立一個 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',
      },
    }
  },
})

這次,如果我們在 nuxt 設定檔中啟用預定義的解析器,錯誤將被轉換為 NuxtServerValidationError 並在用戶端捕獲。

// 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 Lambda)定義自己的後端邏輯,請遵守以下需求清單。

  • 預知請求必須具有
    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啟用用戶端 nuxtErrorParsers 的旗標(在 form.validateform.submit 中)。
enableLaravelClientErrorParser布林值false啟用用戶端 laravelErrorParsers 的旗標(在 form.validateform.submit 中)。
enableLaravelServerErrorParser布林值false啟用用戶端 laravelErrorParsers 的旗標(在 definePrecognitiveEventHandler 中)。

狀態處理常式

官方套件 類似,您可以針對特定錯誤碼定義全域或實例層級的自訂處理常式

// 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. 在需要的地方啟用預知中間件
// 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