You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Evan and the Vue team have done brilliant work with <script setup>’s optimized inline render function, which eliminates the need to return an object for the template compiler …what if we applied the same solution to the normal script block? (see below for reasons why I'm not using <script setup>)
Perhaps Vue SFCs could introduce a returned template macro like so:
This way, normal script block users can also enjoy both not having to return an object as well as the performance of directly returning an optimized render function
Advantages & Drawbacks
Extending a language is not something to be taken lightly, and I suspect the above proposals will cause a lot of visceral reactions. I too vomit in my mouth a little when I see jsx. But I would argue that this is one of those rare cases where leveraging a macro like this can be defensible.
A returned template macro would:
reduce verbosity, saving time and shipping less code
maintain a clear separation of template logic from business logic during development
allow the runtime optimization of an inline render function
The tradeoff being:
the developer has a new concept to learn
the initial confusion when encountering the macro for the first time.
I think this is a relatively small cost. The new concept to learn is an existing programming concept: a macro. All they need to know is that the compiler will replace this macro with a render function generated from the template and everything should make sense. In this way, no Javascript rules are broken (…arguably).
Thoughts on API Design
I think the key to a successful macro in this particular case is to be as transparent as possible about the use of a macro. Either with an explicit label, as with return /*@macro: <BarBlock> */; , or causing the developer’s brain to alert “wtf is this”, as with return $<BarBlock/>;. Since we are in a .vue file, not a .js file, we as the developer can understand that preprocessing is involved.
Contrast with a macro disguised as Javascript such as return $template();. This would raise questions that can only be answered by inventing new strange rules that contradict the existing language: It looks like the function call $template() builds and returns the render function, but how does it gain access to the variables in the setup function scope? Answer: magic ¯\(ツ)/¯.
return $template; reads as returning the value of a global variable. A comment like return /*** <BarBlock> ***/; reads as returning void.
Eliciting a visceral reaction from invalid js syntax is what we need.
Why Not Simply Use <script setup>?
In my project I have found that <script setup> has made development less pleasant for me. The configuration macros feel disjointed as compared to a cohesive defineComponent config, and sometimes I like writing helpers outside of component setup, forcing me to use two script blocks, which gets pretty unwieldy:
It’s difficult to see at a glance where one script block ends and where the other begins. Since they both contain Javascript, they kinda just meld together visually.
When opening a file, you have to mentally register: Am I looking at the module scope or am I looking at the setup function scope?
Granted, these are not an issue for the vast majority of .vue files, but it points to a concern I’ve had with Svelte and <script setup>.
The Concern With Svelte and <Script Setup>
First of all, I raise this with the utmost awe and respect for Evan You and Rich Harris. I adore and look up to their work and innovations (Vue & SFCs, reactivity system, Composition API, Vite & HMR… Svelte, Rollup & tree-shaking, Estree Walker, MagicString… exceptionally brilliant and incredibly well-designed).
The concern: <script setup> and Svelte violate the principle of least surprise.
When I see a script block that opens with import statements, I expect that:
I can export from this script block.
The top-level script will only run once.
<script setup> and Svelte’s <script> defies both these expectations. Vue alleviates this by adding the setup attribute to the script tag as a signal to the developer that this script block behaves differently. But it's still somewhat concerning that <script setup> essentially forces two entirely different behaviors into a single block: The import statements and macros run only once; the rest of the script runs per instance. A developer, whether out of ignorance or absent-mindedness, can easily write code intended to run only once, not realizing that it will actually run with every instantiation of the component. This merging of scopes is particularly dangerous for the new developer who may still be in the process of wrapping their heads around these concepts.
Addressing the Concern
It appears this is not a concern among the community given that I have never seen it raised by anyone else, but I personally feel weird about having what appears to be top-level code running multiple times. So a way to address it in my own project would be to add the normal script block to house the outer module scope for imports, configurations, and helpers. But as I mentioned previously, more than one script block per SFC is seriously unwieldy.
In the end, I think the most elegant solution is simply Vue’s original defineComponent config in a normal script block where you can clearly differentiate the outer module scope from the inner setup function scope at a single glance. ... which brings me to the proposal of a template macro for the normal script block.
Closing
People will have different levels of tolerance for this kind of thing. I acknowledge the template macro may feel like a hack, but imho, the advantages over the tradeoff is worth it. It solves the verbosity issue (of the returned object) while preserving:
clarity and understandability of the different scopes involved
relative consistency with non-SFC apps (important for a progressive framework)
—these things have been lost to some extent in <script setup>.
<script setup> is well-loved for good reasons, so I’m not arguing for its removal, just putting it out there that there is at least one person who prefers defineComponent for what I think are legitimate reasons and who would love to have the same level of verbosity reduction and optimization that <script setup> enjoys.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Evan and the Vue team have done brilliant work with
<script setup>
’s optimized inline render function, which eliminates the need to return an object for the template compiler …what if we applied the same solution to the normal script block? (see below for reasons why I'm not using<script setup>
)Perhaps Vue SFCs could introduce a returned template macro like so:
or by extending the syntax:
compiling to:
This way, normal script block users can also enjoy both not having to return an object as well as the performance of directly returning an optimized render function
Advantages & Drawbacks
Extending a language is not something to be taken lightly, and I suspect the above proposals will cause a lot of visceral reactions. I too vomit in my mouth a little when I see jsx. But I would argue that this is one of those rare cases where leveraging a macro like this can be defensible.
A returned template macro would:
The tradeoff being:
I think this is a relatively small cost. The new concept to learn is an existing programming concept: a macro. All they need to know is that the compiler will replace this macro with a render function generated from the template and everything should make sense. In this way, no Javascript rules are broken (…arguably).
Thoughts on API Design
I think the key to a successful macro in this particular case is to be as transparent as possible about the use of a macro. Either with an explicit label, as with
return /*@macro: <BarBlock> */;
, or causing the developer’s brain to alert “wtf is this”, as withreturn $<BarBlock/>;
. Since we are in a .vue file, not a .js file, we as the developer can understand that preprocessing is involved.Contrast with a macro disguised as Javascript such as
return $template();
. This would raise questions that can only be answered by inventing new strange rules that contradict the existing language: It looks like the function call$template()
builds and returns the render function, but how does it gain access to the variables in the setup function scope? Answer: magic ¯\(ツ)/¯.return $template;
reads as returning the value of a global variable. A comment likereturn /*** <BarBlock> ***/;
reads as returning void.Eliciting a visceral reaction from invalid js syntax is what we need.
Why Not Simply Use
<script setup>
?In my project I have found that
<script setup>
has made development less pleasant for me. The configuration macros feel disjointed as compared to a cohesivedefineComponent
config, and sometimes I like writing helpers outside of component setup, forcing me to use two script blocks, which gets pretty unwieldy:Granted, these are not an issue for the vast majority of .vue files, but it points to a concern I’ve had with Svelte and
<script setup>
.The Concern With Svelte and <Script Setup>
First of all, I raise this with the utmost awe and respect for Evan You and Rich Harris. I adore and look up to their work and innovations (Vue & SFCs, reactivity system, Composition API, Vite & HMR… Svelte, Rollup & tree-shaking, Estree Walker, MagicString… exceptionally brilliant and incredibly well-designed).
The concern:
<script setup>
and Svelte violate the principle of least surprise.When I see a script block that opens with import statements, I expect that:
<script setup>
and Svelte’s<script>
defies both these expectations. Vue alleviates this by adding thesetup
attribute to the script tag as a signal to the developer that this script block behaves differently. But it's still somewhat concerning that<script setup>
essentially forces two entirely different behaviors into a single block: The import statements and macros run only once; the rest of the script runs per instance. A developer, whether out of ignorance or absent-mindedness, can easily write code intended to run only once, not realizing that it will actually run with every instantiation of the component. This merging of scopes is particularly dangerous for the new developer who may still be in the process of wrapping their heads around these concepts.Addressing the Concern
It appears this is not a concern among the community given that I have never seen it raised by anyone else, but I personally feel weird about having what appears to be top-level code running multiple times. So a way to address it in my own project would be to add the normal script block to house the outer module scope for imports, configurations, and helpers. But as I mentioned previously, more than one script block per SFC is seriously unwieldy.
In the end, I think the most elegant solution is simply Vue’s original
defineComponent
config in a normal script block where you can clearly differentiate the outer module scope from the inner setup function scope at a single glance. ... which brings me to the proposal of a template macro for the normal script block.Closing
People will have different levels of tolerance for this kind of thing. I acknowledge the template macro may feel like a hack, but imho, the advantages over the tradeoff is worth it. It solves the verbosity issue (of the returned object) while preserving:
—these things have been lost to some extent in
<script setup>
.<script setup>
is well-loved for good reasons, so I’m not arguing for its removal, just putting it out there that there is at least one person who prefersdefineComponent
for what I think are legitimate reasons and who would love to have the same level of verbosity reduction and optimization that<script setup>
enjoys.Curious to hear how other people feel :)
Beta Was this translation helpful? Give feedback.
All reactions