@ata-project/unplugin 0.5.0 → 0.6.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 CHANGED
@@ -179,7 +179,8 @@ away this way.
179
179
 
180
180
  Where the code only ever calls `isValidObject()` or `isValidJSON()`, the plugin
181
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.
182
+ a small app that only asks for a boolean bundles to 2.6 KB gzipped, against 12.1 KB with
183
+ the full wrapper and 102.0 KB for the runtime, on 0.5.0 with ata-validator 1.40.0.
183
184
 
184
185
  The replacement answers `validate()`, `isValidObject()`, `validateJSON()` and
185
186
  `isValidJSON()` as a `Validator` with default options does: the same verdicts,
@@ -194,7 +195,11 @@ otherwise:
194
195
  - `Validator` is a named import from `ata-validator`;
195
196
  - the schema argument is an object literal, a top-level `const` bound to one,
196
197
  the default import of a relative `.json` file, or `defineSchema(...)` around
197
- one of those;
198
+ one of those; calls to the `t` builder from `ata-validator/t` whose arguments are
199
+ such values are run at build time, since the builder returns plain JSON Schema,
200
+ and are marked pure so a schema only the replaced call used leaves the bundle
201
+ with the builder (`t.refine` and `t.recursive`, which take a function, stay on
202
+ the runtime);
198
203
  - there is no second argument, or it is `{ useDefaults: false }` (or
199
204
  `{ useDefaults: true }`), written as a literal or a top-level `const`; that
200
205
  one option needs ata-validator 1.37.0, and older versions keep such calls on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ata-project/unplugin",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
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",
@@ -29,6 +29,9 @@ import MagicString from 'magic-string'
29
29
 
30
30
  const METHODS = new Set(['validate', 'isValidObject', 'validateJSON', 'isValidJSON'])
31
31
  const NOT_STATIC = Symbol('not static')
32
+ // The builders of ata-validator/t that take only values. refine and
33
+ // recursive take a function and stay on the runtime.
34
+ const T_PURE = new Set(['string', 'number', 'integer', 'boolean', 'null', 'literal', 'const', 'enum', 'array', 'tuple', 'record', 'object', 'optional', 'union', 'intersect', 'ref', 'any', 'unknown', 'never', 'pick', 'omit', 'partial', 'required', 'composite'])
32
35
 
33
36
  // Keys that hold child nodes. Types are left out on purpose: they are erased
34
37
  // before anything runs, so a name used in a type annotation says nothing about
@@ -134,9 +137,30 @@ function staticValue(node, ctx, seen = new Set()) {
134
137
  seen.delete(node.name)
135
138
  return v
136
139
  }
137
- case 'CallExpression':
140
+ case 'CallExpression': {
138
141
  if (node.callee.type === 'Identifier' && node.callee.name === ctx.defineSchema && node.arguments.length === 1) return staticValue(node.arguments[0], ctx, seen)
142
+ // `t.object(...)` and the rest of the builder from ata-validator/t. It
143
+ // returns plain JSON Schema and holds no state, so a call whose
144
+ // arguments the build can read gives here what it gives at run time.
145
+ // The builders that take a function are left out.
146
+ const c = node.callee
147
+ if (ctx.t && ctx.tName && c.type === 'MemberExpression' && !c.computed && c.object.type === 'Identifier' &&
148
+ c.object.name === ctx.tName && ctx.declared.get(ctx.tName) === 1 && c.property.type === 'Identifier' && T_PURE.has(c.property.name) &&
149
+ typeof ctx.t[c.property.name] === 'function') {
150
+ const args = []
151
+ for (const a of node.arguments) {
152
+ if (a.type === 'SpreadElement') return NOT_STATIC
153
+ const v = staticValue(a, ctx, seen)
154
+ if (v === NOT_STATIC) return NOT_STATIC
155
+ args.push(v)
156
+ }
157
+ let v
158
+ try { v = ctx.t[c.property.name](...args) } catch { return NOT_STATIC }
159
+ ctx.tCalls.add(node)
160
+ return v
161
+ }
139
162
  return NOT_STATIC
163
+ }
140
164
  }
141
165
  return NOT_STATIC
142
166
  }
@@ -244,6 +268,15 @@ export function compileAway(code, id, ata) {
244
268
  }
245
269
  }
246
270
  if (!validatorName) return null
271
+ let tName = null
272
+ for (const stmt of ast.program.body) {
273
+ if (stmt.type !== 'ImportDeclaration' || stmt.source.value !== 'ata-validator/t' || stmt.importKind === 'type') continue
274
+ for (const sp of stmt.specifiers) {
275
+ if (sp.type !== 'ImportSpecifier' || sp.importKind === 'type') continue
276
+ const imported = sp.imported.type === 'Identifier' ? sp.imported.name : sp.imported.value
277
+ if (imported === 't') tName = sp.local.name
278
+ }
279
+ }
247
280
  let withKeywords = null
248
281
  for (const stmt of ast.program.body) {
249
282
  if (stmt.type !== 'ImportDeclaration' || stmt.source.value !== '@ata-project/keywords' || stmt.importKind === 'type') continue
@@ -256,7 +289,7 @@ export function compileAway(code, id, ata) {
256
289
 
257
290
  const { declared, refs } = scan(ast)
258
291
  if (declared.get(validatorName) !== 1) return null
259
- const ctx = { declared, refs, constants: constantsOf(ast, id), defineSchema, withKeywords, extendChecks: ata.compiledExtendChecks === true }
292
+ const ctx = { declared, refs, constants: constantsOf(ast, id), defineSchema, withKeywords, extendChecks: ata.compiledExtendChecks === true, t: ata.t || null, tName, tCalls: new Set() }
260
293
 
261
294
  const s = new MagicString(code)
262
295
  const modules = []
@@ -310,6 +343,10 @@ export function compileAway(code, id, ata) {
310
343
  const clause = [def ? text(def) : null, named.length ? `{ ${named.map(text).join(', ')} }` : null].filter(Boolean).join(', ')
311
344
  s.overwrite(importDecl.start, importDecl.end, `import ${clause} from ${code.slice(importDecl.source.start, importDecl.source.end)}`)
312
345
  }
346
+ // The builder calls read here are pure, and marked so: a schema constant
347
+ // only the replaced call used, and the builder with it, can then leave the
348
+ // bundle.
349
+ for (const call of ctx.tCalls) s.appendLeft(call.start, '/*#__PURE__*/ ')
313
350
  if (wrappers.verdict) s.prepend(`import { fromCompiledVerdict as __ataFromCompiledVerdict } from 'ata-validator/compiled-verdict';\n`)
314
351
  if (wrappers.full) s.prepend(`import { fromCompiled as __ataFromCompiled } from 'ata-validator/compiled';\n`)
315
352
  s.appendLeft(lastImportEnd, '\n' + modules.join(''))
package/src/core.js CHANGED
@@ -74,6 +74,12 @@ async function loadAta() {
74
74
  // withKeywords from @ata-project/keywords registers one; from 1.40.0. Asked
75
75
  // of a wrapper itself, so an ata-validator without it keeps such calls on
76
76
  // the runtime.
77
+ // The ata-validator/t builder, run at build time on calls whose arguments
78
+ // the build can read.
79
+ let t = null
80
+ if (canCompileAway) {
81
+ try { const m = await import('ata-validator/t'); t = (m.default ?? m).t ?? m.t ?? null } catch { t = null }
82
+ }
77
83
  let compiledExtendChecks = false
78
84
  if (canCompileAway) {
79
85
  try {
@@ -92,6 +98,7 @@ async function loadAta() {
92
98
  compiledOptions: canCompileAway && Array.isArray(build.compiledOptions) ? build.compiledOptions : [],
93
99
  compiledVerdict,
94
100
  compiledExtendChecks,
101
+ t,
95
102
  }
96
103
  }
97
104