Skip to content

Added custom components interop migration section #177

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

Merged
merged 5 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ const sidebar = {
'migration/fragments',
'migration/render-function-api',
'migration/slots-unification',
'migration/keycode-modifiers'
'migration/keycode-modifiers',
'migration/custom-elements-interop'
]
},
{
Expand Down
29 changes: 29 additions & 0 deletions src/api/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,32 @@

- **See also:**
- [Data Binding Syntax - interpolations](../guide/template-syntax.html#text)

## v-is

> Note: this section only affects cases where Vue templates are directly written in the page's HTML.

- **Expects:** string literal

- **Limited to:** native HTML elements

- **Usage:** When using in-DOM templates, the template is subject to native HTML parsing rules. Some HTML elements, such as `<ul>`, `<ol>`, `<table>` and `<select>` have restrictions on what elements can appear inside them, and some elements such as `<li>`, `<tr>`, and `<option>` can only appear inside certain other elements. As a workaround, we can use `v-is` directive on these elements:

```html
<table>
<tr v-is="'blog-post-row'"></tr>
</table>
```

:::warning
`v-is` functions like a dynamic 2.x `:is` binding - so to render a component by its registered name, its value should be a JavaScript string literal:

```html
<!-- Incorrect, nothing will be rendered -->
<tr v-is="blog-post-row"></tr>

<!-- Correct -->
<tr v-is="'blog-post-row'"></tr>
```

:::
10 changes: 2 additions & 8 deletions src/api/special-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,16 @@

- **Expects:** `string | Object (component’s options object)`

Used for [dynamic components](../guide/component-dynamic-async.html) and to work around [limitations of in-DOM templates](../guide/component-basics.html#dom-template-parsing-caveats).
Used for [dynamic components](../guide/component-dynamic-async.html).

For example:

```html
<!-- component changes when currentView changes -->
<component :is="currentView"></component>

<!-- necessary because `<my-row>` would be invalid inside -->
<!-- a `<table>` element and so would be hoisted out -->
<table>
<tr is="my-row"></tr>
</table>
```

For detailed usage, follow the links in the description above.
```

- **See also:**
- [Dynamic Components](../guide/component-dynamic-async.html)
Expand Down
19 changes: 16 additions & 3 deletions src/guide/component-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ The above is made possible by Vue's `<component>` element with the `is` special

```html
<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>
<component :is="currentTabComponent"></component>
```

In the example above, `currentTabComponent` can contain either:
Expand All @@ -402,14 +402,27 @@ This will lead to issues when using components with elements that have such rest
</table>
```

The custom component `<blog-post-row>` will be hoisted out as invalid content, causing errors in the eventual rendered output. Fortunately, the `is` special attribute offers a workaround:
The custom component `<blog-post-row>` will be hoisted out as invalid content, causing errors in the eventual rendered output. Fortunately, we can use `v-is` special directive as a workaround:

```html
<table>
<tr is="blog-post-row"></tr>
<tr v-is="'blog-post-row'"></tr>
</table>
```

:::warning
`v-is` value should be a JavaScript string literal:

```html
<!-- Incorrect, nothing will be rendered -->
<tr v-is="blog-post-row"></tr>

<!-- Correct -->
<tr v-is="'blog-post-row'"></tr>
```

:::

Also, HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you’re using in-DOM templates, camelCased prop names and event handler parameters need to use their kebab-cased (hyphen-delimited) equivalents:

```js
Expand Down
4 changes: 2 additions & 2 deletions src/guide/component-dynamic-async.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Earlier, we used the `is` attribute to switch between components in a tabbed interface:

```vue-html
<component v-bind:is="currentTabComponent"></component>
<component :is="currentTabComponent"></component>
```

When switching between these components though, you'll sometimes want to maintain their state or avoid re-rendering for performance reasons. For example, when expanding our tabbed interface a little:
Expand All @@ -26,7 +26,7 @@ Recreating dynamic components is normally useful behavior, but in this case, we'
```vue-html
<!-- Inactive components will be cached! -->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
<component :is="currentTabComponent"></component>
</keep-alive>
```

Expand Down
141 changes: 141 additions & 0 deletions src/guide/migration/custom-elements-interop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
types:
- breaking
---

# Custom Elements Interop changes <span v-for="type in $frontmatter.types" class="badge" :key="`type-${type}`">{{ type }}</span>

# Overview

- **BREAKING:** Custom elements whitelisting is now performed during template compilation, and should be configured via compiler options instead of runtime config.
- **BREAKING:** Special `is` prop usage is restricted to the reserved `<component>` tag only.
- **NEW:** There is new `v-is` directive to support 2.x use cases where `is` was used on native elements to work around native HTML parsing restrictions.

## Autonomous Custom Elements

If we want to add a custom element defined outside of Vue (e.g. using the Web Components API), we need to 'instruct' Vue to treat it as a custom element. Let's use the following template as an example.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
If we want to add a custom element defined outside of Vue (e.g. using the Web Components API), we need to 'instruct' Vue to treat it as a custom element. Let's use the following template as an example.
If we want to add a custom element defined outside of Vue (e.g. using the Web Components API), we need to 'instruct' Vue to treat it as a custom element. Let's use the following component as an example.

Is component more accurate?

Copy link
Member Author

Choose a reason for hiding this comment

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

@bencodezen ugh, it's complicated 😅 it's a custom element, not Vue component, that's why I used template word here


```html
<plastic-button></plastic-button>
```

### 2.x Syntax

In Vue 2.x, whitelisting tags as custom elements was done via `Vue.config.ignoredElements`:

```js
// This will make Vue ignore custom element defined outside of Vue
// (e.g., using the Web Components APIs)

Vue.config.ignoredElements = ['plastic-button']
```

### 3.x Syntax

**In Vue 3.0, this check is performed during template compilation.** To instruct the compiler to treat `<plastic-button>` as a custom element:

- If using a build step: pass the `isCustomElement` option to the Vue template compiler. If using `vue-loader`, this should be passed via `vue-loader`'s `compilerOptions` option:

```js
// in webpack config
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
options: {
compilerOptions: {
isCustomElement: tag => tag === 'plastic-button'
}
}
}
// ...
]
```

- If using on-the-fly template compilation, pass it via `app.config.isCustomElement`:

```js
const app = Vue.createApp({})
app.config.isCustomElement = tag => tag === 'plastic-button'
```

It's important to note the runtime config only affects runtime template compilation - it won't affect pre-compiled templates.

## Customized Built-in Elements

The Custom Elements specification provides a way to use custom elements as [Customized Built-in Element](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example) by adding the `is` attribute to a built-in element:

```html
<button is="plastic-button">Click Me!</button>
```

Vue's usage of the `is` special prop was simulating what the native attribute does before it was made universally available in browsers. However, in 2.x it was interpreted as rendering a Vue component with the name `plastic-button`. This blocks the native usage of Customized Built-in Element mentioned above.

In 3.0, we are limiting Vue's special treatment of the `is` prop to the `<component>` tag only.

- When used on the reserved `<component>` tag, it will behave exactly the same as in 2.x;
- When used on normal components, it will behave like a normal prop:

```html
<foo is="bar" />
```

- 2.x behavior: renders the `bar` component.
- 3.x behavior: renders the `foo` component and passing the `is` prop.

- When used on plain elements, it will be passed to the `createElement` call as the `is` option, and also rendered as a native attribute. This supports the usage of customized built-in elements.

```html
<button is="plastic-button">Click Me!</button>
```

- 2.x behavior: renders the `plastic-button` component.
- 3.x behavior: renders a native button by calling

```js
document.createElement('button', { is: 'plastic-button' })
```

## `v-is` for In-DOM Template Parsing Workarounds

> Note: this section only affects cases where Vue templates are directly written in the page's HTML.
> When using in-DOM templates, the template is subject to native HTML parsing rules. Some HTML elements, such as `<ul>`, `<ol>`, `<table>` and `<select>` have restrictions on what elements can appear inside them, and some elements such as `<li>`, `<tr>`, and `<option>` can only appear inside certain other elements.

### 2x Syntax

In Vue 2 we recommended working around with these restrictions by using the `is` prop on a native tag:

```html
<table>
<tr is="blog-post-row"></tr>
</table>
```

### 3.x Syntax

With the behavior change of `is`, we introduce a new directive `v-is` for working around these cases:

```html
<table>
<tr v-is="'blog-post-row'"></tr>
</table>
```

:::warning
`v-is` functions like a dynamic 2.x `:is` binding - so to render a component by its registered name, its value should be a JavaScript string literal:

```html
<!-- Incorrect, nothing will be rendered -->
<tr v-is="blog-post-row"></tr>

<!-- Correct -->
<tr v-is="'blog-post-row'"></tr>
```

:::

## Migration Strategy

- Replace `config.ignoredElements` with either `vue-loader`'s `compilerOptions` (with the build step) or `app.config.isCustomElement` (with on-the-fly template compilation)

- Change all non-`<component>` tags with `is` usage to `<component is="...">` (for SFC templates) or `v-is` (for in-DOM templates).
2 changes: 1 addition & 1 deletion src/guide/migration/functional-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Or, for those who preferred the `<template>` in a single-file component:
// Vue 2 Functional Component Example with <template>
<template functional>
<component
v-bind:is="`h${props.level}`"
:is="`h${props.level}`"
v-bind="attrs"
v-on="listeners"
/>
Expand Down
4 changes: 2 additions & 2 deletions src/guide/migration/v-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

In terms of what has changed, at a high level:

- When used on custom components, `v-model` prop and event default names are changed:
- **BREAKING:**When used on custom components, `v-model` prop and event default names are changed:
- prop: `value` -> `modelValue`;
- event: `input` -> `update:modelValue`;
- Multiple `v-model` bindings on the same component are possible now;
- **BREAKING:** `v-bind`'s `.sync` modifier and component `model` option are removed and replaced with an argument on `v-model`;
- **NEW:** Multiple `v-model` bindings on the same component are possible now;
- **NEW:** Added the ability to create custom `v-model` modifiers.

For more information, read on!
Expand Down