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

3.4 updates #2625

Merged
merged 13 commits into from
Dec 29, 2023
6 changes: 4 additions & 2 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const nav: ThemeConfig['nav'] = [
{ text: 'Quick Start', link: '/guide/quick-start' },
// { text: 'Style Guide', link: '/style-guide/' },
{ text: 'Glossary', link: '/glossary/' },
{ text: 'Error Reference', link: '/error-reference/' },
{
text: 'Vue 2 Docs',
link: 'https://v2.vuejs.org'
Expand Down Expand Up @@ -418,7 +419,8 @@ export const sidebar: ThemeConfig['sidebar'] = {
{ text: 'Render Function', link: '/api/render-function' },
{ text: 'Server-Side Rendering', link: '/api/ssr' },
{ text: 'TypeScript Utility Types', link: '/api/utility-types' },
{ text: 'Custom Renderer', link: '/api/custom-renderer' }
{ text: 'Custom Renderer', link: '/api/custom-renderer' },
{ text: 'Compile-Time Flags', link: '/api/compile-time-flags' }
]
}
],
Expand Down Expand Up @@ -701,7 +703,7 @@ export default defineConfigWithTheme<ThemeConfig>({
markdown: {
config(md) {
md.use(headerPlugin)
// .use(textAdPlugin)
// .use(textAdPlugin)
}
},

Expand Down
6 changes: 5 additions & 1 deletion src/api/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ Execute a callback with the current app as injection context.

- **Details**

Expects a callback function and runs the callback immediately. During the synchronous call of the callback, `inject()` calls are able to look up injections from the values provided by the current app, even when there is no current active component instance. The return value of the callback will also be returned.
Expects a callback function and runs the callback immediately. During the synchronous call of the callback, `inject()` calls are able to look up injections from the values provided by the current app, even when there is no current active component instance. The return value of the callback will also be returned.

- **Example**

Expand Down Expand Up @@ -374,6 +374,10 @@ Assign a global handler for uncaught errors propagating from within the applicat
- Custom directive hooks
- Transition hooks

:::tip
In production, the 3rd argument (`info`) will be a shortened code instead of the full information string. You can find the code to string mapping in the [Production Error Code Reference](/error-reference/#runtime-errors).
:::

- **Example**

```js
Expand Down
7 changes: 6 additions & 1 deletion src/api/built-in-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ Attach an event listener to the element.

Dynamically bind one or more attributes, or a component prop to an expression.

- **Shorthand:** `:` or `.` (when using `.prop` modifier)
- **Shorthand:**
- `:` or `.` (when using `.prop` modifier)
- Omitting value (when attribute and bound value has the same name) <sup class="vt-badge">3.4+</sup>

- **Expects:** `any (with argument) | Object (without argument)`

Expand Down Expand Up @@ -291,6 +293,9 @@ Dynamically bind one or more attributes, or a component prop to an expression.
<!-- shorthand -->
<img :src="imageSrc" />

<!-- same-name shorthand (3.4+), expands to :src="src" -->
<img :src />

<!-- shorthand dynamic attribute name -->
<button :[key]="value"></button>

Expand Down
108 changes: 108 additions & 0 deletions src/api/compile-time-flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
outline: deep
---

# Compile-Time Flags {#compile-time-flags}

:::tip
Compile-time flags only apply when using the `esm-bundler` build of Vue (i.e. `vue/dist/vue.esm-bundler.js`).
:::

When using Vue with a build step, it is possible to configure a number of compile-time flags to enable / disable certain features. The benefit of using compile-time flags is that features disabled this way can be removed from the final bundle via tree-shaking.

Vue will work even if these flags are not explicitly configured. However, it is recommended to always configure them so that the relevant features can be properly removed when possible.

See [Configuration Guides](#configuration-guides) on how to configure them depending on your build tool.

## `__VUE_OPTIONS_API__`

- **Default:** `true`

Enable / disable Options API support. Disabling this will result in smaller bundles, but may affect compatibility with 3rd party libraries if they rely on Options API.

## `__VUE_PROD_DEVTOOLS__`

- **Default:** `false`

Enable / disable devtools support in production builds. This will result in more code included in the bundle, so it is recommended to only enable this for debugging purposes.

## `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__` <sup class="vt-badge" data-text="3.4+" />

- **Default:** `false`

Enable/disable detailed warnings for hydration mismatches in production builds. This will result in more code included in the bundle, so it is recommended to only enable this for debugging purposes.

## Configuration Guides

### Vite

`@vitejs/plugin-vue` automatically provides default values for these flags. To change the default values, use Vite's [`define` config option](https://vitejs.dev/config/shared-options.html#define):

```js
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
define: {
// enable hydration mismatch details in production build
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'true'
}
})
```

### vue-cli

`@vue/cli-service` automatically provides default values for some of these flags. To configure /change the values:

```js
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.plugin('define').tap((definitions) => {
Object.assign(definitions[0], {
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
})
return definitions
})
}
}
```

### webpack

Flags should be defined using webpack's [DefinePlugin](https://webpack.js.org/plugins/define-plugin/):

```js
// webpack.config.js
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
})
]
}
```

### Rollup

Flags should be defined using [@rollup/plugin-replace](https://github.com/rollup/plugins/tree/master/packages/replace):

```js
// rollup.config.js
import replace from '@rollup/plugin-replace'

