Skip to content
New issue

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

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

Already on GitHub? # 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 Single File Components label Jun 29, 2020
@DominusKelvin
Copy link

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

@korkies22
Copy link

korkies22 commented Jun 29, 2020

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

@CyberAP
Copy link
Contributor

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

@andria-dev
Copy link

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

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

@RomainLanz
Copy link

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?

@jacekkarczmarczyk
Copy link

jacekkarczmarczyk commented Jun 29, 2020

@RomainLanz @oswaldofreitas https://github.com/vuejs/rfcs/blob/sfc-improvements/active-rfcs/0000-sfc-script-setup.md#declaring-props-or-additional-options

@Wharley01
Copy link

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

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

@chriscalo
Copy link
Contributor

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?

@andria-dev
Copy link

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 commented Jun 29, 2020

Cool RFCs!

@yyx990803
Copy link
Member Author

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

@yyx990803
Copy link
Member Author

@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

Is it 3.0 thing or 3.next?

@yyx990803
Copy link
Member Author

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

@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

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

Great work!

@CyberAP
Copy link
Contributor

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 has been minimized.

@schtr4jh
Copy link

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 commented Jun 29, 2020

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

@andreiculda
Copy link

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.

</script>
```

The `bindings` object will be:
Copy link

Choose a reason for hiding this comment

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

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

Copy link
Member Author

@yyx990803 yyx990803 Sep 9, 2020

Choose a reason for hiding this comment

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

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.

Copy link

Choose a reason for hiding this comment

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

<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

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

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 commented Sep 19, 2020

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

@Phinome
Copy link

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.

@johnsoncodehk
Copy link
Member

@Phinome I has add language service support: https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar

@ux-engineer
Copy link

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

@mathe42
Copy link

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

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

@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 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 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

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#138

@caikan
Copy link

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

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

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

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
sfc Single File Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.