]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
fix(router-view): preserve keep-alive route guard this context (#344)
authorEvan You <yyx990803@gmail.com>
Thu, 2 Jul 2020 13:49:35 +0000 (09:49 -0400)
committerGitHub <noreply@github.com>
Thu, 2 Jul 2020 13:49:35 +0000 (15:49 +0200)
* refactor: refactor router-view + fix keep-alive route guard this context

* test: add skipped unit test for keep alive

* refactor(e2e): omit props in v-slot

* test(e2e): add guard tests keep alive

* test(e2e): fix keep alive test

* refactor(router-view): render slot content if empty

Co-authored-by: Eduardo San Martin Morote <posva13@gmail.com>
__tests__/RouterView.spec.ts
e2e/keep-alive/index.html [new file with mode: 0644]
e2e/keep-alive/index.ts [new file with mode: 0644]
e2e/scroll-behavior/index.ts
e2e/specs/keep-alive.js [new file with mode: 0644]
e2e/transitions/index.ts
package.json
playground/App.vue
src/RouterView.ts
yarn.lock

index cce62e4e31d853669ff48eddce3eb8a2e25512cf..80a66f49bcc1d32c9fe716a5d422bc6aa58e6d2e 100644 (file)
@@ -292,4 +292,35 @@ describe('RouterView', () => {
     const { wrapper } = await factory(routes.withFnProps)
     expect(wrapper.html()).toBe(`<div>id:2;other:page</div>`)
   })
+
+  describe('KeepAlive', () => {
+    async function factory(
+      initialRoute: RouteLocationNormalizedLoose,
+      propsData: any = {}
+    ) {
+      const route = createMockedRoute(initialRoute)
+      const wrapper = await mount(RouterView, {
+        propsData,
+        provide: route.provides,
+        components: { RouterView },
+        slots: {
+          default: `
+          <keep-alive>
+            <component :is="Component"/>
+          </keep-alive>
+        `,
+        },
+      })
+
+      return { route, wrapper }
+    }
+
+    // TODO: maybe migrating to VTU 2 to handle this properly
+    it.skip('works', async () => {
+      const { route, wrapper } = await factory(routes.root)
+      expect(wrapper.html()).toMatchInlineSnapshot(`"<div>Home</div>"`)
+      await route.set(routes.foo)
+      expect(wrapper.html()).toMatchInlineSnapshot(`"<div>Foo</div>"`)
+    })
+  })
 })