export default {
plugins: [
replace({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
})
]
}
```
4 changes: 4 additions & 0 deletions src/api/composition-api-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ Registers a hook to be called when an error propagating from a descendant compon

The hook receives three arguments: the error, the component instance that triggered the error, and an information string specifying the error source type.

:::tip
In production, the 3rd argument (`info`) will be a shortened code instead of the full information string. You can find the code to string mapping in the [Production Error Code Reference](/error-reference/#runtime-errors).
:::

You can modify component state in `errorCaptured()` to display an error state to the user. However, it is important that the error state should not render the original content that caused the error; otherwise the component will be thrown into an infinite render loop.

The hook can return `false` to stop the error from propagating further. See error propagation details below.
Expand Down
4 changes: 4 additions & 0 deletions src/api/options-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ Called when an error propagating from a descendant component has been captured.

The hook receives three arguments: the error, the component instance that triggered the error, and an information string specifying the error source type.

:::tip
In production, the 3rd argument (`info`) will be a shortened code instead of the full information string. You can find the code to string mapping in the [Production Error Code Reference](/error-reference/#runtime-errors).
:::

You can modify component state in `errorCaptured()` to display an error state to the user. However, it is important that the error state should not render the original content that caused the error; otherwise the component will be thrown into an infinite render loop.

The hook can return `false` to stop the error from propagating further. See error propagation details below.
Expand Down
2 changes: 1 addition & 1 deletion src/api/options-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Declare the props of a component.
type?: PropType<T>
required?: boolean
default?: T | ((rawProps: object) => T)
validator?: (value: unknown) => boolean
validator?: (value: unknown, rawProps: object) => boolean
}

type PropType<T> = { new (): T } | { new (): T }[]
Expand Down
7 changes: 5 additions & 2 deletions src/api/reactivity-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ Takes a getter function and returns a readonly reactive [ref](#ref) object for t
```ts
// read-only
function computed<T>(
getter: () => T,
getter: (oldValue: T | undefined) => T,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it correct that this is : T | undefined instead of ?: T, if I remember correctly there might be a TS compiler options so that | undefined means that it needs to explicitly pass a variable that can be undefined, instead of not passing a variable at all.
However, because this is a callback, it is not affected by this compile option.

Copy link
Member Author

Choose a reason for hiding this comment

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

This function is not passed by the user but provided by the framework, so the argument will always be present.

// see "Computed Debugging" link below
debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// writable
function computed<T>(
options: {
get: () => T
get: (oldValue: T | undefined) => T
set: (value: T) => void
},
debuggerOptions?: DebuggerOptions
Expand Down Expand Up @@ -112,6 +112,7 @@ Takes a getter function and returns a readonly reactive [ref](#ref) object for t
- [Guide - Computed Properties](/guide/essentials/computed)
- [Guide - Computed Debugging](/guide/extras/reactivity-in-depth#computed-debugging)
- [Guide - Typing `computed()`](/guide/typescript/composition-api#typing-computed) <sup class="vt-badge ts" />
- [Guide - Performance - Computed Stability](/guide/best-practices/performance#computed-stability) <sup class="vt-badge" data-text="3.4+" />

## reactive() {#reactive}

Expand Down Expand Up @@ -360,6 +361,7 @@ Watches one or more reactive data sources and invokes a callback function when t
flush?: 'pre' | 'post' | 'sync' // default: 'pre'
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
once?: boolean // default: false (3.4+)
}
```

Expand All @@ -386,6 +388,7 @@ Watches one or more reactive data sources and invokes a callback function when t
- **`deep`**: force deep traversal of the source if it is an object, so that the callback fires on deep mutations. See [Deep Watchers](/guide/essentials/watchers#deep-watchers).
- **`flush`**: adjust the callback's flush timing. See [Callback Flush Timing](/guide/essentials/watchers#callback-flush-timing) and [`watchEffect()`](/api/reactivity-core#watcheffect).
- **`onTrack / onTrigger`**: debug the watcher's dependencies. See [Watcher Debugging](/guide/extras/reactivity-in-depth#watcher-debugging).
- **`once`**: run the callback only once. The watcher is automatically stopped after the first callback run. <sup class="vt-badge" data-text="3.4+" />

Compared to [`watchEffect()`](#watcheffect), `watch()` allows us to:

Expand Down
77 changes: 74 additions & 3 deletions src/api/sfc-script-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,77 @@ const props = withDefaults(defineProps<Props>(), {

This will be compiled to equivalent runtime props `default` options. In addition, the `withDefaults` helper provides type checks for the default values, and ensures the returned `props` type has the optional flags removed for properties that do have default values declared.

## defineModel() <sup class="vt-badge" data-text="3.4+" /> {#definemodel}

This macro can be used to declare a two-way binding prop that can be consumed via `v-model` from the parent component. Example usage is also discussed in the [Component `v-model`](/guide/components/v-model) guide.

Under the hood, this macro declares a model prop and a corresponding value update event. If the first argument is a literal string, it will be used as the prop name; Otherwise the prop name will default to `"modelValue"`. In both cases, you can also pass an additional object which can include the prop's options and the model ref's value transform options.

```js
// declares "modelValue" prop, consumed by parent via v-model
const model = defineModel()
// OR: declares "modelValue" prop with options
const model = defineModel({ type: String })

// emits "update:modelValue" when mutated
model.value = 'hello'

// declares "count" prop, consumed by parent via v-model:count
const count = defineModel('count')
Copy link
Contributor

Choose a reason for hiding this comment

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

some random thoughts

Is it possible to declare multiple models like that?

// v-model:count
const count = defineModel('count')
// v-model:x
const x = defineModel('x')

Or will this result in an error?
What happens if someone accidentally redeclared two times the same?

const count = defineModel('count')
const count2 = defineModel('count')

Should this be handled by a linter? Will there be a Vue runtime error?

Copy link
Member Author

Choose a reason for hiding this comment

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

First case is supported. Second case will throw a compile-time error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we add docs that different defineModels are supported and can be used that way? I think it is really common to have multiple v-model:* but it works differently as to defineProps or defineEmits where you pass multiple values at once, but for defineModel you suddenly need multiple calls.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is already shown in the component v-model guide.

// OR: declares "count" prop with options
const count = defineModel('count', { type: Number, default: 0 })

function inc() {
// emits "update:count" when mutated
count.value++
}
```

### Modifiers and Transformers

To access modifiers used with the `v-model` directive, we can destructure the return value of `defineModel()` like this:

```js
const [modelValue, modelModifiers] = defineModel()

// corresponds to v-model.trim
if (modelModifiers.trim) {
// ...
}
```

When a modifier is present, we likely need to transform the value when reading or syncing it back to the parent. We can achieve this by using the `get` and `set` transformer options:

```js
const [modelValue, modelModifiers] = defineModel({
// get() omitted as it is not needed here
set(value) {
// if the .trim modifier is used, return trimmed value
if (modelModifiers.trim) {
return value.trim()
}
// otherwise, return the value as-is
return value
}
})
```

### Usage with TypeScript <sup class="vt-badge ts" /> {#usage-with-typescript}

Like `defineProps` and `defineEmits`, `defineModel` can also receive type arguments to specify the types of the model value and the modifiers:

```ts
const modelValue = defineModel<string>()
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it that way? It feels like it behaves the opposite of ref 🤔
Here is an example:

first line: when I leave out the types at ref, it results in Ref<undefined> (legit to me 👍)
second line: when I explicitly use <boolean>, undefined is not a valid value (also legit to me 👍)

so I would assume that defineModel<string>() results in string and defineModel<string | undefined>() results in string | undefined and I don't need a required at all (or suddenly mix generics-TS and options object, where as e.g. defineProps explicitly forbids this mixing)

Copy link
Member Author

Choose a reason for hiding this comment

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

Because it accepts parent value via a prop, so it behaves like a prop (can be undefined unless explicitly required)

Copy link
Contributor

@Shinigami92 Shinigami92 Dec 28, 2023

Choose a reason for hiding this comment

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

ah I see 🤔
should this be also documented, maybe with a :::tip? or might I have just oversee that?

// ^? Ref<string | undefined>

// default model with options, required removes possible undefined values
const modelValue = defineModel<string>({ required: true })
// ^? Ref<string>

const [modelValue, modifiers] = defineModel<string, 'trim' | 'uppercase'>()
// ^? Record<'trim' | 'uppercase', true | undefined>
```

## defineExpose() {#defineexpose}

Components using `<script setup>` are **closed by default** - i.e. the public instance of the component, which is retrieved via template refs or `$parent` chains, will **not** expose any of the bindings declared inside `<script setup>`.
Expand All @@ -249,7 +320,7 @@ defineExpose({

When a parent gets an instance of this component via template refs, the retrieved instance will be of the shape `{ a: number, b: number }` (refs are automatically unwrapped just like on normal instances).

## defineOptions() {#defineoptions}
## defineOptions() <sup class="vt-badge" data-text="3.3+" /> {#defineoptions}

This macro can be used to declare component options directly inside `<script setup>` without having to use a separate `<script>` block:

Expand Down Expand Up @@ -379,5 +450,5 @@ defineProps<{

## Restrictions {#restrictions}

* Due to the difference in module execution semantics, code inside `<script setup>` relies on the context of an SFC. When moved into external `.js` or `.ts` files, it may lead to confusion for both developers and tools. Therefore, **`<script setup>`** cannot be used with the `src` attribute.
* `<script setup>` does not support In-DOM Root Component Template.([Related Discussion](https://github.com/vuejs/core/issues/8391))
- Due to the difference in module execution semantics, code inside `<script setup>` relies on the context of an SFC. When moved into external `.js` or `.ts` files, it may lead to confusion for both developers and tools. Therefore, **`<script setup>`** cannot be used with the `src` attribute.
- `<script setup>` does not support In-DOM Root Component Template.([Related Discussion](https://github.com/vuejs/core/issues/8391))
34 changes: 34 additions & 0 deletions src/error-reference/ErrorsTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script setup lang="ts">
defineProps<{
kind: string
errors: Record<any, string>
highlight?: any
}>()
</script>

<template>
<table>
<thead>
<tr>
<th>Code</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<tr
v-for="(msg, code) of errors"
:class="{ highlight: highlight === `${kind}-${code}` }"
>
<td :id="`${kind}-${code}`" v-text="code" />
<td v-text="msg" />
</tr>
</tbody>
</table>
</template>

<style scoped>
.highlight {
color: var(--vt-c-yellow-darker);
font-weight: bold;
}
</style>
17 changes: 17 additions & 0 deletions src/error-reference/errors.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineLoader } from 'vitepress'
import { errorMessages } from 'vue/compiler-sfc'
// @ts-expect-error internal api
import { ErrorTypeStrings } from 'vue'
brc-dd marked this conversation as resolved.
Show resolved Hide resolved

function filterEmptyMsg(data: Record<number, string>) {
return Object.fromEntries(Object.entries(data).filter(([_, msg]) => msg))
}

export default defineLoader({
load() {
return {
compiler: filterEmptyMsg(errorMessages),
runtime: filterEmptyMsg(ErrorTypeStrings)
}
}
})
Loading