Add Vue composition API plugin

Feature code that needs the composition API will need this as long as
we're still on Vue 2. This will also help migrate to Vue 3 more
seamlessly, as working around some breaking changes in Vue 3 requires
using the composition API.

Bug: T251974
Change-Id: I8e334ae5f447a8f9b64a7c910b2c1776cef118db
This commit is contained in:
Roan Kattouw 2021-09-24 18:52:11 -07:00 committed by Jforrester
parent f300c1c51d
commit 10fae048df
8 changed files with 2803 additions and 1 deletions

View file

@ -57,7 +57,7 @@ For notes on 1.36.x and older releases, see HISTORY.
* …
==== New external libraries ====
*
* Added vue-composition-api 1.2.2
===== New development-only external libraries =====
* …

View file

@ -582,6 +582,26 @@ return [
'targets' => [ 'desktop', 'mobile' ],
],
'vue-composition-api' => [
'packageFiles' => [
'resources/src/vue/composition-api.js',
[
'name' => 'resources/lib/vue-composition-api/vue-composition-api.js',
'callback' => static function ( ResourceLoaderContext $context, Config $config ) {
// Use the development version if development mode is enabled, or if we're in debug mode
$file = $config->get( 'VueDevelopmentMode' ) || $context->getDebug() ?
'resources/lib/vue-composition-api/vue-composition-api.js' :
'resources/lib/vue-composition-api/vue-composition-api.prod.js';
return new ResourceLoaderFilePath( $file );
}
]
],
'dependencies' => [
'vue'
],
'targets' => [ 'desktop', 'mobile' ]
],
'vuex' => [
'packageFiles' => [
'resources/src/vue/vuex.js',

View file

@ -324,6 +324,17 @@ vue:
vue-2.6.11/dist/vue.common.prod.js:
vue-2.6.11/dist/vue.common.dev.js:
vue-composition-api:
type: tar
src: https://registry.npmjs.org/@vue/composition-api/-/composition-api-1.2.2.tgz
integrity: sha512-hnnock2l8Uawuzm/Nq8LqaztuOO70rpyX3Gz/Kpl/JtNalsG/EuuHuRQAWPiT3Is8NCB/8V5hiH7fmCmAc0+pQ==
dest:
package/README.md:
package/LICENSE:
package/dist/vue-composition-api.js:
package/dist/vue-composition-api.prod.js:
vuex:
type: tar
src: https://codeload.github.com/vuejs/vuex/tar.gz/v3.1.3

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-present, liximomo(X.L)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,505 @@
# @vue/composition-api
Vue 2 plugin for **Composition API**
[![npm](https://img.shields.io/npm/v/@vue/composition-api)](https://www.npmjs.com/package/@vue/composition-api)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/vuejs/composition-api/Build%20&%20Test)](https://github.com/vuejs/composition-api/actions?query=workflow%3A%22Build+%26+Test%22)
[![Minzipped size](https://badgen.net/bundlephobia/minzip/@vue/composition-api)](https://bundlephobia.com/result?p=@vue/composition-api)
English | [中文](./README.zh-CN.md) ・ [**Composition API Docs**](https://v3.vuejs.org/guide/composition-api-introduction.html)
## Installation
### NPM
```bash
npm install @vue/composition-api
# or
yarn add @vue/composition-api
```
You must install `@vue/composition-api` as a plugin via `Vue.use()` before you can use the [Composition API](https://composition-api.vuejs.org/) to compose your component.
```js
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
```
```js
// use the APIs
import { ref, reactive } from '@vue/composition-api'
```
> :bulb: When you migrate to Vue 3, just replacing `@vue/composition-api` to `vue` and your code should just work.
### CDN
Include `@vue/composition-api` after Vue and it will install itself automatically.
<!--cdn-links-start-->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.6"></script>
<script src="https://cdn.jsdelivr.net/npm/@vue/composition-api@1.2.2"></script>
```
<!--cdn-links-end-->
`@vue/composition-api` will be exposed to global variable `window.VueCompositionAPI`.
```ts
const { ref, reactive } = VueCompositionAPI
```
## TypeScript Support
> TypeScript version **>4.2** is required
To let TypeScript properly infer types inside Vue component options, you need to define components with `defineComponent`
```ts
import { defineComponent } from '@vue/composition-api'
export default defineComponent({
// type inference enabled
})
```
### JSX/TSX
JSX is now officially supported on [vuejs/jsx](https://github.com/vuejs/jsx). You can enable it by following [this document](https://github.com/vuejs/jsx/tree/dev/packages/babel-preset-jsx#usage). A community maintained version can be found at [babel-preset-vca-jsx](https://github.com/luwanquan/babel-preset-vca-jsx) by [@luwanquan](https://github.com/luwanquan).
To support TSX, create a declaration file with the following content in your project.
```ts
// file: shim-tsx.d.ts
import Vue, { VNode } from 'vue';
import { ComponentRenderProxy } from '@vue/composition-api';
declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass extends ComponentRenderProxy {}
interface ElementAttributesProperty {
$props: any; // specify the property name to use
}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
```
## SSR
Even if there is no definitive Vue 3 API for SSR yet, this plugin implements the `onServerPrefetch` lifecycle hook that allows you to use the `serverPrefetch` hook found in the classic API.
```js
import { onServerPrefetch } from '@vue/composition-api'
export default {
setup(props, { ssrContext }) {
const result = ref()
onServerPrefetch(async () => {
result.value = await callApi(ssrContext.someId)
})
return {
result,
}
}
}
```
## Browser Compatibility
`@vue/composition-api` supports all modern browsers and IE11+. For lower versions IE you should install `WeakMap` polyfill (for example from `core-js` package).
## Limitations
> :white_check_mark: Support &nbsp;&nbsp;&nbsp;&nbsp;:x: Not Supported
### `Ref` Unwrap
<details>
<summary>
<b>Should NOT</b> use <code>ref</code> in a plain object when working with <code>Array</code>
</summary>
```js
const a = {
count: ref(0),
}
const b = reactive({
list: [a], // `a.count` will not unwrap!!
})
// no unwrap for `count`, `.value` is required
b.list[0].count.value === 0 // true
```
```js
const b = reactive({
list: [
{
count: ref(0), // no unwrap!!
},
],
})
// no unwrap for `count`, `.value` is required
b.list[0].count.value === 0 // true
```
</details>
<details>
<summary>
<b>Should</b> always use <code>ref</code> in a <code>reactive</code> when working with <code>Array</code>
</summary>
```js
const a = reactive({
list: [
reactive({
count: ref(0),
}),
]
})
// unwrapped
a.list[0].count === 0 // true
a.list.push(
reactive({
count: ref(1),
})
)
// unwrapped
a.list[1].count === 1 // true
```
</details>
### Template Refs
<details>
<summary>
✅ String ref && return it from <code>setup()</code>
</summary>
```html
<template>
<div ref="root"></div>
</template>
<script>
export default {
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div/>
})
return {
root,
}
},
}
</script>
```
</details>
<details>
<summary>
✅ String ref && return it from <code>setup()</code> && Render Function / JSX
</summary>
```jsx
export default {
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div/>
})
return {
root,
}
},
render() {
// with JSX
return () => <div ref="root" />
},
}
```
</details>
<details>
<summary>
❌ Function ref
</summary>
```html
<template>
<div :ref="el => root = el"></div>
</template>
<script>
export default {
setup() {
const root = ref(null)
return {
root,
}
},
}
</script>
```
</details>
<details>
<summary>
❌ Render Function / JSX in <code>setup()</code>
</summary>
```jsx
export default {
setup() {
const root = ref(null)
return () =>
h('div', {
ref: root,
})
// with JSX
return () => <div ref={root} />
},
}
```
</details>
<details>
<summary>
⚠️ <code>$refs</code> accessing workaround
</summary>
<br>
> :warning: **Warning**: The `SetupContext.refs` won't exist in `Vue 3.0`. `@vue/composition-api` provide it as a workaround here.
If you really want to use template refs in this case, you can access `vm.$refs` via `SetupContext.refs`
```jsx
export default {
setup(initProps, setupContext) {
const refs = setupContext.refs
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(refs.root) // <div/>
})
return () =>
h('div', {
ref: 'root',
})
// with JSX
return () => <div ref="root" />
},
}
```
</details>
### Reactive
<details>
<summary>
⚠️ <code>reactive()</code> <b>mutates</b> the original object
</summary>
`reactive` uses `Vue.observable` underneath which will ***mutate*** the original object.
> :bulb: In Vue 3, it will return an new proxy object.
</details>
<details>
<summary>
⚠️ <code>set</code> and <code>del</code> workaround for adding and deleting reactive properties
</summary>
> ⚠️ Warning: `set` and `del` do NOT exist in Vue 3. We provide them as a workaround here, due to the limitation of [Vue 2.x reactivity system](https://vuejs.org/v2/guide/reactivity.html#For-Objects).
>
> In Vue 2, you will need to call `set` to track new keys on an `object`(similar to `Vue.set` but for `reactive objects` created by the Composition API). In Vue 3, you can just assign them like normal objects.
>
> Similarly, in Vue 2 you will need to call `del` to [ensure a key deletion triggers view updates](https://vuejs.org/v2/api/#Vue-delete) in reactive objects (similar to `Vue.delete` but for `reactive objects` created by the Composition API). In Vue 3 you can just delete them by calling `delete foo.bar`.
```ts
import { reactive, set } from '@vue/composition-api'
const a = reactive({
foo: 1
})
// add new reactive key
set(a, 'bar', 1)
// remove a key and trigger reactivity
del(a, 'bar')
```
</details>
### Watch
<details>
<summary>
<code>onTrack</code> and <code>onTrigger</code> are not available in <code>WatchOptions</code>
</summary>
```js
watch(() => {
/* ... */
}, {
immediate: true,
onTrack() {}, // not available
onTrigger() {}, // not available
})
```
</details>
### `createApp`
<details>
<summary>
⚠️ <code>createApp()</code> is global
</summary>
In Vue 3, `createApp()` is introduced to provide context(plugin, components, etc.) isolation between app instances. Due the the design of Vue 2, in this plugin, we provide `createApp()` as a forward compatible API which is just an alias of the global.
```ts
const app1 = createApp(RootComponent1)
app1.component('Foo', Foo) // equivalent to Vue.component('Foo', Foo)
app1.use(VueRouter) // equivalent to Vue.use(VueRouter)
const app2 = createApp(RootComponent2)
app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar)
```
</details>
### `shallowReadonly`
<details>
<summary>
⚠️ <code>shallowReadonly()</code> will create a new object and with the same root properties, new properties added will <b>not</b> be readonly or reactive.
</summary>
> :bulb: In Vue 3, it will return an new proxy object.
</details>
### `readonly`
<details>
<summary>
⚠️ <code>readonly()</code> provides <b>only type-level</b> readonly check.
</summary>
`readonly()` is provided as API alignment with Vue 3 on type-level only. Use <code>isReadonly()</code> on it or it's properties can not be guaranteed.
</details>
### `props`
<details>
<summary>
⚠️ <code>toRefs(props.foo)</code> will incorrectly warn when accessing nested levels of props. <br>
&nbsp;&nbsp;&nbsp;&nbsp;⚠️ <code>isReactive(props.foo)</code> will return false.
</summary>
```ts
defineComponent({
setup(props) {
const { bar } = toRefs(props.foo) // it will `warn`
// use this instead
const { foo } = toRefs(props)
const a = foo.value.bar
}
})
```
</details>
### Missing APIs
The following APIs introduced in Vue 3 are not available in this plugin.
- `onRenderTracked`
- `onRenderTriggered`
- `isProxy`
### Reactive APIs in `data()`
<details>
<summary>
❌ Passing <code>ref</code>, <code>reactive</code> or other reactive apis to <code>data()</code> would not work.
</summary>
```jsx
export default {
data() {
return {
// will result { a: { value: 1 } } in template
a: ref(1),
}
},
}
```
</details>
### `emits` Options
<details>
<summary>
<code>emits</code> option is provided in type-level only, in order to align with Vue 3's type interface. Does NOT have actual effects on the code.
</summary>
```ts
defineComponent({
emits: {
// has no effects
submit: (eventOption) => {
if (...) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
}
})
```
</details>
### Performance Impact
Due the the limitation of Vue2's public API. `@vue/composition-api` inevitably introduces some performance overhead. Note that in most scenarios, this shouldn't be the source of performance issues.
You can check the [benchmark results](https://antfu.github.io/vue-composition-api-benchmark-results/) for more details.

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,12 @@
/* global VueCompositionAPI */
// eslint-disable-next-line no-implicit-globals
var Vue = require( 'vue' );
// vue-composition-api.js requires the window.Vue global
window.Vue = Vue;
// Unfortunately, vue-composition-api.js creates a VueCompositionAPI global rather than exporting it
require( '../../lib/vue-composition-api/vue-composition-api.js' );
Vue.use( VueCompositionAPI );
module.exports = VueCompositionAPI;