From 3a13fab045f0043f044926fa976c353c4500fe4b Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Fri, 28 Jul 2023 16:24:58 +0200 Subject: [PATCH 1/5] expose function to compile schema validators without a file --- .../src/compileSchemaValidators.ts | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/validator-ajv8/src/compileSchemaValidators.ts b/packages/validator-ajv8/src/compileSchemaValidators.ts index 16e78e0d44..10ff3f8aef 100644 --- a/packages/validator-ajv8/src/compileSchemaValidators.ts +++ b/packages/validator-ajv8/src/compileSchemaValidators.ts @@ -5,23 +5,20 @@ import { RJSFSchema, StrictRJSFSchema, schemaParser } from '@rjsf/utils'; import createAjvInstance from './createAjvInstance'; import { CustomValidatorOptionsType } from './types'; -/** The function used to compile a schema into an output file in the form that allows it to be used as a precompiled +/** The function used to compile a schema into javascript code in the form that allows it to be used as a precompiled * validator. The main reasons for using a precompiled validator is reducing code size, improving validation speed and, * most importantly, avoiding dynamic code compilation when prohibited by a browser's Content Security Policy. For more * information about AJV code compilation see: https://ajv.js.org/standalone.html * * @param schema - The schema to be compiled into a set of precompiled validators functions - * @param output - The name of the file into which the precompiled validator functions will be generated * @param [options={}] - The set of `CustomValidatorOptionsType` information used to alter the AJV validator used for * compiling the schema. They are the same options that are passed to the `customizeValidator()` function in * order to modify the behavior of the regular AJV-based validator. */ -export default function compileSchemaValidators( +export function compileSchemaValidatorsCode( schema: S, - output: string, options: CustomValidatorOptionsType = {} ) { - console.log('parsing the schema'); const schemaMaps = schemaParser(schema); const schemas = Object.values(schemaMaps); @@ -34,7 +31,28 @@ export default function compileSchemaValidators( + schema: S, + output: string, + options: CustomValidatorOptionsType = {} +) { + console.log('parsing the schema'); + + const moduleCode = compileSchemaValidatorsCode(schema, options); console.log(`writing ${output}`); fs.writeFileSync(output, moduleCode); } From d99aa6aed29a5bb7c0a9034af3c4769fcca0203e Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Fri, 28 Jul 2023 18:51:23 +0200 Subject: [PATCH 2/5] move compileSchemaValidatorsCode to different file --- .../src/compileSchemaValidators.ts | 35 +--------- .../src/compileSchemaValidatorsCode.ts | 34 ++++++++++ .../test/compileSchemaValidators.test.ts | 62 +++++++----------- .../test/compileSchemaValidatorsCode.test.ts | 64 +++++++++++++++++++ 4 files changed, 125 insertions(+), 70 deletions(-) create mode 100644 packages/validator-ajv8/src/compileSchemaValidatorsCode.ts create mode 100644 packages/validator-ajv8/test/compileSchemaValidatorsCode.test.ts diff --git a/packages/validator-ajv8/src/compileSchemaValidators.ts b/packages/validator-ajv8/src/compileSchemaValidators.ts index 10ff3f8aef..2f9fe2ae60 100644 --- a/packages/validator-ajv8/src/compileSchemaValidators.ts +++ b/packages/validator-ajv8/src/compileSchemaValidators.ts @@ -1,38 +1,9 @@ import fs from 'fs'; -import standaloneCode from 'ajv/dist/standalone'; -import { RJSFSchema, StrictRJSFSchema, schemaParser } from '@rjsf/utils'; - -import createAjvInstance from './createAjvInstance'; +import { RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; import { CustomValidatorOptionsType } from './types'; +import { compileSchemaValidatorsCode } from './compileSchemaValidatorsCode'; -/** The function used to compile a schema into javascript code in the form that allows it to be used as a precompiled - * validator. The main reasons for using a precompiled validator is reducing code size, improving validation speed and, - * most importantly, avoiding dynamic code compilation when prohibited by a browser's Content Security Policy. For more - * information about AJV code compilation see: https://ajv.js.org/standalone.html - * - * @param schema - The schema to be compiled into a set of precompiled validators functions - * @param [options={}] - The set of `CustomValidatorOptionsType` information used to alter the AJV validator used for - * compiling the schema. They are the same options that are passed to the `customizeValidator()` function in - * order to modify the behavior of the regular AJV-based validator. - */ -export function compileSchemaValidatorsCode( - schema: S, - options: CustomValidatorOptionsType = {} -) { - const schemaMaps = schemaParser(schema); - const schemas = Object.values(schemaMaps); - - const { additionalMetaSchemas, customFormats, ajvOptionsOverrides = {}, ajvFormatOptions, AjvClass } = options; - // Allow users to turn off the `lines: true` feature in their own overrides, but NOT the `source: true` - const compileOptions = { - ...ajvOptionsOverrides, - code: { lines: true, ...ajvOptionsOverrides.code, source: true }, - schemas, - }; - const ajv = createAjvInstance(additionalMetaSchemas, customFormats, compileOptions, ajvFormatOptions, AjvClass); - - return standaloneCode(ajv); -} +export { compileSchemaValidatorsCode }; /** The function used to compile a schema into an output file in the form that allows it to be used as a precompiled * validator. The main reasons for using a precompiled validator is reducing code size, improving validation speed and, diff --git a/packages/validator-ajv8/src/compileSchemaValidatorsCode.ts b/packages/validator-ajv8/src/compileSchemaValidatorsCode.ts new file mode 100644 index 0000000000..3b5860c8af --- /dev/null +++ b/packages/validator-ajv8/src/compileSchemaValidatorsCode.ts @@ -0,0 +1,34 @@ +import standaloneCode from 'ajv/dist/standalone'; +import { RJSFSchema, StrictRJSFSchema, schemaParser } from '@rjsf/utils'; + +import createAjvInstance from './createAjvInstance'; +import { CustomValidatorOptionsType } from './types'; + +/** The function used to compile a schema into javascript code in the form that allows it to be used as a precompiled + * validator. The main reasons for using a precompiled validator is reducing code size, improving validation speed and, + * most importantly, avoiding dynamic code compilation when prohibited by a browser's Content Security Policy. For more + * information about AJV code compilation see: https://ajv.js.org/standalone.html + * + * @param schema - The schema to be compiled into a set of precompiled validators functions + * @param [options={}] - The set of `CustomValidatorOptionsType` information used to alter the AJV validator used for + * compiling the schema. They are the same options that are passed to the `customizeValidator()` function in + * order to modify the behavior of the regular AJV-based validator. + */ +export function compileSchemaValidatorsCode( + schema: S, + options: CustomValidatorOptionsType = {} +) { + const schemaMaps = schemaParser(schema); + const schemas = Object.values(schemaMaps); + + const { additionalMetaSchemas, customFormats, ajvOptionsOverrides = {}, ajvFormatOptions, AjvClass } = options; + // Allow users to turn off the `lines: true` feature in their own overrides, but NOT the `source: true` + const compileOptions = { + ...ajvOptionsOverrides, + code: { lines: true, ...ajvOptionsOverrides.code, source: true }, + schemas, + }; + const ajv = createAjvInstance(additionalMetaSchemas, customFormats, compileOptions, ajvFormatOptions, AjvClass); + + return standaloneCode(ajv); +} diff --git a/packages/validator-ajv8/test/compileSchemaValidators.test.ts b/packages/validator-ajv8/test/compileSchemaValidators.test.ts index f984a87b1c..368e3944ae 100644 --- a/packages/validator-ajv8/test/compileSchemaValidators.test.ts +++ b/packages/validator-ajv8/test/compileSchemaValidators.test.ts @@ -1,9 +1,8 @@ -import { readFileSync, writeFileSync } from 'fs'; -import { RJSFSchema, schemaParser } from '@rjsf/utils'; +import { writeFileSync } from 'fs'; +import { RJSFSchema } from '@rjsf/utils'; + +import compileSchemaValidators, { compileSchemaValidatorsCode } from '../src/compileSchemaValidators'; -import compileSchemaValidators from '../src/compileSchemaValidators'; -import createAjvInstance from '../src/createAjvInstance'; -import superSchema from './harness/superSchema.json'; import { CUSTOM_OPTIONS } from './harness/testData'; jest.mock('fs', () => ({ @@ -11,12 +10,15 @@ jest.mock('fs', () => ({ writeFileSync: jest.fn(), })); -jest.mock('../src/createAjvInstance', () => - jest.fn().mockImplementation((...args) => jest.requireActual('../src/createAjvInstance').default(...args)) -); +jest.mock('../src/compileSchemaValidatorsCode', () => { + return { + compileSchemaValidatorsCode: jest.fn(), + }; +}); const OUTPUT_FILE = 'test.js'; +const testSchema = { $id: 'test-schema' } as RJSFSchema; describe('compileSchemaValidators()', () => { let consoleLogSpy: jest.SpyInstance; let expectedCode: string; @@ -27,14 +29,14 @@ describe('compileSchemaValidators()', () => { consoleLogSpy.mockRestore(); }); describe('compiling without additional options', () => { - let schemas: RJSFSchema[]; beforeAll(() => { - schemas = Object.values(schemaParser(superSchema as RJSFSchema)); - expectedCode = readFileSync('./test/harness/superSchema.js').toString(); - compileSchemaValidators(superSchema as RJSFSchema, OUTPUT_FILE); + expectedCode = 'test output 1'; + (compileSchemaValidatorsCode as jest.Mock).mockImplementation(() => expectedCode); + compileSchemaValidators(testSchema, OUTPUT_FILE); }); afterAll(() => { consoleLogSpy.mockClear(); + (compileSchemaValidatorsCode as jest.Mock).mockClear(); (writeFileSync as jest.Mock).mockClear(); }); it('called console.log twice', () => { @@ -46,23 +48,21 @@ describe('compileSchemaValidators()', () => { it('the second time relates to writing the output file', () => { expect(consoleLogSpy).toHaveBeenNthCalledWith(2, `writing ${OUTPUT_FILE}`); }); - it('create AJV instance was called with the expected options', () => { - const expectedCompileOpts = { code: { source: true, lines: true }, schemas }; - expect(createAjvInstance).toHaveBeenCalledWith(undefined, undefined, expectedCompileOpts, undefined, undefined); + it('compileSchemaValidatorsCode was called with the expected options', () => { + expect(compileSchemaValidatorsCode).toHaveBeenCalledWith(testSchema, {}); }); it('wrote the expected output', () => { expect(writeFileSync).toHaveBeenCalledWith(OUTPUT_FILE, expectedCode); }); }); describe('compiling WITH additional options', () => { - let schemas: RJSFSchema[]; + const customOptions = { + ...CUSTOM_OPTIONS, + ajvOptionsOverrides: { ...CUSTOM_OPTIONS.ajvOptionsOverrides, code: { lines: false } }, + }; beforeAll(() => { - schemas = Object.values(schemaParser(superSchema as RJSFSchema)); - expectedCode = readFileSync('./test/harness/superSchemaOptions.js').toString(); - compileSchemaValidators(superSchema as RJSFSchema, OUTPUT_FILE, { - ...CUSTOM_OPTIONS, - ajvOptionsOverrides: { ...CUSTOM_OPTIONS.ajvOptionsOverrides, code: { lines: false } }, - }); + expectedCode = 'expected code 2'; + compileSchemaValidators(testSchema, OUTPUT_FILE, customOptions); }); afterAll(() => { consoleLogSpy.mockClear(); @@ -77,22 +77,8 @@ describe('compileSchemaValidators()', () => { it('the second time relates to writing the output file', () => { expect(consoleLogSpy).toHaveBeenNthCalledWith(2, `writing ${OUTPUT_FILE}`); }); - it('create AJV instance was called with the expected options', () => { - const { - additionalMetaSchemas, - customFormats, - ajvOptionsOverrides = {}, - ajvFormatOptions, - AjvClass, - } = CUSTOM_OPTIONS; - const expectedCompileOpts = { ...ajvOptionsOverrides, code: { source: true, lines: false }, schemas }; - expect(createAjvInstance).toHaveBeenCalledWith( - additionalMetaSchemas, - customFormats, - expectedCompileOpts, - ajvFormatOptions, - AjvClass - ); + it('compileSchemaValidatorsCode was called with the expected options', () => { + expect(compileSchemaValidatorsCode).toHaveBeenCalledWith(testSchema, customOptions); }); it('wrote the expected output', () => { expect(writeFileSync).toHaveBeenCalledWith(OUTPUT_FILE, expectedCode); diff --git a/packages/validator-ajv8/test/compileSchemaValidatorsCode.test.ts b/packages/validator-ajv8/test/compileSchemaValidatorsCode.test.ts new file mode 100644 index 0000000000..93b655f1d4 --- /dev/null +++ b/packages/validator-ajv8/test/compileSchemaValidatorsCode.test.ts @@ -0,0 +1,64 @@ +import { readFileSync } from 'fs'; +import { RJSFSchema, schemaParser } from '@rjsf/utils'; + +import { compileSchemaValidatorsCode } from '../src/compileSchemaValidators'; +import createAjvInstance from '../src/createAjvInstance'; +import superSchema from './harness/superSchema.json'; +import { CUSTOM_OPTIONS } from './harness/testData'; + +jest.mock('../src/createAjvInstance', () => + jest.fn().mockImplementation((...args) => jest.requireActual('../src/createAjvInstance').default(...args)) +); + +describe('compileSchemaValidatorsCode()', () => { + let expectedCode: string; + let generatedCode: string; + + describe('compiling without additional options', () => { + let schemas: RJSFSchema[]; + beforeAll(() => { + schemas = Object.values(schemaParser(superSchema as RJSFSchema)); + expectedCode = readFileSync('./test/harness/superSchema.js').toString(); + generatedCode = compileSchemaValidatorsCode(superSchema as RJSFSchema); + }); + it('create AJV instance was called with the expected options', () => { + const expectedCompileOpts = { code: { source: true, lines: true }, schemas }; + expect(createAjvInstance).toHaveBeenCalledWith(undefined, undefined, expectedCompileOpts, undefined, undefined); + }); + it('generates the expected output', () => { + expect(generatedCode).toBe(expectedCode); + }); + }); + describe('compiling WITH additional options', () => { + let schemas: RJSFSchema[]; + let expectedCode: string; + beforeAll(() => { + schemas = Object.values(schemaParser(superSchema as RJSFSchema)); + expectedCode = readFileSync('./test/harness/superSchemaOptions.js').toString(); + generatedCode = compileSchemaValidatorsCode(superSchema as RJSFSchema, { + ...CUSTOM_OPTIONS, + ajvOptionsOverrides: { ...CUSTOM_OPTIONS.ajvOptionsOverrides, code: { lines: false } }, + }); + }); + it('create AJV instance was called with the expected options', () => { + const { + additionalMetaSchemas, + customFormats, + ajvOptionsOverrides = {}, + ajvFormatOptions, + AjvClass, + } = CUSTOM_OPTIONS; + const expectedCompileOpts = { ...ajvOptionsOverrides, code: { source: true, lines: false }, schemas }; + expect(createAjvInstance).toHaveBeenCalledWith( + additionalMetaSchemas, + customFormats, + expectedCompileOpts, + ajvFormatOptions, + AjvClass + ); + }); + it('generates expected output', () => { + expect(generatedCode).toBe(expectedCode); + }); + }); +}); From d26f93a4ac16aedf81423588f4570696c08c13b8 Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Fri, 28 Jul 2023 18:53:02 +0200 Subject: [PATCH 3/5] add documentation example using compileSchemaValidatorsCode --- packages/docs/docs/usage/validation.md | 133 +++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/packages/docs/docs/usage/validation.md b/packages/docs/docs/usage/validation.md index ae3c23a74e..2557add183 100644 --- a/packages/docs/docs/usage/validation.md +++ b/packages/docs/docs/usage/validation.md @@ -90,6 +90,139 @@ const validator = createPrecompiledValidator(precompiledValidator as ValidatorFu render(
, document.getElementById('app')); ``` +### Dynamically pre-compiling validators + +For more advanced cases when schema needs to be precompiled on request - `compileSchemaValidatorsCode` can be used. +```ts +import { compileSchemaValidatorsCode } from '@rjsf/validator-ajv8/dist/compileSchemaValidators'; + +const code = compileSchemaValidatorsCode(schema, options); +``` + +For the most part it is the same as `compileSchemaValidators`, but instead of writing the file - it returns generated code directly. + +To use it on browser side - some modifications are needed to provide runtime dependencies in generated code needs to be provided. + +Example implementation of it: + +```tsx +import type { ValidatorFunctions } from '@rjsf/validator-ajv8'; + +import ajvRuntimeEqual from 'ajv/dist/runtime/equal'; +import { + parseJson as ajvRuntimeparseJson, + parseJsonNumber as ajvRuntimeparseJsonNumber, + parseJsonString as ajvRuntimeparseJsonString, +} from 'ajv/dist/runtime/parseJson'; +import ajvRuntimeQuote from 'ajv/dist/runtime/quote'; +// import ajvRuntimeRe2 from 'ajv/dist/runtime/re2'; +import ajvRuntimeTimestamp from 'ajv/dist/runtime/timestamp'; +import ajvRuntimeUcs2length from 'ajv/dist/runtime/ucs2length'; +import ajvRuntimeUri from 'ajv/dist/runtime/uri'; +import * as ajvFormats from 'ajv-formats/dist/formats'; + +const validatorsBundleReplacements: Record = { + 'require("ajv/dist/runtime/equal").default': ['ajvRuntimeEqual', ajvRuntimeEqual], + 'require("ajv/dist/runtime/parseJson").parseJson': ['ajvRuntimeparseJson', ajvRuntimeparseJson], + 'require("ajv/dist/runtime/parseJson").parseJsonNumber': [ + 'ajvRuntimeparseJsonNumber', + ajvRuntimeparseJsonNumber, + ], + 'require("ajv/dist/runtime/parseJson").parseJsonString': [ + 'ajvRuntimeparseJsonString', + ajvRuntimeparseJsonString, + ], + 'require("ajv/dist/runtime/quote").default': ['ajvRuntimeQuote', ajvRuntimeQuote], + // re2 by default is not in dependencies for ajv and so is likely not normally used + // 'require("ajv/dist/runtime/re2").default': ['ajvRuntimeRe2', ajvRuntimeRe2], + 'require("ajv/dist/runtime/timestamp").default': ['ajvRuntimeTimestamp', ajvRuntimeTimestamp], + 'require("ajv/dist/runtime/ucs2length").default': ['ajvRuntimeUcs2length', ajvRuntimeUcs2length], + 'require("ajv/dist/runtime/uri").default': ['ajvRuntimeUri', ajvRuntimeUri], + // formats + 'require("ajv-formats/dist/formats")': ['ajvFormats', ajvFormats], +}; + +const regexp = new RegExp( + Object.keys(validatorsBundleReplacements) + .map((key) => key.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')) + .join('|'), + 'g' +); + +function wrapAjvBundle(code: string) { + return `function(${Object.values(validatorsBundleReplacements) + .map(([name]) => name) + .join(', ')}){\nvar exports = {};\n${code.replace( + regexp, + (req) => validatorsBundleReplacements[req][0] + )};\nreturn exports;\n}`; +} + +const windowValidatorOnLoad = '__rjsf_validatorOnLoad'; +const schemas = new Map< + string, + { promise: Promise; resolve: (result: ValidatorFunctions) => void } +>(); +if (typeof window !== 'undefined') { + // @ts-ignore + window[windowValidatorOnLoad] = ( + loadedId: string, + fn: (...args: unknown[]) => ValidatorFunctions + ) => { + const validator = fn(...Object.values(validatorsBundleReplacements).map(([, dep]) => dep)); + let validatorLoader = schemas.get(loadedId); + if (validatorLoader) { + validatorLoader.resolve(validator); + } else { + throw new Error(`Unknown validator loaded id="${loadedId}"`); + } + }; +} + +export function loadSchema(id: string, code: string, nonce: string) { + let maybeValidator = schemas.get(id); + if (maybeValidator) return maybeValidator.promise; + let resolveValidator: (result: ValidatorFunctions) => void; + const validatorPromise = new Promise((resolve) => { + resolveValidator = resolve; + }); + schemas.set(id, { + promise: validatorPromise, + resolve: resolveValidator!, + }); + + const scriptElement = document.createElement('script'); + + scriptElement.setAttribute('nonce', nonce); + scriptElement.text = `window["${windowValidatorOnLoad}"]("${id}", ${wrapAjvBundle(code)})`; + + document.body.appendChild(scriptElement); + return validatorPromise; +} + +``` + +From React component this can be used as following: + +```tsx +let [precompiledValidator, setPrecompiledValidator] = React.useState(); +React.useEffect(() => { + loadSchema( + // some schema id to avoid evaluating it multiple times + schemaId, + // result of compileSchemaValidatorsCode returned from the server + code, + // nonce script tag attribute to allow this ib content security policy for the page + nonce + ).then(setPrecompiledValidator); +}, [entityType.id]); + +if (!precompiledValidator) { + // render loading screen +} +const validator = createPrecompiledValidator(precompiledValidator, schema); +``` + ## Live validation From 820db5e2534d597ff28286ad52119f518fd2fa38 Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Fri, 28 Jul 2023 20:01:55 +0200 Subject: [PATCH 4/5] improve documentation --- packages/docs/docs/usage/validation.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/docs/docs/usage/validation.md b/packages/docs/docs/usage/validation.md index 2557add183..dbe6f76813 100644 --- a/packages/docs/docs/usage/validation.md +++ b/packages/docs/docs/usage/validation.md @@ -121,7 +121,9 @@ import ajvRuntimeUcs2length from 'ajv/dist/runtime/ucs2length'; import ajvRuntimeUri from 'ajv/dist/runtime/uri'; import * as ajvFormats from 'ajv-formats/dist/formats'; +// dependencies to replace in generated code, to be provided by at runtime const validatorsBundleReplacements: Record = { + // '': ['', ], 'require("ajv/dist/runtime/equal").default': ['ajvRuntimeEqual', ajvRuntimeEqual], 'require("ajv/dist/runtime/parseJson").parseJson': ['ajvRuntimeparseJson', ajvRuntimeparseJson], 'require("ajv/dist/runtime/parseJson").parseJsonNumber': [ @@ -179,7 +181,13 @@ if (typeof window !== 'undefined') { }; } -export function loadSchema(id: string, code: string, nonce: string) { +/** + * Evaluate precompiled validator in browser using script tag + * @param id Identifier to avoid evaluating the same code multiple times + * @param code Code generated server side using `compileSchemaValidatorsCode` + * @param nonce nonce attribute to be added to script tag (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce#using_nonce_to_allowlist_a_script_element) + */ +export function evaluateValidator(id: string, code: string, nonce: string): Promise { let maybeValidator = schemas.get(id); if (maybeValidator) return maybeValidator.promise; let resolveValidator: (result: ValidatorFunctions) => void; @@ -207,13 +215,10 @@ From React component this can be used as following: ```tsx let [precompiledValidator, setPrecompiledValidator] = React.useState(); React.useEffect(() => { - loadSchema( - // some schema id to avoid evaluating it multiple times - schemaId, - // result of compileSchemaValidatorsCode returned from the server - code, - // nonce script tag attribute to allow this ib content security policy for the page - nonce + evaluateValidator( + schemaId, // some schema id to avoid evaluating it multiple times + code, // result of compileSchemaValidatorsCode returned from the server + nonce // nonce script tag attribute to allow this ib content security policy for the page ).then(setPrecompiledValidator); }, [entityType.id]); From 4e0ad3f6cbb69a7fe27662311efa47f625f93e6b Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Wed, 2 Aug 2023 20:53:02 +0200 Subject: [PATCH 5/5] update changelog to use next minor version --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2596637263..5233dddb83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ should change the heading of the (upcoming) version to include a major version b - esbuild for CJS bundle - rollup for UMD bundle +## @rjsf/validator-ajv8 + +- Exposing new function `compileSchemaValidatorsCode` to allow creating precompiled validator without a file. This is useful in case when precompiled validator is to be created dynamically. [#3793](https://github.com/rjsf-team/react-jsonschema-form/pull/3793) + # 5.11.2 ## @rjsf/material-ui