diff --git a/e2e/keep-alive/index.html b/e2e/keep-alive/index.html
new file mode 100644 (file)
index 0000000..66d76cf
--- /dev/null
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+    <title>Vue Router e2e tests - Keep Alive</title>
+    <!-- TODO: replace with local imports for promises and anything else needed -->
+    <script src="https://polyfill.io/v3/polyfill.min.js?features=default%2Ces2015"></script>
+  </head>
+  <body>
+    <a href="/">&lt;&lt; Back to Homepage</a>
+    <hr />
+
+    <main id="app"></main>
+  </body>
+</html>
diff --git a/e2e/keep-alive/index.ts b/e2e/keep-alive/index.ts
new file mode 100644 (file)
index 0000000..12d2cab
--- /dev/null
@@ -0,0 +1,92 @@
+import { createRouter, createWebHistory, useRouter } from '../../src'
+import { RouteComponent } from '../../src/types'
+import { createApp, ref } from 'vue'
+
+const Home: RouteComponent = {
+  template: `
+    <div class="home">
+      <h2>Home</h2>
+      <p>Counter: <span id="counter">{{ n }}</span></p>
+      <button @click="n++" id="increment">Increment</button>
+    </div>
+  `,
+  setup() {
+    return {
+      n: ref(0),
+    }
+  },
+}
+
+const Foo: RouteComponent = { template: '<div class="foo">foo</div>' }
+
+const WithGuards: RouteComponent = {
+  template: `<div>
+    <p>Update Count <span id="update-count">{{ updateCount }}</span></p>
+    <p>Leave Count <span id="leave-count">{{ leaveCount }}</span></p>
+    <button id="change-query" @click="changeQuery">Change query</button>
+    <button id="reset" @click="reset">Reset</button>
+    </div>`,
+
+  beforeRouteUpdate(to, from, next) {
+    this.updateCount++
+    next()
+  },
+  beforeRouteLeave(to, from, next) {
+    this.leaveCount++
+    next()
+  },
+
+  setup() {
+    const updateCount = ref(0)
+    const leaveCount = ref(0)
+    const router = useRouter()
+
+    function reset() {
+      updateCount.value = 0
+      leaveCount.value = 0
+    }
+
+    function changeQuery() {
+      router.push({ query: { q: Date.now() } })
+    }
+    return {
+      reset,
+      changeQuery,
+      updateCount,
+      leaveCount,
+    }
+  },
+}
+
+const webHistory = createWebHistory('/' + __dirname)
+const router = createRouter({
+  history: webHistory,
+  routes: [
+    { path: '/', component: Home },
+    { path: '/with-guards', component: WithGuards },
+    {
+      path: '/foo',
+      component: Foo,
+    },
+  ],
+})
+const app = createApp({
+  template: `
+    <div id="app">
+      <h1>KeepAlive</h1>
+      <ul>
+        <li><router-link to="/">/</router-link></li>
+        <li><router-link to="/foo">/foo</router-link></li>
+        <li><router-link to="/with-guards">/with-guards</router-link></li>
+      </ul>
+      <router-view v-slot="{ Component }">
+        <keep-alive>
+          <component class="view" :is="Component" />
+        </keep-alive>
+      </router-view>
+    </div>
+  `,
+})
+app.use(router)
+
+app.mount('#app')
index aa809d07f1cf53ff1fba8a5b6d629dbfb92ff288..f976cd007ef6b7074e9f6c8c73fd7415b9ea80d0 100644 (file)
@@ -106,14 +106,14 @@ const app = createApp({
       <label>
       <input type="checkbox" v-model="smoothScroll"> Use smooth scroll
       </label>
-      <router-view class="view" v-slot="{ Component, props }">
+      <router-view class="view" v-slot="{ Component }">
         <transition
           name="fade"
           mode="out-in"
           @before-enter="flushWaiter"
           @before-leave="setupWaiter"
         >
-          <component :is="Component" v-bind="props"></component>
+          <component :is="Component" />
         </transition>
       </router-view>
     </div>
diff --git a/e2e/specs/keep-alive.js b/e2e/specs/keep-alive.js
new file mode 100644 (file)
index 0000000..a9e1c4e
--- /dev/null
@@ -0,0 +1,38 @@
+const bsStatus = require('../browserstack-send-status')
+
+module.exports = {
+  ...bsStatus(),
+
+  '@tags': [],
+
+  /** @type {import('nightwatch').NightwatchTest} */
+  KeepAlive: function (browser) {
+    browser
+      .url('http://localhost:8080/keep-alive/')
+      .waitForElementPresent('#app > *', 1000)
+
+      .assert.containsText('#counter', '0')
+      .click('#increment')
+      .assert.containsText('#counter', '1')
+
+      .click('li:nth-child(2) a')
+      .assert.containsText('.view', 'foo')
+      .click('li:nth-child(1) a')
+      .assert.containsText('#counter', '1')
+
+      .click('li:nth-child(3) a')
+      .assert.containsText('#update-count', '0')
+      .click('#change-query')
+      .assert.containsText('#update-count', '1')
+      .back()
+      .assert.containsText('#update-count', '2')
+      .assert.containsText('#leave-count', '0')
+      .back()
+      .assert.containsText('#counter', '1')
+      .forward()
+      .assert.containsText('#update-count', '2')
+      .assert.containsText('#leave-count', '1')
+
+      .end()
+  },
+}
index 8745d507787d4459d6a067a783d73b620cbb9e7e..153567248b6571f92f2686fa4d6f943515f30382 100644 (file)
@@ -47,9 +47,9 @@ const Parent: RouteComponent = {
     <div class="parent">
       <h2>Parent</h2>
       {{ transitionName }}
-      <router-view class="child-view" v-slot="{ Component, props }">
+      <router-view class="child-view" v-slot="{ Component }">
         <transition :name="transitionName">
-          <component :is="Component" v-bind="props" />
+          <component :is="Component" />
         </transition>
       </router-view>
     </div>
@@ -96,9 +96,9 @@ const app = createApp({
         <li><router-link to="/parent/foo">/parent/foo</router-link></li>
         <li><router-link to="/parent/bar">/parent/bar</router-link></li>
       </ul>
-      <router-view class="view" v-slot="{ Component, props }">
+      <router-view class="view" v-slot="{ Component }">
         <transition name="fade" mode="out-in">
-          <component v-if="Component" :is="Component" v-bind="props"></component>
+          <component :is="Component" />
         </transition>
       </router-view>
     </div>
index 7adeea641f8b2c84aa214944a1d391454891017e..3a525788492e3e3a9c423f026e78498c8b9472ea 100644 (file)
@@ -35,7 +35,7 @@
     "test": "yarn run test:types && yarn run test:unit && yarn run build && yarn run build:dts && yarn run test:e2e",
     "test:e2e": "yarn run test:e2e:headless && yarn run test:e2e:native",
     "test:e2e:headless": "node e2e/runner.js -e chrome-headless --skiptags no-headless",
-    "test:e2e:native": "node e2e/runner.js -e chrome --tags no-headless",
+    "test:e2e:native": "node e2e/runner.js -e chrome --tag no-headless",
     "test:e2e:ci": "node e2e/runner.js -e firefox --retries 2"
   },
   "gitHooks": {
@@ -59,7 +59,7 @@
     }
   ],
   "peerDependencies": {
-    "vue": "^3.0.0-beta.17"
+    "vue": "^3.0.0-beta.18"
   },
   "devDependencies": {
     "@microsoft/api-documenter": "^7.8.19",
@@ -73,8 +73,8 @@
     "@types/jsdom": "^16.2.3",
     "@types/webpack": "^4.41.18",
     "@types/webpack-env": "^1.15.2",
-    "@vue/compiler-sfc": "3.0.0-beta.17",
-    "@vue/server-renderer": "^3.0.0-beta.17",
+    "@vue/compiler-sfc": "3.0.0-beta.18",
+    "@vue/server-renderer": "^3.0.0-beta.18",
     "axios": "^0.19.2",
     "browserstack-local": "^1.4.5",
     "chalk": "^4.1.0",
     "ts-node": "^8.10.2",
     "tsd": "^0.11.0",
     "typescript": "^3.9.5",
-    "vue": "^3.0.0-beta.17",
+    "vue": "^3.0.0-beta.18",
     "vue-loader": "^16.0.0-beta.4",
     "webpack": "^4.43.0",
     "webpack-bundle-analyzer": "^3.8.0",
index aca3f94a0f5fb3aee7dc9c9ea4c8413d0d99eb0b..62aec5f34d2e27a4a5d719e045723a4c23e4b006 100644 (file)
     <button @click="toggleViewName">Toggle view</button>
     <Suspense>
       <template #default>
-        <router-view :name="viewName" v-slot="{ Component, props }">
-          <!-- <transition
+        <router-view :name="viewName" v-slot="{ Component }">
+          <transition
             name="fade"
             mode="out-in"
             @before-enter="flushWaiter"
             @before-leave="setupWaiter"
-          > -->
-          <component v-if="Component" :is="Component" v-bind="props" />
-          <!-- </transition> -->
+          >
+            <keep-alive>
+              <component :is="Component" />
+            </keep-alive>
+          </transition>
         </router-view>
       </template>
       <template #fallback>
index 6cc477ae081f063d7409de110ffd99cd16c4ec91..a4e816b6b7f4aababffc612cb616415c60b51a3f 100644 (file)
@@ -4,18 +4,20 @@ import {
   provide,
   defineComponent,
   PropType,
-  computed,
   ref,
   ComponentPublicInstance,
   VNodeProps,
+  getCurrentInstance,
+  computed,
 } from 'vue'
-import { RouteLocationNormalizedLoaded, RouteLocationNormalized } from './types'
+import { RouteLocationNormalized, RouteLocationNormalizedLoaded } from './types'
 import {
   matchedRouteKey,
   viewDepthKey,
   routeLocationKey,
 } from './injectionSymbols'
 import { assign } from './utils'
+import { warn } from './warning'
 
 export interface RouterViewProps {
   name?: string
@@ -34,80 +36,68 @@ export const RouterViewImpl = defineComponent({
   },
 
   setup(props, { attrs, slots }) {
-    const realRoute = inject(routeLocationKey)!
-    const route = computed(() => props.route || realRoute)
-
-    const depth: number = inject(viewDepthKey, 0)
-    provide(viewDepthKey, depth + 1)
+    __DEV__ && warnDeprecatedUsage()
 
-    const matchedRoute = computed(
-      () =>
-        route.value.matched[depth] as
-          | RouteLocationNormalizedLoaded['matched'][any]
-          | undefined
-    )
-    const ViewComponent = computed(
-      () => matchedRoute.value && matchedRoute.value.components[props.name]
+    const route = inject(routeLocationKey)!
+    const depth = inject(viewDepthKey, 0)
+    const matchedRouteRef = computed(
+      () => (props.route || route).matched[depth]
     )
 
-    const propsData = computed(() => {
-      // propsData only gets called if ViewComponent.value exists and it depends
-      // on matchedRoute.value
-      const componentProps = matchedRoute.value!.props[props.name]
-      if (!componentProps) return {}
-      // TODO: only add props declared in the component. all if no props
-      if (componentProps === true) return route.value.params
+    provide(viewDepthKey, depth + 1)
+    provide(matchedRouteKey, matchedRouteRef)
 
-      return typeof componentProps === 'object'
-        ? componentProps
-        : componentProps(route.value)
-    })
+    const viewRef = ref<ComponentPublicInstance>()
 
-    provide(matchedRouteKey, matchedRoute)
+    return () => {
+      const matchedRoute = matchedRouteRef.value
+      if (!matchedRoute) {
+        return null
+      }
 
-    const viewRef = ref<ComponentPublicInstance>()
+      const ViewComponent = matchedRoute.components[props.name]
+      if (!ViewComponent) {
+        return null
+      }
 
-    function onVnodeMounted() {
-      // if we mount, there is a matched record
-      matchedRoute.value!.instances[props.name] = viewRef.value
-      // TODO: trigger beforeRouteEnter hooks
-      // TODO: watch name to update the instance record
-    }
+      // props from route configration
+      const routePropsOption = matchedRoute.props[props.name]
+      const routeProps = routePropsOption
+        ? routePropsOption === true
+          ? route.params
+          : typeof routePropsOption === 'function'
+          ? routePropsOption(route)
+          : routePropsOption
+        : null
 
-    return () => {
       // we nee the value at the time we render because when we unmount, we
       // navigated to a different location so the value is different
-      const currentMatched = matchedRoute.value
       const currentName = props.name
-      function onVnodeUnmounted() {
-        if (currentMatched) {
-          // remove the instance reference to prevent leak
-          currentMatched.instances[currentName] = null
-        }
+      const onVnodeMounted = () => {
+        matchedRoute.instances[currentName] = viewRef.value
+        // TODO: trigger beforeRouteEnter hooks
+      }
+      const onVnodeUnmounted = () => {
+        // remove the instance reference to prevent leak
+        matchedRoute.instances[currentName] = null
       }
 
-      let Component = ViewComponent.value
-      const componentProps: Parameters<typeof h>[1] = assign(
-        {},
-        // only compute props if there is a matched record
-        Component && propsData.value,
-        attrs,
-        {
+      const component = h(
+        ViewComponent,
+        assign({}, routeProps, attrs, {
           onVnodeMounted,
           onVnodeUnmounted,
           ref: viewRef,
-        }
+        })
       )
 
-      // NOTE: we could also not render if there is no route match
-      const children =
-        slots.default && slots.default({ Component, props: componentProps })
-
-      return children
-        ? children
-        : Component
-        ? h(Component, componentProps)
-        : null
+      return (
+        // pass the vnode to the slot as a prop.
+        // h and <component :is="..."> both accept vnodes
+        slots.default
+          ? slots.default({ Component: component, route: matchedRoute })
+          : component
+      )
     }
   },
 })
@@ -119,3 +109,22 @@ export const RouterView = (RouterViewImpl as any) as {
     $props: VNodeProps & RouterViewProps
   }
 }
+
+// warn against deprecated usage with <transition> & <keep-alive>
+// due to functional component being no longer eager in Vue 3
+function warnDeprecatedUsage() {
+  const instance = getCurrentInstance()!
+  const parentName = instance.parent && instance.parent.type.name
+  if (parentName === 'KeepAlive' || parentName === 'Transition') {
+    const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition'
+    warn(
+      `<router-view> can no longer be used directly inside <transition> or <keep-alive>.\n` +
+        `Use slot props instead:\n\n` +
+        `<router-view v-slot="{ Component }>\n` +
+        `  <${comp}>\n` +
+        `    <component :is="Component" />\n` +
+        `  </${comp}>\n` +
+        `</router-view>`
+    )
+  }
+}
index b19e0276a035be3e4546cb5b942b640b3e46f545..a9f8a72d4bf5d0a15986fcd6d577311410e4d224 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
   dependencies:
     "@types/node" "*"
 
-"@vue/compiler-core@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.0-beta.17.tgz#92d75f5ae7a03c51694f439dc96939bde9a7074e"
-  integrity sha512-UHv7YFUremfSXf3CAeEoRZOX+n26IZQxFRwREw55spoMRjjpNIH+sSLQz3pwgTnClm90GlzRMzOFYTOQrzAnfQ==
+"@vue/compiler-core@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.0-beta.18.tgz#52cf3e4f1f627230e9affb9da22a408a624e7e50"
+  integrity sha512-3JSSCs11lYuNdfyT5DVB06OeWlT/aAK8JKHLmG8OsXkT0flVSc19mtnqi+EaFhW3rT63qT0fjJfTfU0Wn1PN9Q==
   dependencies:
     "@babel/parser" "^7.8.6"
     "@babel/types" "^7.8.6"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/shared" "3.0.0-beta.18"
     estree-walker "^0.8.1"
     source-map "^0.6.1"
 
-"@vue/compiler-dom@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.0-beta.17.tgz#fa19ad2391cd78dcae121cfbeacf6cc3ebd4ff20"
-  integrity sha512-wj4egu6KzsJy1EG/MHgbEVfVH8oMIGoFqjwkbCyqE5G0uRPAPi0WYHY5lyjAU2gI7cfGxIcFx7UsWT5D9XH0/g==
+"@vue/compiler-dom@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.0-beta.18.tgz#089b37c71850df28d2304e2d5034461af6065ddf"
+  integrity sha512-vTfZNfn/bfGVJCdQwQN5xeBIaFCYPKp/NZCyMewh0wdju2ewzSmQIzG3gaSqEIxYor/FQmFkGuRRzWJJBmcoUQ==
   dependencies:
-    "@vue/compiler-core" "3.0.0-beta.17"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/compiler-core" "3.0.0-beta.18"
+    "@vue/shared" "3.0.0-beta.18"
 
-"@vue/compiler-sfc@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.0.0-beta.17.tgz#d932a9e5fe9074fb447a6d82f22c52503d1da0dd"
-  integrity sha512-MpU0rtDNzSWyhoLCqcov8XyrXIO8lqwA8Nlt7uy/EWndZv/Vw2VQVQYmfQQZzv2fDQ39f4s7LYcgEM5prRHX1Q==
+"@vue/compiler-sfc@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.0.0-beta.18.tgz#3c4660396ca267b443214c09b71923bce17c5d8e"
+  integrity sha512-RSErTbnKWkI4hAFBTOLg1tCHHVP2hG7NDbf2LVJdf9OWBr9FWoTQaMTby25rJs6aiSch7reNRzToq6XMLagQjQ==
   dependencies:
-    "@vue/compiler-core" "3.0.0-beta.17"
-    "@vue/compiler-dom" "3.0.0-beta.17"
-    "@vue/compiler-ssr" "3.0.0-beta.17"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/compiler-core" "3.0.0-beta.18"
+    "@vue/compiler-dom" "3.0.0-beta.18"
+    "@vue/compiler-ssr" "3.0.0-beta.18"
+    "@vue/shared" "3.0.0-beta.18"
     consolidate "^0.15.1"
     hash-sum "^2.0.0"
     lru-cache "^5.1.1"
     postcss-selector-parser "^6.0.2"
     source-map "^0.6.1"
 
-"@vue/compiler-ssr@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.0.0-beta.17.tgz#4aa276891fc73f928cf09e7afdc1d0439b794b08"
-  integrity sha512-iXL/xhb2q9NQgzvIVuwgqJVnaeTNgg2YGGkmajW4KGVEfwcYOqIr8hg2RpEDeTQrHZwIpL6umqTER5cXYgYoew==
+"@vue/compiler-ssr@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.0.0-beta.18.tgz#072048fecd40d39b8762beb73da1ae45f3750bbc"
+  integrity sha512-/W83jw+PToUUX/SouajhwMwewEe66lUiTb4lixZ3f06o0VPJCFCLtZ+WQbN+g2m+iE7DegnkcPq6ly7gflcRXQ==
   dependencies:
-    "@vue/compiler-dom" "3.0.0-beta.17"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/compiler-dom" "3.0.0-beta.18"
+    "@vue/shared" "3.0.0-beta.18"
 
-"@vue/reactivity@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.0-beta.17.tgz#582a0650d3ab5aad1cb15e0e4fdea1dbb1732877"
-  integrity sha512-LPpRAEljlrZjTwTmIxZNMePwTOapWXfAcDRMyFJ/L1MGumfyPl1jHflE8upcUKtUWpXt18+MpNywXwskpr4+4w==
+"@vue/reactivity@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.0-beta.18.tgz#b6562661eca6b7bd4d633db5df867b41c8e3c648"
+  integrity sha512-UFKONXh5XZCwynGrS6CAYTz3AoNVmLUpuQnfl3z8XbHulw9kqVwFoQgXwFBlrizdLsPoNW0s3FHPmtqC9Ohjgg==
   dependencies:
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/shared" "3.0.0-beta.18"
 
-"@vue/runtime-core@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.0-beta.17.tgz#400a977398cb1689e225d884077d2dae7ce1e6b4"
-  integrity sha512-HOud3nuNAxBPPM66Mj4NRomdbeqIdR5ofSB+JgRHn/gS+7C13A/ww1XsKkjN6IzE8VgeJ3XHEBHrtDvBRYPfcg==
+"@vue/runtime-core@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.0-beta.18.tgz#373ccec46e37826b9976f21039287bfa69f4ee16"
+  integrity sha512-ZgIxCSuR1luq3pmWQbsYkn6ybUK/BKAWRQwAxUCP9BjFHnvN0W4BSLwy6VuH2l2ToKGni+xPxpDZhQQQsVAfng==
   dependencies:
-    "@vue/reactivity" "3.0.0-beta.17"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/reactivity" "3.0.0-beta.18"
+    "@vue/shared" "3.0.0-beta.18"
 
-"@vue/runtime-dom@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.0-beta.17.tgz#a296ff81eebbaf7aa5d19cd76d595222f4636e48"
-  integrity sha512-FPV3fT8AFRQHuvjQ5/QxifU3LlMnyg1FzMHENedcu6lRrmUpnttexkOa51nZOrlFPI+Tz3mk297lohCp1QCMEA==
+"@vue/runtime-dom@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.0-beta.18.tgz#9b9f6af723bf9896fdce58857e07401ef521cba3"
+  integrity sha512-2ym4EyWg1C/UDXuVjFyqwLgAjrWRTRM3wHBmp0TlpoXSSNvIEJg5HXRgC1jnHtkVbGmyHHPndsbFP+oBj10tzQ==
   dependencies:
-    "@vue/runtime-core" "3.0.0-beta.17"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/runtime-core" "3.0.0-beta.18"
+    "@vue/shared" "3.0.0-beta.18"
     csstype "^2.6.8"
 
-"@vue/server-renderer@^3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.0.0-beta.17.tgz#38469652f583fd043b4982b0fd6bdff151d76a11"
-  integrity sha512-E2xiRo0YIajVhHn1MB2hUVHzQm+Wt+U4HGt81nJVghFonxKq6SVp8IWHym+jOoMF+olnO4tOhzpsCcwqKuT7sQ==
+"@vue/server-renderer@^3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.0.0-beta.18.tgz#d4e9e09faf9a5cf06ca55b2036428ec5b847c78f"
+  integrity sha512-2VgKZnMtk8ZnA5ZiYP643CaZQTOjFBTb/jOCgVjOJ18SSkvKzmjXbPgBTR9mpNGR0IT47YL+Ym2XPcKAmPeaGQ==
   dependencies:
-    "@vue/compiler-ssr" "3.0.0-beta.17"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/compiler-ssr" "3.0.0-beta.18"
+    "@vue/shared" "3.0.0-beta.18"
 
-"@vue/shared@3.0.0-beta.17":
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.0-beta.17.tgz#c9df723a3a3a39e47044ae8be60cce51c749ceb3"
-  integrity sha512-4vqmNjeY3prPbH2K9hKmXnzfU34ytT+Az971Ybc5WcjG6Vu+gEkXLHAQvQIoi9AYhCCuunvUi1r5IWFhGSHLww==
+"@vue/shared@3.0.0-beta.18":
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.0-beta.18.tgz#1326efa98fe49b9ef3effbec88189b88ed7544a7"
+  integrity sha512-wRvQaTHeCt3xxqj3UpMR2JxkkU7ul/9RMqSxGJhOd4Bsp72Md4H83XkijHy8LkPKZWszmxjEpfCYuw9MU0a4kg==
 
 "@webassemblyjs/ast@1.9.0":
   version "1.9.0"
@@ -10303,14 +10303,14 @@ vue-loader@^16.0.0-beta.4:
     merge-source-map "^1.1.0"
     source-map "^0.6.1"
 
-vue@^3.0.0-beta.17:
-  version "3.0.0-beta.17"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.0-beta.17.tgz#e361cfb18e3cb5576af27724fdb6e67e3fb45020"
-  integrity sha512-FrqKMCFbs3FHnYHf2uX7hkk0DKX+14SixX1vV5to8pwA97A55mrMmm+VqywRbPWfS2If9OByvhwuRL1+HRLY8g==
+vue@^3.0.0-beta.18:
+  version "3.0.0-beta.18"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.0-beta.18.tgz#775b499602ed7ff5e14bef00cd320e77bdfe0dca"
+  integrity sha512-waevph9s+M/aBf4XJIjfwHlHH7SBEr29dTMqq6ymnp/qJQHsavLlQHjsWRcxkYJ5rKfM6omoW+G6EBRBDB05sA==
   dependencies:
-    "@vue/compiler-dom" "3.0.0-beta.17"
-    "@vue/runtime-dom" "3.0.0-beta.17"
-    "@vue/shared" "3.0.0-beta.17"
+    "@vue/compiler-dom" "3.0.0-beta.18"
+    "@vue/runtime-dom" "3.0.0-beta.18"
+    "@vue/shared" "3.0.0-beta.18"
 
 w3c-hr-time@^1.0.2:
   version "1.0.2"