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

SFC Improvements #182

Closed
wants to merge 14 commits into from
Closed

SFC Improvements #182

wants to merge 14 commits into from

Conversation

@yyx990803
Copy link
Member

@yyx990803 yyx990803 commented Jun 29, 2020

Note: the Component Import Sugar RFC has been dropped for now due to various unresolved concerns. However, <script setup> now supports using exported values as components.

This PR includes 2 RFCs related to improving the authoring experience of Vue SFCs (Single File Components):

<script setup> for Composition API in SFCs

Important: this version of <script setup> is being replaced by a new one at #222

[Outdated] Rendered

State-driven CSS Variable Injection in <style>

<template>
  <div class="text">hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  }
}
</script>

<style vars="{ color }">
.text {
  color: var(--color);
}
</style>

Rendered

@yyx990803 yyx990803 added the sfc label Jun 29, 2020
@DominusKelvin
Copy link

@DominusKelvin DominusKelvin commented Jun 29, 2020

The component sugar feature would really increase DX. Thanks Evan!

@korkies22
Copy link

@korkies22 korkies22 commented Jun 29, 2020

Is it possible to use the deep selector with the css variables?

@CyberAP
Copy link
Contributor

@CyberAP CyberAP commented Jun 29, 2020

Not really a fan of implicit component name resolving. Why filename rather than a name property on the component itself has been chosen? WebStorm for example only uses name property on the component to infer its name and does refactoring only based on this property. This change would probably break name refactoring in WebStorm since now it could also be caused by a file rename.

I can see a potential point of confusion between component as an import tag and component as an abstract tag within a template. Googling Vue component would lead to mixed results and also referencing component tag in discussion won't be so straightforward after this change. Maybe there's a better name for it to avoid confusion?

Maybe wrapping component imports in an Import block could avoid the confusion between the same name for abstract and import tag.

<Import>
  <Foo from="./Foo.vue" />
  <Baz from="./Bar.vue" /><!-- no `as` needed anymore -->
  <Qux from="./Qux.vue" async />
</Import>
@ianaya89
Copy link

@ianaya89 ianaya89 commented Jun 29, 2020

I love the idea of:

  • <script setup> for Composition API in SFCs
  • State-driven CSS Variable Injection in <style>

In the other side, I don't feel sure about <component> Import Sugar.
I love current structure of style, script and template. It really represents all the layers inside SFC.
(IMHO) Adding a new component root element will break semantic and will add more noise into the SFC structure. At least for me, component registration should be explicitly keep inside script.

@ChrisBrownie55
Copy link

@ChrisBrownie55 ChrisBrownie55 commented Jun 29, 2020

The <script setup> API looks very promising.
As for the <component> import sugar syntax, how would that play with automatic import features of editor plugins and IDEs?

@Philipp-M
Copy link

@Philipp-M Philipp-M commented Jun 29, 2020

Looks great!

How will props be handled with this new syntax (also interesting: type inference with typescript in parent components)?

Edit: I should've read the rendered RFC, it's mentioned there...

@Wharley01
Copy link

@Wharley01 Wharley01 commented Jun 29, 2020

I love everything about this SFC, brilliant ideas! Well done to Vuejs team

@RomainLanz
Copy link

@RomainLanz RomainLanz commented Jun 29, 2020

Hey there!

Those changes look very good!

Same question as @Philipp-M, how we will be able to define metadata for the component, like name, props or options?

@Wharley01
Copy link

@Wharley01 Wharley01 commented Jun 29, 2020

Hey there!

Those changes look very good!

Same question as @Philipp-M, how we will be able to define metadata for the component, like name, props or options?

This will probably only to be used with Vue3 composition api

@oswaldofreitas
Copy link

@oswaldofreitas oswaldofreitas commented Jun 29, 2020

the css variables injection is fantastic!
how would props or other options API be added with <script setup>?

@chriscalo
Copy link
Contributor

@chriscalo chriscalo commented Jun 29, 2020

Love these solutions for reducing SFC boilerplate. 🤩

In State-driven CSS Variable Injection, it says:

The variables will be applied to the component's root element as inline styles. In the above example, given a :vars binding that evaluates to { color: 'red' }, the rendered HTML will be:

<div style="--color:red" class="text">hello</div>

This assumes a single root node. How will this be handled for multiple root nodes? Apply the same inline styles to all root nodes?

