malli 2025-04-10

Hi! Not super familiar with Malli, but I’ve been tasked with improving the performance of a CLJS app and one big thing I noticed is that we’re doing a lot of schema parsing and validation on startup. Any tips for moving that to build time? Is it just as simple as writing a macro which replaces i.e. a vector syntax schema with the result of calling Malli.core/schema on it? Or convert it to map syntax? Or would I need to somehow disable runtime validation in release builds?

What are the schemas used for?

Validating data from APIs/Backend

Is it even an option to disable validation?

It sounds like an important part of your app

Oh definitely not! I mean the parsing and validation of the schemas themselves, looking at a performance profile of the app it seems like we're spending a lot of time transforming schemas from unverified vector syntax into Schema objects which actually get placed in the registry? Unless I'm misunderstanding something? And because they're constant, we could do that at build time potentially?

Thanks for clarifying.

We were thinking of two definitions of validation 🙂

Schemas cannot be AOT compiled afaik. Maybe there's a speedup with the map syntax. I think that's one of its use cases.

Yeah I had seen that but I thought there may be a way to transform even the map syntax into maybe a Schema object or validation function at build time? Would a macro which calls schema or validator and replaces the vector/map syntax schema literal with the result not achieve this?

No, you'd be inserting JVM objects into JS IIUC.

Too bad! At least moving to map syntax should help

I believe spec is built that way, that's one of the tradeoffs of malli not using macros.

nvm spec is not built that way.

I think it would create other problems like bundle size.

Spec offloads argument validation to compile-time though.

So that's one other approach to consider: make a macro layer that expands to the map syntax.

using e.g., clojure.spec.alpha/tuple as inspiration.

But really I think calling to-ast at compile-time somewhere and inserting the result into the cljs bundle (a separate edn file, or a macro result?) will be enough (to offload the validation part).

Yeah the current plan is to try those and see what benchmarks look like

Thank you so much for your advice!

Ah, the main difference between spec and malli here is that in spec you can avoid constructing the map syntax altogether and call the schema constructors directly. Because of custom registries, you can't do that in malli.

But if you really wanted to I think you could piece it back together.

For example, if you have a global registry defined top-level, you can have your macros output direct calls to the -into-schema constructors instead of constructing intermediate maps.

I found that the map syntax is not lazy. The delayed registry is immediately realized via a call to -property-registry. The fix is not obvious, but I listed some of the possibilities. Opinions welcome https://github.com/metosin/malli/pull/1185