feat(compiler-sfc): introduce `defineRender` macro
⚠️ Status: Experimental
Summary
See the RFC for details.
This PR introduces a new macro called defineRender that allows for defining a render function in <script setup> with or without JSX, instead of template syntax.
This is an experimental feature. We introduced a new compiler script option defineRender, and it's disabled by default.
Basic Usage
<script setup lang="jsx">
import { h } from 'vue'
defineRender(<div />)
defineRender(h('div'))
defineRender(() => {
// ...
return <div />
})
</script>
Prev Solution: return statement
Redundant return statements can be removed by bundlers (tested on both Rollup and esbuild)
Example: https://deploy-preview-9400--vue-sfc-playground.netlify.app/#eNp9kDFPwzAQhf/K4SWtVIWhW9VWAlQJGAABEouXKLmEFOdsne0QKcp/x3bUwoC6We99vvfuRnFjTN57FBuxtSW3xoFF581eUtsZzQ5GYKxhgpp1B1lAM0mlJuugsw3sorvI7lEpDR+aVXWVLSVxmMEEiyXs9pHL+0J5lLS9nkPCeLESzoZBddvkR6spNBglAUhR6s60CvnZuDYESbGB5ESvCDnfj0lz7HF10stPLL/+0Y92iJoUL4wWuUcpzp4ruEE324e3JxzC+2x2uvIq0BfMV7Ra+dhxxm49VaH2Hy61fUh3bKl5t4fBIdnTUrFoJKfESxFue3dh9d+663yd/kmaxPQDLeaULA==
Size Report
Bundles
| File | Size | Gzip | Brotli |
|---|---|---|---|
| runtime-dom.global.prod.js | 85.9 kB | 32.6 kB | 29.5 kB |
| vue.global.prod.js | 132 kB | 49.3 kB | 44.4 kB |
Usages
| Name | Size | Gzip | Brotli |
|---|---|---|---|
| createApp | 47.9 kB | 18.8 kB | 17.2 kB |
| createSSRApp | 50.6 kB | 19.9 kB | 18.2 kB |
| defineCustomElement | 50.3 kB | 19.6 kB | 17.9 kB |
| overall | 61.2 kB | 23.7 kB | 21.6 kB |
Should we warn if there's a return statement inside of if branch?
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
if (msg.value !== '')
return () => msg.value // never return
</script>
The setup function is only called once, so most cases of if + return statement are invalid and likely cause unexpected behaviors.
The setup function is only called once, so most cases of
if + return statementare invalid and likely cause unexpected behaviors.
I wouldn't call "invalid", they are likely to be incorrect, but I think that's a linter's job, not compiler, we don't know how people use it, if you add a warning we need a way to remove the warning I suppose.
Just because this is a perfectly fine component, albeit not very good, but fine
defineComponent({
props: {
test: Number
},
setup(props) {
if (props.test > 1) {
return () => h('div', 'big number')
}
return () => h('div', 'small number')
}
})
After discussing with Evan, we added one more macro, defineRender.
Compared to the return statement:
- Macro is more explicit for defining render. The
returnstatement has unclear semantics. -
returncan be written more than once can could cause losing reactivity like I mentioned before. But indefineRender, it can only called once. - For all macros, the order of calling is not concerned. In other words, it's the same effect, calling
defineRenderwhether it's the first line or the last line. - Like the above one, all macros can only called in the top level of
<script setup>but the return statement is not.
It should not be shared with template syntax, right?
<script setup lang='jsx'>
import { ref } from 'vue'
const msg = ref('Hello World!')
defineRender(<p>{msg.value}</p>)
</script>
<template>
<p>{msg.value}</p>
</template>
Should it go through RFC first?
I would say if defineRender is used the compiler should throw an error if <template> (the render template) is present
It should not be shared with template syntax, right?
<script setup lang='jsx'> import { ref } from 'vue' const msg = ref('Hello World!') defineRender(<p>{msg.value}</p>) </script> <template> <p>{msg.value}</p> </template>
It should be the same as the render function, with the template as the main:
https://play.vuejs.org/#eNp9Uk9PgzAU/yq1F2ZC4LDbRBI1S9SDGjXx0guBB+ssbdOWSbLw3X0tg22J7kBof39efn3v7emd1smuA7qimS0N145YcJ3OmeStVsaRPTFQk4HURrUkQmk0Uw+q1Qc8Sf3FV0KaySwdi2EZvDhotSgc4I2QrOK7cMCj9+RZsI5cOpJZOltoTJ0tlax5k2ytkhh076WMlujiAsyrdlxJy+iKBMZzhRDq5zlgznQQT3i5gfL7D3xre48x+mbAgtkBozPnCtOAG+n1xwv0eJ7JVlWdQPUF8h2sEp3POMruO1lh7BNdSPsUespl82nXvQNpp0f5oF45BD2j2GPfsP+efoy7TJbBx+SAXZzmc5z06YwrqLkMdZUE6WKyORv5jZ8j9EGM0qIT4X9qWRwSGJAVmBVZXJPbnGwWkdWFjGISPQI+k3wpI6qr6BpD4XdhT3AbvDOfQNLaBuUeOl+Q4RdCZeze
It should not be shared with template syntax, right?
<script setup lang='jsx'> import { ref } from 'vue' const msg = ref('Hello World!') defineRender(<p>{msg.value}</p>) </script> <template> <p>{msg.value}</p> </template>It should be the same as the render function, with the template as the main:
https://play.vuejs.org/#eNp9Uk9PgzAU/yq1F2ZC4LDbRBI1S9SDGjXx0guBB+ssbdOWSbLw3X0tg22J7kBof39efn3v7emd1smuA7qimS0N145YcJ3OmeStVsaRPTFQk4HURrUkQmk0Uw+q1Qc8Sf3FV0KaySwdi2EZvDhotSgc4I2QrOK7cMCj9+RZsI5cOpJZOltoTJ0tlax5k2ytkhh076WMlujiAsyrdlxJy+iKBMZzhRDq5zlgznQQT3i5gfL7D3xre48x+mbAgtkBozPnCtOAG+n1xwv0eJ7JVlWdQPUF8h2sEp3POMruO1lh7BNdSPsUespl82nXvQNpp0f5oF45BD2j2GPfsP+efoy7TJbBx+SAXZzmc5z06YwrqLkMdZUE6WKyORv5jZ8j9EGM0qIT4X9qWRwSGJAVmBVZXJPbnGwWkdWFjGISPQI+k3wpI6qr6BpD4XdhT3AbvDOfQNLaBuUeOl+Q4RdCZeze
It should be like this, its behavior should be consistent with the option api situation
I disagree if think we should at the very least alert on both cases, there should not be multiple render functions, because one will never be called. That's more likely to be a bug than intentional.
Synchronous discussion:
The implementation of defineRender is to compile jsx content into the return value of the setup function, which is different from what vue's existing jsx plugin does (for example, @vitejs/plugin-vue-jsx is to convert jsx into a runtime rendering function code).
Therefore, when defineRender and template exist at the same time, their priority issues cannot be discussed together with @vitejs/plugin-vue-jsx. @sxzz Setting defineRender to have higher priority than template has its trade-offs.
At the same time, I also agree with @pikax’s view. When defineRender and template exist at the same time, the compiler reports an error, which can regulate user behavior and avoid potential problems.
Actually, JSX is not within the scope of defineRender. Whatever using or not defineRender, the order of compilation is transforming Vue SFC into JavaScript (or JSX) code (@vitejs/plugin-vue), then transform JSX into JS (@vitejs/plugin-vue-jsx).
Now an error will be threw when defineRender() used with <template>
Should it go through RFC first?
I would say if
defineRenderis used the compiler should throw an error if<template>(the render template) is present
@pikax
- Now The macro cannot be used with
<template>. - The RFC has been written, PTAL https://github.com/vuejs/rfcs/discussions/585