@ChrisBrownie55
Copy link

@ChrisBrownie55 ChrisBrownie55 commented Jun 29, 2020

Along with the above stuff I mentioned, how would the State-driven CSS injection handle values that are injected being changed dynamically at runtime? Perhaps even to animate things, would it be performant enough for something like that?

@MisFis
Copy link

@MisFis MisFis commented Jun 29, 2020

Cool RFCs!

@yyx990803
Copy link
Member Author

@yyx990803 yyx990803 commented Jun 29, 2020

@chriscalo good catch - yes, it will apply it to all root level nodes.

@yyx990803
Copy link
Member Author

@yyx990803 yyx990803 commented Jun 29, 2020

@ChrisBrownie55 internally there will be a watchEffect that updates the inline style variables whenever they change. This should absolutely be suitable for animations since it's really just updating inline styles. In fact, it should be much more performant than template style bindings because it runs independently from Vue's update cycle (assuming the changed variable is not used in the template as well)

@jacekkarczmarczyk
Copy link

@jacekkarczmarczyk jacekkarczmarczyk commented Jun 29, 2020

Is it 3.0 thing or 3.next?

@yyx990803
Copy link
Member Author

@yyx990803 yyx990803 commented Jun 29, 2020

@jacekkarczmarczyk 3.0 since these are not hard to implement, and ideally we'd want to avoid people having to do another round of migration to <script setup> after 3.0 is released.

@yyx990803
Copy link
Member Author

@yyx990803 yyx990803 commented Jun 29, 2020

@ianaya89 you surely can stick to import inside <script> if that's what you prefer - the addition doesn't hurt though, IMO.

@vitorarjol
Copy link

@vitorarjol vitorarjol commented Jun 29, 2020

Wow! The <script setup> is such a great DX.

Great work!

@CyberAP
Copy link
Contributor

@CyberAP CyberAP commented Jun 29, 2020

How script setup is going to handle async setup?

<script setup>
import api from './api.js'

const response = await api()
</script>
@edimitchel

This comment was marked as off-topic.

@schtr4jh
Copy link

@schtr4jh schtr4jh commented Jun 29, 2020

Love these solutions for reducing SFC boilerplate.

In State-driven CSS Variable Injection, it says:

The variables will be applied to the component's root element as inline styles. In the above example, given a :vars binding that evaluates to { color: 'red' }, the rendered HTML will be:

<div style="--color:red" class="text">hello</div>

This assumes a single root node. How will this be handled for multiple root nodes? Apply the same inline styles to all root nodes?

What about CSS pseudo-classes? @yyx990803
<style :vars="{ color }"> .text:last-child { color: var(--color); } </style>

@CyberAP
Copy link
Contributor

@CyberAP CyberAP commented Jun 29, 2020

@edimitchel Easier said than done 😁 How it would deal with exports then?

@andreiculda
Copy link

@andreiculda andreiculda commented Jun 29, 2020

Very cool RFC especially CSS variable injection, although this won't support IE and it is very unfortunate that there are still some of us who still have to. But we can get away with a pollyfill I guess.

Great job VUE team.

@cereschen
Copy link

@cereschen cereschen commented Sep 5, 2020

This will make the code readable and easy to modify
IDE support is just a matter of time

