Open Justineo opened 6 days ago
File | Size | Gzip | Brotli |
---|---|---|---|
runtime-dom.global.prod.js | 100 kB (+42 B) | 38 kB (+21 B) | 34.2 kB (+16 B) |
vue.global.prod.js | 158 kB (+42 B) | 57.8 kB (+22 B) | 51.5 kB (+30 B) |
Name | Size | Gzip | Brotli |
---|---|---|---|
createApp (CAPI only) | 47.2 kB | 18.3 kB | 16.8 kB |
createApp | 55.2 kB | 21.3 kB | 19.5 kB |
createSSRApp | 59.3 kB | 23.1 kB | 21 kB |
defineCustomElement | 60.1 kB | 22.9 kB | 20.8 kB |
overall | 69.1 kB | 26.5 kB | 24.1 kB |
``` pnpm add https://pkg.pr.new/@vue/compiler-core@12461 ```
``` pnpm add https://pkg.pr.new/@vue/compiler-dom@12461 ```
``` pnpm add https://pkg.pr.new/@vue/compiler-ssr@12461 ```
``` pnpm add https://pkg.pr.new/@vue/compiler-sfc@12461 ```
``` pnpm add https://pkg.pr.new/@vue/reactivity@12461 ```
``` pnpm add https://pkg.pr.new/@vue/runtime-core@12461 ```
``` pnpm add https://pkg.pr.new/@vue/runtime-dom@12461 ```
``` pnpm add https://pkg.pr.new/@vue/server-renderer@12461 ```
``` pnpm add https://pkg.pr.new/@vue/shared@12461 ```
``` pnpm add https://pkg.pr.new/vue@12461 ```
``` pnpm add https://pkg.pr.new/@vue/compat@12461 ```
commit: 4b8e266
/ecosystem-ci run
📝 Ran ecosystem CI: Open
suite | result | latest scheduled |
---|---|---|
language-tools | :white_check_mark: success | :white_check_mark: success |
nuxt | :white_check_mark: success | :white_check_mark: success |
pinia | :white_check_mark: success | :white_check_mark: success |
primevue | :white_check_mark: success | :white_check_mark: success |
quasar | :white_check_mark: success | :white_check_mark: success |
radix-vue | :white_check_mark: success | :white_check_mark: success |
router | :white_check_mark: success | :white_check_mark: success |
test-utils | :white_check_mark: success | :white_check_mark: success |
vant | :white_check_mark: success | :white_check_mark: success |
vite-plugin-vue | :white_check_mark: success | :white_check_mark: success |
vitepress | :white_check_mark: success | :white_check_mark: success |
vue-i18n | :white_check_mark: success | :white_check_mark: success |
vue-macros | :white_check_mark: success | :white_check_mark: success |
vuetify | :white_check_mark: success | :white_check_mark: success |
vueuse | :x: failure | :x: failure |
vue-simple-compiler | :white_check_mark: success | :white_check_mark: success |
I think this would also close #7474 and the associated PR #7475.
There's an underlying question here about whether we consider undefined
and null
to be valid values for CSS v-bind()
. Should we support them, or should we just log a warning? It is possible to handle them in userland code instead, e.g. using v-bind(c ?? 'initial')
.
Then there's a follow up decision about where to draw the line. What about an empty string (Playground)? Or a white-space value like " "
? Or an empty array (Playground)? Etc.
I don't want to expand the scope of this PR unnecessarily, but I do think it's important to be clear why we're drawing the line at nullish values.
@skirtles-code Thanks for the feedback! Great questions!
Here’s what I thought:
v-bind
in <style>
blocks is an implementation detail that users are not supposed to be aware of. However, there are notable differences in behavior between color: v-bind('"initial"')
and color: initial
. The former binds initial
to the custom property, making it functionally equivalent to color: unset
, whereas the latter directly applies the initial
keyword as intended in CSS. Making users to write initial
to reset values while it actually behaves like unset
can be quite frustrating, so I would prefer to make it more transparent.initial
.
initial
, as they prevent fallback values from taking effect. Currently, we are removing the custom property for empty strings, but retaining white spaces. This creates an inconsistency if users are binding the value to a custom property themselves (Playground).The situation is more complicated than I expected. I'm investigating the current behavior of Vue and CSS custom properties.
Current behavior:
v-bind value |
SSR output | CSR output | Mismatch |
---|---|---|---|
null |
/ | / | |
undefined |
/ | --foo: undefined |
⚠️ |
empty string | --foo:; |
/ | ⚠️ |
white spaces | --foo: ; |
--foo: ; |
For Vue, undefined
and empty string will cause SSR/CSR mismatch
For CSS custom properties:
cssText
, code in <style>
, and style
attribute behave differently from style.setProperty
. During SSR, we use the former, while during CSR, we use both.--foo:;
differs from style.setProperty('--foo', '')
. The former acts like white spaces, while the latter effectively removes the declaration.Since we are binding values to CSS, we should align with CSS behavior, where an empty string is treated as equivalent to white spaces. To fix the SSR/CSR mismatch and ensure consistent resetting, I propose handling values as follows:
v-bind value |
SSR | CSR |
---|---|---|
null |
--foo: initial |
style.setProperty('--foo', 'initial') |
undefined |
--foo: initial |
style.setProperty('--foo', 'initial') |
empty string | --foo: ; |
style.setProperty('--foo', ' ') |
white spaces | --foo: ; |
style.setProperty('--foo', ' ') |
For other string values, we should output them as-is. For unsupported types of values, we should warn about the binding and retain the current behavior (rendering everything as a string, likely using String(value)
).
WDYT? /cc @adamdehaven @skirtles-code @edison1105
@Justineo I thought we determined that a whitespace character would be ideal?
After rereading the spec, I believe that only the initial
value truly resets the custom property to its default state, as if it were never set.
After rereading the spec, I believe that only the
initial
value truly resets the custom property to its default state, as if it were never set.
The white space character seems to work in practice if you can try?
The white space character seems to work in practice if you can try?
White space sets a valid empty value, which will prevent fallback value to take effect. initial
resets the custom property to its initial value–the guaranteed invalid value, which is the same as it’s not declared. I have tested and can confirm the implementation is aligned with the spec in all major browsers.
The white space character seems to work in practice if you can try?
White space sets a valid empty value, which will prevent fallback value to take effect.
initial
resets the custom property to its initial value–the guaranteed invalid value, which is the same as it’s not declared. I have tested and can confirm the implementation is aligned with the spec in all major browsers.
I'm not sure injecting initial
in all those places though is desirable. Do you have a reproduction with this behavior?
In my testing, initial
and a white space character behave differently.
See the example taken from here
I'm not sure injecting
initial
in all those places though is desirable.
It is the standard way to reset a custom property.
Do you have a reproduction with this behavior?
https://codepen.io/Justineo/pen/VYZZPOz
As shown in this pen, initial
works the same as not setting the value.
In my testing,
initial
and a white space character behave differently.
That’s why we should use initial
instead of white spaces…
See the example taken from here
We certainly don’t want to invalidate the fallback value red
in var(--foo, red)
when no valid value is bound to --foo
. Therefore, using initial
is the only appropriate solution.
The reproduction looks correct; thanks for putting that together
TLDR: I think the latest proposal looks good.
I wasn't initially clear about the difference between initial
and v-bind('"initial"')
, so I put together a Playground to help demonstrate:
v-bind('"initial"')
does indeed behave like unset
, not initial
. I concur that this does make handling undefined
/null
in userland less appealing.
Using initial
for undefined
and null
makes sense to me.
I also like the idea of treating an empty string as a space. That prevents the variable being inherited from the parent component and also seems consistent with vanilla CSS. It wasn't immediately clear to me why a space character would be better than initial
in that case, so I put together another Playground to show an example where it would actually make a difference:
In that example, we would want v-bind('""')
to behave like .bar
and .baz
, not .foo
, so a space character seems to give us what we want.
@skirtles-code Thank you for the demos! I’ll update the code to align with our latest conclusions.
Currently, we generate CSS custom properties on component roots for each
v-bind
in SFC<style>
blocks. When one component instance is nested inside another, the nested instance can inherit values from the outer instance, even if its own binding value isundefined
ornull
. Ideally, style bindings should be scoped per component instance since they depend on the instance's state. However, with the current approach using CSS custom properties, we can't generate unique hashed names for each instance because CSS can only reference a single property.To address this, the improvement here is to "reset" these custom properties when the binding value is nullish.
Current Behavior:
--<hash>-<name>:undefined
forundefined
but ignorenull
, which causes inconsistencies and is related to the hydration warning in #12439.This PR:
--<hash>-<name>:initial
for nullish values. This properly resets the custom property, as explained in the CSS spec. It behaves as if no custom property is defined in ancestor elements.SSR Note:
:
prefix to distinguish style entries coming fromv-bind
in styles. This allows us to process them separately from user-defined custom properties inssrRenderStyle
. Alternatively, we can also create a new runtime helper and modify the codegen here:Not sure if my current approach is preferred. Let me know if you have other opinions.