Skip to content

Commit

Permalink
refactor: optimize bundle size
Browse files Browse the repository at this point in the history
  • Loading branch information
JB AUBREE committed Nov 4, 2024
1 parent 097b745 commit b50325a
Show file tree
Hide file tree
Showing 21 changed files with 264 additions and 290 deletions.
27 changes: 6 additions & 21 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import type { FieldErrors, Form, GetErrorsFn, InputSchema } from './types'
import { getJoiErrors, isJoiSchema } from './joi'
import { getSuperStructErrors, isSuperStructSchema } from './superstruct'
import { getValibotErrors, isValibotSchema } from './valibot'
import { getYupErrors, isYupSchema } from './yup'
import { getZodErrors, isZodSchema } from './zod'
import { validators } from './validators'

export async function getErrors<S extends InputSchema<F>, F extends Form>(
schema: S,
Expand All @@ -19,23 +15,12 @@ export async function getErrors<S, F extends Form>(
form: F,
transformFn?: GetErrorsFn<S, F>,
): Promise<FieldErrors<F>> {
if (transformFn) {
if (transformFn)
return await transformFn(schema, form)
}
if (isZodSchema(schema)) {
return await getZodErrors<F>(schema, form)
}
if (isYupSchema<F>(schema)) {
return await getYupErrors<F>(schema, form)
}
if (isJoiSchema(schema)) {
return await getJoiErrors<F>(schema, form)
}
if (isValibotSchema(schema)) {
return getValibotErrors<F>(schema, form)
}
if (isSuperStructSchema<S, F>(schema)) {
return getSuperStructErrors<S, F>(schema, form)
for (const validator of Object.values(validators)) {
if (validator.check(schema)) {
return await validator.getErrors(schema, form)
}
}
return {}
}
30 changes: 0 additions & 30 deletions src/joi.ts

This file was deleted.

15 changes: 5 additions & 10 deletions src/polyfill.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
export function polyfillGroupBy<T>(): void {
if (!Object.groupBy) {
Object.defineProperty(Object, 'groupBy', {
value(array: T[], keyGetter: (item: T) => string): Record<string, T[]> {
return array.reduce((result, currentItem) => {
const key = keyGetter(currentItem)
if (!result[key]) {
result[key] = []
}
result[key].push(currentItem)
return result
}, {} as Record<string, T[]>)
},
value: (array: T[], keyGetter: (item: T) => string): Record<string, T[]> => array.reduce((result: Record<string, T[]>, item: T) => {
const key = keyGetter(item);
(result[key] ??= []).push(item)
return result
}, {}),
writable: true,
configurable: true,
})
Expand Down
23 changes: 0 additions & 23 deletions src/superstruct.ts

This file was deleted.

7 changes: 1 addition & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,11 @@ interface YupSchema<F> extends AnyObject {
interface ValibotSchema<F> extends AnyObject {
entries: Record<keyof F, unknown>
}
// interface JoiTerms<F> extends AnyObject {
// keys: Array<{ key: keyof F, schema: unknown }>
// }
// interface JoiSchema<F> extends AnyObject {
// $_terms: JoiTerms<F>
// }
interface SuperstructSchema<F> extends AnyObject {
schema: Record<keyof F, unknown>
}

export type Validator = 'Joi' | 'SuperStruct' | 'Valibot' | 'Yup' | 'Zod'
export type Awaitable<T> = T | PromiseLike<T>
export type FieldErrors<F> = Partial<Record<keyof F, string>>
export type Form = Record<string, unknown>
Expand Down
35 changes: 13 additions & 22 deletions src/useFormValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function useFormValidation<S extends InputSchema<F>, F extends Form>(
options?: { mode?: 'eager' | 'lazy', transformFn?: GetErrorsFn<S, F> },
): ReturnType<F> {
polyfillGroupBy()
const opts = Object.assign({}, { mode: 'lazy', transformFn: undefined }, options)
const opts = { mode: 'lazy', transformFn: null, ...options }

const errors = shallowRef<FieldErrors<F>>({})

Expand All @@ -34,36 +34,22 @@ export function useFormValidation<S extends InputSchema<F>, F extends Form>(
}
const getErrorMessage = (path: keyof F): string | undefined => errors.value[path]

let unwatch: null | (() => void) = null
const validationWatch: () => void = () => {
if (unwatch !== null)
return
unwatch = watch(
() => toValue(form),
async () => {
// eslint-disable-next-line ts/no-use-before-define
await validate()
},
{ deep: true },
)
}

const validate = async (): Promise<FieldErrors<F>> => {
isLoading.value = true
clearErrors()
errors.value = opts.transformFn
? await getErrors<S, F>(toValue(schema), toValue(form), opts.transformFn)
: await getErrors<S, F>(toValue(schema), toValue(form))
if (hasError.value) {
validationWatch()
}

if (hasError.value)
// eslint-disable-next-line ts/no-use-before-define
watchFormChanges()
isLoading.value = false
return errors.value
}

const focusInput = ({ inputName }: { inputName: keyof F }): void => {
const element: HTMLInputElement | null = document.querySelector(`input[name="${inputName.toString()}"]`)
element?.focus()
(document.querySelector(`input[name="${inputName.toString()}"]`) as HTMLInputElement | null)?.focus()
}
const focusFirstErroredInput = (): void => {
for (const key in toValue(form)) {
Expand All @@ -74,10 +60,15 @@ export function useFormValidation<S extends InputSchema<F>, F extends Form>(
}
}

if (opts.mode === 'eager') {
validationWatch()
let unwatch: null | (() => void)
const watchFormChanges = (): void | (() => void) => {
if (!unwatch)
unwatch = watch(() => toValue(form), validate, { deep: true })
}

if (opts.mode === 'eager')
watchFormChanges()

return {
validate,
errors,
Expand Down
22 changes: 0 additions & 22 deletions src/valibot.ts

This file was deleted.

18 changes: 18 additions & 0 deletions src/validators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Awaitable, FieldErrors, Form, Validator } from '../types'
import { Joi } from './joi'
import { SuperStruct } from './superstruct'
import { Valibot } from './valibot'
import { Yup } from './yup'
import { Zod } from './zod'

export const validators: Record<Validator, {
check: (schema: unknown) => boolean
errorCheck?: (error: unknown) => boolean
getErrors: <F extends Form>(schema: any, form: F) => Awaitable<FieldErrors<F>>
}> = {
Joi,
SuperStruct,
Valibot,
Yup,
Zod,
}
20 changes: 20 additions & 0 deletions src/validators/joi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
import type { FieldErrors, Form } from '../types'
import { isNonNullObject } from '../utils'

export const Joi = {
check: (schema: unknown): schema is JoiSchema => isNonNullObject(schema) && 'validateAsync' in schema,
errorCheck: (error: unknown): error is JoiError => isNonNullObject(error) && error.isJoi === true,
async getErrors<F extends Form>(schema: JoiSchema, form: F): Promise<FieldErrors<F>> {
const errors: FieldErrors<F> = {}
try {
await schema.validateAsync(form, { abortEarly: false })
}
catch (error) {
if (Joi.errorCheck(error)) {
error.details.forEach(i => errors[i.path[0] as keyof F] = i.message)
}
}
return errors
},
}
13 changes: 13 additions & 0 deletions src/validators/superstruct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Struct } from 'superstruct'
import type { FieldErrors, Form } from '../types'
import { isNonNullObject } from '../utils'

export const SuperStruct = {
check: (schema: unknown) => isNonNullObject(schema) && 'validator' in schema,
getErrors<S, F extends Form>(schema: Struct<F, S>, form: F): FieldErrors<F> {
const errors: FieldErrors<F> = {}
const [structError] = schema.validate(form)
structError?.failures().forEach(i => errors[i.path[0] as keyof F] = i.message)
return errors
},
}
15 changes: 15 additions & 0 deletions src/validators/valibot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { BaseIssue, BaseSchema } from 'valibot'
import type { FieldErrors, Form } from '../types'
import { isNonNullObject } from '../utils'

type ValibotSchema = BaseSchema<unknown, unknown, BaseIssue<unknown>>

export const Valibot = {
check: (schema: unknown) => isNonNullObject(schema) && '_run' in schema,
getErrors<F extends Form>(schema: ValibotSchema, form: F): FieldErrors<F> {
const errors: FieldErrors<F> = {}
const result = schema._run({ typed: false, value: form }, {})
result.issues?.forEach(i => errors[i.path?.[0].key as keyof F] = i.message)
return errors
},
}
20 changes: 20 additions & 0 deletions src/validators/yup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ObjectSchema, ValidationError as YupError } from 'yup'
import type { FieldErrors, Form } from '../types'
import { isNonNullObject } from '../utils'

export const Yup = {
check: (schema: unknown) => isNonNullObject(schema) && '__isYupSchema__' in schema,
errorCheck: (error: unknown): error is YupError => isNonNullObject(error) && !!error.inner,
async getErrors<F extends Form>(schema: ObjectSchema<F>, form: F): Promise<FieldErrors<F>> {
const errors: FieldErrors<F> = {}
try {
await schema.validate(form, { abortEarly: false })
}
catch (error) {
if (Yup.errorCheck(error)) {
error.inner.forEach(i => errors[i.path?.split('.')[0] as keyof F] = i.message)
}
}
return errors
},
}
13 changes: 13 additions & 0 deletions src/validators/zod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ZodSchema } from 'zod'
import type { FieldErrors, Form } from '../types'
import { isNonNullObject } from '../utils'

export const Zod = {
check: (schema: unknown): schema is ZodSchema => isNonNullObject(schema) && !!schema.safeParseAsync,
async getErrors<F extends Form>(schema: ZodSchema, form: F): Promise<FieldErrors<F>> {
const errors: FieldErrors<F> = {}
const result = await schema.safeParseAsync(form)
result.error?.issues.forEach(i => errors[i.path[0] as keyof F] = i.message)
return errors
},
}
31 changes: 0 additions & 31 deletions src/yup.ts

This file was deleted.

16 changes: 0 additions & 16 deletions src/zod.ts

This file was deleted.

Loading

0 comments on commit b50325a

Please sign in to comment.