</script>
```

The `bindings` object will be:

This comment has been minimized.

@jods4

jods4 Sep 6, 2020

@yyx990803 I looked up the BindingMetadata typedef in compiler-core. Following types are defined: 'data' | 'props' | 'setup' | 'options'

I think an additional value A: 'import', for export { A } from './a' could lead to interesting optimizations.

The compiler would then be free to reference the import directly, without reactivity and bypassing the setup state for references to A.

That's particularly interesting for local components (or directives).
This RFC also looks for a syntax to import local components. With the ability to recognize imports (constants) and reference them directly, it doesn't need one!

If the template does <MyButton /> and the script setup contains an export { MyButton } from './my-button' identified as such, the compiler could produce the equivalent of h(MyButton, ...).

Without this knowledge, the local component would suffer two drawbacks:

  • going through the reactivity layer to access MyButton on the setup object.
  • assuming that MyButton is a variable that could change, so the component is dynamic and precludes the static optimizations.

This comment has been minimized.

@yyx990803

yyx990803 Sep 9, 2020
Author Member

The reason we are not doing this is that the template and script parts of an SFC are typically executed as separate modules to
1. allow each having its own loader pipelines (webpack specific)
2. allow template to be individually hot-reloaded (thus preserving component state).

Regarding the drawbacks:

  • Component access only goes through the setup object, which is not a full reactive object (it's a proxy that only checks for ref unwrapping, so the cost is fairly cheap).

  • <MyButton/> directly compiles to h($setup.MyButton), so there is no dynamic assumptions here.

This comment has been minimized.

@jods4

jods4 Sep 9, 2020

<MyButton/> directly compiles to h($setup.MyButton), so there is no dynamic assumptions here.

I think I'm missing a piece here.

Setup properties can mutate, right? Do you know/assume $setup.MyButton is a constant rather than a reactive value that will change?

If you assume MyButton can mutate, then isn't the code equivalent to <component :is="MyButton">, which precludes some optimizations compared to a static <MyButton>?
https://vue-next-template-explorer.netlify.app/#%7B%22src%22%3A%22%3Ccomponent%20%3Ais%3D%5C%22xy%5C%22%20%2F%3E%5Cr%5Cn%3Cmy-xy%20%2F%3E%22%2C%22options%22%3A%7B%22mode%22%3A%22module%22%2C%22prefixIdentifiers%22%3Afalse%2C%22optimizeImports%22%3Afalse%2C%22hoistStatic%22%3Afalse%2C%22cacheHandlers%22%3Afalse%2C%22scopeId%22%3Anull%2C%22ssrCssVars%22%3A%22%7B%20color%20%7D%22%2C%22bindingMetadata%22%3A%7B%22TestComponent%22%3A%22setup%22%2C%22foo%22%3A%22setup%22%2C%22bar%22%3A%22props%22%7D%2C%22optimizeBindings%22%3Afalse%7D%7D

Or does the syntax <MyButton> imply that it must be static, even though it comes from setup?
In this case, what happens if that assumption is violated by user, do you emit a warning in DEV?

@yyx990803
Copy link
Member Author

@yyx990803 yyx990803 commented Sep 9, 2020

Update: features proposed in this PR are now implemented in vue-loader@16.0.0-beta.6 and are available in a Vue 3 project created via vue-cli.

@cereschen
Copy link

@cereschen cereschen commented Sep 9, 2020

Update: features proposed in this PR are now implemented in vue-loader@16.0.0-beta.6 and are available in a Vue 3 project created via vue-cli.
Good Job, with my no-ref plug-in, let me think about what this looks like... svelte XD
https://github.com/cereschen/no-ref

@Phinome
Copy link

@Phinome Phinome commented Sep 19, 2020

brilliant ideas! but, how to solve eslint "no-defined" error when using props.
image

@Phinome
Copy link

@Phinome Phinome commented Sep 20, 2020

@Phinome See https://github.com/vuejs/rfcs/blob/sfc-improvements/active-rfcs/0000-sfc-script-setup.md#using-setup-arguments

I mean how to pass the static check of eslint in the sfc file. eg:

<script setup="props, { emit }">
import { watchEffect } from 'vue'

watchEffect(() => console.log(props.msg))
emit('foo')
</script>

when eslint running, eslint will show 'props' is not defined.

@boonyasukd boonyasukd mentioned this pull request Sep 20, 2020
1 of 1 task complete
@ux-engineer
Copy link

@ux-engineer ux-engineer commented Sep 22, 2020

Is there are way in TypeScript example of Declaring props or additional options to declare props with default values?

@mathe42
Copy link

@mathe42 mathe42 commented Sep 22, 2020

@ux-engineer you can add a default export wich means you have to declare your props twice

@ux-engineer
Copy link

@ux-engineer ux-engineer commented Sep 22, 2020

Note that the props type declaration value cannot be an imported type, because the SFC compiler does not process external files to extract the prop names.

Would it be possible later to overcome this restriction somehow? It feels quite limiting not being able to import and use types defined in other files.

If helpful here, TypeScript 3.8 added type only imports: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export

@Aaron-Pool
Copy link

@Aaron-Pool Aaron-Pool commented Sep 23, 2020

@yyx990803 I can't tell from the RFC whether members exported in the <script setup> block are supposed to be available on the this context in the default export options object (methods, computed, etc)? I can't think of any reason why they wouldn't be, based on the implementation. Although, by the same point, I also can't really imagine how you could the this context typescript friendly, when used the the default export of the setup block.

Any thoughts on this?

And second, slightly less important question, any clue when this RFC will move to the final comments stage? Seems like the discussion has slowed down pretty significantly.

@shawn-yee
Copy link

@shawn-yee shawn-yee commented Sep 27, 2020

How about this YAML/JSON import once style ?
It is clean and no other syntax noise.
It is friendly to transform and IDE highlight.
Not just javascript, we treat it as other preprocessors.

/* YAML style */
<components lang="yaml">

    // 1. alias name style
    my-header: ./component/my-header.vue
    my-footer: ./component/my-footer.vue

    // 2. default name style
    - ./component/my-header.vue
    - ./component/my-footer.vue
    
</components>


/* JSON style */
<components lang="json">
    // 1. alias name style
    {
        "my-header": "./component/my-header.vue",
        "my-footer": "./component/my-footer.vue"
    }

    // 2. default name style
    [
        "./component/my-header.vue",
        "./component/my-footer.vue"
    ]
</components>
@NvdB31
Copy link

@NvdB31 NvdB31 commented Sep 30, 2020

@yyx990803 Great work on the state-driven CSS Variable Injection! I'm wondering though, since styles are injected in the inline style tag, will this support CSS pseudo classes? And media queries?

EDIT: Never mind, just checked and I saw how it works. Awesome!

@ux-engineer
Copy link

@ux-engineer ux-engineer commented Oct 7, 2020

Is there a way for package developers to add their own methods or instances available in script setup's context scope? Or would this be advised against of?

For example:

<script setup="_, { $t }" lang="ts">
export const name = 'ViewName';

export const metaInfo = {
  title: $t('PAGE_TITLE'),
  meta: [
    { name: 'description', content: $t('PAGE_DESCRIPTION') },
  ],
};
</script>

See my proposal and feature request: intlify/vue-i18n-next#138

@caikan
Copy link

@caikan caikan commented Oct 21, 2020

I think that if a new kind of syntactic sugar, the export statement in the function body, is added to JS, maybe it can achieve the same effect, but introduce as few new problems as possible:
我觉得如果在JS中添加一种新的语法糖——函数体内的export语句,也许可以实现同样的功能,又能尽可能少地引入新的问题:

<template>
  <button @click="inc">{{ count }}</button>
</template>

<script>
import { ref } from 'vue'

export default {
  setup() {
    export const count = ref(0)
    export const inc = () => count.value++
  }
}
</script>

The export in the function body can be used to replace the return statement. The above setup function can be compiled as:
函数体内的export可以用来替代return语句,上面的setup函数可以被编译为:

setup() {
  const exports = {}
  const count = exports.count = ref(0)
  const inc = exports.inc = () => count.value++
  return exports
}

Its behavior within the function body is basically the same as that of the ES module. You can refer to the processing method of loading the ES module in the CommonJS module.
The only difference is that the module can only be loaded once, while the function can be called multiple times.
In addition, because the function is called dynamically at runtime, there is no need to consider the problem of static analysis, so the ʻexport` in the function body can be written in a loop or conditional block.

Perhaps this idea is more suitable as a new TC39 proposal, but before that, it can be implemented as a special Vue SFC syntax.

它在函数体内部的行为方式与ES模块基本一致,可以参考在CommonJS模块中加载ES模块的处理方式。
唯一的区别是,模块只能被加载一次,而函数可以被多次调用。
另外,因为函数是在运行时动态被调用的,无需考虑静态分析的问题,因此函数体内的export可以写在循环或条件语句块中。

或许这个想法更适合作为新的TC39提案,但是在那之前,可以先作为特殊的Vue SFC语法来实现。

@yyx990803
Copy link
Member Author

@yyx990803 yyx990803 commented Nov 9, 2020

This PR is being closed since <script setup> as proposed in this PR is being replaced by new versions.

The <style vars> RFC has been split into its own PR in #226 and is now in final comments period.

@quanzaiyu
Copy link

@quanzaiyu quanzaiyu commented Feb 18, 2021

How script setup is going to handle async setup?

<script setup>
import api from './api.js'

const response = await api()
</script>

I use async function like this

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment