@ata-project/unplugin 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/package.json +4 -3
- package/src/compile-away.js +43 -10
- package/src/core.js +21 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +7 -2
package/README.md
CHANGED
|
@@ -6,6 +6,11 @@ The generated module imports nothing. A small schema compiles to about 1.3 KB gz
|
|
|
6
6
|
|
|
7
7
|
Schemas can be authored as `.json`, `.js` or `.ts`.
|
|
8
8
|
|
|
9
|
+
It also compiles away `new Validator(schema)` in your own code wherever the schema
|
|
10
|
+
is known at build time and the result is the same, so the runtime compiler stays
|
|
11
|
+
out of the bundle: for the test entry, 122.5 KB gzipped becomes 16.7 KB. See
|
|
12
|
+
[compileAway](#compileaway-keep-new-validator-drop-the-compiler).
|
|
13
|
+
|
|
9
14
|
## Install
|
|
10
15
|
|
|
11
16
|
```bash
|
|
@@ -145,12 +150,12 @@ Other sources (`.json` without the `.schema` suffix, `.js`, `.ts`) produce
|
|
|
145
150
|
| `nameFromFile` | file name in PascalCase | Type name for schemas without `title` or `$id`. |
|
|
146
151
|
| `root` | from the bundler | Where the globs resolve from. Vite's root, Webpack's and Rspack's `context` and esbuild's `absWorkingDir` are read; Rollup and Rolldown need it passed. |
|
|
147
152
|
| `alias` | Vite's `resolve.alias` | Import aliases inside `.ts` schema files. tsconfig `paths` work without configuration. |
|
|
148
|
-
| `compileAway` | `
|
|
153
|
+
| `compileAway` | `true` | Replace `new Validator(schema)` in your code with a validator compiled at build time, where that gives the same results. See below. Needs ata-validator 1.36.0; with an older version the runtime stays, and a warning is printed only if you set the option yourself. |
|
|
149
154
|
|
|
150
155
|
## compileAway: keep `new Validator`, drop the compiler
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
Code written against the runtime API is compiled at build time without being
|
|
158
|
+
changed. This is on by default; `compileAway: false` turns it off.
|
|
154
159
|
|
|
155
160
|
```js
|
|
156
161
|
import { Validator } from 'ata-validator'
|
|
@@ -168,10 +173,14 @@ The plugin puts a compiled validator in place of the `new Validator(...)` call,
|
|
|
168
173
|
and once nothing else in the file uses the `ata-validator` import, the import
|
|
169
174
|
goes with it, so the runtime compiler is not in the bundle. For the three-schema
|
|
170
175
|
entry in `test/fixtures/compile-away`, one of them with defaults, a minified Vite
|
|
171
|
-
library build is
|
|
172
|
-
ata-validator 1.
|
|
176
|
+
library build is 122.5 KB gzipped without it and 16.7 KB with it (gzip level 9), on
|
|
177
|
+
ata-validator 1.39.2. Across the 977 schemas of SchemaStore, 725 can be compiled
|
|
173
178
|
away this way.
|
|
174
179
|
|
|
180
|
+
Where the code only ever calls `isValidObject()` or `isValidJSON()`, the plugin
|
|
181
|
+
uses a smaller wrapper without the error machinery (needs ata-validator 1.40.0):
|
|
182
|
+
a small app that only asks for a boolean bundles to 2.1 KB gzipped.
|
|
183
|
+
|
|
175
184
|
The replacement answers `validate()`, `isValidObject()`, `validateJSON()` and
|
|
176
185
|
`isValidJSON()` as a `Validator` with default options does: the same verdicts,
|
|
177
186
|
defaults filled in, `data` on success, and the same errors, enriched the same
|
|
@@ -192,7 +201,9 @@ otherwise:
|
|
|
192
201
|
the runtime;
|
|
193
202
|
- the result goes into a `const` that is not exported and is only used as
|
|
194
203
|
`name.validate(...)`, `name.isValidObject(...)`, `name.validateJSON(...)` or
|
|
195
|
-
`name.isValidJSON(...)`;
|
|
204
|
+
`name.isValidJSON(...)`; the call may be wrapped in `withKeywords(...)` from
|
|
205
|
+
`@ata-project/keywords`, which then registers its keywords on the compiled
|
|
206
|
+
validator as it does on a `Validator` (needs ata-validator 1.40.0);
|
|
196
207
|
- ata-validator can compile the schema to the same results. It declines custom
|
|
197
208
|
`errorMessage`s and shapes its code generator cannot express, which the
|
|
198
209
|
runtime answers with its interpreted engine.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ata-project/unplugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Build-time JSON Schema for Vite, Webpack, Rollup, Rolldown, esbuild and Rspack. compileAway takes the runtime compiler out of the bundle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -77,8 +77,9 @@
|
|
|
77
77
|
"unplugin": "^3.3.0"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
+
"@ata-project/keywords": "^0.3.3",
|
|
80
81
|
"@rspack/core": "^1.7.0",
|
|
81
|
-
"ata-validator": "^1.
|
|
82
|
+
"ata-validator": "^1.40.0",
|
|
82
83
|
"esbuild": "^0.28.0",
|
|
83
84
|
"rolldown": "^1.2.8",
|
|
84
85
|
"rollup": "^4.60.0",
|
package/src/compile-away.js
CHANGED
|
@@ -163,26 +163,44 @@ function constantsOf(ast, id) {
|
|
|
163
163
|
// `const name = new Validator(...)`, declared once, not exported, and used only
|
|
164
164
|
// as `name.<method>(...)` with a supported method.
|
|
165
165
|
function replaceableBinding(newExpr, ctx) {
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
let decl = newExpr.__parent
|
|
167
|
+
// `withKeywords(new Validator(schema))` from @ata-project/keywords: the
|
|
168
|
+
// wrapper takes the keywords' check the way a Validator does, so the inner
|
|
169
|
+
// call is replaced and withKeywords stays to register the check on it.
|
|
170
|
+
if (decl && decl.type === 'CallExpression' && ctx.withKeywords && ctx.extendChecks &&
|
|
171
|
+
decl.callee.type === 'Identifier' && decl.callee.name === ctx.withKeywords &&
|
|
172
|
+
ctx.declared.get(ctx.withKeywords) === 1 && decl.arguments.length === 1 && decl.arguments[0] === newExpr) {
|
|
173
|
+
decl = decl.__parent
|
|
174
|
+
if (!decl || decl.type !== 'VariableDeclarator' || decl.init !== newExpr.__parent) return null
|
|
175
|
+
}
|
|
176
|
+
if (!decl || decl.type !== 'VariableDeclarator' || decl.id.type !== 'Identifier') return null
|
|
177
|
+
if (decl.init !== newExpr && decl.init !== newExpr.__parent) return null
|
|
168
178
|
const list = decl.__parent
|
|
169
179
|
if (!list || list.type !== 'VariableDeclaration' || list.kind !== 'const') return null
|
|
170
180
|
if (list.__parent && list.__parent.type === 'ExportNamedDeclaration') return null
|
|
171
181
|
const name = decl.id.name
|
|
172
182
|
if (ctx.declared.get(name) !== 1) return null
|
|
183
|
+
const methods = new Set()
|
|
173
184
|
for (const r of ctx.refs) {
|
|
174
185
|
if (r.node.name !== name) continue
|
|
175
186
|
const p = r.parent
|
|
176
187
|
const member = p && (p.type === 'MemberExpression' || p.type === 'OptionalMemberExpression') && r.key === 'object' && !p.computed && p.property.type === 'Identifier' && METHODS.has(p.property.name)
|
|
177
188
|
const called = member && p.__parent && (p.__parent.type === 'CallExpression' || p.__parent.type === 'OptionalCallExpression') && p.__parent.callee === p
|
|
178
189
|
if (!called) return null
|
|
190
|
+
methods.add(p.property.name)
|
|
179
191
|
}
|
|
180
|
-
return name
|
|
192
|
+
return { name, methods }
|
|
181
193
|
}
|
|
182
194
|
|
|
183
|
-
|
|
195
|
+
// Code that only asks for a boolean never reads an error.
|
|
196
|
+
const VERDICT_METHODS = new Set(['isValidObject', 'isValidJSON'])
|
|
197
|
+
const verdictOnly = (methods) => [...methods].every((m) => VERDICT_METHODS.has(m))
|
|
198
|
+
|
|
199
|
+
// A verdict-only module hands out isValid alone, so the error function inside
|
|
200
|
+
// it is unreferenced and the bundler drops it.
|
|
201
|
+
function inlineModule(src, index, verdict) {
|
|
184
202
|
const body = src.split('\n').filter((l) => !/^export\s/.test(l)).join('\n')
|
|
185
|
-
return `const __ataCompiled${index} = (() => {\n${body}\nreturn { validate, isValid };\n})();\n`
|
|
203
|
+
return `const __ataCompiled${index} = (() => {\n${body}\nreturn ${verdict ? '{ isValid }' : '{ validate, isValid }'};\n})();\n`
|
|
186
204
|
}
|
|
187
205
|
|
|
188
206
|
// The options of `new Validator(schema, options)` as a third argument for
|
|
@@ -226,14 +244,24 @@ export function compileAway(code, id, ata) {
|
|
|
226
244
|
}
|
|
227
245
|
}
|
|
228
246
|
if (!validatorName) return null
|
|
247
|
+
let withKeywords = null
|
|
248
|
+
for (const stmt of ast.program.body) {
|
|
249
|
+
if (stmt.type !== 'ImportDeclaration' || stmt.source.value !== '@ata-project/keywords' || stmt.importKind === 'type') continue
|
|
250
|
+
for (const sp of stmt.specifiers) {
|
|
251
|
+
if (sp.type !== 'ImportSpecifier' || sp.importKind === 'type') continue
|
|
252
|
+
const imported = sp.imported.type === 'Identifier' ? sp.imported.name : sp.imported.value
|
|
253
|
+
if (imported === 'withKeywords') withKeywords = sp.local.name
|
|
254
|
+
}
|
|
255
|
+
}
|
|
229
256
|
|
|
230
257
|
const { declared, refs } = scan(ast)
|
|
231
258
|
if (declared.get(validatorName) !== 1) return null
|
|
232
|
-
const ctx = { declared, refs, constants: constantsOf(ast, id), defineSchema }
|
|
259
|
+
const ctx = { declared, refs, constants: constantsOf(ast, id), defineSchema, withKeywords, extendChecks: ata.compiledExtendChecks === true }
|
|
233
260
|
|
|
234
261
|
const s = new MagicString(code)
|
|
235
262
|
const modules = []
|
|
236
263
|
const done = new Set()
|
|
264
|
+
const wrappers = { full: false, verdict: false }
|
|
237
265
|
for (const r of refs) {
|
|
238
266
|
if (r.node.name !== validatorName) continue
|
|
239
267
|
const expr = r.parent
|
|
@@ -244,15 +272,19 @@ export function compileAway(code, id, ata) {
|
|
|
244
272
|
optionsArg = compiledOptionsArg(expr.arguments[1], ctx, supportedOptions)
|
|
245
273
|
if (optionsArg === NOT_STATIC) continue
|
|
246
274
|
}
|
|
247
|
-
|
|
275
|
+
const binding = replaceableBinding(expr, ctx)
|
|
276
|
+
if (!binding) continue
|
|
248
277
|
const schema = staticValue(expr.arguments[0], ctx)
|
|
249
278
|
if (schema === NOT_STATIC || schema === null || typeof schema !== 'object' || Array.isArray(schema)) continue
|
|
250
279
|
let src = null
|
|
251
280
|
try { src = compiledModuleFor(schema, { format: 'esm' }) } catch { src = null }
|
|
252
281
|
if (!src) continue
|
|
253
282
|
const index = modules.length
|
|
254
|
-
|
|
255
|
-
|
|
283
|
+
const verdict = ata.compiledVerdict === true && verdictOnly(binding.methods)
|
|
284
|
+
modules.push(inlineModule(src, index, verdict))
|
|
285
|
+
const wrap = verdict ? '__ataFromCompiledVerdict' : '__ataFromCompiled'
|
|
286
|
+
if (verdict) wrappers.verdict = true; else wrappers.full = true
|
|
287
|
+
s.overwrite(expr.start, expr.end, `${wrap}(__ataCompiled${index}, ${JSON.stringify(compiledSchemaFor(schema))}${optionsArg ? ', ' + optionsArg : ''})`)
|
|
256
288
|
done.add(expr)
|
|
257
289
|
}
|
|
258
290
|
if (done.size === 0) return null
|
|
@@ -278,7 +310,8 @@ export function compileAway(code, id, ata) {
|
|
|
278
310
|
const clause = [def ? text(def) : null, named.length ? `{ ${named.map(text).join(', ')} }` : null].filter(Boolean).join(', ')
|
|
279
311
|
s.overwrite(importDecl.start, importDecl.end, `import ${clause} from ${code.slice(importDecl.source.start, importDecl.source.end)}`)
|
|
280
312
|
}
|
|
281
|
-
s.prepend(`import {
|
|
313
|
+
if (wrappers.verdict) s.prepend(`import { fromCompiledVerdict as __ataFromCompiledVerdict } from 'ata-validator/compiled-verdict';\n`)
|
|
314
|
+
if (wrappers.full) s.prepend(`import { fromCompiled as __ataFromCompiled } from 'ata-validator/compiled';\n`)
|
|
282
315
|
s.appendLeft(lastImportEnd, '\n' + modules.join(''))
|
|
283
316
|
return { code: s.toString(), map: s.generateMap({ hires: true, source: id, includeContent: true }), replaced: done.size }
|
|
284
317
|
}
|
package/src/core.js
CHANGED
|
@@ -41,7 +41,7 @@ const DEFAULT_OPTIONS = {
|
|
|
41
41
|
format: 'esm',
|
|
42
42
|
abortEarly: false,
|
|
43
43
|
types: true,
|
|
44
|
-
compileAway:
|
|
44
|
+
compileAway: true,
|
|
45
45
|
nameFromFile: (file) => {
|
|
46
46
|
const base = path.basename(file, path.extname(file)).replace(/\.schema$/i, '')
|
|
47
47
|
return pascal(base)
|
|
@@ -64,6 +64,24 @@ async function loadAta() {
|
|
|
64
64
|
// compiledModuleFor exists from ata-validator 1.35.0; compile-away needs it
|
|
65
65
|
// and is skipped with a warning when it is missing.
|
|
66
66
|
const canCompileAway = typeof build.compiledModuleFor === 'function' && typeof build.compiledSchemaFor === 'function'
|
|
67
|
+
// The verdict-only wrapper, from ata-validator 1.40.0: code that never reads
|
|
68
|
+
// errors gets it and the error pipeline stays out of the bundle.
|
|
69
|
+
let compiledVerdict = false
|
|
70
|
+
if (canCompileAway) {
|
|
71
|
+
try { compiledVerdict = typeof (await import('ata-validator/compiled-verdict')).fromCompiledVerdict === 'function' } catch { compiledVerdict = false }
|
|
72
|
+
}
|
|
73
|
+
// Whether the wrappers take a check registered with _extendChecks, as
|
|
74
|
+
// withKeywords from @ata-project/keywords registers one; from 1.40.0. Asked
|
|
75
|
+
// of a wrapper itself, so an ata-validator without it keeps such calls on
|
|
76
|
+
// the runtime.
|
|
77
|
+
let compiledExtendChecks = false
|
|
78
|
+
if (canCompileAway) {
|
|
79
|
+
try {
|
|
80
|
+
const c = await import('ata-validator/compiled')
|
|
81
|
+
const fromCompiled = (c.default ?? c).fromCompiled ?? c.fromCompiled
|
|
82
|
+
compiledExtendChecks = typeof fromCompiled({ validate: () => ({ valid: true, errors: [] }), isValid: () => true }, {}, { useDefaults: false })._extendChecks === 'function'
|
|
83
|
+
} catch { compiledExtendChecks = false }
|
|
84
|
+
}
|
|
67
85
|
return {
|
|
68
86
|
...api,
|
|
69
87
|
toStandaloneModule: build.toStandaloneModule,
|
|
@@ -72,6 +90,8 @@ async function loadAta() {
|
|
|
72
90
|
// The Validator options fromCompiled() reproduces, from ata-validator
|
|
73
91
|
// 1.37.0; before it, only calls without options are replaced.
|
|
74
92
|
compiledOptions: canCompileAway && Array.isArray(build.compiledOptions) ? build.compiledOptions : [],
|
|
93
|
+
compiledVerdict,
|
|
94
|
+
compiledExtendChecks,
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
|
package/src/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface Options {
|
|
|
27
27
|
* only used through validate(), isValidObject(), validateJSON() and
|
|
28
28
|
* isValidJSON(), so the runtime compiler stays out of the bundle. Results are
|
|
29
29
|
* the ones the runtime gives. Needs ata-validator 1.35.0 or later.
|
|
30
|
-
* Default: `false
|
|
30
|
+
* Default: `true`; `false` turns it off.
|
|
31
31
|
*/
|
|
32
32
|
compileAway?: boolean
|
|
33
33
|
}
|
package/src/index.js
CHANGED
|
@@ -34,8 +34,11 @@ export const unpluginFactory = (userOptions = {}) => {
|
|
|
34
34
|
// compileAway: `new Validator(schema)` with a schema the build can read
|
|
35
35
|
// becomes a validator compiled here, and the runtime compiler leaves the
|
|
36
36
|
// bundle. See src/compile-away.js for what is replaced and what is not.
|
|
37
|
+
// On unless the caller turned it off. It replaces a call only where the
|
|
38
|
+
// compiled validator answers exactly as the runtime would, so being on by
|
|
39
|
+
// default can cost a saving but not change a result.
|
|
37
40
|
transformInclude(id) {
|
|
38
|
-
if (
|
|
41
|
+
if (userOptions.compileAway === false) return false
|
|
39
42
|
const file = id.split('?')[0]
|
|
40
43
|
return SOURCE.test(file) && !file.split(/[\\/]/).includes('node_modules')
|
|
41
44
|
},
|
|
@@ -44,7 +47,9 @@ export const unpluginFactory = (userOptions = {}) => {
|
|
|
44
47
|
if (!code.includes('ata-validator')) return null
|
|
45
48
|
const api = await session.api()
|
|
46
49
|
if (!api.compiledModuleFor) {
|
|
47
|
-
|
|
50
|
+
// Only a caller who asked for it hears that it could not happen; on
|
|
51
|
+
// by default, an older ata-validator just keeps the runtime.
|
|
52
|
+
if (userOptions.compileAway === true && !session.warnedCompileAway) {
|
|
48
53
|
session.warnedCompileAway = true
|
|
49
54
|
session.logger?.warn?.('[unplugin-ata] compileAway needs ata-validator 1.36.0 or later; nothing was replaced')
|
|
50
55
|
}
|