Supports for define custom macros
What problem does this feature solve?
For example:
I want implement a component.
It need to provide v-model
for parent component and may need to use v-model
to child component.
Use current features would be like this:
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', newModelValue: string): void
}>()
// This link inner value change
const innerValue = ref(props.modelValue)
watch(innerValue, newInnerValue => {
emit('update:modelValue', newInnerValue)
})
watch(() => props.modelValue, newModelValue => {
innerValue.value = newModelValue
})
</script>
<template>
<Child v-model="innerValue" />
</template>
I want to replace the above with a custom defineModel
<script setup lang="ts">
const innerValue = defineModel({ linkInnerValue: true })
</script>
<template>
<Child v-model="innerValue" />
</template>
And this is just an example. There are lots of different custom macros developers may want to achieve. Looking forward to an official API to support for defining custom macros.
What does the proposed API look like?
Maybe with the vite plugin vue It's a preliminary idea.
// vite.config.js
import vue, { defineMacro } from '@vitejs/plugin-vue'
export default {
plugins: [
vue({
customMacros: [
defineMacro({
name: 'model', // used with defineModel
processor: (builtinMacros, options) => {
// buitinMacros are defineProps, defineEmits ...
// options is some custom options use for defineModel(options)
return some reactive status
}
})
]
})
]
}
I see the point for a now you can use https://vueuse.org/core/usevmodel/#usevmodel
I see the point for a now you can use https://vueuse.org/core/usevmodel/#usevmodel
I know this function. Even with this I still need to repeatedly declare modelValue
prop and update:modelValue
with defineProps
and defineEmits
I see the point for a now you can use https://vueuse.org/core/usevmodel/#usevmodel
I know this function. Even with this I still need to repeatedly declare
modelValue
prop andupdate:modelValue
withdefineProps
anddefineEmits
yes, it's quite annoying and it's also quite serious to me :D . The absolute best option seems to me to make support for the composition api (defineProps, defineEmits...) in composuables.
Root elemnet
<template>
<div>
root
</div>
<c1 v-model="value" />
</template>
<script setup lang="ts">
import c1 from './c1.vue'
const value = ref<string>('example')
</script>
Chidld 1
<template>
<div>
Child 1
</div>
<c2 v-model="value" />
</template>
<script setup lang="ts">
import c2 from './c2.vue'
import { useComposuable } from './composuable'
const {
value
} = useComposuable()
</script>
Child 2
<template>
<div class="">
<div>
Child 2
</div>
<input type="text" v-model="value">
</div>
</template>
<script setup lang="ts">
import { useComposuable } from './composuable'
const {
value
} = useComposuable()
</script>
Composuable
import { defineProps, defineEmits } from "vue";
export function useComposuable() {
const props = defineProps<{
modelValue: string;
}>()
/*
* Emits
*/
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
const value = computed<string>({
get: (): string => props.modelValue,
set: (value: string): void => emit('update:modelValue', value)
})
return {
props,
emit,
value
}
}
(yes, we can use provide/inject with computed value, but that may not be the ideal scenario every time.)
Mabey open new feature request from this
This means the built-in macros need to be used outside a SFC file. That also a solution. I don't know which can be easy to implement.
In my opinion defining built-in macros outside the SFC file also means that built-in macros can be defined repeatedly, otherwise you won't be able to define other parameters and events.
In my opinion defining built-in macros outside the SFC file also means that built-in macros can be defined repeatedly, otherwise you won't be able to define other parameters and events.
That's theoretically possible. The implemention should solve this by combining the outside macros and the those inside SFC files.
I don't think the Vue compiler need to provide such an API. The custom macros need to modify many places of codes. A general way to process and transform macros is to wrote a bundler plugin, parse and manipulate AST yourself.
For example, https://github.com/sxzz/unplugin-vue-macros. I implemented defineModel
and defineOptions
in it. I wrote many codes to transform the codes of besides defineModel
- defineProps
and defineEmits
.
So I think it's very hard to provide a universal API to handle these cases.
I don't think the Vue compiler need to provide such an API. The custom macros need to modify many places of codes. A general way to process and transform macros is to wrote a bundler plugin, parse and manipulate AST yourself.
For example, https://github.com/sxzz/unplugin-vue-macros. I implemented
defineModel
anddefineOptions
in it. I wrote many codes to transform the codes of besidesdefineModel
-defineProps
anddefineEmits
. So I think it's very hard to provide a universal API to handle these cases.
It doesn't have to change Vue' s compiler. Can be a feature for vite plugin vue.
And it is very disappointing that a proposal is refused base on it will change a lot of source codes. Instead of offering a better solution or try to implement it.
I mean if you implement a macro, you need to transform a lot of codes, not Vue's code will change a lot. Furthermore, the proposal API you mentioned above is not universal enough. Even if this API is implemented, unplugin-vue-macros
is unable to implement the feature by using it. You can read my code, then think about a more universal API.
By the way, if this is a Vite plugin proposal, maybe we should discuss it in vite repo.
You're misunderstanding what I'm trying to describe.
defineModel
is just one example. defineOptions
too. These are just some fixed hard coding macros.
What I want is a way to define any macros developers may want.
It is precisely because the complexity of implementing custom macros is currently too high. We will need a more easy-to-use API to define custom macros.
By the way. The Reactivity Transform is a compile time feature that implemented by vite-plugin-vue and vue-loader. It's still on Vue's official documentation site. There's no need to classify define custom macros to any specific repo. It's a discussion for Vue relative features. The proposed API is just a preliminary idea. Doesn't mean this feature have to be implemented by vite-plugin-vue.
I'm thinking about another form of API. Here is an very prototype example:
import { isStringLiteral } from '@babel/types'
import type { Node } from '@babel/types'
import type { ComponentObjectPropsOptions, EmitsOptions } from 'vue'
declare function defineMacro(
name: string,
fn: (ctx: {
componentInfo: ComponentInfo
args: Node[]
typeArgs: Node[]
getNodeString: (node: Node) => string
}) => ComponentInfo
): void
interface ComponentInfo {
props: ComponentObjectPropsOptions | string[]
emits: EmitsOptions
}
defineMacro('defineVModel', ({ componentInfo, args }) => {
if (!isStringLiteral(args[0])) throw new Error('prop name must be a string')
const propName = args[0].value
const eventName = `update:${propName}`
if (Array.isArray(componentInfo.props)) {
componentInfo.props.push(propName)
} else {
componentInfo.props[propName] = {
type: null,
required: true,
}
}
if (Array.isArray(componentInfo.emits)) {
componentInfo.emits.push(eventName)
} else {
componentInfo.emits[eventName] = () => true
}
return componentInfo
})
// usage
declare function defineVModel(propName: string): void
defineVModel('modelValue')
// equals ⬇️
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
I'm thinking about another form of API. Here is an very prototype example:
import { isStringLiteral } from '@babel/types' import type { Node } from '@babel/types' import type { ComponentObjectPropsOptions, EmitsOptions } from 'vue' declare function defineMacro( name: string, fn: (ctx: { componentInfo: ComponentInfo args: Node[] typeArgs: Node[] getNodeString: (node: Node) => string }) => ComponentInfo ): void interface ComponentInfo { props: ComponentObjectPropsOptions | string[] emits: EmitsOptions } defineMacro('defineVModel', ({ componentInfo, args }) => { if (!isStringLiteral(args[0])) throw new Error('prop name must be a string') const propName = args[0].value const eventName = `update:${propName}` if (Array.isArray(componentInfo.props)) { componentInfo.props.push(propName) } else { componentInfo.props[propName] = { type: null, required: true, } } if (Array.isArray(componentInfo.emits)) { componentInfo.emits.push(eventName) } else { componentInfo.emits[eventName] = () => true } return componentInfo })
// usage declare function defineVModel(propName: string): void defineVModel('modelValue') // equals ⬇️ defineProps(['modelValue']) defineEmits(['update:modelValue'])
It look so cool