Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compiler-sfc): introduce defineRender macro #9400

Open
wants to merge 8 commits into
base: minor
Choose a base branch
from

Conversation

sxzz
Copy link
Member

@sxzz sxzz commented Oct 13, 2023

⚠️ 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==

image

@github-actions
Copy link

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

@sxzz sxzz changed the title feat(compiler-sfc): allow return render in <script setup> feat(compiler-sfc): allow return render in <script setup> Oct 13, 2023
@sxzz sxzz changed the base branch from main to minor October 13, 2023 17:23
@sxzz sxzz changed the title feat(compiler-sfc): allow return render in <script setup> feat(compiler-sfc): allow return statement in <script setup> Oct 13, 2023
Copy link
Member

@pikax pikax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but there's changes on the dependencies which shouldn't be necessary here

packages/vue/__tests__/e2e/markdown.spec.ts Outdated Show resolved Hide resolved
@sxzz
Copy link
Member Author

sxzz commented Oct 14, 2023

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.

@pikax
Copy link
Member

pikax commented Oct 14, 2023

The setup function is only called once, so most cases of if + return statement are 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')
    }
})

@sxzz sxzz changed the title feat(compiler-sfc): allow return statement in <script setup> feat(compiler-sfc): introduce defineRender macro Oct 17, 2023
@sxzz sxzz marked this pull request as draft October 17, 2023 02:54
@sxzz
Copy link
Member Author

sxzz commented Oct 17, 2023

After discussing with Evan, we added one more macro, defineRender.

Compared to the return statement:

  • Macro is more explicit for defining render. The return statement has unclear semantics.
  • return can be written more than once can could cause losing reactivity like I mentioned before. But in defineRender, it can only called once.
  • For all macros, the order of calling is not concerned. In other words, it's the same effect, calling defineRender whether 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.

@baiwusanyu-c
Copy link
Member

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>

@pikax
Copy link
Member

pikax commented Oct 17, 2023

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

@Alfred-Skyblue
Copy link
Member

@baiwusanyu-c
Copy link
Member

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

@pikax
Copy link
Member

pikax commented Oct 17, 2023

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.

@sxzz sxzz marked this pull request as ready for review October 17, 2023 12:38
@baiwusanyu-c
Copy link
Member

baiwusanyu-c commented Oct 17, 2023

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.

@sxzz
Copy link
Member Author

sxzz commented Oct 18, 2023

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).

@sxzz
Copy link
Member Author

sxzz commented Oct 18, 2023

Now an error will be threw when defineRender() used with <template>

@sxzz sxzz mentioned this pull request Oct 19, 2023
@sxzz
Copy link
Member Author

sxzz commented Oct 19, 2023

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

@pikax

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Ready to merge
Status: No status
Development

Successfully merging this pull request may close these issues.

5 participants