Skip to content

Commit ec96c87

Browse files
rehanvdmepoberezkin
andauthoredJan 15, 2022
Updated standalone documentation and add new examples (#1866)
* docs: updated standalone documentation and added new examples #1831 * remove unnecessary line breaks * remove style changes * minor corrections * minor corrections * Update docs/standalone.md * Update docs/standalone.md * Update docs/standalone.md Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
1 parent f2e590a commit ec96c87

File tree

1 file changed

+190
-33
lines changed

1 file changed

+190
-33
lines changed
 

‎docs/standalone.md

+190-33
Original file line numberDiff line numberDiff line change
@@ -2,83 +2,241 @@
22

33
[[toc]]
44

5-
Ajv supports generating standalone modules with exported validation function(s), with one default export or multiple named exports, that are pre-compiled and can be used without Ajv. It is useful for several reasons:
5+
Ajv supports generating standalone validation functions from JSON Schemas at compile/build time. These functions can then be used during runtime to do validation without initialising Ajv. It is useful for several reasons:
66

77
- to reduce the browser bundle size - Ajv is not included in the bundle (although if you have a large number of schemas the bundle can become bigger - when the total size of generated validation code is bigger than Ajv code).
88
- to reduce the start-up time - the validation and compilation of schemas will happen during build time.
99
- to avoid dynamic code evaluation with Function constructor (used for schema compilation) - when it is prohibited by the browser page [Content Security Policy](./security.md#content-security-policy).
1010

11-
This functionality in Ajv v7 supersedes deprecated package ajv-pack that can be used with Ajv v6. All schemas, including those with recursive references, formats and user-defined keywords are supported, with few [limitations](#configuration-and-limitations).
11+
This functionality in Ajv v7 supersedes the deprecated package ajv-pack that can be used with Ajv v6. All schemas, including those with recursive references, formats and user-defined keywords are supported, with few [limitations](#configuration-and-limitations).
1212

13-
## Usage with CLI
13+
## Two-step process
1414

15-
In most cases you would use this functionality via [ajv-cli](https://github.com/ajv-validator/ajv-cli) (>= 4.0.0) to generate module that exports validation function.
15+
The **first step** is to **generate** the standalone validation function code. This is done at compile/build time of your project and the output is a generated JS file. The **second step** is to **use** the generated JS validation function.
1616

17+
There are two methods to generate the code, using either the Ajv CLI or the Ajv JS library. There are also a few different options that can be passed when generating code. Below is just a highlight of a few options:
18+
19+
- Set the `code.source` (JS) value to true or use the `compile` (CLI) command to generate standalone code.
20+
- The standalone code can be generated in either ES5 or ES6, it defaults to ES6. Set the `code.es5` (JS) value to true or pass the `--code-es5` (CLI) flag to true if you want ES5 code.
21+
- The standalone code can be generated in either CJS (module.export & require) or ESM (exports & import), it defaults to CJS. Set the `code.esm` (JS) value to true or pass the `--code-esm` (CLI) flag if you want ESM exported code.
22+
23+
Note that the way the function is exported, differs if you are exporting a single or multiple schemas. See examples below.
24+
25+
### Generating function(s) using CLI
26+
27+
In most cases you would use this functionality via [ajv-cli](https://github.com/ajv-validator/ajv-cli) (>= 4.0.0) to generate the standalone code that exports the validation function. See [ajv-cli](https://github.com/ajv-validator/ajv-cli#compile-schemas) docs and the [cli options](https://github.com/ajv-validator/ajv-cli#ajv-options) for additional information.
28+
29+
#### Using the defaults - ES6 and CJS exports
1730
```sh
1831
npm install -g ajv-cli
1932
ajv compile -s schema.json -o validate_schema.js
2033
```
2134

22-
`validate_schema.js` will contain the module exporting validation function that can be bundled into your application.
23-
24-
See [ajv-cli](https://github.com/ajv-validator/ajv-cli) docs for additional information.
25-
26-
## Usage from code
35+
### Generating using the JS library
2736

37+
Install the package, version >= v7.0.0:
2838
```sh
2939
npm install ajv
3040
```
3141

42+
#### Generating functions(s) for a single schema using the JS library - ES6 and CJS exports
43+
3244
```javascript
33-
const Ajv = require("ajv") // version >= v7.0.0
34-
const ajv = new Ajv({code: {source: true}}) // this option is required to generate standalone code
45+
const fs = require("fs")
46+
const path = require("path")
47+
const Ajv = require("ajv")
3548
const standaloneCode = require("ajv/dist/standalone").default
3649

3750
const schema = {
38-
$id: "https://example.com/object.json",
51+
$id: "https://example.com/bar.json",
52+
$schema: "http://json-schema.org/draft-07/schema#",
3953
type: "object",
4054
properties: {
41-
foo: {
42-
type: "string",
43-
pattern: "^[a-z]+$",
44-
},
55+
bar: {type: "string"},
4556
},
57+
"required": ["bar"]
4658
}
4759

48-
// 1. generate module with a single default export (CommonJS and ESM compatible):
60+
// The generated code will have a default export:
61+
// `module.exports = <validateFunctionCode>;module.exports.default = <validateFunctionCode>;`
62+
const ajv = new Ajv({code: {source: true}})
4963
const validate = ajv.compile(schema)
5064
let moduleCode = standaloneCode(ajv, validate)
5165

52-
// 2. pass map of schema IDs to generate multiple exports,
53-
// it avoids code duplication if schemas are mutually recursive or have some share elements:
54-
let moduleCode = standaloneCode(ajv, {
55-
validateObject: "https://example.com/object.json",
56-
})
66+
// Now you can write the module code to file
67+
fs.writeFileSync(path.join(__dirname, "../consume/validate-cjs.js"), moduleCode)
68+
```
69+
70+
#### Generating functions(s) for multiple schemas using the JS library - ES6 and CJS exports
71+
72+
```javascript
73+
const fs = require("fs")
74+
const path = require("path")
75+
const Ajv = require("ajv")
76+
const standaloneCode = require("ajv/dist/standalone").default
5777

58-
// 3. or generate module with all schemas added to the instance (excluding meta-schemas),
59-
// export names would use schema IDs (or keys passed to addSchema method):
78+
const schemaFoo = {
79+
$id: "#/definitions/Foo",
80+
$schema: "http://json-schema.org/draft-07/schema#",
81+
type: "object",
82+
properties: {
83+
foo: {"$ref": "#/definitions/Bar"}
84+
}
85+
}
86+
const schemaBar = {
87+
$id: "#/definitions/Bar",
88+
$schema: "http://json-schema.org/draft-07/schema#",
89+
type: "object",
90+
properties: {
91+
bar: {type: "string"},
92+
},
93+
"required": ["bar"]
94+
}
95+
96+
// For CJS, it generates an exports array, will generate
97+
// `exports["#/definitions/Foo"] = ...;exports["#/definitions/Bar"] = ... ;`
98+
const ajv = new Ajv({schemas: [schemaFoo, schemaBar], code: {source: true}})
6099
let moduleCode = standaloneCode(ajv)
61100

62-
// now you can
63-
// write module code to file
101+
// Now you can write the module code to file
102+
fs.writeFileSync(path.join(__dirname, "../consume/validate-cjs.js"), moduleCode)
103+
```
104+
105+
#### Generating functions(s) for multiple schemas using the JS library - ES6 and ESM exports
106+
107+
```javascript
64108
const fs = require("fs")
65109
const path = require("path")
66-
fs.writeFileSync(path.join(__dirname, "/validate.js"), moduleCode)
110+
const Ajv = require("ajv")
111+
const standaloneCode = require("ajv/dist/standalone").default
112+
113+
const schemaFoo = {
114+
$id: "#/definitions/Foo",
115+
$schema: "http://json-schema.org/draft-07/schema#",
116+
type: "object",
117+
properties: {
118+
foo: {"$ref": "#/definitions/Bar"}
119+
}
120+
}
121+
const schemaBar = {
122+
$id: "#/definitions/Bar",
123+
$schema: "http://json-schema.org/draft-07/schema#",
124+
type: "object",
125+
properties: {
126+
bar: {type: "string"},
127+
},
128+
"required": ["bar"]
129+
}
67130

68-
// ... or require module from string
69-
const requireFromString = require("require-from-string")
70-
const standaloneValidate = requireFromString(moduleCode) // for a single default export
131+
// For ESM, the export name needs to be a valid export name, it can not be `export const #/definitions/Foo = ...;` so we
132+
// need to provide a mapping between a valid name and the $id field. Below will generate
133+
// `export const Foo = ...;export const Bar = ...;`
134+
// This mapping would not have been needed if the `$ids` was just `Bar` and `Foo` instead of `#/definitions/Foo`
135+
// and `#/definitions/Bar` respectfully
136+
const ajv = new Ajv({schemas: [schemaFoo, schemaBar], code: {source: true, esm: true}})
137+
let moduleCode = standaloneCode(ajv, {
138+
"Foo": "#/definitions/Foo",
139+
"Bar": "#/definitions/Bar"
140+
})
141+
142+
// Now you can write the module code to file
143+
fs.writeFileSync(path.join(__dirname, "../consume/validate-esm.mjs"), moduleCode)
144+
```
145+
146+
::: warning ESM name mapping
147+
The ESM version only requires the mapping if the ids are not valid export names. If the $ids were just the
148+
`Foo` and `Bar` instead of `#/definitions/Foo` and `#/definitions/Bar` then the mapping would not be needed.
149+
:::
150+
151+
152+
## Using the validation function(s)
153+
154+
### Validating a single schemas using the JS library - ES6 and CJS
155+
156+
```javascript
157+
const Bar = require('./validate-cjs')
158+
159+
const barPass = {
160+
bar: "something"
161+
}
162+
163+
const barFail = {
164+
// bar: "something" // <= empty/omitted property that is required
165+
}
166+
167+
let validateBar = Bar
168+
if (!validateBar(barPass))
169+
console.log("ERRORS 1:", validateBar.errors) //Never reaches this because valid
170+
171+
if (!validateBar(barFail))
172+
console.log("ERRORS 2:", validateBar.errors) //Errors array gets logged
173+
```
174+
175+
### Validating multiple schemas using the JS library - ES6 and CJS
176+
177+
```javascript
178+
const validations = require('./validate-cjs')
179+
180+
const fooPass = {
181+
foo: {
182+
bar: "something"
183+
}
184+
}
185+
186+
const fooFail = {
187+
foo: {
188+
// bar: "something" // <= empty/omitted property that is required
189+
}
190+
}
191+
192+
let validateFoo = validations["#/definitions/Foo"];
193+
if (!validateFoo(fooPass))
194+
console.log("ERRORS 1:", validateFoo.errors); //Never reaches this because valid
195+
196+
if (!validateFoo(fooFail))
197+
console.log("ERRORS 2:", validateFoo.errors); //Errors array gets logged
198+
199+
```
200+
201+
### Validating multiple schemas using the JS library - ES6 and ESM
202+
203+
```javascript
204+
import {Foo, Bar} from './validate-multiple-esm.mjs';
205+
206+
const fooPass = {
207+
foo: {
208+
bar: "something"
209+
}
210+
}
211+
212+
const fooFail = {
213+
foo: {
214+
// bar: "something" // bar: "something" <= empty properties
215+
}
216+
}
217+
218+
let validateFoo = Foo;
219+
if (!validateFoo(fooPass))
220+
console.log("ERRORS 1:", validateFoo.errors); //Never reaches here because valid
221+
222+
if (!validateFoo(fooFail))
223+
console.log("ERRORS 2:", validateFoo.errors); //Errors array gets logged
71224
```
72225

226+
73227
### Requirement at runtime
74228

75-
To run the standalone generated functions, the Ajv package should still be a run-time dependency for most schemas, but generated modules can only depend on code in [runtime](https://github.com/ajv-validator/ajv/tree/master/lib/runtime) folder, so the whole Ajv will not be included in the bundle (or executed) if you require the modules with standalone validation code from your application code.
229+
One of the main reason for using the standalone mode is to start applications faster to avoid runtime schema compilation.
230+
231+
The standalone generated functions still has a dependency on the Ajv. Specifically on the code in the [runtime](https://github.com/ajv-validator/ajv/tree/master/lib/runtime) folder of the package.
232+
233+
Completely isolated validation functions can be generated if desired (won't be for most use cases). Run the generated code through a bundler like ES Build to create completely isolated validation functions that can be imported/required without any dependency on Ajv. This is also not needed if your project is already using a bundler.
76234

77235
## Configuration and limitations
78236

79237
To support standalone code generation:
80238

81-
- Ajv option `source.code` must be set to `true`
239+
- Ajv option `code.source` must be set to `true`
82240
- only `code` and `macro` user-defined keywords are supported (see [User defined keywords](./keywords.md)).
83241
- when `code` keywords define variables in shared scope using `gen.scopeValue`, they must provide `code` property with the code snippet. See source code of pre-defined keywords for examples in [vocabularies folder](https://github.com/ajv-validator/ajv/blob/master/lib/vocabularies).
84242
- if formats are used in standalone code, ajv option `code.formats` should contain the code snippet that will evaluate to an object with all used format definitions - it can be a call to `require("...")` with the correct path (relative to the location of saved module):
@@ -93,6 +251,5 @@ const ajv = new Ajv({
93251
formats: _`require("./my-formats")`,
94252
},
95253
})
96-
```
97254

98255
If you only use formats from [ajv-formats](https://github.com/ajv-validator/ajv-formats) this option will be set by this package automatically.

0 commit comments

Comments
 (0)
Please sign in to comment.