Tweaks to SFC compiler: user nodeTransforms
What problem does this feature solve?
I would like to suggest 2 tweaks to the SFC compiler, related to user nodeTransforms
.
Tweak 1: run user transforms first
I didn't find documentation for custom nodeTransform
but reading the source code I noticed they run after the built-in transforms.
This means that it's too late if you wanted to customize, or leverage, what a built-in transform does.
I'd rather have user transforms run first, or maybe have 2 options: preNodeTransforms
and postNodeTransforms
(is that even useful?).
Use-case: custom v-if
I'm trying to build a directive that removes elements from DOM (sometimes), i.e. that works like v-if
.
The v-if
magic is all compile-time so this can't be done with a normal directive at runtime, instead a custom compiler transform to process my v-magic
is required.
The thing is: transformIf
is not easily re-usable. A large portion of its logic is not exported and wrapped into a function that can't be re-wrapped itself. Copy-pasting here is a bad idea because it could break with any release.
So I thought: easy enough, I'm gonna do a transformMagic
that changes the node.props
to replace its own v-magic
with an equivalent v-if
.
In other words: instead of implementing a kind of if
transform, I expand my v-magic
directive into an equivalent v-if
with a condition built by my transform.
This, of course, would only work if the built-in transformIf
runs after my user transform, otherwise the v-if
expansion would go unnoticed by the compiler.
Tweak 2: Import stuff into the codegen
When emitting custom code, you will quickly want to reuse logic from helpers.
It seems context.helper
is hard-coded for a list of internal Vue helpers and I didn't find an API to import arbitrary code, which is quickly limiting when you want to generate your own code.
So I suggest that context
has a new API to enable import arbitrary code from arbitrary modules, see Proposed API.
What does the proposed API look like?
For tweak 2, maybe something like:
let hat = context.import("hat", "@/magic.js");
This would ensure that the codegen contains import { hat } from "@/magic.js"
.
A few notes on its behavior:
- Context should ensure there is no conflict in imported names. It returns the imported alias and is free to create aliases as it sees fit.
- The function should be idempotent: making the same call multiple times should only include the module once and always return the same identifier.
- Importing multiple values from the same module should end up concatenated
import { hat, wand } from "@/magic.js"
.
This is... somewhat intended. Opening it up too much essentially allows you to not just extend the Vue template, but create your own dialect. For example a custom v-if
is definitely something I do not want to support.
The compiler API serves primarily for Vue internal uses - it is exposed for simple single node transforms. If we have to tweak it to suit more complex use cases, it's increasing the maintenance burden for some very very niche needs, which isn't worthwhile.
I understand the maintenance bit. It's a bit unfortunate though that removing a node from DOM is something only the compiler can do :(
Here's a use case, let me know if you think of a better way.
I would like to create a directive to easily apply some security policies throughout our application.
Something like v-policy:Admins
.
You can easily make it set display: none
but there are edge cases where it doesn't totally cut it.
For example if you need to put it on a <template>
that encloses several elements.
So if v-if
is the single, blessed way to remove something from DOM, we would probably configure a function to be globally available (I think this is possible, right?) and then write v-if="policy('Admins')"
instead.
Sure, it works, but at large scale v-policy:Admins
feels more convenient and expressive.
RE: creating dialects, this is about creating a directive, it's already possible in the compiler (in a limited way) and I don't feel like it creates more of a dialect than creating a regular runtime directive.